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}