Skip to content

Commit b851a6c

Browse files
authored
feat: better artifact filenames for different profiles (#241)
Closes foundry-rs/foundry#9722 This PR introduces a concept of "primary" profile. Primary profile is the one which we've chosen for input source when analyzing it on its own (i.e not as a dependency of other contract). That way `Counter.json` would always correspond to an artifact compiled with the default profile if it was possible.
1 parent 55f0e01 commit b851a6c

File tree

4 files changed

+72
-19
lines changed

4 files changed

+72
-19
lines changed

crates/compilers/src/artifact_output/mod.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use semver::Version;
1717
use serde::{de::DeserializeOwned, Deserialize, Serialize};
1818
use std::{
1919
borrow::Cow,
20-
collections::{btree_map::BTreeMap, HashSet},
20+
collections::{btree_map::BTreeMap, HashMap, HashSet},
2121
ffi::OsString,
2222
fmt, fs,
2323
hash::Hash,
@@ -621,8 +621,10 @@ pub trait ArtifactOutput {
621621
sources: &VersionedSourceFiles,
622622
layout: &ProjectPathsConfig<L>,
623623
ctx: OutputContext<'_>,
624+
primary_profiles: &HashMap<PathBuf, &str>,
624625
) -> Result<Artifacts<Self::Artifact>> {
625-
let mut artifacts = self.output_to_artifacts(contracts, sources, ctx, layout);
626+
let mut artifacts =
627+
self.output_to_artifacts(contracts, sources, ctx, layout, primary_profiles);
626628
fs::create_dir_all(&layout.artifacts).map_err(|err| {
627629
error!(dir=?layout.artifacts, "Failed to create artifacts folder");
628630
SolcIoError::new(err, &layout.artifacts)
@@ -850,6 +852,7 @@ pub trait ArtifactOutput {
850852
sources: &VersionedSourceFiles,
851853
ctx: OutputContext<'_>,
852854
layout: &ProjectPathsConfig<C>,
855+
primary_profiles: &HashMap<PathBuf, &str>,
853856
) -> Artifacts<Self::Artifact> {
854857
let mut artifacts = ArtifactsMap::new();
855858

@@ -877,6 +880,8 @@ pub trait ArtifactOutput {
877880
versioned_contracts.iter().map(|c| &c.version).collect::<HashSet<_>>();
878881
let unique_profiles =
879882
versioned_contracts.iter().map(|c| &c.profile).collect::<HashSet<_>>();
883+
let primary_profile = primary_profiles.get(file);
884+
880885
for contract in versioned_contracts {
881886
non_standalone_sources.insert(file);
882887

@@ -892,7 +897,8 @@ pub trait ArtifactOutput {
892897
&contract.version,
893898
&contract.profile,
894899
unique_versions.len() > 1,
895-
unique_profiles.len() > 1,
900+
unique_profiles.len() > 1
901+
&& primary_profile.is_none_or(|p| p != &contract.profile),
896902
);
897903

898904
taken_paths_lowercase.insert(artifact_path.to_slash_lossy().to_lowercase());
@@ -1122,8 +1128,15 @@ impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback {
11221128
sources: &VersionedSourceFiles,
11231129
layout: &ProjectPathsConfig<C>,
11241130
ctx: OutputContext<'_>,
1131+
primary_profiles: &HashMap<PathBuf, &str>,
11251132
) -> Result<Artifacts<Self::Artifact>> {
1126-
MinimalCombinedArtifacts::default().on_output(output, sources, layout, ctx)
1133+
MinimalCombinedArtifacts::default().on_output(
1134+
output,
1135+
sources,
1136+
layout,
1137+
ctx,
1138+
primary_profiles,
1139+
)
11271140
}
11281141

11291142
fn read_cached_artifact(path: &Path) -> Result<Self::Artifact> {

crates/compilers/src/compile/project.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ use crate::{
108108
filter::SparseOutputFilter,
109109
output::{AggregatedCompilerOutput, Builds},
110110
report,
111-
resolver::GraphEdges,
111+
resolver::{GraphEdges, ResolvedSources},
112112
ArtifactOutput, CompilerSettings, Graph, Project, ProjectCompileOutput, Sources,
113113
};
114114
use foundry_compilers_core::error::Result;
@@ -128,6 +128,8 @@ pub struct ProjectCompiler<
128128
/// Contains the relationship of the source files and their imports
129129
edges: GraphEdges<C::ParsedSource>,
130130
project: &'a Project<C, T>,
131+
/// A mapping from a source file path to the primary profile name selected for it.
132+
primary_profiles: HashMap<PathBuf, &'a str>,
131133
/// how to compile all the sources
132134
sources: CompilerSources<'a, C::Language, C::Settings>,
133135
}
@@ -152,7 +154,8 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
152154
sources.retain(|f, _| filter.is_match(f))
153155
}
154156
let graph = Graph::resolve_sources(&project.paths, sources)?;
155-
let (sources, edges) = graph.into_sources_by_version(project)?;
157+
let ResolvedSources { sources, primary_profiles, edges } =
158+
graph.into_sources_by_version(project)?;
156159

157160
// If there are multiple different versions, and we can use multiple jobs we can compile
158161
// them in parallel.
@@ -162,7 +165,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
162165
sources,
163166
};
164167

165-
Ok(Self { edges, project, sources })
168+
Ok(Self { edges, primary_profiles, project, sources })
166169
}
167170

168171
/// Compiles all the sources of the `Project` in the appropriate mode
@@ -199,7 +202,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
199202
/// - check cache
200203
fn preprocess(self) -> Result<PreprocessedState<'a, T, C>> {
201204
trace!("preprocessing");
202-
let Self { edges, project, mut sources } = self;
205+
let Self { edges, project, mut sources, primary_profiles } = self;
203206

204207
// convert paths on windows to ensure consistency with the `CompilerOutput` `solc` emits,
205208
// which is unix style `/`
@@ -209,7 +212,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
209212
// retain and compile only dirty sources and all their imports
210213
sources.filter(&mut cache);
211214

212-
Ok(PreprocessedState { sources, cache })
215+
Ok(PreprocessedState { sources, cache, primary_profiles })
213216
}
214217
}
215218

@@ -224,6 +227,9 @@ struct PreprocessedState<'a, T: ArtifactOutput<CompilerContract = C::CompilerCon
224227

225228
/// Cache that holds `CacheEntry` objects if caching is enabled and the project is recompiled
226229
cache: ArtifactsCache<'a, T, C>,
230+
231+
/// A mapping from a source file path to the primary profile name selected for it.
232+
primary_profiles: HashMap<PathBuf, &'a str>,
227233
}
228234

229235
impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
@@ -232,7 +238,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
232238
/// advance to the next state by compiling all sources
233239
fn compile(self) -> Result<CompiledState<'a, T, C>> {
234240
trace!("compiling");
235-
let PreprocessedState { sources, mut cache } = self;
241+
let PreprocessedState { sources, mut cache, primary_profiles } = self;
236242

237243
let mut output = sources.compile(&mut cache)?;
238244

@@ -243,7 +249,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
243249
// contracts again
244250
output.join_all(cache.project().root());
245251

246-
Ok(CompiledState { output, cache })
252+
Ok(CompiledState { output, cache, primary_profiles })
247253
}
248254
}
249255

