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