Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] feat: allow multiple compiler configs #170

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions crates/artifacts/solc/src/output_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::{collections::BTreeMap, fmt, str::FromStr};
pub type FileOutputSelection = BTreeMap<String, Vec<String>>;

/// Represents the selected output of files and contracts
///
/// The first level key is the file name and the second level key is the
/// contract name. An empty contract name is used for outputs that are
/// not tied to a contract but to the whole source file like the AST.
Expand Down
136 changes: 75 additions & 61 deletions crates/compilers/src/artifact_output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ mod hh;
pub use hh::*;

use crate::{
cache::{CachedArtifact, CompilerCache},
cache::{CachedArtifacts, CompilerCache},
output::{
contracts::VersionedContracts,
sources::{VersionedSourceFile, VersionedSourceFiles},
Expand All @@ -52,6 +52,7 @@ pub struct ArtifactId {
pub version: Version,
/// `solc` build id
pub build_id: String,
pub profile: String,
}

impl ArtifactId {
Expand Down Expand Up @@ -119,6 +120,7 @@ pub struct ArtifactFile<T> {
/// `solc` version that produced this artifact
pub version: Version,
pub build_id: String,
pub profile: String,
}

impl<T: Serialize> ArtifactFile<T> {
Expand Down Expand Up @@ -298,6 +300,7 @@ impl<T> Artifacts<T> {
source: source.clone(),
version: artifact.version.clone(),
build_id: artifact.build_id.clone(),
profile: artifact.profile.clone(),
}
.with_slashed_paths(),
&artifact.artifact,
Expand All @@ -324,6 +327,7 @@ impl<T> Artifacts<T> {
source: source.clone(),
version: artifact.version,
build_id: artifact.build_id.clone(),
profile: artifact.profile.clone(),
}
.with_slashed_paths(),
artifact.artifact,
Expand Down Expand Up @@ -642,14 +646,22 @@ pub trait ArtifactOutput {

/// Returns the file name for the contract's artifact
/// `Greeter.json`
fn output_file_name(name: &str) -> PathBuf {
format!("{name}.json").into()
}

/// Returns the file name for the contract's artifact and the given version
/// `Greeter.0.8.11.json`
fn output_file_name_versioned(name: &str, version: &Version) -> PathBuf {
format!("{}.{}.{}.{}.json", name, version.major, version.minor, version.patch).into()
fn output_file_name(
name: &str,
version: &Version,
profile: &str,
with_version: bool,
with_profile: bool,
) -> PathBuf {
let mut name = name.to_string();
if with_version {
name.push_str(&format!(".{}.{}.{}", version.major, version.minor, version.patch));
}
if with_profile {
name.push_str(&format!(".{profile}"));
}
name.push_str(".json");
name.into()
}

/// Returns the appropriate file name for the conflicting file.
Expand Down Expand Up @@ -724,24 +736,23 @@ pub trait ArtifactOutput {
/// Returns the path to the contract's artifact location based on the contract's file and name
///
/// This returns `contract.sol/contract.json` by default
fn output_file(contract_file: &Path, name: &str) -> PathBuf {
contract_file
.file_name()
.map(Path::new)
.map(|p| p.join(Self::output_file_name(name)))
.unwrap_or_else(|| Self::output_file_name(name))
}

/// Returns the path to the contract's artifact location based on the contract's file, name and
/// version
///
/// This returns `contract.sol/contract.0.8.11.json` by default
fn output_file_versioned(contract_file: &Path, name: &str, version: &Version) -> PathBuf {
fn output_file(
contract_file: &Path,
name: &str,
version: &Version,
profile: &str,
with_version: bool,
with_profile: bool,
) -> PathBuf {
contract_file
.file_name()
.map(Path::new)
.map(|p| p.join(Self::output_file_name_versioned(name, version)))
.unwrap_or_else(|| Self::output_file_name_versioned(name, version))
.map(|p| {
p.join(Self::output_file_name(name, version, profile, with_version, with_profile))
})
.unwrap_or_else(|| {
Self::output_file_name(name, version, profile, with_version, with_profile)
})
}

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

/// Whether the corresponding artifact of the given contract file and name exists
fn output_exists(contract_file: &Path, name: &str, root: &Path) -> bool {
root.join(Self::output_file(contract_file, name)).exists()
}

/// Read the artifact that's stored at the given path
///
/// # Errors
Expand Down Expand Up @@ -800,28 +806,27 @@ pub trait ArtifactOutput {

/// Generates a path for an artifact based on already taken paths by either cached or compiled
/// artifacts.
#[allow(clippy::too_many_arguments)]
fn get_artifact_path(
ctx: &OutputContext<'_>,
already_taken: &HashSet<String>,
file: &Path,
name: &str,
artifacts_folder: &Path,
version: &Version,
versioned: bool,
profile: &str,
with_version: bool,
with_profile: bool,
) -> PathBuf {
// if an artifact for the contract already exists (from a previous compile job)
// we reuse the path, this will make sure that even if there are conflicting
// files (files for witch `T::output_file()` would return the same path) we use
// consistent output paths
if let Some(existing_artifact) = ctx.existing_artifact(file, name, version) {
if let Some(existing_artifact) = ctx.existing_artifact(file, name, version, profile) {
trace!("use existing artifact file {:?}", existing_artifact,);
existing_artifact.to_path_buf()
} else {
let path = if versioned {
Self::output_file_versioned(file, name, version)
} else {
Self::output_file(file, name)
};
let path = Self::output_file(file, name, version, profile, with_version, with_profile);

let path = artifacts_folder.join(path);

Expand Down Expand Up @@ -854,7 +859,9 @@ pub trait ArtifactOutput {
let mut taken_paths_lowercase = ctx
.existing_artifacts
.values()
.flat_map(|artifacts| artifacts.values().flat_map(|artifacts| artifacts.values()))
.flat_map(|artifacts| artifacts.values())
.flat_map(|artifacts| artifacts.values())
.flat_map(|artifacts| artifacts.values())
.map(|a| a.path.to_slash_lossy().to_lowercase())
.collect::<HashSet<_>>();

Expand All @@ -865,22 +872,26 @@ pub trait ArtifactOutput {
});
for file in files {
for (name, versioned_contracts) in &contracts[file] {
let unique_versions =
versioned_contracts.iter().map(|c| &c.version).collect::<HashSet<_>>();
let unique_profiles =
versioned_contracts.iter().map(|c| &c.profile).collect::<HashSet<_>>();
for contract in versioned_contracts {
non_standalone_sources.insert(file);

// track `SourceFile`s that can be mapped to contracts
let source_file = sources.find_file_and_version(file, &contract.version);

if let Some(source) = source_file {
non_standalone_sources.insert((source.id, &contract.version));
}

let artifact_path = Self::get_artifact_path(
&ctx,
&taken_paths_lowercase,
file,
name,
layout.artifacts.as_path(),
&contract.version,
versioned_contracts.len() > 1,
&contract.profile,
unique_versions.len() > 1,
unique_profiles.len() > 1,
);

taken_paths_lowercase.insert(artifact_path.to_slash_lossy().to_lowercase());
Expand All @@ -904,6 +915,7 @@ pub trait ArtifactOutput {
file: artifact_path,
version: contract.version.clone(),
build_id: contract.build_id.clone(),
profile: contract.profile.clone(),
};

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

let entries = artifacts
taken_paths_lowercase
.insert(artifact_path.to_slash_lossy().to_lowercase());

artifacts
.entry(file.clone())
.or_default()
.entry(name.to_string())
.or_default();

if entries.iter().all(|entry| entry.version != source.version) {
taken_paths_lowercase
.insert(artifact_path.to_slash_lossy().to_lowercase());

entries.push(ArtifactFile {
.or_default()
.push(ArtifactFile {
artifact,
file: artifact_path,
version: source.version.clone(),
build_id: source.build_id.clone(),
profile: source.profile.clone(),
});
}
}
}
}
Expand Down Expand Up @@ -1015,8 +1029,7 @@ pub struct OutputContext<'a> {
/// └── inner
/// └── a.sol
/// ```
pub existing_artifacts:
BTreeMap<&'a Path, &'a BTreeMap<String, BTreeMap<Version, CachedArtifact>>>,
pub existing_artifacts: BTreeMap<&'a Path, &'a CachedArtifacts>,
}

// === impl OutputContext
Expand All @@ -1042,13 +1055,14 @@ impl<'a> OutputContext<'a> {
file: &Path,
contract: &str,
version: &Version,
profile: &str,
) -> Option<&Path> {
self.existing_artifacts.get(file).and_then(|contracts| {
contracts
.get(contract)
.and_then(|versions| versions.get(version))
.map(|a| a.path.as_path())
})
self.existing_artifacts
.get(file)
.and_then(|contracts| contracts.get(contract))
.and_then(|versions| versions.get(version))
.and_then(|profiles| profiles.get(profile))
.map(|a| a.path.as_path())
}
}

Expand Down
Loading