@@ -252,6 +258,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
252258
struct CompiledState<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler> {
253259
output: AggregatedCompilerOutput<C>,
254260
cache: ArtifactsCache<'a, T, C>,
261+
primary_profiles: HashMap<PathBuf, &'a str>,
255262
}
256263

257264
impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
@@ -263,7 +270,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
263270
/// successful
264271
#[instrument(skip_all, name = "write-artifacts")]
265272
fn write_artifacts(self) -> Result<ArtifactsState<'a, T, C>> {
266-
let CompiledState { output, cache } = self;
273+
let CompiledState { output, cache, primary_profiles } = self;
267274

268275
let project = cache.project();
269276
let ctx = cache.output_ctx();
@@ -275,6 +282,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
275282
&output.sources,
276283
ctx,
277284
&project.paths,
285+
&primary_profiles,
278286
)
279287
} else if output.has_error(
280288
&project.ignored_error_codes,
@@ -287,6 +295,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
287295
&output.sources,
288296
ctx,
289297
&project.paths,
298+
&primary_profiles,
290299
)
291300
} else {
292301
trace!(
@@ -300,6 +309,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
300309
&output.sources,
301310
&project.paths,
302311
ctx,
312+
&primary_profiles,
303313
)?;
304314

305315
// emits all the build infos, if they exist

crates/compilers/src/lib.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,8 +732,9 @@ impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler> Art
732732
sources: &VersionedSourceFiles,
733733
layout: &ProjectPathsConfig<CP>,
734734
ctx: OutputContext<'_>,
735+
primary_profiles: &HashMap<PathBuf, &str>,
735736
) -> Result<Artifacts<Self::Artifact>> {
736-
self.artifacts_handler().on_output(contracts, sources, layout, ctx)
737+
self.artifacts_handler().on_output(contracts, sources, layout, ctx, primary_profiles)
737738
}
738739

