ethers_solc/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(rustdoc::broken_intra_doc_links)]
3#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
4
5pub mod artifacts;
6pub mod sourcemap;
7
8pub use artifacts::{CompilerInput, CompilerOutput, EvmVersion};
9use std::collections::{BTreeMap, HashSet};
10
11mod artifact_output;
12pub mod buildinfo;
13pub mod cache;
14pub mod hh;
15pub use artifact_output::*;
16
17pub mod resolver;
18pub use hh::{HardhatArtifact, HardhatArtifacts};
19pub use resolver::Graph;
20
21mod compile;
22pub use compile::{
23    output::{AggregatedCompilerOutput, ProjectCompileOutput},
24    *,
25};
26
27mod config;
28pub use config::{AllowedLibPaths, PathStyle, ProjectPaths, ProjectPathsConfig, SolcConfig};
29
30pub mod remappings;
31use crate::artifacts::{Source, SourceFile, StandardJsonCompilerInput};
32
33pub mod error;
34mod filter;
35pub mod report;
36pub mod utils;
37pub use filter::{FileFilter, TestFileFilter};
38
39use crate::{
40    artifacts::Sources,
41    cache::SolFilesCache,
42    config::IncludePaths,
43    error::{SolcError, SolcIoError},
44    sources::{VersionedSourceFile, VersionedSourceFiles},
45};
46use artifacts::{contract::Contract, Severity};
47use compile::output::contracts::VersionedContracts;
48use error::Result;
49use semver::Version;
50use std::path::{Path, PathBuf};
51
52/// Utilities for creating, mocking and testing of (temporary) projects
53#[cfg(feature = "project-util")]
54pub mod project_util;
55
56/// Represents a project workspace and handles `solc` compiling of all contracts in that workspace.
57#[derive(Debug)]
58pub struct Project<T: ArtifactOutput = ConfigurableArtifacts> {
59    /// The layout of the project
60    pub paths: ProjectPathsConfig,
61    /// Where to find solc
62    pub solc: Solc,
63    /// How solc invocation should be configured.
64    pub solc_config: SolcConfig,
65    /// Whether caching is enabled
66    pub cached: bool,
67    /// Whether to output build information with each solc call.
68    pub build_info: bool,
69    /// Whether writing artifacts to disk is enabled
70    pub no_artifacts: bool,
71    /// Whether writing artifacts to disk is enabled
72    pub auto_detect: bool,
73    /// Handles all artifacts related tasks, reading and writing from the artifact dir.
74    pub artifacts: T,
75    /// Errors/Warnings which match these error codes are not going to be logged
76    pub ignored_error_codes: Vec<u64>,
77    /// The minimum severity level that is treated as a compiler error
78    pub compiler_severity_filter: Severity,
79    /// The paths which will be allowed for library inclusion
80    pub allowed_paths: AllowedLibPaths,
81    /// The paths which will be used with solc's `--include-path` attribute
82    pub include_paths: IncludePaths,
83    /// Maximum number of `solc` processes to run simultaneously.
84    solc_jobs: usize,
85    /// Offline mode, if set, network access (download solc) is disallowed
86    pub offline: bool,
87    /// Windows only config value to ensure the all paths use `/` instead of `\\`, same as `solc`
88    ///
89    /// This is a noop on other platforms
90    pub slash_paths: bool,
91}
92
93impl Project {
94    /// Convenience function to call `ProjectBuilder::default()`
95    ///
96    /// # Example
97    ///
98    /// Configure with `ConfigurableArtifacts` artifacts output
99    ///
100    /// ```rust
101    /// use ethers_solc::Project;
102    /// let config = Project::builder().build().unwrap();
103    /// ```
104    ///
105    /// To configure any a project with any `ArtifactOutput` use either
106    ///
107    /// ```rust
108    /// use ethers_solc::Project;
109    /// let config = Project::builder().build().unwrap();
110    /// ```
111    ///
112    /// or use the builder directly
113    ///
114    /// ```rust
115    /// use ethers_solc::{ConfigurableArtifacts, ProjectBuilder};
116    /// let config = ProjectBuilder::<ConfigurableArtifacts>::default().build().unwrap();
117    /// ```
118    pub fn builder() -> ProjectBuilder {
119        ProjectBuilder::default()
120    }
121}
122
123impl<T: ArtifactOutput> Project<T> {
124    /// Returns the path to the artifacts directory
125    pub fn artifacts_path(&self) -> &PathBuf {
126        &self.paths.artifacts
127    }
128
129    /// Returns the path to the sources directory
130    pub fn sources_path(&self) -> &PathBuf {
131        &self.paths.sources
132    }
133
134    /// Returns the path to the cache file
135    pub fn cache_path(&self) -> &PathBuf {
136        &self.paths.cache
137    }
138
139    /// Returns the path to the `build-info` directory nested in the artifacts dir
140    pub fn build_info_path(&self) -> &PathBuf {
141        &self.paths.build_infos
142    }
143
144    /// Returns the root directory of the project
145    pub fn root(&self) -> &PathBuf {
146        &self.paths.root
147    }
148
149    /// Returns the handler that takes care of processing all artifacts
150    pub fn artifacts_handler(&self) -> &T {
151        &self.artifacts
152    }
153
154    /// Convenience function to read the cache file.
155    /// See also [SolFilesCache::read_joined()]
156    pub fn read_cache_file(&self) -> Result<SolFilesCache> {
157        SolFilesCache::read_joined(&self.paths)
158    }
159
160    /// Applies the configured arguments to the given `Solc`
161    ///
162    /// See [Self::configure_solc_with_version()]
163    pub(crate) fn configure_solc(&self, solc: Solc) -> Solc {
164        let version = solc.version().ok();
165        self.configure_solc_with_version(solc, version, Default::default())
166    }
167
168    /// Applies the configured arguments to the given `Solc`
169    ///
170    /// This will set the `--allow-paths` to the paths configured for the `Project`, if any.
171    ///
172    /// If a version is provided and it is applicable it will also set `--base-path` and
173    /// `--include-path` This will set the `--allow-paths` to the paths configured for the
174    /// `Project`, if any.
175    /// This also accepts additional `include_paths`
176    pub(crate) fn configure_solc_with_version(
177        &self,
178        mut solc: Solc,
179        version: Option<Version>,
180        mut include_paths: IncludePaths,
181    ) -> Solc {
182        if !solc.args.iter().any(|arg| arg == "--allow-paths") {
183            if let Some([allow, libs]) = self.allowed_paths.args() {
184                solc = solc.arg(allow).arg(libs);
185            }
186        }
187        if let Some(version) = version {
188            if SUPPORTS_BASE_PATH.matches(&version) {
189                let base_path = format!("{}", self.root().display());
190                if !base_path.is_empty() {
191                    solc = solc.with_base_path(self.root());
192                    if SUPPORTS_INCLUDE_PATH.matches(&version) {
193                        include_paths.extend(self.include_paths.paths().cloned());
194                        // `--base-path` and `--include-path` conflict if set to the same path, so
195                        // as a precaution, we ensure here that the `--base-path` is not also used
196                        // for `--include-path`
197                        include_paths.remove(self.root());
198                        solc = solc.args(include_paths.args());
199                    }
200                }
201            } else {
202                solc.base_path.take();
203            }
204        }
205        solc
206    }
207
208    /// Sets the maximum number of parallel `solc` processes to run simultaneously.
209    ///
210    /// # Panics
211    ///
212    /// if `jobs == 0`
213    pub fn set_solc_jobs(&mut self, jobs: usize) {
214        assert!(jobs > 0);
215        self.solc_jobs = jobs;
216    }
217
218    /// Returns all sources found under the project's configured sources path
219    #[tracing::instrument(skip_all, fields(name = "sources"))]
220    pub fn sources(&self) -> Result<Sources> {
221        self.paths.read_sources()
222    }
223
224    /// This emits the cargo [`rerun-if-changed`](https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorerun-if-changedpath) instruction.
225    /// Which tells Cargo to re-run the build script if a file inside the project's sources
226    /// directory has changed.
227    ///
228    /// Use this if you compile a project in a `build.rs` file.
229    ///
230    /// # Example `build.rs` file
231    ///
232    ///
233    /// ```no_run
234    /// use ethers_solc::{Project, ProjectPathsConfig};
235    /// // configure the project with all its paths, solc, cache etc. where the root dir is the current rust project.
236    /// let project = Project::builder()
237    ///     .paths(ProjectPathsConfig::hardhat(env!("CARGO_MANIFEST_DIR")).unwrap())
238    ///     .build()
239    ///     .unwrap();
240    /// let output = project.compile().unwrap();
241    /// // Tell Cargo that if a source file changes, to rerun this build script.
242    /// project.rerun_if_sources_changed();
243    /// ```
244    pub fn rerun_if_sources_changed(&self) {
245        println!("cargo:rerun-if-changed={}", self.paths.sources.display())
246    }
247
248    /// Attempts to compile the contracts found at the configured source location, see
249    /// `ProjectPathsConfig::sources`.
250    ///
251    /// NOTE: this does not check if the contracts were successfully compiled, see
252    /// `CompilerOutput::has_error` instead.
253    ///
254    /// NB: If the `svm` feature is enabled, this function will automatically detect
255    /// solc versions across files.
256    ///
257    /// # Example
258    ///
259    /// ```
260    /// use ethers_solc::Project;
261    /// # fn demo(project: Project) {
262    /// let project = Project::builder().build().unwrap();
263    /// let output = project.compile().unwrap();
264    /// # }
265    /// ```
266    #[tracing::instrument(skip_all, name = "compile")]
267    pub fn compile(&self) -> Result<ProjectCompileOutput<T>> {
268        let sources = self.paths.read_input_files()?;
269        tracing::trace!("found {} sources to compile: {:?}", sources.len(), sources.keys());
270
271        #[cfg(all(feature = "svm-solc", not(target_arch = "wasm32")))]
272        if self.auto_detect {
273            tracing::trace!("using solc auto detection to compile sources");
274            return self.svm_compile(sources)
275        }
276
277        self.compile_with_version(&self.solc, sources)
278    }
279
280    /// Compiles a set of contracts using `svm` managed solc installs
281    ///
282    /// This will autodetect the appropriate `Solc` version(s) to use when compiling the provided
283    /// `Sources`. Solc auto-detection follows semver rules, see also
284    /// `Graph::get_input_node_versions`
285    ///
286    /// # Errors
287    ///
288    /// This returns an error if contracts in the `Sources` set are incompatible (violate semver
289    /// rules) with their imports, for example source contract `A(=0.8.11)` imports dependency
290    /// `C(<0.8.0)`, which are incompatible.
291    ///
292    /// # Example
293    ///
294    /// ```
295    /// use ethers_solc::{artifacts::Source, Project, utils};
296    /// # fn demo(project: Project) {
297    /// let project = Project::builder().build().unwrap();
298    /// let files = utils::source_files("./src");
299    /// let sources = Source::read_all(files).unwrap();
300    /// let output = project.svm_compile(sources).unwrap();
301    /// # }
302    /// ```
303    #[cfg(all(feature = "svm-solc", not(target_arch = "wasm32")))]
304    pub fn svm_compile(&self, sources: Sources) -> Result<ProjectCompileOutput<T>> {
305        project::ProjectCompiler::with_sources(self, sources)?.compile()
306    }
307
308    /// Convenience function to compile a single solidity file with the project's settings.
309    /// Same as [`Self::svm_compile()`] but with the given `file` as input.
310    ///
311    /// # Example
312    ///
313    /// ```
314    /// use ethers_solc::Project;
315    /// # fn demo(project: Project) {
316    /// let project = Project::builder().build().unwrap();
317    /// let output = project.compile_file("example/Greeter.sol").unwrap();
318    /// # }
319    /// ```
320    #[cfg(all(feature = "svm-solc", not(target_arch = "wasm32")))]
321    pub fn compile_file(&self, file: impl Into<PathBuf>) -> Result<ProjectCompileOutput<T>> {
322        let file = file.into();
323        let source = Source::read(&file)?;
324        project::ProjectCompiler::with_sources(self, Sources::from([(file, source)]))?.compile()
325    }
326
327    /// Convenience function to compile a series of solidity files with the project's settings.
328    /// Same as [`Self::compile()`] but with the given `files` as input.
329    ///
330    /// # Example
331    ///
332    /// ```
333    /// use ethers_solc::Project;
334    /// # fn demo(project: Project) {
335    /// let project = Project::builder().build().unwrap();
336    /// let output = project
337    ///     .compile_files(
338    ///         vec!["examples/Foo.sol", "examples/Bar.sol"]
339    ///     ).unwrap();
340    /// # }
341    /// ```
342    pub fn compile_files<P, I>(&self, files: I) -> Result<ProjectCompileOutput<T>>
343    where
344        I: IntoIterator<Item = P>,
345        P: Into<PathBuf>,
346    {
347        let sources = Source::read_all(files)?;
348
349        #[cfg(all(feature = "svm-solc", not(target_arch = "wasm32")))]
350        if self.auto_detect {
351            return project::ProjectCompiler::with_sources(self, sources)?.compile()
352        }
353
354        let solc = self.configure_solc(self.solc.clone());
355        self.compile_with_version(&solc, sources)
356    }
357
358    /// Convenience function to compile only (re)compile files that match the provided [FileFilter].
359    /// Same as [`Self::compile()`] but with only with those files as input that match
360    /// [FileFilter::is_match()].
361    ///
362    /// # Example - Only compile Test files
363    ///
364    /// ```
365    /// use ethers_solc::{Project, TestFileFilter};
366    /// # fn demo(project: Project) {
367    /// let project = Project::builder().build().unwrap();
368    /// let output = project
369    ///     .compile_sparse(
370    ///         TestFileFilter::default()
371    ///     ).unwrap();
372    /// # }
373    /// ```
374    ///
375    /// # Example - Apply a custom filter
376    ///
377    /// ```
378    /// use std::path::Path;
379    /// use ethers_solc::Project;
380    /// # fn demo(project: Project) {
381    /// let project = Project::builder().build().unwrap();
382    /// let output = project
383    ///     .compile_sparse(
384    ///         |path: &Path| path.ends_with("Greeter.sol")
385    ///     ).unwrap();
386    /// # }
387    /// ```
388    pub fn compile_sparse<F: FileFilter + 'static>(
389        &self,
390        filter: F,
391    ) -> Result<ProjectCompileOutput<T>> {
392        let sources =
393            Source::read_all(self.paths.input_files().into_iter().filter(|p| filter.is_match(p)))?;
394        let filter: Box<dyn FileFilter> = Box::new(filter);
395
396        #[cfg(all(feature = "svm-solc", not(target_arch = "wasm32")))]
397        if self.auto_detect {
398            return project::ProjectCompiler::with_sources(self, sources)?
399                .with_sparse_output(filter)
400                .compile()
401        }
402
403        project::ProjectCompiler::with_sources_and_solc(self, sources, self.solc.clone())?
404            .with_sparse_output(filter)
405            .compile()
406    }
407
408    /// Compiles the given source files with the exact `Solc` executable
409    ///
410    /// First all libraries for the sources are resolved by scanning all their imports.
411    /// If caching is enabled for the `Project`, then all unchanged files are filtered from the
412    /// sources and their existing artifacts are read instead. This will also update the cache
413    /// file and cleans up entries for files which may have been removed. Unchanged files that
414    /// for which an artifact exist, are not compiled again.
415    ///
416    /// # Example
417    ///
418    /// ```
419    /// use ethers_solc::{Project, Solc};
420    /// # fn demo(project: Project) {
421    /// let project = Project::builder().build().unwrap();
422    /// let sources = project.paths.read_sources().unwrap();
423    /// project
424    ///     .compile_with_version(
425    ///         &Solc::find_svm_installed_version("0.8.11").unwrap().unwrap(),
426    ///         sources,
427    ///     )
428    ///     .unwrap();
429    /// # }
430    /// ```
431    pub fn compile_with_version(
432        &self,
433        solc: &Solc,
434        sources: Sources,
435    ) -> Result<ProjectCompileOutput<T>> {
436        project::ProjectCompiler::with_sources_and_solc(self, sources, solc.clone())?.compile()
437    }
438
439    /// Removes the project's artifacts and cache file
440    ///
441    /// If the cache file was the only file in the folder, this also removes the empty folder.
442    ///
443    /// # Example
444    ///
445    /// ```
446    /// use ethers_solc::Project;
447    /// # fn demo(project: Project) {
448    /// let project = Project::builder().build().unwrap();
449    /// let _ = project.compile().unwrap();
450    /// assert!(project.artifacts_path().exists());
451    /// assert!(project.cache_path().exists());
452    ///
453    /// project.cleanup();
454    /// assert!(!project.artifacts_path().exists());
455    /// assert!(!project.cache_path().exists());
456    /// # }
457    /// ```
458    pub fn cleanup(&self) -> std::result::Result<(), SolcIoError> {
459        tracing::trace!("clean up project");
460        if self.cache_path().exists() {
461            std::fs::remove_file(self.cache_path())
462                .map_err(|err| SolcIoError::new(err, self.cache_path()))?;
463            if let Some(cache_folder) = self.cache_path().parent() {
464                // remove the cache folder if the cache file was the only file
465                if cache_folder
466                    .read_dir()
467                    .map_err(|err| SolcIoError::new(err, cache_folder))?
468                    .next()
469                    .is_none()
470                {
471                    std::fs::remove_dir(cache_folder)
472                        .map_err(|err| SolcIoError::new(err, cache_folder))?;
473                }
474            }
475            tracing::trace!("removed cache file \"{}\"", self.cache_path().display());
476        }
477        if self.artifacts_path().exists() {
478            std::fs::remove_dir_all(self.artifacts_path())
479                .map_err(|err| SolcIoError::new(err, self.artifacts_path().clone()))?;
480            tracing::trace!("removed artifacts dir \"{}\"", self.artifacts_path().display());
481        }
482
483        if self.build_info_path().exists() {
484            std::fs::remove_dir_all(self.build_info_path())
485                .map_err(|err| SolcIoError::new(err, self.build_info_path().clone()))?;
486            tracing::trace!("removed build-info dir \"{}\"", self.build_info_path().display());
487        }
488
489        Ok(())
490    }
491
492    /// Flattens the target solidity file into a single string suitable for verification.
493    ///
494    /// This method uses a dependency graph to resolve imported files and substitute
495    /// import directives with the contents of target files. It will strip the pragma
496    /// version directives and SDPX license identifiers from all imported files.
497    ///
498    /// NB: the SDPX license identifier will be removed from the imported file
499    /// only if it is found at the beginning of the file.
500    pub fn flatten(&self, target: &Path) -> Result<String> {
501        self.paths.flatten(target)
502    }
503
504    /// Returns standard-json-input to compile the target contract
505    pub fn standard_json_input(
506        &self,
507        target: impl AsRef<Path>,
508    ) -> Result<StandardJsonCompilerInput> {
509        use path_slash::PathExt;
510
511        let target = target.as_ref();
512        tracing::trace!("Building standard-json-input for {:?}", target);
513        let graph = Graph::resolve(&self.paths)?;
514        let target_index = graph.files().get(target).ok_or_else(|| {
515            SolcError::msg(format!("cannot resolve file at {:?}", target.display()))
516        })?;
517
518        let mut sources = Vec::new();
519        let mut unique_paths = HashSet::new();
520        let (path, source) = graph.node(*target_index).unpack();
521        unique_paths.insert(path.clone());
522        sources.push((path, source));
523        sources.extend(
524            graph
525                .all_imported_nodes(*target_index)
526                .map(|index| graph.node(index).unpack())
527                .filter(|(p, _)| unique_paths.insert(p.to_path_buf())),
528        );
529
530        let root = self.root();
531        let sources = sources
532            .into_iter()
533            .map(|(path, source)| {
534                let path: PathBuf = if let Ok(stripped) = path.strip_prefix(root) {
535                    stripped.to_slash_lossy().into_owned().into()
536                } else {
537                    path.to_slash_lossy().into_owned().into()
538                };
539                (path, source.clone())
540            })
541            .collect();
542
543        let mut settings = self.solc_config.settings.clone();
544        // strip the path to the project root from all remappings
545        settings.remappings = self
546            .paths
547            .remappings
548            .clone()
549            .into_iter()
550            .map(|r| r.into_relative(self.root()).to_relative_remapping())
551            .collect::<Vec<_>>();
552
553        let input = StandardJsonCompilerInput::new(sources, settings);
554
555        Ok(input)
556    }
557}
558
559pub struct ProjectBuilder<T: ArtifactOutput = ConfigurableArtifacts> {
560    /// The layout of the
561    paths: Option<ProjectPathsConfig>,
562    /// Where to find solc
563    solc: Option<Solc>,
564    /// How solc invocation should be configured.
565    solc_config: Option<SolcConfig>,
566    /// Whether caching is enabled, default is true.
567    cached: bool,
568    /// Whether to output build information with each solc call.
569    build_info: bool,
570    /// Whether writing artifacts to disk is enabled, default is true.
571    no_artifacts: bool,
572    /// Whether automatic solc version detection is enabled
573    auto_detect: bool,
574    /// Use offline mode
575    offline: bool,
576    /// Whether to slash paths of the `ProjectCompilerOutput`
577    slash_paths: bool,
578    /// handles all artifacts related tasks
579    artifacts: T,
580    /// Which error codes to ignore
581    pub ignored_error_codes: Vec<u64>,
582    /// The minimum severity level that is treated as a compiler error
583    compiler_severity_filter: Severity,
584    /// All allowed paths for solc's `--allowed-paths`
585    allowed_paths: AllowedLibPaths,
586    /// Paths to use for solc's `--include-path`
587    include_paths: IncludePaths,
588    solc_jobs: Option<usize>,
589}
590
591impl<T: ArtifactOutput> ProjectBuilder<T> {
592    /// Create a new builder with the given artifacts handler
593    pub fn new(artifacts: T) -> Self {
594        Self {
595            paths: None,
596            solc: None,
597            solc_config: None,
598            cached: true,
599            build_info: false,
600            no_artifacts: false,
601            auto_detect: true,
602            offline: false,
603            slash_paths: true,
604            artifacts,
605            ignored_error_codes: Vec::new(),
606            compiler_severity_filter: Severity::Error,
607            allowed_paths: Default::default(),
608            include_paths: Default::default(),
609            solc_jobs: None,
610        }
611    }
612
613    #[must_use]
614    pub fn paths(mut self, paths: ProjectPathsConfig) -> Self {
615        self.paths = Some(paths);
616        self
617    }
618
619    #[must_use]
620    pub fn solc(mut self, solc: impl Into<Solc>) -> Self {
621        self.solc = Some(solc.into());
622        self
623    }
624
625    #[must_use]
626    pub fn solc_config(mut self, solc_config: SolcConfig) -> Self {
627        self.solc_config = Some(solc_config);
628        self
629    }
630
631    #[must_use]
632    pub fn ignore_error_code(mut self, code: u64) -> Self {
633        self.ignored_error_codes.push(code);
634        self
635    }
636
637    #[must_use]
638    pub fn ignore_error_codes(mut self, codes: impl IntoIterator<Item = u64>) -> Self {
639        for code in codes {
640            self = self.ignore_error_code(code);
641        }
642        self
643    }
644
645    #[must_use]
646    pub fn set_compiler_severity_filter(mut self, compiler_severity_filter: Severity) -> Self {
647        self.compiler_severity_filter = compiler_severity_filter;
648        self
649    }
650
651    /// Disables cached builds
652    #[must_use]
653    pub fn ephemeral(self) -> Self {
654        self.set_cached(false)
655    }
656
657    /// Sets the cache status
658    #[must_use]
659    pub fn set_cached(mut self, cached: bool) -> Self {
660        self.cached = cached;
661        self
662    }
663
664    /// Sets the build info value
665    #[must_use]
666    pub fn set_build_info(mut self, build_info: bool) -> Self {
667        self.build_info = build_info;
668        self
669    }
670
671    /// Activates offline mode
672    ///
673    /// Prevents network possible access to download/check solc installs
674    #[must_use]
675    pub fn offline(self) -> Self {
676        self.set_offline(true)
677    }
678
679    /// Sets the offline status
680    #[must_use]
681    pub fn set_offline(mut self, offline: bool) -> Self {
682        self.offline = offline;
683        self
684    }
685
686    /// Sets whether to slash all paths on windows
687    ///
688    /// If set to `true` all `\\` separators are replaced with `/`, same as solc
689    #[must_use]
690    pub fn set_slashed_paths(mut self, slashed_paths: bool) -> Self {
691        self.slash_paths = slashed_paths;
692        self
693    }
694
695    /// Disables writing artifacts to disk
696    #[must_use]
697    pub fn no_artifacts(self) -> Self {
698        self.set_no_artifacts(true)
699    }
700
701    /// Sets the no artifacts status
702    #[must_use]
703    pub fn set_no_artifacts(mut self, artifacts: bool) -> Self {
704        self.no_artifacts = artifacts;
705        self
706    }
707
708    /// Sets automatic solc version detection
709    #[must_use]
710    pub fn set_auto_detect(mut self, auto_detect: bool) -> Self {
711        self.auto_detect = auto_detect;
712        self
713    }
714
715    /// Disables automatic solc version detection
716    #[must_use]
717    pub fn no_auto_detect(self) -> Self {
718        self.set_auto_detect(false)
719    }
720
721    /// Sets the maximum number of parallel `solc` processes to run simultaneously.
722    ///
723    /// # Panics
724    ///
725    /// `jobs` must be at least 1
726    #[must_use]
727    pub fn solc_jobs(mut self, jobs: usize) -> Self {
728        assert!(jobs > 0);
729        self.solc_jobs = Some(jobs);
730        self
731    }
732
733    /// Sets the number of parallel `solc` processes to `1`, no parallelization
734    #[must_use]
735    pub fn single_solc_jobs(self) -> Self {
736        self.solc_jobs(1)
737    }
738
739    /// Set arbitrary `ArtifactOutputHandler`
740    pub fn artifacts<A: ArtifactOutput>(self, artifacts: A) -> ProjectBuilder<A> {
741        let ProjectBuilder {
742            paths,
743            solc,
744            solc_config,
745            cached,
746            no_artifacts,
747            auto_detect,
748            ignored_error_codes,
749            compiler_severity_filter,
750            allowed_paths,
751            include_paths,
752            solc_jobs,
753            offline,
754            build_info,
755            slash_paths,
756            ..
757        } = self;
758        ProjectBuilder {
759            paths,
760            solc,
761            solc_config,
762            cached,
763            no_artifacts,
764            auto_detect,
765            offline,
766            slash_paths,
767            artifacts,
768            ignored_error_codes,
769            compiler_severity_filter,
770            allowed_paths,
771            include_paths,
772            solc_jobs,
773            build_info,
774        }
775    }
776
777    /// Adds an allowed-path to the solc executable
778    #[must_use]
779    pub fn allowed_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
780        self.allowed_paths.insert(path.into());
781        self
782    }
783
784    /// Adds multiple allowed-path to the solc executable
785    #[must_use]
786    pub fn allowed_paths<I, S>(mut self, args: I) -> Self
787    where
788        I: IntoIterator<Item = S>,
789        S: Into<PathBuf>,
790    {
791        for arg in args {
792            self = self.allowed_path(arg);
793        }
794        self
795    }
796
797    /// Adds an `--include-path` to the solc executable
798    #[must_use]
799    pub fn include_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
800        self.include_paths.insert(path.into());
801        self
802    }
803
804    /// Adds multiple include-path to the solc executable
805    #[must_use]
806    pub fn include_paths<I, S>(mut self, args: I) -> Self
807    where
808        I: IntoIterator<Item = S>,
809        S: Into<PathBuf>,
810    {
811        for arg in args {
812            self = self.include_path(arg);
813        }
814        self
815    }
816
817    pub fn build(self) -> Result<Project<T>> {
818        let Self {
819            paths,
820            solc,
821            solc_config,
822            cached,
823            no_artifacts,
824            auto_detect,
825            artifacts,
826            ignored_error_codes,
827            compiler_severity_filter,
828            mut allowed_paths,
829            include_paths,
830            solc_jobs,
831            offline,
832            build_info,
833            slash_paths,
834        } = self;
835
836        let mut paths = paths.map(Ok).unwrap_or_else(ProjectPathsConfig::current_hardhat)?;
837
838        if slash_paths {
839            // ensures we always use `/` paths
840            paths.slash_paths();
841        }
842
843        let solc = solc.unwrap_or_default();
844        let solc_config = solc_config.unwrap_or_else(|| SolcConfig::builder().build());
845
846        // allow every contract under root by default
847        allowed_paths.insert(paths.root.clone());
848
849        Ok(Project {
850            paths,
851            solc,
852            solc_config,
853            cached,
854            build_info,
855            no_artifacts,
856            auto_detect,
857            artifacts,
858            ignored_error_codes,
859            compiler_severity_filter,
860            allowed_paths,
861            include_paths,
862            solc_jobs: solc_jobs.unwrap_or_else(num_cpus::get),
863            offline,
864            slash_paths,
865        })
866    }
867}
868
869impl<T: ArtifactOutput + Default> Default for ProjectBuilder<T> {
870    fn default() -> Self {
871        Self::new(T::default())
872    }
873}
874
875impl<T: ArtifactOutput> ArtifactOutput for Project<T> {
876    type Artifact = T::Artifact;
877
878    fn on_output(
879        &self,
880        contracts: &VersionedContracts,
881        sources: &VersionedSourceFiles,
882        layout: &ProjectPathsConfig,
883        ctx: OutputContext,
884    ) -> Result<Artifacts<Self::Artifact>> {
885        self.artifacts_handler().on_output(contracts, sources, layout, ctx)
886    }
887
888    fn write_contract_extras(&self, contract: &Contract, file: &Path) -> Result<()> {
889        self.artifacts_handler().write_contract_extras(contract, file)
890    }
891
892    fn write_extras(
893        &self,
894        contracts: &VersionedContracts,
895        artifacts: &Artifacts<Self::Artifact>,
896    ) -> Result<()> {
897        self.artifacts_handler().write_extras(contracts, artifacts)
898    }
899
900    fn output_file_name(name: impl AsRef<str>) -> PathBuf {
901        T::output_file_name(name)
902    }
903
904    fn output_file_name_versioned(name: impl AsRef<str>, version: &Version) -> PathBuf {
905        T::output_file_name_versioned(name, version)
906    }
907
908    fn output_file(contract_file: impl AsRef<Path>, name: impl AsRef<str>) -> PathBuf {
909        T::output_file(contract_file, name)
910    }
911
912    fn output_file_versioned(
913        contract_file: impl AsRef<Path>,
914        name: impl AsRef<str>,
915        version: &Version,
916    ) -> PathBuf {
917        T::output_file_versioned(contract_file, name, version)
918    }
919
920    fn contract_name(file: impl AsRef<Path>) -> Option<String> {
921        T::contract_name(file)
922    }
923
924    fn output_exists(
925        contract_file: impl AsRef<Path>,
926        name: impl AsRef<str>,
927        root: impl AsRef<Path>,
928    ) -> bool {
929        T::output_exists(contract_file, name, root)
930    }
931
932    fn read_cached_artifact(path: impl AsRef<Path>) -> Result<Self::Artifact> {
933        T::read_cached_artifact(path)
934    }
935
936    fn read_cached_artifacts<P, I>(files: I) -> Result<BTreeMap<PathBuf, Self::Artifact>>
937    where
938        I: IntoIterator<Item = P>,
939        P: Into<PathBuf>,
940    {
941        T::read_cached_artifacts(files)
942    }
943
944    fn contract_to_artifact(
945        &self,
946        file: &str,
947        name: &str,
948        contract: Contract,
949        source_file: Option<&SourceFile>,
950    ) -> Self::Artifact {
951        self.artifacts_handler().contract_to_artifact(file, name, contract, source_file)
952    }
953
954    fn output_to_artifacts(
955        &self,
956        contracts: &VersionedContracts,
957        sources: &VersionedSourceFiles,
958        ctx: OutputContext,
959        layout: &ProjectPathsConfig,
960    ) -> Artifacts<Self::Artifact> {
961        self.artifacts_handler().output_to_artifacts(contracts, sources, ctx, layout)
962    }
963
964    fn standalone_source_file_to_artifact(
965        &self,
966        path: &str,
967        file: &VersionedSourceFile,
968    ) -> Option<Self::Artifact> {
969        self.artifacts_handler().standalone_source_file_to_artifact(path, file)
970    }
971}
972
973#[cfg(test)]
974#[cfg(all(feature = "svm-solc", not(target_arch = "wasm32")))]
975mod tests {
976    use super::*;
977    use crate::remappings::Remapping;
978
979    #[test]
980    fn test_build_all_versions() {
981        let paths = ProjectPathsConfig::builder()
982            .root("./test-data/test-contract-versions")
983            .sources("./test-data/test-contract-versions")
984            .build()
985            .unwrap();
986        let project = Project::builder().paths(paths).no_artifacts().ephemeral().build().unwrap();
987        let contracts = project.compile().unwrap().succeeded().output().contracts;
988        // Contracts A to F
989        assert_eq!(contracts.contracts().count(), 5);
990    }
991
992    #[test]
993    fn test_build_many_libs() {
994        let root = utils::canonicalize("./test-data/test-contract-libs").unwrap();
995
996        let paths = ProjectPathsConfig::builder()
997            .root(&root)
998            .sources(root.join("src"))
999            .lib(root.join("lib1"))
1000            .lib(root.join("lib2"))
1001            .remappings(
1002                Remapping::find_many(root.join("lib1"))
1003                    .into_iter()
1004                    .chain(Remapping::find_many(root.join("lib2"))),
1005            )
1006            .build()
1007            .unwrap();
1008        let project = Project::builder()
1009            .paths(paths)
1010            .no_artifacts()
1011            .ephemeral()
1012            .no_artifacts()
1013            .build()
1014            .unwrap();
1015        let contracts = project.compile().unwrap().succeeded().output().contracts;
1016        assert_eq!(contracts.contracts().count(), 3);
1017    }
1018
1019    #[test]
1020    fn test_build_remappings() {
1021        let root = utils::canonicalize("./test-data/test-contract-remappings").unwrap();
1022        let paths = ProjectPathsConfig::builder()
1023            .root(&root)
1024            .sources(root.join("src"))
1025            .lib(root.join("lib"))
1026            .remappings(Remapping::find_many(root.join("lib")))
1027            .build()
1028            .unwrap();
1029        let project = Project::builder().no_artifacts().paths(paths).ephemeral().build().unwrap();
1030        let contracts = project.compile().unwrap().succeeded().output().contracts;
1031        assert_eq!(contracts.contracts().count(), 2);
1032    }
1033}