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 a unique identifier as their name.
611    pub fn write_build_infos(&self, build_info_dir: &Path) -> Result<(), SolcError> {
612        if self.build_infos.is_empty() {
613            return Ok(());
614        }
615        std::fs::create_dir_all(build_info_dir)
616            .map_err(|err| SolcIoError::new(err, build_info_dir))?;
617        for build_info in &self.build_infos {
618            trace!("writing build info file {}", build_info.id);
619            let file_name = format!("{}.json", build_info.id);
620            let file = build_info_dir.join(file_name);
621            std::fs::write(&file, &serde_json::to_string(build_info)?)
622                .map_err(|err| SolcIoError::new(err, file))?;
623        }
624        Ok(())
625    }
626
627    /// Finds the _first_ contract with the given name
628    ///
629    /// # Examples
630    /// ```no_run
631    /// use foundry_compilers::{artifacts::*, Project};
632    ///
633    /// let project = Project::builder().build(Default::default())?;
634    /// let output = project.compile()?.into_output();
635    /// let contract = output.find_first("Greeter").unwrap();
636    /// # Ok::<_, Box<dyn std::error::Error>>(())
637    /// ```
638    pub fn find_first(&self, contract: &str) -> Option<CompactContractRef<'_>> {
639        self.contracts.find_first(contract)
640    }
641
642    /// Removes the _first_ contract with the given name from the set
643    ///
644    /// # Examples
645    /// ```no_run
646    /// use foundry_compilers::{artifacts::*, Project};
647    ///
648    /// let project = Project::builder().build(Default::default())?;
649    /// let mut output = project.compile()?.into_output();
650    /// let contract = output.remove_first("Greeter").unwrap();
651    /// # Ok::<_, Box<dyn std::error::Error>>(())
652    /// ```
653    pub fn remove_first(&mut self, contract: &str) -> Option<C::CompilerContract> {
654        self.contracts.remove_first(contract)
655    }
656
657    /// Removes the contract with matching path and name
658    ///
659    /// # Examples
660    /// ```no_run
661    /// use foundry_compilers::{artifacts::*, Project};
662    ///
663    /// let project = Project::builder().build(Default::default())?;
664    /// let mut output = project.compile()?.into_output();
665    /// let contract = output.remove("src/Greeter.sol".as_ref(), "Greeter").unwrap();
666    /// # Ok::<_, Box<dyn std::error::Error>>(())
667    /// ```
668    pub fn remove(&mut self, path: &Path, contract: &str) -> Option<C::CompilerContract> {
669        self.contracts.remove(path, contract)
670    }
671
672    /// Removes the contract with matching path and name using the `<path>:<contractname>` pattern
673    /// where `path` is optional.
674    ///
675    /// If the `path` segment is `None`, then the first matching `Contract` is returned, see
676    /// [Self::remove_first]
677    ///
678    /// # Examples
679    /// ```no_run
680    /// use foundry_compilers::{artifacts::*, info::ContractInfo, Project};
681    ///
682    /// let project = Project::builder().build(Default::default())?;
683    /// let mut output = project.compile()?.into_output();
684    /// let info = ContractInfo::new("src/Greeter.sol:Greeter");
685    /// let contract = output.remove_contract(&info).unwrap();
686    /// # Ok::<_, Box<dyn std::error::Error>>(())
687    /// ```
688    pub fn remove_contract<'a>(
689        &mut self,
690        info: impl Into<ContractInfoRef<'a>>,
691    ) -> Option<C::CompilerContract> {
692        let ContractInfoRef { path, name } = info.into();
693        if let Some(path) = path {
694            self.remove(path[..].as_ref(), &name)
695        } else {
696            self.remove_first(&name)
697        }
698    }
699
700    /// Iterate over all contracts and their names
701    pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &C::CompilerContract)> {
702        self.contracts.contracts()
703    }
704
705    /// Iterate over all contracts and their names
706    pub fn contracts_into_iter(self) -> impl Iterator<Item = (String, C::CompilerContract)> {
707        self.contracts.into_contracts()
708    }
709
710    /// Returns an iterator over (`file`, `name`, `Contract`)
711    pub fn contracts_with_files_iter(
712        &self,
713    ) -> impl Iterator<Item = (&PathBuf, &String, &C::CompilerContract)> {
714        self.contracts.contracts_with_files()
715    }
716
717    /// Returns an iterator over (`file`, `name`, `Contract`)
718    pub fn contracts_with_files_into_iter(
719        self,
720    ) -> impl Iterator<Item = (PathBuf, String, C::CompilerContract)> {
721        self.contracts.into_contracts_with_files()
722    }
723
724    /// Returns an iterator over (`file`, `name`, `Contract`, `Version`)
725    pub fn contracts_with_files_and_version_iter(
726        &self,
727    ) -> impl Iterator<Item = (&PathBuf, &String, &C::CompilerContract, &Version)> {
728        self.contracts.contracts_with_files_and_version()
729    }
730
731    /// Returns an iterator over (`file`, `name`, `Contract`, `Version`)
732    pub fn contracts_with_files_and_version_into_iter(
733        self,
734    ) -> impl Iterator<Item = (PathBuf, String, C::CompilerContract, Version)> {
735        self.contracts.into_contracts_with_files_and_version()
736    }
737
738    /// Given the contract file's path and the contract's name, tries to return the contract's
739    /// bytecode, runtime bytecode, and ABI.
740    ///
741    /// # Examples
742    /// ```no_run
743    /// use foundry_compilers::{artifacts::*, Project};
744    ///
745    /// let project = Project::builder().build(Default::default())?;
746    /// let output = project.compile()?.into_output();
747    /// let contract = output.get("src/Greeter.sol".as_ref(), "Greeter").unwrap();
748    /// # Ok::<_, Box<dyn std::error::Error>>(())
749    /// ```
750    pub fn get(&self, path: &Path, contract: &str) -> Option<CompactContractRef<'_>> {
751        self.contracts.get(path, contract)
752    }
753
754    /// Returns the output's source files and contracts separately, wrapped in helper types that
755    /// provide several helper methods
756    ///
757    /// # Examples
758    /// ```no_run
759    /// use foundry_compilers::Project;
760    ///
761    /// let project = Project::builder().build(Default::default())?;
762    /// let output = project.compile()?.into_output();
763    /// let (sources, contracts) = output.split();
764    /// # Ok::<_, Box<dyn std::error::Error>>(())
765    /// ```
766    pub fn split(self) -> (VersionedSourceFiles, VersionedContracts<C::CompilerContract>) {
767        (self.sources, self.contracts)
768    }
769
770    /// Joins all file path with `root`
771    pub fn join_all(&mut self, root: &Path) -> &mut Self {
772        self.contracts.join_all(root);
773        self.sources.join_all(root);
774        self
775    }
776
777    /// Strips the given prefix from all file paths to make them relative to the given
778    /// `base` argument.
779    ///
780    /// Convenience method for [Self::strip_prefix_all()] that consumes the type.
781    ///
782    /// # Examples
783    ///
784    /// Make all sources and contracts relative to the project's root directory
785    /// ```no_run
786    /// use foundry_compilers::Project;
787    ///
788    /// let project = Project::builder().build(Default::default())?;
789    /// let output = project.compile()?.into_output().with_stripped_file_prefixes(project.root());
790    /// # Ok::<_, Box<dyn std::error::Error>>(())
791    /// ```
792    pub fn with_stripped_file_prefixes(mut self, base: &Path) -> Self {
793        self.contracts.strip_prefix_all(base);
794        self.sources.strip_prefix_all(base);
795        self
796    }
797
798    /// Removes `base` from all contract paths
799    pub fn strip_prefix_all(&mut self, base: &Path) -> &mut Self {
800        self.contracts.strip_prefix_all(base);
801        self.sources.strip_prefix_all(base);
802        self
803    }
804}
805
806impl<C: Compiler> AggregatedCompilerOutput<C> {
807    /// Whether the output contains a compiler error
808    ///
809    /// This adheres to the given `compiler_severity_filter` and also considers [CompilationError]
810    /// with the given [Severity] as errors. For example [Severity::Warning] will consider
811    /// [CompilationError]s with [Severity::Warning] and [Severity::Error] as errors.
812    pub fn has_error(
813        &self,
814        ignored_error_codes: &[u64],
815        ignored_file_paths: &[PathBuf],
816        compiler_severity_filter: &Severity,
817    ) -> bool {
818        self.errors.iter().any(|err| {
819            if err.is_error() {
820                // [Severity::Error] is always treated as an error
821                return true;
822            }
823            // check if the filter is set to something higher than the error's severity
824            if compiler_severity_filter.ge(&err.severity()) {
825                if compiler_severity_filter.is_warning() {
826                    // skip ignored error codes and file path from warnings
827                    return self.has_warning(ignored_error_codes, ignored_file_paths);
828                }
829                return true;
830            }
831            false
832        })
833    }
834
835    /// Checks if there are any compiler warnings that are not ignored by the specified error codes
836    /// and file paths.
837    pub fn has_warning(&self, ignored_error_codes: &[u64], ignored_file_paths: &[PathBuf]) -> bool {
838        self.errors
839            .iter()
840            .any(|error| !self.should_ignore(ignored_error_codes, ignored_file_paths, error))
841    }
842
843    pub fn should_ignore(
844        &self,
845        ignored_error_codes: &[u64],
846        ignored_file_paths: &[PathBuf],
847        error: &C::CompilationError,
848    ) -> bool {
849        if !error.is_warning() {
850            return false;
851        }
852
853        let mut ignore = false;
854
855        if let Some(code) = error.error_code() {
856            ignore |= ignored_error_codes.contains(&code);
857            if let Some(loc) = error.source_location() {
858                let path = Path::new(&loc.file);
859                ignore |=
860                    ignored_file_paths.iter().any(|ignored_path| path.starts_with(ignored_path));
861
862                // we ignore spdx and contract size warnings in test
863                // files. if we are looking at one of these warnings
864                // from a test file we skip
865                ignore |= self.is_test(path) && (code == 1878 || code == 5574);
866            }
867        }
868
869        ignore
870    }
871
872    /// Returns true if the contract is a expected to be a test
873    fn is_test(&self, contract_path: &Path) -> bool {
874        if contract_path.to_string_lossy().ends_with(".t.sol") {
875            return true;
876        }
877
878        self.contracts.contracts_with_files().filter(|(path, _, _)| *path == contract_path).any(
879            |(_, _, contract)| {
880                contract.abi_ref().is_some_and(|abi| abi.functions.contains_key("IS_TEST"))
881            },
882        )
883    }
884}
885
886/// Helper type to implement display for solc errors
887#[derive(Clone, Debug)]
888pub struct OutputDiagnostics<'a, C: Compiler> {
889    /// output of the compiled project
890    compiler_output: &'a AggregatedCompilerOutput<C>,
891    /// the error codes to ignore
892    ignored_error_codes: &'a [u64],
893    /// the file paths to ignore
894    ignored_file_paths: &'a [PathBuf],
895    /// set minimum level of severity that is treated as an error
896    compiler_severity_filter: Severity,
897}
898
899impl<C: Compiler> OutputDiagnostics<'_, C> {
900    /// Returns true if there is at least one error of high severity
901    pub fn has_error(&self) -> bool {
902        self.compiler_output.has_error(
903            self.ignored_error_codes,
904            self.ignored_file_paths,
905            &self.compiler_severity_filter,
906        )
907    }
908
909    /// Returns true if there is at least one warning
910    pub fn has_warning(&self) -> bool {
911        self.compiler_output.has_warning(self.ignored_error_codes, self.ignored_file_paths)
912    }
913}
914
915impl<C: Compiler> fmt::Display for OutputDiagnostics<'_, C> {
916    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
917        f.write_str("Compiler run ")?;
918        if self.has_error() {
919            write!(f, "{}:", "failed".red())
920        } else if self.has_warning() {
921            write!(f, "{}:", "successful with warnings".yellow())
922        } else {
923            write!(f, "{}!", "successful".green())
924        }?;
925
926        for err in &self.compiler_output.errors {
927            if !self.compiler_output.should_ignore(
928                self.ignored_error_codes,
929                self.ignored_file_paths,
930                err,
931            ) {
932                f.write_str("\n")?;
933                fmt::Display::fmt(&err, f)?;
934            }
935        }
936
937        Ok(())
938    }
939}