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}