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