Skip to content

Commit b5c2a49

Browse files
authored
feat: allow multiple compiler configs (#170)
This adds `CompilerSettings::Restrictions` associated type which allows configuring constraints for compilation settings. Those constraints can be checked against a settings object and merged. Ideally given a set of constraints we'd be able to automatically resolve configurations given only basic (default) config, though I think we can do this in follow-up. Current implementation requires user to provide us with concrete profiles such that for each defined constraint at least one of the profiles matches it. This should be enough for most of the usecases for now. Profile to use is resolved in `Graph` similarly to version resolution. The way this could look in foundry.toml is: ```toml [profile.default] # by default no via-ir and evm version is paris for everything evm_version = "paris" via_ir = false # add cancun profile and via_ir profiles additional_compiler_profiles = [{ evm_version = "cancun" }, {via_ir = true} ] # enforce compiling some contracts with cancun, and some with via-ir # if those contracts are imported by some items in the project, constraints will apply as well compilation_constraints = [{path = "./**/*.cancun.sol", min_evm_version = "cancun" }, {path = "SomeComplexFile.sol", via_ir = true }] ``` When writing artifacts, a profile name is added to artifact file name if the same contract was compiled with multiple artifacts.
1 parent 034ecd6 commit b5c2a49

File tree

21 files changed

+842
-309
lines changed

21 files changed

+842
-309
lines changed

crates/artifacts/solc/src/contract.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ pub struct CompactContractRefSome<'a> {
455455
pub bin_runtime: &'a BytecodeObject,
456456
}
457457

