1use crate::{
2 cache::SOLIDITY_FILES_CACHE_FILENAME,
3 compilers::{multi::MultiCompilerLanguage, Language},
4 flatten::{collect_ordered_deps, combine_version_pragmas},
5 resolver::{parse::SolData, 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::<SolData>::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 = Path::new(&r.path).join(stripped_import);
528
529 if let Ok(adjusted_import) = stripped_import.strip_prefix("contracts/") {
533 if r.path.ends_with("contracts/") && !lib_path.exists() {
534 return Path::new(&r.path).join(adjusted_import);
535 }
536 }
537 lib_path
538 })
539 })
540 {
541 Some(self.root.join(path))
542 } else {
543 utils::resolve_library(&self.libraries, import)
544 }
545 }
546
547 pub fn with_language<Lang>(self) -> ProjectPathsConfig<Lang> {
548 let Self {
549 root,
550 cache,
551 artifacts,
552 build_infos,
553 sources,
554 tests,
555 scripts,
556 libraries,
557 remappings,
558 include_paths,
559 allowed_paths,
560 _l,
561 } = self;
562
563 ProjectPathsConfig {
564 root,
565 cache,
566 artifacts,
567 build_infos,
568 sources,
569 tests,
570 scripts,
571 libraries,
572 remappings,
573 include_paths,
574 allowed_paths,
575 _l: PhantomData,
576 }
577 }
578
579 pub fn apply_lib_remappings(&self, mut libraries: Libraries) -> Libraries {
580 libraries.libs = libraries.libs
581 .into_iter()
582 .map(|(file, target)| {
583 let file = self.resolve_import(&self.root, &file).unwrap_or_else(|err| {
584 warn!(target: "libs", "Failed to resolve library `{}` for linking: {:?}", file.display(), err);
585 file
586 });
587 (file, target)
588 })
589 .collect();
590 libraries
591 }
592}
593
594impl<L: Language> ProjectPathsConfig<L> {
595 pub fn read_sources(&self) -> Result<Sources> {
597 trace!("reading all sources from \"{}\"", self.sources.display());
598 Ok(Source::read_all_from(&self.sources, L::FILE_EXTENSIONS)?)
599 }
600
601 pub fn read_tests(&self) -> Result<Sources> {
603 trace!("reading all tests from \"{}\"", self.tests.display());
604 Ok(Source::read_all_from(&self.tests, L::FILE_EXTENSIONS)?)
605 }
606
607 pub fn read_scripts(&self) -> Result<Sources> {
609 trace!("reading all scripts from \"{}\"", self.scripts.display());
610 Ok(Source::read_all_from(&self.scripts, L::FILE_EXTENSIONS)?)
611 }
612
613 pub fn has_input_files(&self) -> bool {
617 self.input_files_iter().next().is_some()
618 }
619
620 pub fn input_files_iter(&self) -> impl Iterator<Item = PathBuf> + '_ {
623 utils::source_files_iter(&self.sources, L::FILE_EXTENSIONS)
624 .chain(utils::source_files_iter(&self.tests, L::FILE_EXTENSIONS))
625 .chain(utils::source_files_iter(&self.scripts, L::FILE_EXTENSIONS))
626 }
627
628 pub fn input_files(&self) -> Vec<PathBuf> {
631 self.input_files_iter().collect()
632 }
633
634 pub fn read_input_files(&self) -> Result<Sources> {
636 Ok(Source::read_all_files(self.input_files())?)
637 }
638}
639
640impl fmt::Display for ProjectPathsConfig {
641 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
642 writeln!(f, "root: {}", self.root.display())?;
643 writeln!(f, "contracts: {}", self.sources.display())?;
644 writeln!(f, "artifacts: {}", self.artifacts.display())?;
645 writeln!(f, "tests: {}", self.tests.display())?;
646 writeln!(f, "scripts: {}", self.scripts.display())?;
647 writeln!(f, "libs:")?;
648 for lib in &self.libraries {
649 writeln!(f, " {}", lib.display())?;
650 }
651 writeln!(f, "remappings:")?;
652 for remapping in &self.remappings {
653 writeln!(f, " {remapping}")?;
654 }
655 Ok(())
656 }
657}
658
659#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
661pub struct ProjectPaths {
662 pub artifacts: PathBuf,
663 pub build_infos: PathBuf,
664 pub sources: PathBuf,
665 pub tests: PathBuf,
666 pub scripts: PathBuf,
667 pub libraries: BTreeSet<PathBuf>,
668}
669
670impl ProjectPaths {
671 pub fn join_all(&mut self, root: &Path) -> &mut Self {
673 self.artifacts = root.join(&self.artifacts);
674 self.build_infos = root.join(&self.build_infos);
675 self.sources = root.join(&self.sources);
676 self.tests = root.join(&self.tests);
677 self.scripts = root.join(&self.scripts);
678 let libraries = std::mem::take(&mut self.libraries);
679 self.libraries.extend(libraries.into_iter().map(|p| root.join(p)));
680 self
681 }
682
683 pub fn strip_prefix_all(&mut self, base: &Path) -> &mut Self {
685 if let Ok(stripped) = self.artifacts.strip_prefix(base) {
686 self.artifacts = stripped.to_path_buf();
687 }
688 if let Ok(stripped) = self.build_infos.strip_prefix(base) {
689 self.build_infos = stripped.to_path_buf();
690 }
691 if let Ok(stripped) = self.sources.strip_prefix(base) {
692 self.sources = stripped.to_path_buf();
693 }
694 if let Ok(stripped) = self.tests.strip_prefix(base) {
695 self.tests = stripped.to_path_buf();
696 }
697 if let Ok(stripped) = self.scripts.strip_prefix(base) {
698 self.scripts = stripped.to_path_buf();
699 }
700 self.libraries = std::mem::take(&mut self.libraries)
701 .into_iter()
702 .map(|path| strip_prefix_owned(path, base))
703 .collect();
704 self
705 }
706
707 pub fn is_test_or_script(&self, path: &Path) -> bool {
709 self.is_test(path) || self.is_script(path)
710 }
711
712 pub fn is_test(&self, path: &Path) -> bool {
714 path.starts_with(&self.tests)
715 }
716
717 pub fn is_script(&self, path: &Path) -> bool {
719 path.starts_with(&self.scripts)
720 }
721
722 pub fn is_source_file(&self, path: &Path) -> bool {
724 !self.is_test_or_script(path)
725 }
726}
727
728impl Default for ProjectPaths {
729 fn default() -> Self {
730 Self {
731 artifacts: "out".into(),
732 build_infos: ["out", "build-info"].iter().collect::<PathBuf>(),
733 sources: "src".into(),
734 tests: "test".into(),
735 scripts: "script".into(),
736 libraries: Default::default(),
737 }
738 }
739}
740
741#[derive(Clone, Debug, PartialEq, Eq)]
742pub enum PathStyle {
743 HardHat,
744 Dapptools,
745}
746
747impl PathStyle {
748 pub fn paths<C>(&self, root: &Path) -> Result<ProjectPathsConfig<C>> {
750 let root = utils::canonicalize(root)?;
751
752 Ok(match self {
753 Self::Dapptools => ProjectPathsConfig::builder()
754 .sources(root.join("src"))
755 .artifacts(root.join("out"))
756 .build_infos(root.join("out").join("build-info"))
757 .lib(root.join("lib"))
758 .remappings(Remapping::find_many(&root.join("lib")))
759 .root(root)
760 .build()?,
761 Self::HardHat => ProjectPathsConfig::builder()
762 .sources(root.join("contracts"))
763 .artifacts(root.join("artifacts"))
764 .build_infos(root.join("artifacts").join("build-info"))
765 .lib(root.join("node_modules"))
766 .root(root)
767 .build()?,
768 })
769 }
770}
771
772#[derive(Clone, Debug, Default)]
773pub struct ProjectPathsConfigBuilder {
774 root: Option<PathBuf>,
775 cache: Option<PathBuf>,
776 artifacts: Option<PathBuf>,
777 build_infos: Option<PathBuf>,
778 sources: Option<PathBuf>,
779 tests: Option<PathBuf>,
780 scripts: Option<PathBuf>,
781 libraries: Option<Vec<PathBuf>>,
782 remappings: Option<Vec<Remapping>>,
783 include_paths: BTreeSet<PathBuf>,
784 allowed_paths: BTreeSet<PathBuf>,
785}
786
787impl ProjectPathsConfigBuilder {
788 pub fn root(mut self, root: impl Into<PathBuf>) -> Self {
789 self.root = Some(utils::canonicalized(root));
790 self
791 }
792
793 pub fn cache(mut self, cache: impl Into<PathBuf>) -> Self {
794 self.cache = Some(utils::canonicalized(cache));
795 self
796 }
797
798 pub fn artifacts(mut self, artifacts: impl Into<PathBuf>) -> Self {
799 self.artifacts = Some(utils::canonicalized(artifacts));
800 self
801 }
802
803 pub fn build_infos(mut self, build_infos: impl Into<PathBuf>) -> Self {
804 self.build_infos = Some(utils::canonicalized(build_infos));
805 self
806 }
807
808 pub fn sources(mut self, sources: impl Into<PathBuf>) -> Self {
809 self.sources = Some(utils::canonicalized(sources));
810 self
811 }
812
813 pub fn tests(mut self, tests: impl Into<PathBuf>) -> Self {
814 self.tests = Some(utils::canonicalized(tests));
815 self
816 }
817
818 pub fn scripts(mut self, scripts: impl Into<PathBuf>) -> Self {
819 self.scripts = Some(utils::canonicalized(scripts));
820 self
821 }
822
823 pub fn no_libs(mut self) -> Self {
825 self.libraries = Some(Vec::new());
826 self
827 }
828
829 pub fn lib(mut self, lib: impl Into<PathBuf>) -> Self {
830 self.libraries.get_or_insert_with(Vec::new).push(utils::canonicalized(lib));
831 self
832 }
833
834 pub fn libs(mut self, libs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
835 let libraries = self.libraries.get_or_insert_with(Vec::new);
836 for lib in libs.into_iter() {
837 libraries.push(utils::canonicalized(lib));
838 }
839 self
840 }
841
842 pub fn remapping(mut self, remapping: Remapping) -> Self {
843 self.remappings.get_or_insert_with(Vec::new).push(remapping);
844 self
845 }
846
847 pub fn remappings(mut self, remappings: impl IntoIterator<Item = Remapping>) -> Self {
848 let our_remappings = self.remappings.get_or_insert_with(Vec::new);
849 for remapping in remappings.into_iter() {
850 our_remappings.push(remapping);
851 }
852 self
853 }
854
855 pub fn allowed_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
857 self.allowed_paths.insert(path.into());
858 self
859 }
860
861 pub fn allowed_paths<I, S>(mut self, args: I) -> Self
863 where
864 I: IntoIterator<Item = S>,
865 S: Into<PathBuf>,
866 {
867 for arg in args {
868 self = self.allowed_path(arg);
869 }
870 self
871 }
872
873 pub fn include_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
875 self.include_paths.insert(path.into());
876 self
877 }
878
879 pub fn include_paths<I, S>(mut self, args: I) -> Self
881 where
882 I: IntoIterator<Item = S>,
883 S: Into<PathBuf>,
884 {
885 for arg in args {
886 self = self.include_path(arg);
887 }
888 self
889 }
890
891 pub fn build_with_root<C>(self, root: impl Into<PathBuf>) -> ProjectPathsConfig<C> {
892 let root = utils::canonicalized(root);
893
894 let libraries = self.libraries.unwrap_or_else(|| ProjectPathsConfig::find_libs(&root));
895 let artifacts =
896 self.artifacts.unwrap_or_else(|| ProjectPathsConfig::find_artifacts_dir(&root));
897
898 let mut allowed_paths = self.allowed_paths;
899 allowed_paths.insert(root.clone());
901
902 ProjectPathsConfig {
903 cache: self
904 .cache
905 .unwrap_or_else(|| root.join("cache").join(SOLIDITY_FILES_CACHE_FILENAME)),
906 build_infos: self.build_infos.unwrap_or_else(|| artifacts.join("build-info")),
907 artifacts,
908 sources: self.sources.unwrap_or_else(|| ProjectPathsConfig::find_source_dir(&root)),
909 tests: self.tests.unwrap_or_else(|| root.join("test")),
910 scripts: self.scripts.unwrap_or_else(|| root.join("script")),
911 remappings: self.remappings.unwrap_or_else(|| {
912 libraries.iter().flat_map(|p| Remapping::find_many(p)).collect()
913 }),
914 libraries,
915 root,
916 include_paths: self.include_paths,
917 allowed_paths,
918 _l: PhantomData,
919 }
920 }
921
922 pub fn build<C>(self) -> std::result::Result<ProjectPathsConfig<C>, SolcIoError> {
923 let root = self
924 .root
925 .clone()
926 .map(Ok)
927 .unwrap_or_else(std::env::current_dir)
928 .map_err(|err| SolcIoError::new(err, "."))?;
929 Ok(self.build_with_root(root))
930 }
931}
932
933#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
935pub struct SolcConfig {
936 pub settings: Settings,
938}
939
940impl SolcConfig {
941 pub fn builder() -> SolcConfigBuilder {
953 SolcConfigBuilder::default()
954 }
955}
956
957impl From<SolcConfig> for Settings {
958 fn from(config: SolcConfig) -> Self {
959 config.settings
960 }
961}
962
963#[derive(Default)]
964pub struct SolcConfigBuilder {
965 settings: Option<Settings>,
966
967 output_selection: Vec<ContractOutputSelection>,
969
970 ast: bool,
972}
973
974impl SolcConfigBuilder {
975 pub fn settings(mut self, settings: Settings) -> Self {
976 self.settings = Some(settings);
977 self
978 }
979
980 #[must_use]
982 pub fn additional_output(mut self, output: impl Into<ContractOutputSelection>) -> Self {
983 self.output_selection.push(output.into());
984 self
985 }
986
987 #[must_use]
989 pub fn additional_outputs<I, S>(mut self, outputs: I) -> Self
990 where
991 I: IntoIterator<Item = S>,
992 S: Into<ContractOutputSelection>,
993 {
994 for out in outputs {
995 self = self.additional_output(out);
996 }
997 self
998 }
999
1000 pub fn ast(mut self, yes: bool) -> Self {
1001 self.ast = yes;
1002 self
1003 }
1004
1005 pub fn build(self) -> Settings {
1007 let Self { settings, output_selection, ast } = self;
1008 let mut settings = settings.unwrap_or_default();
1009 settings.push_all(output_selection);
1010 if ast {
1011 settings = settings.with_ast();
1012 }
1013 settings
1014 }
1015}
1016
1017fn path_starts_with_rooted(a: &Path, b: &Path, root: &Path) -> bool {
1019 if a.starts_with(b) {
1020 return true;
1021 }
1022 if let Ok(b) = b.strip_prefix(root) {
1023 return a.starts_with(b);
1024 }
1025 false
1026}
1027
1028#[cfg(test)]
1029mod tests {
1030 use super::*;
1031
1032 #[test]
1033 fn can_autodetect_dirs() {
1034 let root = utils::tempdir("root").unwrap();
1035 let out = root.path().join("out");
1036 let artifacts = root.path().join("artifacts");
1037 let build_infos = artifacts.join("build-info");
1038 let contracts = root.path().join("contracts");
1039 let src = root.path().join("src");
1040 let lib = root.path().join("lib");
1041 let node_modules = root.path().join("node_modules");
1042
1043 let root = root.path();
1044 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
1045 std::fs::create_dir_all(&contracts).unwrap();
1046 assert_eq!(ProjectPathsConfig::find_source_dir(root), contracts,);
1047 assert_eq!(
1048 ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
1049 utils::canonicalized(contracts),
1050 );
1051 std::fs::create_dir_all(&src).unwrap();
1052 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
1053 assert_eq!(
1054 ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
1055 utils::canonicalized(src),
1056 );
1057
1058 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
1059 std::fs::create_dir_all(&artifacts).unwrap();
1060 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), artifacts,);
1061 assert_eq!(
1062 ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
1063 utils::canonicalized(artifacts),
1064 );
1065 std::fs::create_dir_all(&build_infos).unwrap();
1066 assert_eq!(
1067 ProjectPathsConfig::builder().build_with_root::<()>(root).build_infos,
1068 utils::canonicalized(build_infos)
1069 );
1070
1071 std::fs::create_dir_all(&out).unwrap();
1072 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
1073 assert_eq!(
1074 ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
1075 utils::canonicalized(out),
1076 );
1077
1078 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
1079 std::fs::create_dir_all(&node_modules).unwrap();
1080 assert_eq!(ProjectPathsConfig::find_libs(root), vec![node_modules.clone()],);
1081 assert_eq!(
1082 ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
1083 vec![utils::canonicalized(node_modules)],
1084 );
1085 std::fs::create_dir_all(&lib).unwrap();
1086 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
1087 assert_eq!(
1088 ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
1089 vec![utils::canonicalized(lib)],
1090 );
1091 }
1092
1093 #[test]
1094 fn can_have_sane_build_info_default() {
1095 let root = utils::tempdir("root").unwrap();
1096 let root = root.path();
1097 let artifacts = root.join("forge-artifacts");
1098
1099 let paths = ProjectPathsConfig::builder().artifacts(&artifacts).build_with_root::<()>(root);
1102
1103 assert_eq!(paths.artifacts, utils::canonicalized(artifacts));
1105
1106 assert_eq!(paths.build_infos, utils::canonicalized(paths.artifacts.join("build-info")));
1108 }
1109
1110 #[test]
1111 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
1112 fn can_find_library_ancestor() {
1113 let mut config = ProjectPathsConfig::builder().lib("lib").build::<()>().unwrap();
1114 config.root = "/root/".into();
1115
1116 assert_eq!(
1117 config.find_library_ancestor("lib/src/Greeter.sol".as_ref()).unwrap(),
1118 Path::new("lib")
1119 );
1120
1121 assert_eq!(
1122 config.find_library_ancestor("/root/lib/src/Greeter.sol".as_ref()).unwrap(),
1123 Path::new("lib")
1124 );
1125
1126 config.libraries.push("/root/test/".into());
1127
1128 assert_eq!(
1129 config.find_library_ancestor("test/src/Greeter.sol".as_ref()).unwrap(),
1130 Path::new("/root/test/")
1131 );
1132
1133 assert_eq!(
1134 config.find_library_ancestor("/root/test/src/Greeter.sol".as_ref()).unwrap(),
1135 Path::new("/root/test/")
1136 );
1137 }
1138
1139 #[test]
1140 fn can_resolve_import() {
1141 let dir = tempfile::tempdir().unwrap();
1142 let config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1143 config.create_all().unwrap();
1144
1145 fs::write(config.sources.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
1146
1147 assert_eq!(
1149 config
1150 .resolve_import_and_include_paths(
1151 &config.sources,
1152 Path::new("./A.sol"),
1153 &mut Default::default(),
1154 )
1155 .unwrap(),
1156 config.sources.join("A.sol")
1157 );
1158
1159 assert_eq!(
1161 config
1162 .resolve_import_and_include_paths(
1163 &config.sources,
1164 Path::new("src/A.sol"),
1165 &mut Default::default(),
1166 )
1167 .unwrap(),
1168 config.sources.join("A.sol")
1169 );
1170 }
1171
1172 #[test]
1173 fn can_resolve_remapped_import() {
1174 let dir = tempfile::tempdir().unwrap();
1175 let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1176 config.create_all().unwrap();
1177
1178 let dependency = config.root.join("dependency");
1179 fs::create_dir(&dependency).unwrap();
1180 fs::write(dependency.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
1181
1182 config.remappings.push(Remapping {
1183 context: None,
1184 name: "@dependency/".into(),
1185 path: "dependency/".into(),
1186 });
1187
1188 assert_eq!(
1189 config
1190 .resolve_import_and_include_paths(
1191 &config.sources,
1192 Path::new("@dependency/A.sol"),
1193 &mut Default::default(),
1194 )
1195 .unwrap(),
1196 dependency.join("A.sol")
1197 );
1198 }
1199}