Skip to main content

foundry_compilers/compile/output/
mod.rs

1//! The output of a compiled project
2use contracts::{VersionedContract, VersionedContracts};
3use foundry_compilers_artifacts::{CompactContractBytecode, CompactContractRef, Severity};
4use foundry_compilers_core::error::{SolcError, SolcIoError};
5use info::ContractInfoRef;
6use semver::Version;
7use serde::{Deserialize, Serialize};
8use sources::{VersionedSourceFile, VersionedSourceFiles};
9use std::{
10    collections::BTreeMap,
11    fmt,
12    ops::{Deref, DerefMut},
13    path::{Path, PathBuf},
14};
15use yansi::Paint;
16
17use crate::{
18    Artifact, ArtifactId, ArtifactOutput, Artifacts, ConfigurableArtifacts,
19    buildinfo::{BuildContext, RawBuildInfo},
20    compilers::{
21        CompilationError, Compiler, CompilerContract, CompilerOutput, multi::MultiCompiler,
22    },
23    resolver::GraphEdges,
24};
25
26pub mod contracts;
27pub mod info;
28pub mod sources;
29
30/// A mapping from build_id to [BuildContext].
31#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(transparent)]
33pub struct Builds<L>(pub BTreeMap<String, BuildContext<L>>);
34
35impl<L> Default for Builds<L> {
36    fn default() -> Self {
37        Self(Default::default())
38    }
39}
40
41impl<L> Deref for Builds<L> {
42    type Target = BTreeMap<String, BuildContext<L>>;
43
44    fn deref(&self) -> &Self::Target {
45        &self.0
46    }
47}
48
49impl<L> DerefMut for Builds<L> {
50    fn deref_mut(&mut self) -> &mut Self::Target {
51        &mut self.0
52    }
53}
54
55impl<L> IntoIterator for Builds<L> {
56    type Item = (String, BuildContext<L>);
57    type IntoIter = std::collections::btree_map::IntoIter<String, BuildContext<L>>;
58
59    fn into_iter(self) -> Self::IntoIter {
60        self.0.into_iter()
61    }
62}
63
64/// Contains a mixture of already compiled/cached artifacts and the input set of sources that still
65/// need to be compiled.
66#[derive(Clone, Debug, Default)]
67pub struct ProjectCompileOutput<
68    C: Compiler = MultiCompiler,
69    T: ArtifactOutput<CompilerContract = C::CompilerContract> = ConfigurableArtifacts,
70> {
71    /// contains the aggregated `CompilerOutput`
72    pub(crate) compiler_output: AggregatedCompilerOutput<C>,
73    /// all artifact files from `output` that were freshly compiled and written
74    pub(crate) compiled_artifacts: Artifacts<T::Artifact>,
75    /// All artifacts that were read from cache
76    pub(crate) cached_artifacts: Artifacts<T::Artifact>,
77    /// errors that should be omitted
78    pub(crate) ignored_error_codes: Vec<u64>,
79    /// errors that should be omitted from each path prefix
80    pub(crate) ignored_error_codes_from: Vec<(PathBuf, Vec<u64>)>,
81    /// paths that should be omitted
82    pub(crate) ignored_file_paths: Vec<PathBuf>,
83    /// set minimum level of severity that is treated as an error
84    pub(crate) compiler_severity_filter: Severity,
85    /// all build infos that were just compiled
86    pub(crate) builds: Builds<C::Language>,
87    /// The relationship between the source files and their imports
88    pub(crate) edges: GraphEdges<C::Parser>,
89}
90
91impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
92    ProjectCompileOutput<C, T>
93{
94    /// Returns the parser used to parse the sources.
95    pub const fn parser(&self) -> &C::Parser {
96        self.edges.parser()
97    }
98
99    /// Returns the parser used to parse the sources.
100    pub const fn parser_mut(&mut self) -> &mut C::Parser {
101        self.edges.parser_mut()
102    }
103
104    /// Converts all `\\` separators in _all_ paths to `/`
105    #[instrument(skip_all)]
106    pub fn slash_paths(&mut self) {
107        self.compiler_output.slash_paths();
108        self.compiled_artifacts.slash_paths();
109        self.cached_artifacts.slash_paths();
110    }
111
112    /// Convenience function for [`Self::slash_paths()`]
113    pub fn with_slashed_paths(mut self) -> Self {
114        self.slash_paths();
115        self
116    }
117
118    /// All artifacts together with their contract file name and name `<file name>:<name>`.
119    ///
120    /// This returns a chained iterator of both cached and recompiled contract artifacts.
121    ///
122    /// Borrowed version of [`Self::into_artifacts`].
123    pub fn artifact_ids(&self) -> impl Iterator<Item = (ArtifactId, &T::Artifact)> + '_ {
124        let Self { cached_artifacts, compiled_artifacts, .. } = self;
125        cached_artifacts.artifacts::<T>().chain(compiled_artifacts.artifacts::<T>())
126    }
127
128    /// All artifacts together with their contract file name and name `<file name>:<name>`
129    ///
130    /// This returns a chained iterator of both cached and recompiled contract artifacts
131    ///
132    /// # Examples
133    /// ```no_run
134    /// use foundry_compilers::{ArtifactId, Project, artifacts::ConfigurableContractArtifact};
135    /// use std::collections::btree_map::BTreeMap;
136    ///
137    /// let project = Project::builder().build(Default::default())?;
138    /// let contracts: BTreeMap<ArtifactId, ConfigurableContractArtifact> =
139    ///     project.compile()?.into_artifacts().collect();
140    /// # Ok::<_, Box<dyn std::error::Error>>(())
141    /// ```
142    pub fn into_artifacts(self) -> impl Iterator<Item = (ArtifactId, T::Artifact)> {
143        let Self { cached_artifacts, compiled_artifacts, .. } = self;
144        cached_artifacts.into_artifacts::<T>().chain(compiled_artifacts.into_artifacts::<T>())
145    }
146
147    /// This returns a chained iterator of both cached and recompiled contract artifacts that yields
148    /// the contract name and the corresponding artifact
149    ///
150    /// # Examples
151    /// ```no_run
152    /// use foundry_compilers::{Project, artifacts::ConfigurableContractArtifact};
153    /// use std::collections::btree_map::BTreeMap;
154    ///
155    /// let project = Project::builder().build(Default::default())?;
156    /// let artifacts: BTreeMap<String, &ConfigurableContractArtifact> =
157    ///     project.compile()?.artifacts().collect();
158    /// # Ok::<_, Box<dyn std::error::Error>>(())
159    /// ```
160    pub fn artifacts(&self) -> impl Iterator<Item = (String, &T::Artifact)> {
161        self.versioned_artifacts().map(|(name, (artifact, _))| (name, artifact))
162    }
163
164    /// This returns a chained iterator of both cached and recompiled contract artifacts that yields
165    /// the contract name and the corresponding artifact with its version
166    ///
167    /// # Examples
168    /// ```no_run
169    /// use foundry_compilers::{Project, artifacts::ConfigurableContractArtifact};
170    /// use semver::Version;
171    /// use std::collections::btree_map::BTreeMap;
172    ///
173    /// let project = Project::builder().build(Default::default())?;
174    /// let artifacts: BTreeMap<String, (&ConfigurableContractArtifact, &Version)> =
175    ///     project.compile()?.versioned_artifacts().collect();
176    /// # Ok::<_, Box<dyn std::error::Error>>(())
177    /// ```
178    pub fn versioned_artifacts(&self) -> impl Iterator<Item = (String, (&T::Artifact, &Version))> {
179        self.cached_artifacts
180            .artifact_files()
181            .chain(self.compiled_artifacts.artifact_files())
182            .filter_map(|artifact| {
183                T::contract_name(&artifact.file)
184                    .map(|name| (name, (&artifact.artifact, &artifact.version)))
185            })
186    }
187
188    /// All artifacts together with their contract file and name as tuple `(file, contract
189    /// name, artifact)`
190    ///
191    /// This returns a chained iterator of both cached and recompiled contract artifacts
192    ///
193    /// Borrowed version of [`Self::into_artifacts_with_files`].
194    ///
195    /// **NOTE** the `file` will be returned as is, see also
196    /// [`Self::with_stripped_file_prefixes()`].
197    pub fn artifacts_with_files(
198        &self,
199    ) -> impl Iterator<Item = (&PathBuf, &String, &T::Artifact)> + '_ {
200        let Self { cached_artifacts, compiled_artifacts, .. } = self;
201        cached_artifacts.artifacts_with_files().chain(compiled_artifacts.artifacts_with_files())
202    }
203
204    /// All artifacts together with their contract file and name as tuple `(file, contract
205    /// name, artifact)`
206    ///
207    /// This returns a chained iterator of both cached and recompiled contract artifacts
208    ///
209    /// # Examples
210    /// ```no_run
211    /// use foundry_compilers::{Project, artifacts::ConfigurableContractArtifact};
212    /// use std::{collections::btree_map::BTreeMap, path::PathBuf};
213    ///
214    /// let project = Project::builder().build(Default::default())?;
215    /// let contracts: Vec<(PathBuf, String, ConfigurableContractArtifact)> =
216    ///     project.compile()?.into_artifacts_with_files().collect();
217    /// # Ok::<_, Box<dyn std::error::Error>>(())
218    /// ```
219    ///
220    /// **NOTE** the `file` will be returned as is, see also [`Self::with_stripped_file_prefixes()`]
221    pub fn into_artifacts_with_files(self) -> impl Iterator<Item = (PathBuf, String, T::Artifact)> {
222        let Self { cached_artifacts, compiled_artifacts, .. } = self;
223        cached_artifacts
224            .into_artifacts_with_files()
225            .chain(compiled_artifacts.into_artifacts_with_files())
226    }
227
228    /// All artifacts together with their ID and the sources of the project.
229    ///
230    /// Note: this only returns the `SourceFiles` for freshly compiled contracts because, if not
231    /// included in the `Artifact` itself (see
232    /// [`foundry_compilers_artifacts::ConfigurableContractArtifact::source_file()`]), is only
233    /// available via the solc `CompilerOutput`
234    pub fn into_artifacts_with_sources(
235        self,
236    ) -> (BTreeMap<ArtifactId, T::Artifact>, VersionedSourceFiles) {
237        let Self { cached_artifacts, compiled_artifacts, compiler_output, .. } = self;
238
239        (
240            cached_artifacts
241                .into_artifacts::<T>()
242                .chain(compiled_artifacts.into_artifacts::<T>())
243                .collect(),
244            compiler_output.sources,
245        )
246    }
247
248    /// Strips the given prefix from all artifact file paths to make them relative to the given
249    /// `base` argument
250    ///
251    /// # Examples
252    ///
253    /// Make all artifact files relative to the project's root directory
254    /// ```no_run
255    /// use foundry_compilers::Project;
256    ///
257    /// let project = Project::builder().build(Default::default())?;
258    /// let output = project.compile()?.with_stripped_file_prefixes(project.root());
259    /// # Ok::<_, Box<dyn std::error::Error>>(())
260    pub fn with_stripped_file_prefixes(mut self, base: &Path) -> Self {
261        self.cached_artifacts = self.cached_artifacts.into_stripped_file_prefixes(base);
262        self.compiled_artifacts = self.compiled_artifacts.into_stripped_file_prefixes(base);
263        self.compiler_output.strip_prefix_all(base);
264        self
265    }
266
267    /// Returns a reference to the (merged) solc compiler output.
268    ///
269    /// # Examples
270    /// ```no_run
271    /// use foundry_compilers::{Project, artifacts::contract::Contract};
272    /// use std::collections::btree_map::BTreeMap;
273    ///
274    /// let project = Project::builder().build(Default::default())?;
275    /// let contracts: BTreeMap<String, Contract> =
276    ///     project.compile()?.into_output().contracts_into_iter().collect();
277    /// # Ok::<_, Box<dyn std::error::Error>>(())
278    /// ```
279    pub const fn output(&self) -> &AggregatedCompilerOutput<C> {
280        &self.compiler_output
281    }
282
283    /// Returns a mutable reference to the (merged) solc compiler output.
284    pub const fn output_mut(&mut self) -> &mut AggregatedCompilerOutput<C> {
285        &mut self.compiler_output
286    }
287
288    /// Consumes the output and returns the (merged) solc compiler output.
289    pub fn into_output(self) -> AggregatedCompilerOutput<C> {
290        self.compiler_output
291    }
292
293    /// Returns whether this type has a compiler output.
294    pub fn has_compiled_contracts(&self) -> bool {
295        self.compiler_output.is_empty()
296    }
297
298    /// Returns whether this type does not contain compiled contracts.
299    pub fn is_unchanged(&self) -> bool {
300        self.compiler_output.is_unchanged()
301    }
302
303    /// Returns the set of `Artifacts` that were cached and got reused during
304    /// [`crate::Project::compile()`]
305    pub const fn cached_artifacts(&self) -> &Artifacts<T::Artifact> {
306        &self.cached_artifacts
307    }
308
309    /// Returns the set of `Artifacts` that were compiled with `solc` in
310    /// [`crate::Project::compile()`]
311    pub const fn compiled_artifacts(&self) -> &Artifacts<T::Artifact> {
312        &self.compiled_artifacts
313    }
314
315    /// Sets the compiled artifacts for this output.
316    pub fn set_compiled_artifacts(&mut self, new_compiled_artifacts: Artifacts<T::Artifact>) {
317        self.compiled_artifacts = new_compiled_artifacts;
318    }
319
320    /// Returns a `BTreeMap` that maps the compiler version used during
321    /// [`crate::Project::compile()`] to a Vector of tuples containing the contract name and the
322    /// `Contract`
323    pub fn compiled_contracts_by_compiler_version(
324        &self,
325    ) -> BTreeMap<Version, Vec<(String, impl CompilerContract)>> {
326        let mut contracts: BTreeMap<_, Vec<_>> = BTreeMap::new();
327        let versioned_contracts = &self.compiler_output.contracts;
328        for (_, name, contract, version) in versioned_contracts.contracts_with_files_and_version() {
329            contracts.entry(version.to_owned()).or_default().push((name.clone(), contract.clone()));
330        }
331        contracts
332    }
333
334    /// Removes the contract with matching path and name using the `<path>:<contractname>` pattern
335    /// where `path` is optional.
336    ///
337    /// If the `path` segment is `None`, then the first matching `Contract` is returned, see
338    /// [`Self::remove_first`].
339    ///
340    /// # Examples
341    /// ```no_run
342    /// use foundry_compilers::{Project, artifacts::*, info::ContractInfo};
343    ///
344    /// let project = Project::builder().build(Default::default())?;
345    /// let output = project.compile()?;
346    /// let info = ContractInfo::new("src/Greeter.sol:Greeter");
347    /// let contract = output.find_contract(&info).unwrap();
348    /// # Ok::<_, Box<dyn std::error::Error>>(())
349    /// ```
350    pub fn find_contract<'a>(&self, info: impl Into<ContractInfoRef<'a>>) -> Option<&T::Artifact> {
351        let ContractInfoRef { path, name } = info.into();
352        if let Some(path) = path {
353            self.find(path[..].as_ref(), &name)
354        } else {
355            self.find_first(&name)
356        }
357    }
358
359    /// Finds the artifact with matching path and name
360    ///
361    /// # Examples
362    /// ```no_run
363    /// use foundry_compilers::{Project, artifacts::*};
364    ///
365    /// let project = Project::builder().build(Default::default())?;
366    /// let output = project.compile()?;
367    /// let contract = output.find("src/Greeter.sol".as_ref(), "Greeter").unwrap();
368    /// # Ok::<_, Box<dyn std::error::Error>>(())
369    /// ```
370    pub fn find(&self, path: &Path, name: &str) -> Option<&T::Artifact> {
371        if let artifact @ Some(_) = self.compiled_artifacts.find(path, name) {
372            return artifact;
373        }
374        self.cached_artifacts.find(path, name)
375    }
376
377    /// Finds the first contract with the given name
378    pub fn find_first(&self, name: &str) -> Option<&T::Artifact> {
379        if let artifact @ Some(_) = self.compiled_artifacts.find_first(name) {
380            return artifact;
381        }
382        self.cached_artifacts.find_first(name)
383    }
384
385    /// Finds the artifact with matching path and name
386    ///
387    /// # Examples
388    /// ```no_run
389    /// use foundry_compilers::{Project, artifacts::*};
390    ///
391    /// let project = Project::builder().build(Default::default())?;
392    /// let output = project.compile()?;
393    /// let contract = output.find("src/Greeter.sol".as_ref(), "Greeter").unwrap();
394    /// # Ok::<_, Box<dyn std::error::Error>>(())
395    /// ```
396    pub fn remove(&mut self, path: &Path, name: &str) -> Option<T::Artifact> {
397        if let artifact @ Some(_) = self.compiled_artifacts.remove(path, name) {
398            return artifact;
399        }
400        self.cached_artifacts.remove(path, name)
401    }
402
403    /// Removes the _first_ contract with the given name from the set
404    ///
405    /// # Examples
406    /// ```no_run
407    /// use foundry_compilers::{Project, artifacts::*};
408    ///
409    /// let project = Project::builder().build(Default::default())?;
410    /// let mut output = project.compile()?;
411    /// let contract = output.remove_first("Greeter").unwrap();
412    /// # Ok::<_, Box<dyn std::error::Error>>(())
413    /// ```
414    pub fn remove_first(&mut self, name: &str) -> Option<T::Artifact> {
415        if let artifact @ Some(_) = self.compiled_artifacts.remove_first(name) {
416            return artifact;
417        }
418        self.cached_artifacts.remove_first(name)
419    }
420
421    /// Removes the contract with matching path and name using the `<path>:<contractname>` pattern
422    /// where `path` is optional.
423    ///
424    /// If the `path` segment is `None`, then the first matching `Contract` is returned, see
425    /// [Self::remove_first]
426    ///
427    ///
428    /// # Examples
429    /// ```no_run
430    /// use foundry_compilers::{Project, artifacts::*, info::ContractInfo};
431    ///
432    /// let project = Project::builder().build(Default::default())?;
433    /// let mut output = project.compile()?;
434    /// let info = ContractInfo::new("src/Greeter.sol:Greeter");
435    /// let contract = output.remove_contract(&info).unwrap();
436    /// # Ok::<_, Box<dyn std::error::Error>>(())
437    /// ```
438    pub fn remove_contract<'a>(
439        &mut self,
440        info: impl Into<ContractInfoRef<'a>>,
441    ) -> Option<T::Artifact> {
442        let ContractInfoRef { path, name } = info.into();
443        if let Some(path) = path {
444            self.remove(path[..].as_ref(), &name)
445        } else {
446            self.remove_first(&name)
447        }
448    }
449
450    /// A helper functions that extracts the underlying [`CompactContractBytecode`] from the
451    /// [`foundry_compilers_artifacts::ConfigurableContractArtifact`]
452    ///
453    /// # Examples
454    /// ```no_run
455    /// use foundry_compilers::{
456    ///     ArtifactId, Project, artifacts::contract::CompactContractBytecode,
457    ///     contracts::ArtifactContracts,
458    /// };
459    /// use std::collections::btree_map::BTreeMap;
460    ///
461    /// let project = Project::builder().build(Default::default())?;
462    /// let contracts: ArtifactContracts = project.compile()?.into_contract_bytecodes().collect();
463    /// # Ok::<_, Box<dyn std::error::Error>>(())
464    /// ```
465    pub fn into_contract_bytecodes(
466        self,
467    ) -> impl Iterator<Item = (ArtifactId, CompactContractBytecode)> {
468        self.into_artifacts()
469            .map(|(artifact_id, artifact)| (artifact_id, artifact.into_contract_bytecode()))
470    }
471
472    pub fn builds(&self) -> impl Iterator<Item = (&String, &BuildContext<C::Language>)> {
473        self.builds.iter()
474    }
475
476    /// Returns the source graph of the project.
477    pub const fn graph(&self) -> &GraphEdges<C::Parser> {
478        &self.edges
479    }
480}
481
482impl<C: Compiler, T: ArtifactOutput<CompilerContract = C::CompilerContract>>
483    ProjectCompileOutput<C, T>
484{
485    /// Returns whether any errors were emitted by the compiler.
486    pub fn has_compiler_errors(&self) -> bool {
487        self.compiler_output.has_error(
488            &self.ignored_error_codes,
489            &self.ignored_error_codes_from,
490            &self.ignored_file_paths,
491            &self.compiler_severity_filter,
492        )
493    }
494
495    /// Returns whether any warnings were emitted by the compiler.
496    pub fn has_compiler_warnings(&self) -> bool {
497        self.compiler_output.has_warning(
498            &self.ignored_error_codes,
499            &self.ignored_error_codes_from,
500            &self.ignored_file_paths,
501        )
502    }
503
504    /// Panics if any errors were emitted by the compiler.
505    #[track_caller]
506    pub fn succeeded(self) -> Self {
507        self.assert_success();
508        self
509    }
510
511    /// Panics if any errors were emitted by the compiler.
512    #[track_caller]
513    pub fn assert_success(&self) {
514        assert!(!self.has_compiler_errors(), "\n{self}\n");
515    }
516}
517
518impl<C: Compiler, T: ArtifactOutput<CompilerContract = C::CompilerContract>> fmt::Display
519    for ProjectCompileOutput<C, T>
520{
521    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522        if self.compiler_output.is_unchanged() {
523            f.write_str("Nothing to compile")
524        } else {
525            self.compiler_output
526                .diagnostics(
527                    &self.ignored_error_codes,
528                    &self.ignored_error_codes_from,
529                    &self.ignored_file_paths,
530                    self.compiler_severity_filter,
531                )
532                .fmt(f)
533        }
534    }
535}
536
537/// The aggregated output of (multiple) compile jobs
538///
539/// This is effectively a solc version aware `CompilerOutput`
540#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
541pub struct AggregatedCompilerOutput<C: Compiler> {
542    /// all errors from all `CompilerOutput`
543    pub errors: Vec<C::CompilationError>,
544    /// All source files combined with the solc version used to compile them
545    pub sources: VersionedSourceFiles,
546    /// All compiled contracts combined with the solc version used to compile them
547    pub contracts: VersionedContracts<C::CompilerContract>,
548    // All the `BuildInfo`s of solc invocations.
549    pub build_infos: Vec<RawBuildInfo<C::Language>>,
550}
551
552impl<C: Compiler> Default for AggregatedCompilerOutput<C> {
553    fn default() -> Self {
554        Self {
555            errors: Vec::new(),
556            sources: Default::default(),
557            contracts: Default::default(),
558            build_infos: Default::default(),
559        }
560    }
561}
562
563impl<C: Compiler> AggregatedCompilerOutput<C> {
564    /// Converts all `\\` separators in _all_ paths to `/`
565    #[allow(clippy::missing_const_for_fn)]
566    pub fn slash_paths(&mut self) {
567        self.sources.slash_paths();
568        self.contracts.slash_paths();
569    }
570
571    pub const fn diagnostics<'a>(
572        &'a self,
573        ignored_error_codes: &'a [u64],
574        ignored_error_codes_from: &'a [(PathBuf, Vec<u64>)],
575        ignored_file_paths: &'a [PathBuf],
576        compiler_severity_filter: Severity,
577    ) -> OutputDiagnostics<'a, C> {
578        OutputDiagnostics {
579            compiler_output: self,
580            ignored_error_codes,
581            ignored_error_codes_from,
582            ignored_file_paths,
583            compiler_severity_filter,
584        }
585    }
586
587    pub fn is_empty(&self) -> bool {
588        self.contracts.is_empty()
589    }
590
591    pub fn is_unchanged(&self) -> bool {
592        self.contracts.is_empty() && self.errors.is_empty()
593    }
594
595    /// adds a new `CompilerOutput` to the aggregated output
596    pub fn extend(
597        &mut self,
598        version: Version,
599        build_info: RawBuildInfo<C::Language>,
600        profile: &str,
601        output: CompilerOutput<C::CompilationError, C::CompilerContract>,
602    ) {
603        let build_id = build_info.id.clone();
604        self.build_infos.push(build_info);
605
606        let CompilerOutput { errors, sources, contracts, .. } = output;
607        self.errors.extend(errors);
608
609        for (path, source_file) in sources {
610            let sources = self.sources.as_mut().entry(path).or_default();
611            sources.push(VersionedSourceFile {
612                source_file,
613                version: version.clone(),
614                build_id: build_id.clone(),
615                profile: profile.to_string(),
616            });
617        }
618
619        for (file_name, new_contracts) in contracts {
620            let contracts = self.contracts.0.entry(file_name).or_default();
621            for (contract_name, contract) in new_contracts {
622                let versioned = contracts.entry(contract_name).or_default();
623                versioned.push(VersionedContract {
624                    contract,
625                    version: version.clone(),
626                    build_id: build_id.clone(),
627                    profile: profile.to_string(),
628                });
629            }
630        }
631    }
632
633    /// Creates all `BuildInfo` files in the given `build_info_dir`
634    ///
635    /// There can be multiple `BuildInfo`, since we support multiple versions.
636    ///
637    /// The created files have a unique identifier as their name.
638    pub fn write_build_infos(&self, build_info_dir: &Path) -> Result<(), SolcError> {
639        if self.build_infos.is_empty() {
640            return Ok(());
641        }
642        std::fs::create_dir_all(build_info_dir)
643            .map_err(|err| SolcIoError::new(err, build_info_dir))?;
644        for build_info in &self.build_infos {
645            trace!("writing build info file {}", build_info.id);
646            let file_name = format!("{}.json", build_info.id);
647            let file = build_info_dir.join(file_name);
648            std::fs::write(&file, &serde_json::to_string(build_info)?)
649                .map_err(|err| SolcIoError::new(err, file))?;
650        }
651        Ok(())
652    }
653
654    /// Finds the _first_ contract with the given name
655    ///
656    /// # Examples
657    /// ```no_run
658    /// use foundry_compilers::{Project, artifacts::*};
659    ///
660    /// let project = Project::builder().build(Default::default())?;
661    /// let output = project.compile()?.into_output();
662    /// let contract = output.find_first("Greeter").unwrap();
663    /// # Ok::<_, Box<dyn std::error::Error>>(())
664    /// ```
665    pub fn find_first(&self, contract: &str) -> Option<CompactContractRef<'_>> {
666        self.contracts.find_first(contract)
667    }
668
669    /// Removes the _first_ contract with the given name from the set
670    ///
671    /// # Examples
672    /// ```no_run
673    /// use foundry_compilers::{Project, artifacts::*};
674    ///
675    /// let project = Project::builder().build(Default::default())?;
676    /// let mut output = project.compile()?.into_output();
677    /// let contract = output.remove_first("Greeter").unwrap();
678    /// # Ok::<_, Box<dyn std::error::Error>>(())
679    /// ```
680    pub fn remove_first(&mut self, contract: &str) -> Option<C::CompilerContract> {
681        self.contracts.remove_first(contract)
682    }
683
684    /// Removes the contract with matching path and name
685    ///
686    /// # Examples
687    /// ```no_run
688    /// use foundry_compilers::{Project, artifacts::*};
689    ///
690    /// let project = Project::builder().build(Default::default())?;
691    /// let mut output = project.compile()?.into_output();
692    /// let contract = output.remove("src/Greeter.sol".as_ref(), "Greeter").unwrap();
693    /// # Ok::<_, Box<dyn std::error::Error>>(())
694    /// ```
695    pub fn remove(&mut self, path: &Path, contract: &str) -> Option<C::CompilerContract> {
696        self.contracts.remove(path, contract)
697    }
698
699    /// Removes the contract with matching path and name using the `<path>:<contractname>` pattern
700    /// where `path` is optional.
701    ///
702    /// If the `path` segment is `None`, then the first matching `Contract` is returned, see
703    /// [Self::remove_first]
704    ///
705    /// # Examples
706    /// ```no_run
707    /// use foundry_compilers::{Project, artifacts::*, info::ContractInfo};
708    ///
709    /// let project = Project::builder().build(Default::default())?;
710    /// let mut output = project.compile()?.into_output();
711    /// let info = ContractInfo::new("src/Greeter.sol:Greeter");
712    /// let contract = output.remove_contract(&info).unwrap();
713    /// # Ok::<_, Box<dyn std::error::Error>>(())
714    /// ```
715    pub fn remove_contract<'a>(
716        &mut self,
717        info: impl Into<ContractInfoRef<'a>>,
718    ) -> Option<C::CompilerContract> {
719        let ContractInfoRef { path, name } = info.into();
720        if let Some(path) = path {
721            self.remove(path[..].as_ref(), &name)
722        } else {
723            self.remove_first(&name)
724        }
725    }
726
727    /// Iterate over all contracts and their names
728    pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &C::CompilerContract)> {
729        self.contracts.contracts()
730    }
731
732    /// Iterate over all contracts and their names
733    pub fn contracts_into_iter(self) -> impl Iterator<Item = (String, C::CompilerContract)> {
734        self.contracts.into_contracts()
735    }
736
737    /// Returns an iterator over (`file`, `name`, `Contract`)
738    pub fn contracts_with_files_iter(
739        &self,
740    ) -> impl Iterator<Item = (&PathBuf, &String, &C::CompilerContract)> {
741        self.contracts.contracts_with_files()
742    }
743
744    /// Returns an iterator over (`file`, `name`, `Contract`)
745    pub fn contracts_with_files_into_iter(
746        self,
747    ) -> impl Iterator<Item = (PathBuf, String, C::CompilerContract)> {
748        self.contracts.into_contracts_with_files()
749    }
750
751    /// Returns an iterator over (`file`, `name`, `Contract`, `Version`)
752    pub fn contracts_with_files_and_version_iter(
753        &self,
754    ) -> impl Iterator<Item = (&PathBuf, &String, &C::CompilerContract, &Version)> {
755        self.contracts.contracts_with_files_and_version()
756    }
757
758    /// Returns an iterator over (`file`, `name`, `Contract`, `Version`)
759    pub fn contracts_with_files_and_version_into_iter(
760        self,
761    ) -> impl Iterator<Item = (PathBuf, String, C::CompilerContract, Version)> {
762        self.contracts.into_contracts_with_files_and_version()
763    }
764
765    /// Given the contract file's path and the contract's name, tries to return the contract's
766    /// bytecode, runtime bytecode, and ABI.
767    ///
768    /// # Examples
769    /// ```no_run
770    /// use foundry_compilers::{Project, artifacts::*};
771    ///
772    /// let project = Project::builder().build(Default::default())?;
773    /// let output = project.compile()?.into_output();
774    /// let contract = output.get("src/Greeter.sol".as_ref(), "Greeter").unwrap();
775    /// # Ok::<_, Box<dyn std::error::Error>>(())
776    /// ```
777    pub fn get(&self, path: &Path, contract: &str) -> Option<CompactContractRef<'_>> {
778        self.contracts.get(path, contract)
779    }
780
781    /// Returns the output's source files and contracts separately, wrapped in helper types that
782    /// provide several helper methods
783    ///
784    /// # Examples
785    /// ```no_run
786    /// use foundry_compilers::Project;
787    ///
788    /// let project = Project::builder().build(Default::default())?;
789    /// let output = project.compile()?.into_output();
790    /// let (sources, contracts) = output.split();
791    /// # Ok::<_, Box<dyn std::error::Error>>(())
792    /// ```
793    pub fn split(self) -> (VersionedSourceFiles, VersionedContracts<C::CompilerContract>) {
794        (self.sources, self.contracts)
795    }
796
797    /// Joins all file path with `root`
798    pub fn join_all(&mut self, root: &Path) -> &mut Self {
799        self.contracts.join_all(root);
800        self.sources.join_all(root);
801        self
802    }
803
804    /// Strips the given prefix from all file paths to make them relative to the given
805    /// `base` argument.
806    ///
807    /// Convenience method for [Self::strip_prefix_all()] that consumes the type.
808    ///
809    /// # Examples
810    ///
811    /// Make all sources and contracts relative to the project's root directory
812    /// ```no_run
813    /// use foundry_compilers::Project;
814    ///
815    /// let project = Project::builder().build(Default::default())?;
816    /// let output = project.compile()?.into_output().with_stripped_file_prefixes(project.root());
817    /// # Ok::<_, Box<dyn std::error::Error>>(())
818    /// ```
819    pub fn with_stripped_file_prefixes(mut self, base: &Path) -> Self {
820        self.contracts.strip_prefix_all(base);
821        self.sources.strip_prefix_all(base);
822        self
823    }
824
825    /// Removes `base` from all contract paths
826    pub fn strip_prefix_all(&mut self, base: &Path) -> &mut Self {
827        self.contracts.strip_prefix_all(base);
828        self.sources.strip_prefix_all(base);
829        self
830    }
831}
832
833impl<C: Compiler> AggregatedCompilerOutput<C> {
834    /// Whether the output contains a compiler error
835    ///
836    /// This adheres to the given `compiler_severity_filter` and also considers [CompilationError]
837    /// with the given [Severity] as errors. For example [Severity::Warning] will consider
838    /// [CompilationError]s with [Severity::Warning] and [Severity::Error] as errors.
839    pub fn has_error(
840        &self,
841        ignored_error_codes: &[u64],
842        ignored_error_codes_from: &[(PathBuf, Vec<u64>)],
843        ignored_file_paths: &[PathBuf],
844        compiler_severity_filter: &Severity,
845    ) -> bool {
846        self.errors.iter().any(|err| {
847            if err.is_error() {
848                // [Severity::Error] is always treated as an error
849                return true;
850            }
851            // check if the filter is set to something higher than the error's severity
852            if compiler_severity_filter.ge(&err.severity()) {
853                if compiler_severity_filter.is_warning() {
854                    // skip ignored error codes and file path from warnings
855                    return self.has_warning(
856                        ignored_error_codes,
857                        ignored_error_codes_from,
858                        ignored_file_paths,
859                    );
860                }
861                return true;
862            }
863            false
864        })
865    }
866
867    /// Checks if there are any compiler warnings that are not ignored by the specified error codes
868    /// and file paths.
869    pub fn has_warning(
870        &self,
871        ignored_error_codes: &[u64],
872        ignored_error_codes_from: &[(PathBuf, Vec<u64>)],
873        ignored_file_paths: &[PathBuf],
874    ) -> bool {
875        self.errors.iter().any(|error| {
876            !self.should_ignore(
877                ignored_error_codes,
878                ignored_error_codes_from,
879                ignored_file_paths,
880                error,
881            )
882        })
883    }
884
885    pub fn should_ignore(
886        &self,
887        ignored_error_codes: &[u64],
888        ignored_error_codes_from: &[(PathBuf, Vec<u64>)],
889        ignored_file_paths: &[PathBuf],
890        error: &C::CompilationError,
891    ) -> bool {
892        if !error.is_warning() {
893            return false;
894        }
895
896        let mut ignore = false;
897
898        if let Some(code) = error.error_code() {
899            ignore |= ignored_error_codes.contains(&code);
900            if let Some(loc) = error.source_location() {
901                let path = Path::new(&loc.file);
902                ignore |=
903                    ignored_file_paths.iter().any(|ignored_path| path.starts_with(ignored_path));
904                ignore |= ignored_error_codes_from
905                    .iter()
906                    .any(|(prefix, codes)| path.starts_with(prefix) && codes.contains(&code));
907
908                // we ignore spdx and contract size warnings in test
909                // files. if we are looking at one of these warnings
910                // from a test file we skip
911                ignore |= self.is_test(path) && (code == 1878 || code == 5574);
912            }
913        }
914
915        ignore
916    }
917
918    /// Returns true if the contract is a expected to be a test
919    fn is_test(&self, contract_path: &Path) -> bool {
920        if contract_path.to_string_lossy().ends_with(".t.sol") {
921            return true;
922        }
923
924        self.contracts.contracts_with_files().filter(|(path, _, _)| *path == contract_path).any(
925            |(_, _, contract)| {
926                contract.abi_ref().is_some_and(|abi| abi.functions.contains_key("IS_TEST"))
927            },
928        )
929    }
930}
931
932/// Helper type to implement display for solc errors
933#[derive(Clone, Debug)]
934pub struct OutputDiagnostics<'a, C: Compiler> {
935    /// output of the compiled project
936    compiler_output: &'a AggregatedCompilerOutput<C>,
937    /// the error codes to ignore
938    ignored_error_codes: &'a [u64],
939    /// the error codes to ignore from each path prefix
940    ignored_error_codes_from: &'a [(PathBuf, Vec<u64>)],
941    /// the file paths to ignore
942    ignored_file_paths: &'a [PathBuf],
943    /// set minimum level of severity that is treated as an error
944    compiler_severity_filter: Severity,
945}
946
947impl<C: Compiler> OutputDiagnostics<'_, C> {
948    /// Returns true if there is at least one error of high severity
949    pub fn has_error(&self) -> bool {
950        self.compiler_output.has_error(
951            self.ignored_error_codes,
952            self.ignored_error_codes_from,
953            self.ignored_file_paths,
954            &self.compiler_severity_filter,
955        )
956    }
957
958    /// Returns true if there is at least one warning
959    pub fn has_warning(&self) -> bool {
960        self.compiler_output.has_warning(
961            self.ignored_error_codes,
962            self.ignored_error_codes_from,
963            self.ignored_file_paths,
964        )
965    }
966}
967
968impl<C: Compiler> fmt::Display for OutputDiagnostics<'_, C> {
969    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
970        f.write_str("Compiler run ")?;
971        if self.has_error() {
972            write!(f, "{}:", "failed".red())
973        } else if self.has_warning() {
974            write!(f, "{}:", "successful with warnings".yellow())
975        } else {
976            write!(f, "{}!", "successful".green())
977        }?;
978
979        for err in &self.compiler_output.errors {
980            if !self.compiler_output.should_ignore(
981                self.ignored_error_codes,
982                self.ignored_error_codes_from,
983                self.ignored_file_paths,
984                err,
985            ) {
986                f.write_str("\n")?;
987                fmt::Display::fmt(&err, f)?;
988            }
989        }
990
991        Ok(())
992    }
993}