458-
impl<'a> CompactContractRefSome<'a> {
458+
impl CompactContractRefSome<'_> {
459459
/// Returns the individual parts of this contract.
460460
///
461461
/// If the values are `None`, then `Default` is returned.

crates/artifacts/solc/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1150,7 +1150,7 @@ impl<'de> Deserialize<'de> for LosslessMetadata {
11501150
{
11511151
struct LosslessMetadataVisitor;
11521152

1153-
impl<'de> Visitor<'de> for LosslessMetadataVisitor {
1153+
impl Visitor<'_> for LosslessMetadataVisitor {
11541154
type Value = LosslessMetadata;
11551155

11561156
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {

crates/artifacts/solc/src/sourcemap.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ enum Token<'a> {
9292
Regular,
9393
}
9494

95-
impl<'a> fmt::Debug for Token<'a> {
95+
impl fmt::Debug for Token<'_> {
9696
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
9797
match self {
9898
Token::Number(s) => write!(f, "NUMBER({s:?})"),
@@ -105,7 +105,7 @@ impl<'a> fmt::Debug for Token<'a> {
105105
}
106106
}
107107

108-
impl<'a> fmt::Display for Token<'a> {
108+
impl fmt::Display for Token<'_> {
109109
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110110
match self {
111111
Token::Number(_) => write!(f, "number"),
@@ -531,7 +531,7 @@ impl<'input> Parser<'input> {
531531
}
532532
}
533533

534-
impl<'input> Iterator for Parser<'input> {
534+
impl Iterator for Parser<'_> {
535535
type Item = Result<SourceElement, SyntaxError>;
536536

537537
fn next(&mut self) -> Option<Self::Item> {

crates/compilers/src/artifact_output/mod.rs

Lines changed: 75 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ mod hh;
3232
pub use hh::*;
3333

3434
use crate::{
35-
cache::{CachedArtifact, CompilerCache},
35+
cache::{CachedArtifacts, CompilerCache},
3636
output::{
3737
contracts::VersionedContracts,
3838
sources::{VersionedSourceFile, VersionedSourceFiles},
@@ -52,6 +52,7 @@ pub struct ArtifactId {
5252
pub version: Version,
5353
/// `solc` build id
5454
pub build_id: String,
55+
pub profile: String,
5556
}
5657

5758
impl ArtifactId {
@@ -119,6 +120,7 @@ pub struct ArtifactFile<T> {
119120
/// `solc` version that produced this artifact
120121
pub version: Version,
121122
pub build_id: String,
123+
pub profile: String,
122124
}
123125

124126
impl<T: Serialize> ArtifactFile<T> {
@@ -298,6 +300,7 @@ impl<T> Artifacts<T> {
298300
source: source.clone(),
299301
version: artifact.version.clone(),
300302
build_id: artifact.build_id.clone(),
303+
profile: artifact.profile.clone(),
301304
}
302305
.with_slashed_paths(),
303306
&artifact.artifact,
@@ -324,6 +327,7 @@ impl<T> Artifacts<T> {
324327
source: source.clone(),
325328
version: artifact.version,
326329
build_id: artifact.build_id.clone(),
330+
profile: artifact.profile.clone(),
327331
}
328332
.with_slashed_paths(),
329333
artifact.artifact,
@@ -642,14 +646,22 @@ pub trait ArtifactOutput {
642646

643647
/// Returns the file name for the contract's artifact
644648
/// `Greeter.json`
645-
fn output_file_name(name: &str) -> PathBuf {
646-
format!("{name}.json").into()
647-
}
648-
649-
/// Returns the file name for the contract's artifact and the given version
650-
/// `Greeter.0.8.11.json`
651-
fn output_file_name_versioned(name: &str, version: &Version) -> PathBuf {
652-
format!("{}.{}.{}.{}.json", name, version.major, version.minor, version.patch).into()
649+
fn output_file_name(
650+
name: &str,
651+
version: &Version,
652+
profile: &str,
653+
with_version: bool,
654+
with_profile: bool,
655+
) -> PathBuf {
656+
let mut name = name.to_string();
657+
if with_version {
658+
name.push_str(&format!(".{}.{}.{}", version.major, version.minor, version.patch));
659+
}
660+
if with_profile {
661+
name.push_str(&format!(".{profile}"));
662+
}
663+
name.push_str(".json");
664+
name.into()
653665
}
654666

655667
/// Returns the appropriate file name for the conflicting file.
@@ -724,24 +736,23 @@ pub trait ArtifactOutput {
724736
/// Returns the path to the contract's artifact location based on the contract's file and name
725737
///
726738
/// This returns `contract.sol/contract.json` by default
727-
fn output_file(contract_file: &Path, name: &str) -> PathBuf {
728-
contract_file
729-
.file_name()
730-
.map(Path::new)
731-
.map(|p| p.join(Self::output_file_name(name)))
732-
.unwrap_or_else(|| Self::output_file_name(name))
733-
}
734-
735-
/// Returns the path to the contract's artifact location based on the contract's file, name and
736-
/// version
737-
///
738-
/// This returns `contract.sol/contract.0.8.11.json` by default
739-
fn output_file_versioned(contract_file: &Path, name: &str, version: &Version) -> PathBuf {
739+
fn output_file(
740+
contract_file: &Path,
741+
name: &str,
742+
version: &Version,
743+
profile: &str,
744+
with_version: bool,
745+
with_profile: bool,
746+
) -> PathBuf {
740747
contract_file
741748
.file_name()
742749
.map(Path::new)
743-
.map(|p| p.join(Self::output_file_name_versioned(name, version)))
744-
.unwrap_or_else(|| Self::output_file_name_versioned(name, version))
750+
.map(|p| {
751+
p.join(Self::output_file_name(name, version, profile, with_version, with_profile))
752+
})
753+
.unwrap_or_else(|| {
754+
Self::output_file_name(name, version, profile, with_version, with_profile)
755+
})
745756
}
746757

747758
/// The inverse of `contract_file_name`
@@ -752,11 +763,6 @@ pub trait ArtifactOutput {
752763
file.file_stem().and_then(|s| s.to_str().map(|s| s.to_string()))
753764
}
754765

755-
/// Whether the corresponding artifact of the given contract file and name exists
756-
fn output_exists(contract_file: &Path, name: &str, root: &Path) -> bool {
757-
root.join(Self::output_file(contract_file, name)).exists()
758-
}
759-
760766
/// Read the artifact that's stored at the given path
761767
///
762768
/// # Errors
@@ -800,28 +806,27 @@ pub trait ArtifactOutput {
800806

801807
/// Generates a path for an artifact based on already taken paths by either cached or compiled
802808
/// artifacts.
809+
#[allow(clippy::too_many_arguments)]
803810
fn get_artifact_path(
804811
ctx: &OutputContext<'_>,
805812
already_taken: &HashSet<String>,
806813
file: &Path,
807814
name: &str,
808815
artifacts_folder: &Path,
809816
version: &Version,
810-
versioned: bool,
817+
profile: &str,
818+
with_version: bool,
819+
with_profile: bool,
811820
) -> PathBuf {
812821
// if an artifact for the contract already exists (from a previous compile job)
813822
// we reuse the path, this will make sure that even if there are conflicting
814823
// files (files for witch `T::output_file()` would return the same path) we use
815824
// consistent output paths
816-
if let Some(existing_artifact) = ctx.existing_artifact(file, name, version) {
825+
if let Some(existing_artifact) = ctx.existing_artifact(file, name, version, profile) {
817826
trace!("use existing artifact file {:?}", existing_artifact,);
818827
existing_artifact.to_path_buf()
819828
} else {
820-
let path = if versioned {
821-
Self::output_file_versioned(file, name, version)
822-
} else {
823-
Self::output_file(file, name)
824-
};
829+
let path = Self::output_file(file, name, version, profile, with_version, with_profile);
825830

826831
let path = artifacts_folder.join(path);
827832

@@ -854,7 +859,9 @@ pub trait ArtifactOutput {
854859
let mut taken_paths_lowercase = ctx
855860
.existing_artifacts
856861
.values()
857-
.flat_map(|artifacts| artifacts.values().flat_map(|artifacts| artifacts.values()))
862+
.flat_map(|artifacts| artifacts.values())
863+
.flat_map(|artifacts| artifacts.values())
864+
.flat_map(|artifacts| artifacts.values())
858865
.map(|a| a.path.to_slash_lossy().to_lowercase())
859866
.collect::<HashSet<_>>();
860867

@@ -865,22 +872,26 @@ pub trait ArtifactOutput {
865872
});
866873
for file in files {
867874
for (name, versioned_contracts) in &contracts[file] {
875+
let unique_versions =
876+
versioned_contracts.iter().map(|c| &c.version).collect::<HashSet<_>>();
877+
let unique_profiles =
878+
versioned_contracts.iter().map(|c| &c.profile).collect::<HashSet<_>>();
868879
for contract in versioned_contracts {
880+
non_standalone_sources.insert(file);
881+
869882
// track `SourceFile`s that can be mapped to contracts
870883
let source_file = sources.find_file_and_version(file, &contract.version);
871884

872-
if let Some(source) = source_file {
873-
non_standalone_sources.insert((source.id, &contract.version));
874-
}
875-
876885
let artifact_path = Self::get_artifact_path(
877886
&ctx,
878887
&taken_paths_lowercase,
879888
file,
880889
name,
881890
layout.artifacts.as_path(),
882891
&contract.version,
883-
versioned_contracts.len() > 1,
892+
&contract.profile,
893+
unique_versions.len() > 1,
894+
unique_profiles.len() > 1,
884895
);
885896

886897
taken_paths_lowercase.insert(artifact_path.to_slash_lossy().to_lowercase());
@@ -904,6 +915,7 @@ pub trait ArtifactOutput {
904915
file: artifact_path,
905916
version: contract.version.clone(),
906917
build_id: contract.build_id.clone(),
918+
profile: contract.profile.clone(),
907919
};
908920

909921
artifacts
@@ -921,8 +933,10 @@ pub trait ArtifactOutput {
921933
// any contract definition, which are not included in the `CompilerOutput` but we want to
922934
// create Artifacts for them regardless
923935
for (file, sources) in sources.as_ref().iter() {
936+
let unique_versions = sources.iter().map(|s| &s.version).collect::<HashSet<_>>();
937+
let unique_profiles = sources.iter().map(|s| &s.profile).collect::<HashSet<_>>();
924938
for source in sources {
925-
if !non_standalone_sources.contains(&(source.source_file.id, &source.version)) {
939+
if !non_standalone_sources.contains(file) {
926940
// scan the ast as a safe measure to ensure this file does not include any
927941
// source units
928942
// there's also no need to create a standalone artifact for source files that
@@ -945,26 +959,26 @@ pub trait ArtifactOutput {
945959
name,
946960
&layout.artifacts,
947961
&source.version,
948-
sources.len() > 1,
962+
&source.profile,
963+
unique_versions.len() > 1,
964+
unique_profiles.len() > 1,
949965
);
950966

951-
let entries = artifacts
967+
taken_paths_lowercase
968+
.insert(artifact_path.to_slash_lossy().to_lowercase());
969+
970+
artifacts
952971
.entry(file.clone())
953972
.or_default()
954973
.entry(name.to_string())
955-
.or_default();
956-
957-
if entries.iter().all(|entry| entry.version != source.version) {
958-
taken_paths_lowercase
959-
.insert(artifact_path.to_slash_lossy().to_lowercase());
960-
961-
entries.push(ArtifactFile {
974+
.or_default()
975+
.push(ArtifactFile {
962976
artifact,
963977
file: artifact_path,
964978
version: source.version.clone(),
965979
build_id: source.build_id.clone(),
980+
profile: source.profile.clone(),
966981
});
967-
}
968982
}
969983
}
970984
}
@@ -1015,8 +1029,7 @@ pub struct OutputContext<'a> {
10151029
/// └── inner
10161030
/// └── a.sol
10171031
/// ```
1018-
pub existing_artifacts:
1019-
BTreeMap<&'a Path, &'a BTreeMap<String, BTreeMap<Version, CachedArtifact>>>,
1032+
pub existing_artifacts: BTreeMap<&'a Path, &'a CachedArtifacts>,
10201033
}
10211034

10221035
// === impl OutputContext
@@ -1042,13 +1055,14 @@ impl<'a> OutputContext<'a> {
10421055
file: &Path,
10431056
contract: &str,
10441057
version: &Version,
1058+
profile: &str,
10451059
) -> Option<&Path> {
1046-
self.existing_artifacts.get(file).and_then(|contracts| {
1047-
contracts
1048-
.get(contract)
1049-
.and_then(|versions| versions.get(version))
1050-
.map(|a| a.path.as_path())
1051-
})
1060+
self.existing_artifacts
1061+
.get(file)
1062+
.and_then(|contracts| contracts.get(contract))
1063+
.and_then(|versions| versions.get(version))
1064+
.and_then(|profiles| profiles.get(profile))
1065+
.map(|a| a.path.as_path())
10521066
}
10531067
}
10541068

0 commit comments

Comments
 (0)