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#[cfg(feature = "project-util")]
54pub mod project_util;
55
56#[derive(Debug)]
58pub struct Project<T: ArtifactOutput = ConfigurableArtifacts> {
59 pub paths: ProjectPathsConfig,
61 pub solc: Solc,
63 pub solc_config: SolcConfig,
65 pub cached: bool,
67 pub build_info: bool,
69 pub no_artifacts: bool,
71 pub auto_detect: bool,
73 pub artifacts: T,
75 pub ignored_error_codes: Vec<u64>,
77 pub compiler_severity_filter: Severity,
79 pub allowed_paths: AllowedLibPaths,
81 pub include_paths: IncludePaths,
83 solc_jobs: usize,
85 pub offline: bool,
87 pub slash_paths: bool,
91}
92
93impl Project {
94 pub fn builder() -> ProjectBuilder {
119 ProjectBuilder::default()
120 }
121}
122
123impl<T: ArtifactOutput> Project<T> {
124 pub fn artifacts_path(&self) -> &PathBuf {
126 &self.paths.artifacts
127 }
128
129 pub fn sources_path(&self) -> &PathBuf {
131 &self.paths.sources
132 }
133
134 pub fn cache_path(&self) -> &PathBuf {
136 &self.paths.cache
137 }
138
139 pub fn build_info_path(&self) -> &PathBuf {
141 &self.paths.build_infos
142 }
143
144 pub fn root(&self) -> &PathBuf {
146 &self.paths.root
147 }
148
149 pub fn artifacts_handler(&self) -> &T {
151 &self.artifacts
152 }
153
154 pub fn read_cache_file(&self) -> Result<SolFilesCache> {
157 SolFilesCache::read_joined(&self.paths)
158 }
159
160 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 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 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 pub fn set_solc_jobs(&mut self, jobs: usize) {
214 assert!(jobs > 0);
215 self.solc_jobs = jobs;
216 }
217
218 #[tracing::instrument(skip_all, fields(name = "sources"))]
220 pub fn sources(&self) -> Result<Sources> {
221 self.paths.read_sources()
222 }
223
224 pub fn rerun_if_sources_changed(&self) {
245 println!("cargo:rerun-if-changed={}", self.paths.sources.display())
246 }
247
248 #[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 #[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 #[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 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 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 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 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 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 pub fn flatten(&self, target: &Path) -> Result<String> {
501 self.paths.flatten(target)
502 }
503
504 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 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 paths: Option<ProjectPathsConfig>,
562 solc: Option<Solc>,
564 solc_config: Option<SolcConfig>,
566 cached: bool,
568 build_info: bool,
570 no_artifacts: bool,
572 auto_detect: bool,
574 offline: bool,
576 slash_paths: bool,
578 artifacts: T,
580 pub ignored_error_codes: Vec<u64>,
582 compiler_severity_filter: Severity,
584 allowed_paths: AllowedLibPaths,
586 include_paths: IncludePaths,
588 solc_jobs: Option<usize>,
589}
590
591impl<T: ArtifactOutput> ProjectBuilder<T> {
592 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 #[must_use]
653 pub fn ephemeral(self) -> Self {
654 self.set_cached(false)
655 }
656
657 #[must_use]
659 pub fn set_cached(mut self, cached: bool) -> Self {
660 self.cached = cached;
661 self
662 }
663
664 #[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 #[must_use]
675 pub fn offline(self) -> Self {
676 self.set_offline(true)
677 }
678
679 #[must_use]
681 pub fn set_offline(mut self, offline: bool) -> Self {
682 self.offline = offline;
683 self
684 }
685
686 #[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 #[must_use]
697 pub fn no_artifacts(self) -> Self {
698 self.set_no_artifacts(true)
699 }
700
701 #[must_use]
703 pub fn set_no_artifacts(mut self, artifacts: bool) -> Self {
704 self.no_artifacts = artifacts;
705 self
706 }
707
708 #[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 #[must_use]
717 pub fn no_auto_detect(self) -> Self {
718 self.set_auto_detect(false)
719 }
720
721 #[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 #[must_use]
735 pub fn single_solc_jobs(self) -> Self {
736 self.solc_jobs(1)
737 }
738
739 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 #[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 #[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 #[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 #[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 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 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 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}