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