1use crate::{
2 cache::SOLIDITY_FILES_CACHE_FILENAME,
3 compilers::{multi::MultiCompilerLanguage, Language},
4 flatten::{collect_ordered_deps, combine_version_pragmas},
5 resolver::{parse::SolParser, SolImportAlias},
6 Graph,
7};
8use foundry_compilers_artifacts::{
9 output_selection::ContractOutputSelection,
10 remappings::Remapping,
11 sources::{Source, Sources},
12 Libraries, Settings, SolcLanguage,
13};
14use foundry_compilers_core::{
15 error::{Result, SolcError, SolcIoError},
16 utils::{self, strip_prefix_owned},
17};
18use serde::{Deserialize, Serialize};
19use std::{
20 collections::BTreeSet,
21 fmt::{self, Formatter},
22 fs,
23 marker::PhantomData,
24 path::{Component, Path, PathBuf},
25};
26
27#[derive(Clone, Debug, Serialize, Deserialize)]
29pub struct ProjectPathsConfig<L = MultiCompilerLanguage> {
30 pub root: PathBuf,
32 pub cache: PathBuf,
34 pub artifacts: PathBuf,
36 pub build_infos: PathBuf,
38 pub sources: PathBuf,
40 pub tests: PathBuf,
42 pub scripts: PathBuf,
44 pub libraries: Vec<PathBuf>,
46 pub remappings: Vec<Remapping>,
48 pub include_paths: BTreeSet<PathBuf>,
50 pub allowed_paths: BTreeSet<PathBuf>,
52
53 pub _l: PhantomData<L>,
54}
55
56impl ProjectPathsConfig {
57 pub fn builder() -> ProjectPathsConfigBuilder {
58 ProjectPathsConfigBuilder::default()
59 }
60
61 pub fn find_artifacts_dir(root: &Path) -> PathBuf {
68 utils::find_fave_or_alt_path(root, "out", "artifacts")
69 }
70
71 pub fn find_source_dir(root: &Path) -> PathBuf {
78 utils::find_fave_or_alt_path(root, "src", "contracts")
79 }
80
81 pub fn find_libs(root: &Path) -> Vec<PathBuf> {
88 vec![utils::find_fave_or_alt_path(root, "lib", "node_modules")]
89 }
90}
91
92impl ProjectPathsConfig<SolcLanguage> {
93 pub fn flatten(&self, target: &Path) -> Result<String> {
102 trace!("flattening file");
103 let mut input_files = self.input_files();
104
105 let flatten_target = target.to_path_buf();
108 if !input_files.contains(&flatten_target) {
109 input_files.push(flatten_target.clone());
110 }
111
112 let sources = Source::read_all_files(input_files)?;
113 let graph = Graph::<SolParser>::resolve_sources(self, sources)?;
114 let ordered_deps = collect_ordered_deps(&flatten_target, self, &graph)?;
115
116 #[cfg(windows)]
117 let ordered_deps = {
118 use path_slash::PathBufExt;
119
120 let mut deps = ordered_deps;
121 for p in &mut deps {
122 *p = PathBuf::from(p.to_slash_lossy().to_string());
123 }
124 deps
125 };
126
127 let mut sources = Vec::new();
128 let mut experimental_pragma = None;
129 let mut version_pragmas = Vec::new();
130
131 let mut result = String::new();
132
133 for path in ordered_deps.iter() {
134 let node_id = *graph.files().get(path).ok_or_else(|| {
135 SolcError::msg(format!("cannot resolve file at {}", path.display()))
136 })?;
137 let node = graph.node(node_id);
138 node.data.parse_result()?;
139 let content = node.content();
140
141 let mut ranges_to_remove = Vec::new();
144
145 if let Some(license) = &node.data.license {
146 ranges_to_remove.push(license.span());
147 if *path == flatten_target {
148 result.push_str(&content[license.span()]);
149 result.push('\n');
150 }
151 }
152 if let Some(version) = &node.data.version {
153 let content = &content[version.span()];
154 ranges_to_remove.push(version.span());
155 version_pragmas.push(content);
156 }
157 if let Some(experimental) = &node.data.experimental {
158 ranges_to_remove.push(experimental.span());
159 if experimental_pragma.is_none() {
160 experimental_pragma = Some(content[experimental.span()].to_owned());
161 }
162 }
163 for import in &node.data.imports {
164 ranges_to_remove.push(import.span());
165 }
166 ranges_to_remove.sort_by_key(|loc| loc.start);
167
168 let mut content = content.as_bytes().to_vec();
169 let mut offset = 0_isize;
170
171 for range in ranges_to_remove {
172 let repl_range = utils::range_by_offset(&range, offset);
173 offset -= repl_range.len() as isize;
174 content.splice(repl_range, std::iter::empty());
175 }
176
177 let mut content = String::from_utf8(content).map_err(|err| {
178 SolcError::msg(format!("failed to convert extended bytes to string: {err}"))
179 })?;
180
181 for alias in node.data.imports.iter().flat_map(|i| i.data().aliases()) {
183 let (alias, target) = match alias {
184 SolImportAlias::Contract(alias, target) => (alias.clone(), target.clone()),
185 _ => continue,
186 };
187 let name_regex = utils::create_contract_or_lib_name_regex(&alias);
188 let target_len = target.len() as isize;
189 let mut replace_offset = 0;
190 for cap in name_regex.captures_iter(&content.clone()) {
191 if cap.name("ignore").is_some() {
192 continue;
193 }
194 if let Some(name_match) =
195 ["n1", "n2", "n3"].iter().find_map(|name| cap.name(name))
196 {
197 let name_match_range =
198 utils::range_by_offset(&name_match.range(), replace_offset);
199 replace_offset += target_len - (name_match_range.len() as isize);
200 content.replace_range(name_match_range, &target);
201 }
202 }
203 }
204
205 let content = format!(
206 "// {}\n{}",
207 path.strip_prefix(&self.root).unwrap_or(path).display(),
208 content
209 );
210
211 sources.push(content);
212 }
213
214 if let Some(version) = combine_version_pragmas(&version_pragmas) {
215 result.push_str(&version);
216 result.push('\n');
217 }
218 if let Some(experimental) = experimental_pragma {
219 result.push_str(&experimental);
220 result.push('\n');
221 }
222
223 for source in sources {
224 result.push_str("\n\n");
225 result.push_str(&source);
226 }
227
228 Ok(format!("{}\n", utils::RE_THREE_OR_MORE_NEWLINES.replace_all(&result, "\n\n").trim()))
229 }
230}
231
232impl<L> ProjectPathsConfig<L> {
233 pub fn hardhat(root: &Path) -> Result<Self> {
235 PathStyle::HardHat.paths(root)
236 }
237
238 pub fn dapptools(root: &Path) -> Result<Self> {
240 PathStyle::Dapptools.paths(root)
241 }
242
243 pub fn current_hardhat() -> Result<Self> {
245 Self::hardhat(&std::env::current_dir().map_err(|err| SolcError::io(err, "."))?)
246 }
247
248 pub fn current_dapptools() -> Result<Self> {
250 Self::dapptools(&std::env::current_dir().map_err(|err| SolcError::io(err, "."))?)
251 }
252
253 pub fn is_test_or_script(&self, path: &Path) -> bool {
255 self.is_test(path) || self.is_script(path)
256 }
257
258 pub fn is_test(&self, path: &Path) -> bool {
260 path_starts_with_rooted(path, &self.tests, &self.root)
261 }
262
263 pub fn is_script(&self, path: &Path) -> bool {
265 path_starts_with_rooted(path, &self.scripts, &self.root)
266 }
267
268 pub fn is_source_file(&self, path: &Path) -> bool {
270 !self.is_test_or_script(path)
271 }
272
273 pub fn paths(&self) -> ProjectPaths {
276 ProjectPaths {
277 artifacts: self.artifacts.clone(),
278 build_infos: self.build_infos.clone(),
279 sources: self.sources.clone(),
280 tests: self.tests.clone(),
281 scripts: self.scripts.clone(),
282 libraries: self.libraries.iter().cloned().collect(),
283 }
284 }
285
286 pub fn paths_relative(&self) -> ProjectPaths {
290 let mut paths = self.paths();
291 paths.strip_prefix_all(&self.root);
292 paths
293 }
294
295 pub fn create_all(&self) -> std::result::Result<(), SolcIoError> {
297 if let Some(parent) = self.cache.parent() {
298 fs::create_dir_all(parent).map_err(|err| SolcIoError::new(err, parent))?;
299 }
300 fs::create_dir_all(&self.artifacts)
301 .map_err(|err| SolcIoError::new(err, &self.artifacts))?;
302 fs::create_dir_all(&self.sources).map_err(|err| SolcIoError::new(err, &self.sources))?;
303 fs::create_dir_all(&self.tests).map_err(|err| SolcIoError::new(err, &self.tests))?;
304 fs::create_dir_all(&self.scripts).map_err(|err| SolcIoError::new(err, &self.scripts))?;
305 for lib in &self.libraries {
306 fs::create_dir_all(lib).map_err(|err| SolcIoError::new(err, lib))?;
307 }
308 Ok(())
309 }
310
311 pub fn slash_paths(&mut self) {
313 #[cfg(windows)]
314 {
315 use path_slash::PathBufExt;
316
317 let slashed = |p: &mut PathBuf| {
318 *p = p.to_slash_lossy().as_ref().into();
319 };
320 slashed(&mut self.root);
321 slashed(&mut self.cache);
322 slashed(&mut self.artifacts);
323 slashed(&mut self.build_infos);
324 slashed(&mut self.sources);
325 slashed(&mut self.tests);
326 slashed(&mut self.scripts);
327
328 self.libraries.iter_mut().for_each(slashed);
329 self.remappings.iter_mut().for_each(Remapping::slash_path);
330
331 self.include_paths = std::mem::take(&mut self.include_paths)
332 .into_iter()
333 .map(|mut p| {
334 slashed(&mut p);
335 p
336 })
337 .collect();
338 self.allowed_paths = std::mem::take(&mut self.allowed_paths)
339 .into_iter()
340 .map(|mut p| {
341 slashed(&mut p);
342 p
343 })
344 .collect();
345 }
346 }
347
348 pub fn has_library_ancestor(&self, file: &Path) -> bool {
350 self.find_library_ancestor(file).is_some()
351 }
352
353 pub fn find_library_ancestor(&self, file: &Path) -> Option<&Path> {
374 for lib in &self.libraries {
375 if lib.is_relative()
376 && file.is_absolute()
377 && file.starts_with(&self.root)
378 && file.starts_with(self.root.join(lib))
379 || file.is_relative()
380 && lib.is_absolute()
381 && lib.starts_with(&self.root)
382 && self.root.join(file).starts_with(lib)
383 {
384 return Some(lib);
385 }
386 if file.starts_with(lib) {
387 return Some(lib);
388 }
389 }
390
391 None
392 }
393
394 pub fn resolve_import_and_include_paths(
401 &self,
402 cwd: &Path,
403 import: &Path,
404 include_paths: &mut BTreeSet<PathBuf>,
405 ) -> Result<PathBuf> {
406 let component = import
407 .components()
408 .next()
409 .ok_or_else(|| SolcError::msg(format!("Empty import path {}", import.display())))?;
410
411 if component == Component::CurDir || component == Component::ParentDir {
412 utils::normalize_solidity_import_path(cwd, import).map_err(|err| {
415 SolcError::msg(format!("failed to resolve relative import \"{err:?}\""))
416 })
417 } else {
418 let resolved = self.resolve_library_import(cwd.as_ref(), import.as_ref());
420
421 if resolved.is_none() {
422 if let Some(lib) = self.find_library_ancestor(cwd) {
426 if let Some((include_path, import)) =
427 utils::resolve_absolute_library(lib, cwd, import)
428 {
429 include_paths.insert(include_path);
431 return Ok(import);
432 }
433 }
434 for path in [&self.root, &self.sources, &self.tests, &self.scripts] {
436 if cwd.starts_with(path) {
437 if let Ok(import) = utils::normalize_solidity_import_path(path, import) {
438 return Ok(import);
439 }
440 }
441 }
442 }
443
444 resolved.ok_or_else(|| {
445 SolcError::msg(format!(
446 "failed to resolve library import \"{:?}\"",
447 import.display()
448 ))
449 })
450 }
451 }
452
453 pub fn resolve_import(&self, cwd: &Path, import: &Path) -> Result<PathBuf> {
457 self.resolve_import_and_include_paths(cwd, import, &mut Default::default())
458 }
459
460 pub fn resolve_library_import(&self, cwd: &Path, import: &Path) -> Option<PathBuf> {
511 let cwd = cwd.strip_prefix(&self.root).unwrap_or(cwd);
514 if let Some(path) = self
515 .remappings
516 .iter()
517 .filter(|r| {
518 if let Some(ctx) = r.context.as_ref() {
520 cwd.starts_with(ctx)
521 } else {
522 true
523 }
524 })
525 .find_map(|r| {
526 import.strip_prefix(&r.name).ok().map(|stripped_import| {
527 let lib_path =
528 if stripped_import.as_os_str().is_empty() && r.path.ends_with(".sol") {
529 r.path.clone().into()
530 } else {
531 Path::new(&r.path).join(stripped_import)
532 };
533
534 if let Ok(adjusted_import) = stripped_import.strip_prefix("contracts/") {
538 if r.path.ends_with("contracts/") && !lib_path.exists() {
539 return Path::new(&r.path).join(adjusted_import);
540 }
541 }
542 lib_path
543 })
544 })
545 {
546 Some(self.root.join(path))
547 } else {
548 utils::resolve_library(&self.libraries, import)
549 }
550 }
551
552 pub fn with_language_ref<Lang>(&self) -> &ProjectPathsConfig<Lang> {
553 unsafe { std::mem::transmute(self) }
555 }
556
557 pub fn with_language<Lang>(self) -> ProjectPathsConfig<Lang> {
558 unsafe { std::mem::transmute(self) }
560 }
561
562 pub fn apply_lib_remappings(&self, mut libraries: Libraries) -> Libraries {
563 libraries.libs = libraries.libs
564 .into_iter()
565 .map(|(file, target)| {
566 let file = self.resolve_import(&self.root, &file).unwrap_or_else(|err| {
567 warn!(target: "libs", "Failed to resolve library `{}` for linking: {:?}", file.display(), err);
568 file
569 });
570 (file, target)
571 })
572 .collect();
573 libraries
574 }
575}
576
577impl<L: Language> ProjectPathsConfig<L> {
578 pub fn read_sources(&self) -> Result<Sources> {
580 trace!("reading all sources from \"{}\"", self.sources.display());
581 Ok(Source::read_all_from(&self.sources, L::FILE_EXTENSIONS)?)
582 }
583
584 pub fn read_tests(&self) -> Result<Sources> {
586 trace!("reading all tests from \"{}\"", self.tests.display());
587 Ok(Source::read_all_from(&self.tests, L::FILE_EXTENSIONS)?)
588 }
589
590 pub fn read_scripts(&self) -> Result<Sources> {
592 trace!("reading all scripts from \"{}\"", self.scripts.display());
593 Ok(Source::read_all_from(&self.scripts, L::FILE_EXTENSIONS)?)
594 }
595
596 pub fn has_input_files(&self) -> bool {
600 self.input_files_iter().next().is_some()
601 }
602
603 pub fn input_files_iter(&self) -> impl Iterator<Item = PathBuf> + '_ {
606 utils::source_files_iter(&self.sources, L::FILE_EXTENSIONS)
607 .chain(utils::source_files_iter(&self.tests, L::FILE_EXTENSIONS))
608 .chain(utils::source_files_iter(&self.scripts, L::FILE_EXTENSIONS))
609 }
610
611 pub fn input_files(&self) -> Vec<PathBuf> {
614 self.input_files_iter().collect()
615 }
616
617 pub fn read_input_files(&self) -> Result<Sources> {
619 Ok(Source::read_all_files(self.input_files())?)
620 }
621}
622
623impl fmt::Display for ProjectPathsConfig {
624 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
625 writeln!(f, "root: {}", self.root.display())?;
626 writeln!(f, "contracts: {}", self.sources.display())?;
627 writeln!(f, "artifacts: {}", self.artifacts.display())?;
628 writeln!(f, "tests: {}", self.tests.display())?;
629 writeln!(f, "scripts: {}", self.scripts.display())?;
630 writeln!(f, "libs:")?;
631 for lib in &self.libraries {
632 writeln!(f, " {}", lib.display())?;
633 }
634 writeln!(f, "remappings:")?;
635 for remapping in &self.remappings {
636 writeln!(f, " {remapping}")?;
637 }
638 Ok(())
639 }
640}
641
642#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
644pub struct ProjectPaths {
645 pub artifacts: PathBuf,
646 pub build_infos: PathBuf,
647 pub sources: PathBuf,
648 pub tests: PathBuf,
649 pub scripts: PathBuf,
650 pub libraries: BTreeSet<PathBuf>,
651}
652
653impl ProjectPaths {
654 pub fn join_all(&mut self, root: &Path) -> &mut Self {
656 self.artifacts = root.join(&self.artifacts);
657 self.build_infos = root.join(&self.build_infos);
658 self.sources = root.join(&self.sources);
659 self.tests = root.join(&self.tests);
660 self.scripts = root.join(&self.scripts);
661 let libraries = std::mem::take(&mut self.libraries);
662 self.libraries.extend(libraries.into_iter().map(|p| root.join(p)));
663 self
664 }
665
666 pub fn strip_prefix_all(&mut self, base: &Path) -> &mut Self {
668 if let Ok(stripped) = self.artifacts.strip_prefix(base) {
669 self.artifacts = stripped.to_path_buf();
670 }
671 if let Ok(stripped) = self.build_infos.strip_prefix(base) {
672 self.build_infos = stripped.to_path_buf();
673 }
674 if let Ok(stripped) = self.sources.strip_prefix(base) {
675 self.sources = stripped.to_path_buf();
676 }
677 if let Ok(stripped) = self.tests.strip_prefix(base) {
678 self.tests = stripped.to_path_buf();
679 }
680 if let Ok(stripped) = self.scripts.strip_prefix(base) {
681 self.scripts = stripped.to_path_buf();
682 }
683 self.libraries = std::mem::take(&mut self.libraries)
684 .into_iter()
685 .map(|path| strip_prefix_owned(path, base))
686 .collect();
687 self
688 }
689
690 pub fn is_test_or_script(&self, path: &Path) -> bool {
692 self.is_test(path) || self.is_script(path)
693 }
694
695 pub fn is_test(&self, path: &Path) -> bool {
697 path.starts_with(&self.tests)
698 }
699
700 pub fn is_script(&self, path: &Path) -> bool {
702 path.starts_with(&self.scripts)
703 }
704
705 pub fn is_source_file(&self, path: &Path) -> bool {
707 !self.is_test_or_script(path)
708 }
709}
710
711impl Default for ProjectPaths {
712 fn default() -> Self {
713 Self {
714 artifacts: "out".into(),
715 build_infos: ["out", "build-info"].iter().collect::<PathBuf>(),
716 sources: "src".into(),
717 tests: "test".into(),
718 scripts: "script".into(),
719 libraries: Default::default(),
720 }
721 }
722}
723
724#[derive(Clone, Debug, PartialEq, Eq)]
725pub enum PathStyle {
726 HardHat,
727 Dapptools,
728}
729
730impl PathStyle {
731 pub fn paths<C>(&self, root: &Path) -> Result<ProjectPathsConfig<C>> {
733 let root = utils::canonicalize(root)?;
734
735 Ok(match self {
736 Self::Dapptools => ProjectPathsConfig::builder()
737 .sources(root.join("src"))
738 .artifacts(root.join("out"))
739 .build_infos(root.join("out").join("build-info"))
740 .lib(root.join("lib"))
741 .remappings(Remapping::find_many(&root.join("lib")))
742 .root(root)
743 .build()?,
744 Self::HardHat => ProjectPathsConfig::builder()
745 .sources(root.join("contracts"))
746 .artifacts(root.join("artifacts"))
747 .build_infos(root.join("artifacts").join("build-info"))
748 .lib(root.join("node_modules"))
749 .root(root)
750 .build()?,
751 })
752 }
753}
754
755#[derive(Clone, Debug, Default)]
756pub struct ProjectPathsConfigBuilder {
757 root: Option<PathBuf>,
758 cache: Option<PathBuf>,
759 artifacts: Option<PathBuf>,
760 build_infos: Option<PathBuf>,
761 sources: Option<PathBuf>,
762 tests: Option<PathBuf>,
763 scripts: Option<PathBuf>,
764 libraries: Option<Vec<PathBuf>>,
765 remappings: Option<Vec<Remapping>>,
766 include_paths: BTreeSet<PathBuf>,
767 allowed_paths: BTreeSet<PathBuf>,
768}
769
770impl ProjectPathsConfigBuilder {
771 pub fn root(mut self, root: impl Into<PathBuf>) -> Self {
772 self.root = Some(utils::canonicalized(root));
773 self
774 }
775
776 pub fn cache(mut self, cache: impl Into<PathBuf>) -> Self {
777 self.cache = Some(utils::canonicalized(cache));
778 self
779 }
780
781 pub fn artifacts(mut self, artifacts: impl Into<PathBuf>) -> Self {
782 self.artifacts = Some(utils::canonicalized(artifacts));
783 self
784 }
785
786 pub fn build_infos(mut self, build_infos: impl Into<PathBuf>) -> Self {
787 self.build_infos = Some(utils::canonicalized(build_infos));
788 self
789 }
790
791 pub fn sources(mut self, sources: impl Into<PathBuf>) -> Self {
792 self.sources = Some(utils::canonicalized(sources));
793 self
794 }
795
796 pub fn tests(mut self, tests: impl Into<PathBuf>) -> Self {
797 self.tests = Some(utils::canonicalized(tests));
798 self
799 }
800
801 pub fn scripts(mut self, scripts: impl Into<PathBuf>) -> Self {
802 self.scripts = Some(utils::canonicalized(scripts));
803 self
804 }
805
806 pub fn no_libs(mut self) -> Self {
808 self.libraries = Some(Vec::new());
809 self
810 }
811
812 pub fn lib(mut self, lib: impl Into<PathBuf>) -> Self {
813 self.libraries.get_or_insert_with(Vec::new).push(utils::canonicalized(lib));
814 self
815 }
816
817 pub fn libs(mut self, libs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
818 let libraries = self.libraries.get_or_insert_with(Vec::new);
819 for lib in libs.into_iter() {
820 libraries.push(utils::canonicalized(lib));
821 }
822 self
823 }
824
825 pub fn remapping(mut self, remapping: Remapping) -> Self {
826 self.remappings.get_or_insert_with(Vec::new).push(remapping);
827 self
828 }
829
830 pub fn remappings(mut self, remappings: impl IntoIterator<Item = Remapping>) -> Self {
831 let our_remappings = self.remappings.get_or_insert_with(Vec::new);
832 for remapping in remappings.into_iter() {
833 our_remappings.push(remapping);
834 }
835 self
836 }
837
838 pub fn allowed_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
840 self.allowed_paths.insert(path.into());
841 self
842 }
843
844 pub fn allowed_paths<I, S>(mut self, args: I) -> Self
846 where
847 I: IntoIterator<Item = S>,
848 S: Into<PathBuf>,
849 {
850 for arg in args {
851 self = self.allowed_path(arg);
852 }
853 self
854 }
855
856 pub fn include_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
858 self.include_paths.insert(path.into());
859 self
860 }
861
862 pub fn include_paths<I, S>(mut self, args: I) -> Self
864 where
865 I: IntoIterator<Item = S>,
866 S: Into<PathBuf>,
867 {
868 for arg in args {
869 self = self.include_path(arg);
870 }
871 self
872 }
873
874 pub fn build_with_root<C>(self, root: impl Into<PathBuf>) -> ProjectPathsConfig<C> {
875 let root = utils::canonicalized(root);
876
877 let libraries = self.libraries.unwrap_or_else(|| ProjectPathsConfig::find_libs(&root));
878 let artifacts =
879 self.artifacts.unwrap_or_else(|| ProjectPathsConfig::find_artifacts_dir(&root));
880
881 let mut allowed_paths = self.allowed_paths;
882 allowed_paths.insert(root.clone());
884
885 ProjectPathsConfig {
886 cache: self
887 .cache
888 .unwrap_or_else(|| root.join("cache").join(SOLIDITY_FILES_CACHE_FILENAME)),
889 build_infos: self.build_infos.unwrap_or_else(|| artifacts.join("build-info")),
890 artifacts,
891 sources: self.sources.unwrap_or_else(|| ProjectPathsConfig::find_source_dir(&root)),
892 tests: self.tests.unwrap_or_else(|| root.join("test")),
893 scripts: self.scripts.unwrap_or_else(|| root.join("script")),
894 remappings: self.remappings.unwrap_or_else(|| {
895 libraries.iter().flat_map(|p| Remapping::find_many(p)).collect()
896 }),
897 libraries,
898 root,
899 include_paths: self.include_paths,
900 allowed_paths,
901 _l: PhantomData,
902 }
903 }
904
905 pub fn build<C>(self) -> std::result::Result<ProjectPathsConfig<C>, SolcIoError> {
906 let root = self
907 .root
908 .clone()
909 .map(Ok)
910 .unwrap_or_else(std::env::current_dir)
911 .map_err(|err| SolcIoError::new(err, "."))?;
912 Ok(self.build_with_root(root))
913 }
914}
915
916#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
918pub struct SolcConfig {
919 pub settings: Settings,
921}
922
923impl SolcConfig {
924 pub fn builder() -> SolcConfigBuilder {
936 SolcConfigBuilder::default()
937 }
938}
939
940impl From<SolcConfig> for Settings {
941 fn from(config: SolcConfig) -> Self {
942 config.settings
943 }
944}
945
946#[derive(Default)]
947pub struct SolcConfigBuilder {
948 settings: Option<Settings>,
949
950 output_selection: Vec<ContractOutputSelection>,
952
953 ast: bool,
955}
956
957impl SolcConfigBuilder {
958 pub fn settings(mut self, settings: Settings) -> Self {
959 self.settings = Some(settings);
960 self
961 }
962
963 #[must_use]
965 pub fn additional_output(mut self, output: impl Into<ContractOutputSelection>) -> Self {
966 self.output_selection.push(output.into());
967 self
968 }
969
970 #[must_use]
972 pub fn additional_outputs<I, S>(mut self, outputs: I) -> Self
973 where
974 I: IntoIterator<Item = S>,
975 S: Into<ContractOutputSelection>,
976 {
977 for out in outputs {
978 self = self.additional_output(out);
979 }
980 self
981 }
982
983 pub fn ast(mut self, yes: bool) -> Self {
984 self.ast = yes;
985 self
986 }
987
988 pub fn build(self) -> Settings {
990 let Self { settings, output_selection, ast } = self;
991 let mut settings = settings.unwrap_or_default();
992 settings.push_all(output_selection);
993 if ast {
994 settings = settings.with_ast();
995 }
996 settings
997 }
998}
999
1000fn path_starts_with_rooted(a: &Path, b: &Path, root: &Path) -> bool {
1002 if a.starts_with(b) {
1003 return true;
1004 }
1005 if let Ok(b) = b.strip_prefix(root) {
1006 return a.starts_with(b);
1007 }
1008 false
1009}
1010
1011#[cfg(test)]
1012mod tests {
1013 use super::*;
1014
1015 #[test]
1016 fn can_autodetect_dirs() {
1017 let root = utils::tempdir("root").unwrap();
1018 let out = root.path().join("out");
1019 let artifacts = root.path().join("artifacts");
1020 let build_infos = artifacts.join("build-info");
1021 let contracts = root.path().join("contracts");
1022 let src = root.path().join("src");
1023 let lib = root.path().join("lib");
1024 let node_modules = root.path().join("node_modules");
1025
1026 let root = root.path();
1027 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
1028 std::fs::create_dir_all(&contracts).unwrap();
1029 assert_eq!(ProjectPathsConfig::find_source_dir(root), contracts,);
1030 assert_eq!(
1031 ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
1032 utils::canonicalized(contracts),
1033 );
1034 std::fs::create_dir_all(&src).unwrap();
1035 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
1036 assert_eq!(
1037 ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
1038 utils::canonicalized(src),
1039 );
1040
1041 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
1042 std::fs::create_dir_all(&artifacts).unwrap();
1043 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), artifacts,);
1044 assert_eq!(
1045 ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
1046 utils::canonicalized(artifacts),
1047 );
1048 std::fs::create_dir_all(&build_infos).unwrap();
1049 assert_eq!(
1050 ProjectPathsConfig::builder().build_with_root::<()>(root).build_infos,
1051 utils::canonicalized(build_infos)
1052 );
1053
1054 std::fs::create_dir_all(&out).unwrap();
1055 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
1056 assert_eq!(
1057 ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
1058 utils::canonicalized(out),
1059 );
1060
1061 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
1062 std::fs::create_dir_all(&node_modules).unwrap();
1063 assert_eq!(ProjectPathsConfig::find_libs(root), vec![node_modules.clone()],);
1064 assert_eq!(
1065 ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
1066 vec![utils::canonicalized(node_modules)],
1067 );
1068 std::fs::create_dir_all(&lib).unwrap();
1069 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
1070 assert_eq!(
1071 ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
1072 vec![utils::canonicalized(lib)],
1073 );
1074 }
1075
1076 #[test]
1077 fn can_have_sane_build_info_default() {
1078 let root = utils::tempdir("root").unwrap();
1079 let root = root.path();
1080 let artifacts = root.join("forge-artifacts");
1081
1082 let paths = ProjectPathsConfig::builder().artifacts(&artifacts).build_with_root::<()>(root);
1085
1086 assert_eq!(paths.artifacts, utils::canonicalized(artifacts));
1088
1089 assert_eq!(paths.build_infos, utils::canonicalized(paths.artifacts.join("build-info")));
1091 }
1092
1093 #[test]
1094 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
1095 fn can_find_library_ancestor() {
1096 let mut config = ProjectPathsConfig::builder().lib("lib").build::<()>().unwrap();
1097 config.root = "/root/".into();
1098
1099 assert_eq!(
1100 config.find_library_ancestor("lib/src/Greeter.sol".as_ref()).unwrap(),
1101 Path::new("lib")
1102 );
1103
1104 assert_eq!(
1105 config.find_library_ancestor("/root/lib/src/Greeter.sol".as_ref()).unwrap(),
1106 Path::new("lib")
1107 );
1108
1109 config.libraries.push("/root/test/".into());
1110
1111 assert_eq!(
1112 config.find_library_ancestor("test/src/Greeter.sol".as_ref()).unwrap(),
1113 Path::new("/root/test/")
1114 );
1115
1116 assert_eq!(
1117 config.find_library_ancestor("/root/test/src/Greeter.sol".as_ref()).unwrap(),
1118 Path::new("/root/test/")
1119 );
1120 }
1121
1122 #[test]
1123 fn can_resolve_import() {
1124 let dir = tempfile::tempdir().unwrap();
1125 let config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1126 config.create_all().unwrap();
1127
1128 fs::write(config.sources.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
1129
1130 assert_eq!(
1132 config
1133 .resolve_import_and_include_paths(
1134 &config.sources,
1135 Path::new("./A.sol"),
1136 &mut Default::default(),
1137 )
1138 .unwrap(),
1139 config.sources.join("A.sol")
1140 );
1141
1142 assert_eq!(
1144 config
1145 .resolve_import_and_include_paths(
1146 &config.sources,
1147 Path::new("src/A.sol"),
1148 &mut Default::default(),
1149 )
1150 .unwrap(),
1151 config.sources.join("A.sol")
1152 );
1153 }
1154
1155 #[test]
1156 fn can_resolve_remapped_import() {
1157 let dir = tempfile::tempdir().unwrap();
1158 let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1159 config.create_all().unwrap();
1160
1161 let dependency = config.root.join("dependency");
1162 fs::create_dir(&dependency).unwrap();
1163 fs::write(dependency.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
1164
1165 config.remappings.push(Remapping {
1166 context: None,
1167 name: "@dependency/".into(),
1168 path: "dependency/".into(),
1169 });
1170
1171 assert_eq!(
1172 config
1173 .resolve_import_and_include_paths(
1174 &config.sources,
1175 Path::new("@dependency/A.sol"),
1176 &mut Default::default(),
1177 )
1178 .unwrap(),
1179 dependency.join("A.sol")
1180 );
1181 }
1182
1183 #[test]
1184 fn can_resolve_single_file_mapped_import() {
1185 let dir = tempfile::tempdir().unwrap();
1186 let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1187 config.create_all().unwrap();
1188
1189 fs::write(
1190 config.sources.join("A.sol"),
1191 r#"pragma solidity ^0.8.0; import "@my-lib/B.sol"; contract A is B {}"#,
1192 )
1193 .unwrap();
1194
1195 let dependency = config.root.join("my-lib");
1196 fs::create_dir(&dependency).unwrap();
1197 fs::write(dependency.join("B.sol"), r"pragma solidity ^0.8.0; contract B {}").unwrap();
1198
1199 config.remappings.push(Remapping {
1200 context: None,
1201 name: "@my-lib/B.sol".into(),
1202 path: "my-lib/B.sol".into(),
1203 });
1204
1205 assert!(config
1207 .resolve_import_and_include_paths(
1208 &config.sources,
1209 Path::new("@my-lib/B.sol"),
1210 &mut Default::default(),
1211 )
1212 .unwrap()
1213 .to_str()
1214 .unwrap()
1215 .ends_with("my-lib/B.sol"));
1216 }
1217}