1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(test), warn(unused_crate_dependencies))]
3#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
4
5#[macro_use]
6extern crate tracing;
7
8#[cfg(feature = "project-util")]
9#[macro_use]
10extern crate foundry_compilers_core;
11
12mod artifact_output;
13pub use artifact_output::*;
14
15pub mod buildinfo;
16
17pub mod cache;
18
19pub mod flatten;
20
21pub mod resolver;
22pub use resolver::Graph;
23
24pub mod compilers;
25pub use compilers::*;
26
27mod compile;
28pub use compile::{
29 output::{AggregatedCompilerOutput, ProjectCompileOutput},
30 *,
31};
32
33mod config;
34pub use config::{PathStyle, ProjectPaths, ProjectPathsConfig, SolcConfig};
35
36mod filter;
37pub use filter::{FileFilter, SparseOutputFilter, TestFileFilter};
38
39pub mod report;
40
41#[cfg(feature = "project-util")]
43pub mod project_util;
44
45pub use foundry_compilers_artifacts as artifacts;
46pub use foundry_compilers_core::{error, utils};
47
48use cache::CompilerCache;
49use compile::output::contracts::VersionedContracts;
50use compilers::multi::MultiCompiler;
51use foundry_compilers_artifacts::{
52 output_selection::OutputSelection,
53 solc::{
54 sources::{Source, SourceCompilationKind, Sources},
55 Severity, SourceFile, StandardJsonCompilerInput,
56 },
57};
58use foundry_compilers_core::error::{Result, SolcError, SolcIoError};
59use output::sources::{VersionedSourceFile, VersionedSourceFiles};
60use project::ProjectCompiler;
61use semver::Version;
62use solc::SolcSettings;
63use std::{
64 collections::{BTreeMap, HashMap, HashSet},
65 path::{Path, PathBuf},
66};
67
68#[derive(Clone, derive_more::Debug)]
70pub struct Project<
71 C: Compiler = MultiCompiler,
72 T: ArtifactOutput<CompilerContract = C::CompilerContract> = ConfigurableArtifacts,
73> {
74 pub compiler: C,
75 pub paths: ProjectPathsConfig<C::Language>,
77 pub settings: C::Settings,
79 pub additional_settings: BTreeMap<String, C::Settings>,
82 pub restrictions:
87 BTreeMap<PathBuf, RestrictionsWithVersion<<C::Settings as CompilerSettings>::Restrictions>>,
88 pub cached: bool,
90 pub build_info: bool,
92 pub no_artifacts: bool,
94 pub artifacts: T,
96 pub ignored_error_codes: Vec<u64>,
98 pub ignored_file_paths: Vec<PathBuf>,
100 pub compiler_severity_filter: Severity,
102 solc_jobs: usize,
104 pub offline: bool,
106 pub slash_paths: bool,
110 #[debug(skip)]
112 pub sparse_output: Option<Box<dyn FileFilter>>,
113}
114
115impl Project {
116 pub fn builder() -> ProjectBuilder {
144 ProjectBuilder::default()
145 }
146}
147
148impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler> Project<C, T> {
149 pub fn artifacts_handler(&self) -> &T {
151 &self.artifacts
152 }
153
154 pub fn settings_profiles(&self) -> impl Iterator<Item = (&str, &C::Settings)> {
155 std::iter::once(("default", &self.settings))
156 .chain(self.additional_settings.iter().map(|(p, s)| (p.as_str(), s)))
157 }
158}
159
160impl<C: Compiler, T: ArtifactOutput<CompilerContract = C::CompilerContract>> Project<C, T>
161where
162 C::Settings: Into<SolcSettings>,
163{
164 pub fn standard_json_input(&self, target: &Path) -> Result<StandardJsonCompilerInput> {
166 trace!(?target, "Building standard-json-input");
167 let graph = Graph::<C::ParsedSource>::resolve(&self.paths)?;
168 let target_index = graph.files().get(target).ok_or_else(|| {
169 SolcError::msg(format!("cannot resolve file at {:?}", target.display()))
170 })?;
171
172 let mut sources = Vec::new();
173 let mut unique_paths = HashSet::new();
174 let (path, source) = graph.node(*target_index).unpack();
175 unique_paths.insert(path.clone());
176 sources.push((path, source));
177 sources.extend(
178 graph
179 .all_imported_nodes(*target_index)
180 .map(|index| graph.node(index).unpack())
181 .filter(|(p, _)| unique_paths.insert(p.to_path_buf())),
182 );
183
184 let root = self.root();
185 let sources = sources
186 .into_iter()
187 .map(|(path, source)| (rebase_path(root, path), source.clone()))
188 .collect();
189
190 let mut settings = self.settings.clone().into();
191 settings.remappings = self
193 .paths
194 .remappings
195 .clone()
196 .into_iter()
197 .map(|r| r.into_relative(self.root()).to_relative_remapping())
198 .collect::<Vec<_>>();
199
200 let input = StandardJsonCompilerInput::new(sources, settings.settings);
201
202 Ok(input)
203 }
204}
205
206impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler> Project<C, T> {
207 pub fn artifacts_path(&self) -> &PathBuf {
209 &self.paths.artifacts
210 }
211
212 pub fn sources_path(&self) -> &PathBuf {
214 &self.paths.sources
215 }
216
217 pub fn cache_path(&self) -> &PathBuf {
219 &self.paths.cache
220 }
221
222 pub fn build_info_path(&self) -> &PathBuf {
224 &self.paths.build_infos
225 }
226
227 pub fn root(&self) -> &PathBuf {
229 &self.paths.root
230 }
231
232 pub fn read_cache_file(&self) -> Result<CompilerCache<C::Settings>> {
235 CompilerCache::read_joined(&self.paths)
236 }
237
238 pub fn set_solc_jobs(&mut self, jobs: usize) {
244 assert!(jobs > 0);
245 self.solc_jobs = jobs;
246 }
247
248 #[instrument(skip_all, fields(name = "sources"))]
250 pub fn sources(&self) -> Result<Sources> {
251 self.paths.read_sources()
252 }
253
254 pub fn rerun_if_sources_changed(&self) {
276 println!("cargo:rerun-if-changed={}", self.paths.sources.display())
277 }
278
279 pub fn compile(&self) -> Result<ProjectCompileOutput<C, T>> {
280 project::ProjectCompiler::new(self)?.compile()
281 }
282
283 pub fn compile_file(&self, file: impl Into<PathBuf>) -> Result<ProjectCompileOutput<C, T>> {
294 let file = file.into();
295 let source = Source::read(&file)?;
296 project::ProjectCompiler::with_sources(self, Sources::from([(file, source)]))?.compile()
297 }
298
299 pub fn compile_files<P, I>(&self, files: I) -> Result<ProjectCompileOutput<C, T>>
311 where
312 I: IntoIterator<Item = P>,
313 P: Into<PathBuf>,
314 {
315 let sources = Source::read_all(files)?;
316
317 ProjectCompiler::with_sources(self, sources)?.compile()
318 }
319
320 pub fn cleanup(&self) -> std::result::Result<(), SolcIoError> {
339 trace!("clean up project");
340 if self.cache_path().exists() {
341 std::fs::remove_file(self.cache_path())
342 .map_err(|err| SolcIoError::new(err, self.cache_path()))?;
343 if let Some(cache_folder) =
344 self.cache_path().parent().filter(|cache_folder| self.root() != cache_folder)
345 {
346 if cache_folder
348 .read_dir()
349 .map_err(|err| SolcIoError::new(err, cache_folder))?
350 .next()
351 .is_none()
352 {
353 std::fs::remove_dir(cache_folder)
354 .map_err(|err| SolcIoError::new(err, cache_folder))?;
355 }
356 }
357 trace!("removed cache file \"{}\"", self.cache_path().display());
358 }
359
360 if self.artifacts_path().exists() && self.root() != self.artifacts_path() {
362 std::fs::remove_dir_all(self.artifacts_path())
363 .map_err(|err| SolcIoError::new(err, self.artifacts_path().clone()))?;
364 trace!("removed artifacts dir \"{}\"", self.artifacts_path().display());
365 }
366
367 if self.build_info_path().exists() && self.root() != self.build_info_path() {
369 std::fs::remove_dir_all(self.build_info_path())
370 .map_err(|err| SolcIoError::new(err, self.build_info_path().clone()))?;
371 tracing::trace!("removed build-info dir \"{}\"", self.build_info_path().display());
372 }
373
374 Ok(())
375 }
376
377 fn collect_contract_names(&self) -> Result<HashMap<String, Vec<PathBuf>>>
379 where
380 T: Clone,
381 C: Clone,
382 {
383 let graph = Graph::<C::ParsedSource>::resolve(&self.paths)?;
384 let mut contracts: HashMap<String, Vec<PathBuf>> = HashMap::new();
385 if !graph.is_empty() {
386 for node in &graph.nodes {
387 for contract_name in node.data.contract_names() {
388 contracts
389 .entry(contract_name.clone())
390 .or_default()
391 .push(node.path().to_path_buf());
392 }
393 }
394 }
395 Ok(contracts)
396 }
397
398 pub fn find_contract_path(&self, target_name: &str) -> Result<PathBuf>
401 where
402 T: Clone,
403 C: Clone,
404 {
405 let mut contracts = self.collect_contract_names()?;
406
407 if contracts.get(target_name).is_none_or(|paths| paths.is_empty()) {
408 return Err(SolcError::msg(format!("No contract found with the name `{target_name}`")));
409 }
410 let mut paths = contracts.remove(target_name).unwrap();
411 if paths.len() > 1 {
412 return Err(SolcError::msg(format!(
413 "Multiple contracts found with the name `{target_name}`"
414 )));
415 }
416
417 Ok(paths.remove(0))
418 }
419
420 pub fn update_output_selection(&mut self, f: impl FnOnce(&mut OutputSelection) + Copy) {
423 self.settings.update_output_selection(f);
424 self.additional_settings.iter_mut().for_each(|(_, s)| {
425 s.update_output_selection(f);
426 });
427 }
428}
429
430pub struct ProjectBuilder<
431 C: Compiler = MultiCompiler,
432 T: ArtifactOutput<CompilerContract = C::CompilerContract> = ConfigurableArtifacts,
433> {
434 paths: Option<ProjectPathsConfig<C::Language>>,
436 settings: Option<C::Settings>,
438 additional_settings: BTreeMap<String, C::Settings>,
439 restrictions:
440 BTreeMap<PathBuf, RestrictionsWithVersion<<C::Settings as CompilerSettings>::Restrictions>>,
441 cached: bool,
443 build_info: bool,
445 no_artifacts: bool,
447 offline: bool,
449 slash_paths: bool,
451 artifacts: T,
453 pub ignored_error_codes: Vec<u64>,
455 pub ignored_file_paths: Vec<PathBuf>,
457 compiler_severity_filter: Severity,
459 solc_jobs: Option<usize>,
460 sparse_output: Option<Box<dyn FileFilter>>,
462}
463
464impl<C: Compiler, T: ArtifactOutput<CompilerContract = C::CompilerContract>> ProjectBuilder<C, T> {
465 pub fn new(artifacts: T) -> Self {
467 Self {
468 paths: None,
469 cached: true,
470 build_info: false,
471 no_artifacts: false,
472 offline: false,
473 slash_paths: true,
474 artifacts,
475 ignored_error_codes: Vec::new(),
476 ignored_file_paths: Vec::new(),
477 compiler_severity_filter: Severity::Error,
478 solc_jobs: None,
479 settings: None,
480 sparse_output: None,
481 additional_settings: BTreeMap::new(),
482 restrictions: BTreeMap::new(),
483 }
484 }
485
486 #[must_use]
487 pub fn paths(mut self, paths: ProjectPathsConfig<C::Language>) -> Self {
488 self.paths = Some(paths);
489 self
490 }
491
492 #[must_use]
493 pub fn settings(mut self, settings: C::Settings) -> Self {
494 self.settings = Some(settings);
495 self
496 }
497
498 #[must_use]
499 pub fn ignore_error_code(mut self, code: u64) -> Self {
500 self.ignored_error_codes.push(code);
501 self
502 }
503
504 #[must_use]
505 pub fn ignore_error_codes(mut self, codes: impl IntoIterator<Item = u64>) -> Self {
506 for code in codes {
507 self = self.ignore_error_code(code);
508 }
509 self
510 }
511
512 pub fn ignore_paths(mut self, paths: Vec<PathBuf>) -> Self {
513 self.ignored_file_paths = paths;
514 self
515 }
516
517 #[must_use]
518 pub fn set_compiler_severity_filter(mut self, compiler_severity_filter: Severity) -> Self {
519 self.compiler_severity_filter = compiler_severity_filter;
520 self
521 }
522
523 #[must_use]
525 pub fn ephemeral(self) -> Self {
526 self.set_cached(false)
527 }
528
529 #[must_use]
531 pub fn set_cached(mut self, cached: bool) -> Self {
532 self.cached = cached;
533 self
534 }
535
536 #[must_use]
538 pub fn set_build_info(mut self, build_info: bool) -> Self {
539 self.build_info = build_info;
540 self
541 }
542
543 #[must_use]
547 pub fn offline(self) -> Self {
548 self.set_offline(true)
549 }
550
551 #[must_use]
553 pub fn set_offline(mut self, offline: bool) -> Self {
554 self.offline = offline;
555 self
556 }
557
558 #[must_use]
562 pub fn set_slashed_paths(mut self, slashed_paths: bool) -> Self {
563 self.slash_paths = slashed_paths;
564 self
565 }
566
567 #[must_use]
569 pub fn no_artifacts(self) -> Self {
570 self.set_no_artifacts(true)
571 }
572
573 #[must_use]
575 pub fn set_no_artifacts(mut self, artifacts: bool) -> Self {
576 self.no_artifacts = artifacts;
577 self
578 }
579
580 #[must_use]
586 pub fn solc_jobs(mut self, jobs: usize) -> Self {
587 assert!(jobs > 0);
588 self.solc_jobs = Some(jobs);
589 self
590 }
591
592 #[must_use]
594 pub fn single_solc_jobs(self) -> Self {
595 self.solc_jobs(1)
596 }
597
598 #[must_use]
599 pub fn sparse_output<F>(mut self, filter: F) -> Self
600 where
601 F: FileFilter + 'static,
602 {
603 self.sparse_output = Some(Box::new(filter));
604 self
605 }
606
607 #[must_use]
608 pub fn additional_settings(mut self, additional: BTreeMap<String, C::Settings>) -> Self {
609 self.additional_settings = additional;
610 self
611 }
612
613 #[must_use]
614 pub fn restrictions(
615 mut self,
616 restrictions: BTreeMap<
617 PathBuf,
618 RestrictionsWithVersion<<C::Settings as CompilerSettings>::Restrictions>,
619 >,
620 ) -> Self {
621 self.restrictions = restrictions;
622 self
623 }
624
625 pub fn artifacts<A: ArtifactOutput<CompilerContract = C::CompilerContract>>(
627 self,
628 artifacts: A,
629 ) -> ProjectBuilder<C, A> {
630 let Self {
631 paths,
632 cached,
633 no_artifacts,
634 ignored_error_codes,
635 compiler_severity_filter,
636 solc_jobs,
637 offline,
638 build_info,
639 slash_paths,
640 ignored_file_paths,
641 settings,
642 sparse_output,
643 additional_settings,
644 restrictions,
645 ..
646 } = self;
647 ProjectBuilder {
648 paths,
649 cached,
650 no_artifacts,
651 additional_settings,
652 restrictions,
653 offline,
654 slash_paths,
655 artifacts,
656 ignored_error_codes,
657 ignored_file_paths,
658 compiler_severity_filter,
659 solc_jobs,
660 build_info,
661 settings,
662 sparse_output,
663 }
664 }
665
666 pub fn build(self, compiler: C) -> Result<Project<C, T>> {
667 let Self {
668 paths,
669 cached,
670 no_artifacts,
671 artifacts,
672 ignored_error_codes,
673 ignored_file_paths,
674 compiler_severity_filter,
675 solc_jobs,
676 offline,
677 build_info,
678 slash_paths,
679 settings,
680 sparse_output,
681 additional_settings,
682 restrictions,
683 } = self;
684
685 let mut paths = paths.map(Ok).unwrap_or_else(ProjectPathsConfig::current_hardhat)?;
686
687 if slash_paths {
688 paths.slash_paths();
690 }
691
692 Ok(Project {
693 compiler,
694 paths,
695 cached,
696 build_info,
697 no_artifacts,
698 artifacts,
699 ignored_error_codes,
700 ignored_file_paths,
701 compiler_severity_filter,
702 solc_jobs: solc_jobs
703 .or_else(|| std::thread::available_parallelism().ok().map(|n| n.get()))
704 .unwrap_or(1),
705 offline,
706 slash_paths,
707 settings: settings.unwrap_or_default(),
708 sparse_output,
709 additional_settings,
710 restrictions,
711 })
712 }
713}
714
715impl<C: Compiler, T: ArtifactOutput<CompilerContract = C::CompilerContract> + Default> Default
716 for ProjectBuilder<C, T>
717{
718 fn default() -> Self {
719 Self::new(T::default())
720 }
721}
722
723impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler> ArtifactOutput
724 for Project<C, T>
725{
726 type Artifact = T::Artifact;
727 type CompilerContract = C::CompilerContract;
728
729 fn on_output<CP>(
730 &self,
731 contracts: &VersionedContracts<C::CompilerContract>,
732 sources: &VersionedSourceFiles,
733 layout: &ProjectPathsConfig<CP>,
734 ctx: OutputContext<'_>,
735 primary_profiles: &HashMap<PathBuf, &str>,
736 ) -> Result<Artifacts<Self::Artifact>> {
737 self.artifacts_handler().on_output(contracts, sources, layout, ctx, primary_profiles)
738 }
739
740 fn handle_artifacts(
741 &self,
742 contracts: &VersionedContracts<C::CompilerContract>,
743 artifacts: &Artifacts<Self::Artifact>,
744 ) -> Result<()> {
745 self.artifacts_handler().handle_artifacts(contracts, artifacts)
746 }
747
748 fn output_file_name(
749 name: &str,
750 version: &Version,
751 profile: &str,
752 with_version: bool,
753 with_profile: bool,
754 ) -> PathBuf {
755 T::output_file_name(name, version, profile, with_version, with_profile)
756 }
757
758 fn output_file(
759 contract_file: &Path,
760 name: &str,
761 version: &Version,
762 profile: &str,
763 with_version: bool,
764 with_profile: bool,
765 ) -> PathBuf {
766 T::output_file(contract_file, name, version, profile, with_version, with_profile)
767 }
768
769 fn contract_name(file: &Path) -> Option<String> {
770 T::contract_name(file)
771 }
772
773 fn read_cached_artifact(path: &Path) -> Result<Self::Artifact> {
774 T::read_cached_artifact(path)
775 }
776
777 fn read_cached_artifacts<P, I>(files: I) -> Result<BTreeMap<PathBuf, Self::Artifact>>
778 where
779 I: IntoIterator<Item = P>,
780 P: Into<PathBuf>,
781 {
782 T::read_cached_artifacts(files)
783 }
784
785 fn contract_to_artifact(
786 &self,
787 file: &Path,
788 name: &str,
789 contract: C::CompilerContract,
790 source_file: Option<&SourceFile>,
791 ) -> Self::Artifact {
792 self.artifacts_handler().contract_to_artifact(file, name, contract, source_file)
793 }
794
795 fn output_to_artifacts<CP>(
796 &self,
797 contracts: &VersionedContracts<C::CompilerContract>,
798 sources: &VersionedSourceFiles,
799 ctx: OutputContext<'_>,
800 layout: &ProjectPathsConfig<CP>,
801 primary_profiles: &HashMap<PathBuf, &str>,
802 ) -> Artifacts<Self::Artifact> {
803 self.artifacts_handler().output_to_artifacts(
804 contracts,
805 sources,
806 ctx,
807 layout,
808 primary_profiles,
809 )
810 }
811
812 fn standalone_source_file_to_artifact(
813 &self,
814 path: &Path,
815 file: &VersionedSourceFile,
816 ) -> Option<Self::Artifact> {
817 self.artifacts_handler().standalone_source_file_to_artifact(path, file)
818 }
819
820 fn is_dirty(&self, artifact_file: &ArtifactFile<Self::Artifact>) -> Result<bool> {
821 self.artifacts_handler().is_dirty(artifact_file)
822 }
823
824 fn handle_cached_artifacts(&self, artifacts: &Artifacts<Self::Artifact>) -> Result<()> {
825 self.artifacts_handler().handle_cached_artifacts(artifacts)
826 }
827}
828
829fn rebase_path(base: &Path, path: &Path) -> PathBuf {
857 use path_slash::PathExt;
858
859 let mut base_components = base.components();
860 let mut path_components = path.components();
861
862 let mut new_path = PathBuf::new();
863
864 while let Some(path_component) = path_components.next() {
865 let base_component = base_components.next();
866
867 if Some(path_component) != base_component {
868 if base_component.is_some() {
869 new_path.extend(std::iter::repeat_n(
870 std::path::Component::ParentDir,
871 base_components.count() + 1,
872 ));
873 }
874
875 new_path.push(path_component);
876 new_path.extend(path_components);
877
878 break;
879 }
880 }
881
882 new_path.to_slash_lossy().into_owned().into()
883}
884
885#[cfg(test)]
886#[cfg(feature = "svm-solc")]
887mod tests {
888 use foundry_compilers_artifacts::Remapping;
889 use foundry_compilers_core::utils::{self, mkdir_or_touch, tempdir};
890
891 use super::*;
892
893 #[test]
894 #[cfg_attr(windows, ignore = "<0.7 solc is flaky")]
895 fn test_build_all_versions() {
896 let paths = ProjectPathsConfig::builder()
897 .root("../../test-data/test-contract-versions")
898 .sources("../../test-data/test-contract-versions")
899 .build()
900 .unwrap();
901 let project = Project::builder()
902 .paths(paths)
903 .no_artifacts()
904 .ephemeral()
905 .build(Default::default())
906 .unwrap();
907 let contracts = project.compile().unwrap().succeeded().into_output().contracts;
908 assert_eq!(contracts.contracts().count(), 3);
910 }
911
912 #[test]
913 fn test_build_many_libs() {
914 let root = utils::canonicalize("../../test-data/test-contract-libs").unwrap();
915
916 let paths = ProjectPathsConfig::builder()
917 .root(&root)
918 .sources(root.join("src"))
919 .lib(root.join("lib1"))
920 .lib(root.join("lib2"))
921 .remappings(
922 Remapping::find_many(&root.join("lib1"))
923 .into_iter()
924 .chain(Remapping::find_many(&root.join("lib2"))),
925 )
926 .build()
927 .unwrap();
928 let project = Project::builder()
929 .paths(paths)
930 .no_artifacts()
931 .ephemeral()
932 .no_artifacts()
933 .build(Default::default())
934 .unwrap();
935 let contracts = project.compile().unwrap().succeeded().into_output().contracts;
936 assert_eq!(contracts.contracts().count(), 3);
937 }
938
939 #[test]
940 fn test_build_remappings() {
941 let root = utils::canonicalize("../../test-data/test-contract-remappings").unwrap();
942 let paths = ProjectPathsConfig::builder()
943 .root(&root)
944 .sources(root.join("src"))
945 .lib(root.join("lib"))
946 .remappings(Remapping::find_many(&root.join("lib")))
947 .build()
948 .unwrap();
949 let project = Project::builder()
950 .no_artifacts()
951 .paths(paths)
952 .ephemeral()
953 .build(Default::default())
954 .unwrap();
955 let contracts = project.compile().unwrap().succeeded().into_output().contracts;
956 assert_eq!(contracts.contracts().count(), 2);
957 }
958
959 #[test]
960 fn can_rebase_path() {
961 let rebase_path = |a: &str, b: &str| rebase_path(a.as_ref(), b.as_ref());
962
963 assert_eq!(rebase_path("a/b", "a/b/c"), PathBuf::from("c"));
964 assert_eq!(rebase_path("a/b", "a/c"), PathBuf::from("../c"));
965 assert_eq!(rebase_path("a/b", "c"), PathBuf::from("../../c"));
966
967 assert_eq!(
968 rebase_path("/home/user/project", "/home/user/project/A.sol"),
969 PathBuf::from("A.sol")
970 );
971 assert_eq!(
972 rebase_path("/home/user/project", "/home/user/project/src/A.sol"),
973 PathBuf::from("src/A.sol")
974 );
975 assert_eq!(
976 rebase_path("/home/user/project", "/home/user/project/lib/forge-std/src/Test.sol"),
977 PathBuf::from("lib/forge-std/src/Test.sol")
978 );
979 assert_eq!(
980 rebase_path("/home/user/project", "/home/user/A.sol"),
981 PathBuf::from("../A.sol")
982 );
983 assert_eq!(rebase_path("/home/user/project", "/home/A.sol"), PathBuf::from("../../A.sol"));
984 assert_eq!(rebase_path("/home/user/project", "/A.sol"), PathBuf::from("../../../A.sol"));
985 assert_eq!(
986 rebase_path("/home/user/project", "/tmp/A.sol"),
987 PathBuf::from("../../../tmp/A.sol")
988 );
989
990 assert_eq!(
991 rebase_path("/Users/ah/temp/verif", "/Users/ah/temp/remapped/Child.sol"),
992 PathBuf::from("../remapped/Child.sol")
993 );
994 assert_eq!(
995 rebase_path("/Users/ah/temp/verif", "/Users/ah/temp/verif/../remapped/Parent.sol"),
996 PathBuf::from("../remapped/Parent.sol")
997 );
998 }
999
1000 #[test]
1001 fn can_resolve_oz_remappings() {
1002 let tmp_dir = tempdir("node_modules").unwrap();
1003 let tmp_dir_node_modules = tmp_dir.path().join("node_modules");
1004 let paths = [
1005 "node_modules/@openzeppelin/contracts/interfaces/IERC1155.sol",
1006 "node_modules/@openzeppelin/contracts/finance/VestingWallet.sol",
1007 "node_modules/@openzeppelin/contracts/proxy/Proxy.sol",
1008 "node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol",
1009 ];
1010 mkdir_or_touch(tmp_dir.path(), &paths[..]);
1011 let remappings = Remapping::find_many(&tmp_dir_node_modules);
1012 let mut paths = ProjectPathsConfig::<()>::hardhat(tmp_dir.path()).unwrap();
1013 paths.remappings = remappings;
1014
1015 let resolved = paths
1016 .resolve_library_import(
1017 tmp_dir.path(),
1018 Path::new("@openzeppelin/contracts/token/ERC20/IERC20.sol"),
1019 )
1020 .unwrap();
1021 assert!(resolved.exists());
1022
1023 paths.remappings[0].name = "@openzeppelin/".to_string();
1025
1026 let resolved = paths
1027 .resolve_library_import(
1028 tmp_dir.path(),
1029 Path::new("@openzeppelin/contracts/token/ERC20/IERC20.sol"),
1030 )
1031 .unwrap();
1032 assert!(resolved.exists());
1033 }
1034}