739740
fn handle_artifacts(
@@ -797,8 +798,15 @@ impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler> Art
797798
sources: &VersionedSourceFiles,
798799
ctx: OutputContext<'_>,
799800
layout: &ProjectPathsConfig<CP>,
801+
primary_profiles: &HashMap<PathBuf, &str>,
800802
) -> Artifacts<Self::Artifact> {
801-
self.artifacts_handler().output_to_artifacts(contracts, sources, ctx, layout)
803+
self.artifacts_handler().output_to_artifacts(
804+
contracts,
805+
sources,
806+
ctx,
807+
layout,
808+
primary_profiles,
809+
)
802810
}
803811

804812
fn standalone_source_file_to_artifact(

crates/compilers/src/resolver/mod.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,26 @@ mod tree;
7272
pub use parse::SolImportAlias;
7373
pub use tree::{print, Charset, TreeOptions};
7474

75+
/// Container for result of version and profile resolution of sources contained in [`Graph`].
76+
#[derive(Debug)]
77+
pub struct ResolvedSources<'a, C: Compiler> {
78+
/// Resolved set of sources.
79+
///
80+
/// Mapping from language to a [`Vec`] of compiler inputs consisting of version, sources set
81+
/// and settings.
82+
pub sources: VersionedSources<'a, C::Language, C::Settings>,
83+
/// A mapping from a source file path to the primary profile name selected for it.
84+
///
85+
/// This is required because the same source file might be compiled with multiple different
86+
/// profiles if it's present as a dependency for other sources. We want to keep a single name
87+
/// of the profile which was chosen specifically for each source so that we can default to it.
88+
/// Right now, this is used when generating artifact names, "primary" artifact will never have
89+
/// a profile suffix.
90+
pub primary_profiles: HashMap<PathBuf, &'a str>,
91+
/// Graph edges.
92+
pub edges: GraphEdges<C::ParsedSource>,
93+
}
94+
7595
/// The underlying edges of the graph which only contains the raw relationship data.
7696
///
7797
/// This is kept separate from the `Graph` as the `Node`s get consumed when the `Solc` to `Sources`
@@ -468,14 +488,13 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
468488
///
469489
/// First we determine the compatible version for each input file (from sources and test folder,
470490
/// see `Self::resolve`) and then we add all resolved library imports.
471-
pub fn into_sources_by_version<C, T, S>(
491+
pub fn into_sources_by_version<C, T>(
472492
self,
473493
project: &Project<C, T>,
474-
) -> Result<(VersionedSources<'_, L, S>, GraphEdges<D>)>
494+
) -> Result<ResolvedSources<'_, C>>
475495
where
476496
T: ArtifactOutput<CompilerContract = C::CompilerContract>,
477-
S: CompilerSettings,
478-
C: Compiler<ParsedSource = D, Language = L, Settings = S>,
497+
C: Compiler<ParsedSource = D, Language = L>,
479498
{
480499
/// insert the imports of the given node into the sources map
481500
/// There can be following graph:
@@ -514,6 +533,7 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
514533
let mut all_nodes = nodes.into_iter().enumerate().collect::<HashMap<_, _>>();
515534

516535
let mut resulted_sources = HashMap::new();
536+
let mut default_profiles = HashMap::new();
517537

518538
let profiles = project.settings_profiles().collect::<Vec<_>>();
519539

@@ -534,6 +554,8 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
534554
// set
535555
let (path, source) =
536556
all_nodes.get(&idx).cloned().expect("node is preset. qed");
557+
558+
default_profiles.insert(path.clone(), profiles[profile_idx].0);
537559
sources.insert(path, source);
538560
insert_imports(
539561
idx,
@@ -550,7 +572,7 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
550572
resulted_sources.insert(language, versioned_sources);
551573
}
552574

553-
Ok((resulted_sources, edges))
575+
Ok(ResolvedSources { sources: resulted_sources, primary_profiles: default_profiles, edges })
554576
}
555577

556578
/// Writes the list of imported files into the given formatter:

0 commit comments

Comments
 (0)