1use crate::{
2 Graph,
3 cache::SOLIDITY_FILES_CACHE_FILENAME,
4 compilers::{Language, multi::MultiCompilerLanguage},
5 flatten::{collect_ordered_deps, combine_version_pragmas},
6 resolver::{SolImportAlias, parse::SolParser},
7};
8use foundry_compilers_artifacts::{
9 Libraries, Settings, SolcLanguage,
10 output_selection::ContractOutputSelection,
11 remappings::Remapping,
12 sources::{Source, Sources},
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 {
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 #[allow(clippy::missing_const_for_fn)]
313 pub fn slash_paths(&mut self) {
314 #[cfg(windows)]
315 {
316 use path_slash::PathBufExt;
317
318 let slashed = |p: &mut PathBuf| {
319 *p = p.to_slash_lossy().as_ref().into();
320 };
321 slashed(&mut self.root);
322 slashed(&mut self.cache);
323 slashed(&mut self.artifacts);
324 slashed(&mut self.build_infos);
325 slashed(&mut self.sources);
326 slashed(&mut self.tests);
327 slashed(&mut self.scripts);
328
329 self.libraries.iter_mut().for_each(slashed);
330 self.remappings.iter_mut().for_each(Remapping::slash_path);
331
332 self.include_paths = std::mem::take(&mut self.include_paths)
333 .into_iter()
334 .map(|mut p| {
335 slashed(&mut p);
336 p
337 })
338 .collect();
339 self.allowed_paths = std::mem::take(&mut self.allowed_paths)
340 .into_iter()
341 .map(|mut p| {
342 slashed(&mut p);
343 p
344 })
345 .collect();
346 }
347 }
348
349 pub fn has_library_ancestor(&self, file: &Path) -> bool {
351 self.find_library_ancestor(file).is_some()
352 }
353
354 pub fn find_library_ancestor(&self, file: &Path) -> Option<&Path> {
375 for lib in &self.libraries {
376 if lib.is_relative()
377 && file.is_absolute()
378 && file.starts_with(&self.root)
379 && file.starts_with(self.root.join(lib))
380 || file.is_relative()
381 && lib.is_absolute()
382 && lib.starts_with(&self.root)
383 && self.root.join(file).starts_with(lib)
384 {
385 return Some(lib);
386 }
387 if file.starts_with(lib) {
388 return Some(lib);
389 }
390 }
391
392 None
393 }
394
395 pub fn resolve_import_and_include_paths(
402 &self,
403 cwd: &Path,
404 import: &Path,
405 include_paths: &mut BTreeSet<PathBuf>,
406 ) -> Result<PathBuf> {
407 let component = import
408 .components()
409 .next()
410 .ok_or_else(|| SolcError::msg(format!("Empty import path {}", import.display())))?;
411
412 if component == Component::CurDir || component == Component::ParentDir {
413 let resolved = utils::normalize_solidity_import_path(cwd, import).map_err(|err| {
416 SolcError::msg(format!("failed to resolve relative import \"{err:?}\""))
417 })?;
418 Ok(self.apply_remapping_to_path(cwd, &resolved))
420 } else {
421 let resolved = self.resolve_library_import(cwd.as_ref(), import.as_ref());
423
424 if resolved.is_none() {
425 if let Some(lib) = self.find_library_ancestor(cwd)
429 && let Some((include_path, import)) =
430 utils::resolve_absolute_library(lib, cwd, import)
431 {
432 include_paths.insert(include_path);
434 return Ok(import);
435 }
436 for path in [&self.root, &self.sources, &self.tests, &self.scripts] {
438 if cwd.starts_with(path)
439 && let Ok(import) = utils::normalize_solidity_import_path(path, import)
440 {
441 return Ok(import);
442 }
443 }
444 }
445
446 resolved.ok_or_else(|| {
447 SolcError::msg(format!(
448 "failed to resolve library import \"{:?}\"",
449 import.display()
450 ))
451 })
452 }
453 }
454
455 pub fn resolve_import(&self, cwd: &Path, import: &Path) -> Result<PathBuf> {
459 self.resolve_import_and_include_paths(cwd, import, &mut Default::default())
460 }
461
462 pub fn resolve_library_import(&self, cwd: &Path, import: &Path) -> Option<PathBuf> {
513 let cwd = cwd.strip_prefix(&self.root).unwrap_or(cwd);
516 if let Some(path) = self
517 .remappings
518 .iter()
519 .filter(|r| {
520 if let Some(ctx) = r.context.as_ref() { cwd.starts_with(ctx) } else { true }
522 })
523 .find_map(|r| {
524 import.strip_prefix(&r.name).ok().map(|stripped_import| {
525 let lib_path =
526 if stripped_import.as_os_str().is_empty() && r.path.ends_with(".sol") {
527 r.path.clone().into()
528 } else {
529 Path::new(&r.path).join(stripped_import)
530 };
531
532 if let Ok(adjusted_import) = stripped_import.strip_prefix("contracts/")
536 && r.path.ends_with("contracts/")
537 && !self.root.join(&lib_path).exists()
538 {
539 return Path::new(&r.path).join(adjusted_import);
540 }
541 lib_path
542 })
543 })
544 {
545 Some(self.root.join(path))
546 } else {
547 utils::resolve_library(&self.libraries, import)
548 }
549 }
550
551 pub fn apply_remapping_to_path(&self, cwd: &Path, resolved_path: &Path) -> PathBuf {
568 let relative_path = match resolved_path.strip_prefix(&self.root) {
570 Ok(p) => p,
571 Err(_) => return resolved_path.to_path_buf(),
572 };
573
574 let cwd_relative = cwd.strip_prefix(&self.root).unwrap_or(cwd);
575
576 for r in &self.remappings {
578 if let Some(ctx) = r.context.as_ref()
580 && !cwd_relative.starts_with(ctx)
581 {
582 continue;
583 }
584
585 if let Ok(stripped) = relative_path.strip_prefix(&r.name) {
587 let remapped_relative = Path::new(&r.path).join(stripped);
588 let remapped_absolute = self.root.join(&remapped_relative);
589
590 if remapped_absolute.exists() {
592 return remapped_absolute;
593 }
594 }
595 }
596
597 resolved_path.to_path_buf()
598 }
599
600 pub fn with_language_ref<Lang>(&self) -> &ProjectPathsConfig<Lang> {
601 unsafe { std::mem::transmute(self) }
603 }
604
605 pub fn with_language<Lang>(self) -> ProjectPathsConfig<Lang> {
606 unsafe { std::mem::transmute(self) }
608 }
609
610 pub fn apply_lib_remappings(&self, mut libraries: Libraries) -> Libraries {
611 libraries.libs = libraries.libs
612 .into_iter()
613 .map(|(file, target)| {
614 let file = self.resolve_import(&self.root, &file).unwrap_or_else(|err| {
615 warn!(target: "libs", "Failed to resolve library `{}` for linking: {:?}", file.display(), err);
616 file
617 });
618 (file, target)
619 })
620 .collect();
621 libraries
622 }
623}
624
625impl<L: Language> ProjectPathsConfig<L> {
626 pub fn read_sources(&self) -> Result<Sources> {
628 trace!("reading all sources from \"{}\"", self.sources.display());
629 Ok(Source::read_all_from(&self.sources, L::FILE_EXTENSIONS)?)
630 }
631
632 pub fn read_tests(&self) -> Result<Sources> {
634 trace!("reading all tests from \"{}\"", self.tests.display());
635 Ok(Source::read_all_from(&self.tests, L::FILE_EXTENSIONS)?)
636 }
637
638 pub fn read_scripts(&self) -> Result<Sources> {
640 trace!("reading all scripts from \"{}\"", self.scripts.display());
641 Ok(Source::read_all_from(&self.scripts, L::FILE_EXTENSIONS)?)
642 }
643
644 pub fn has_input_files(&self) -> bool {
648 self.input_files_iter().next().is_some()
649 }
650
651 pub fn input_files_iter(&self) -> impl Iterator<Item = PathBuf> + '_ {
654 utils::source_files_iter(&self.sources, L::FILE_EXTENSIONS)
655 .chain(utils::source_files_iter(&self.tests, L::FILE_EXTENSIONS))
656 .chain(utils::source_files_iter(&self.scripts, L::FILE_EXTENSIONS))
657 }
658
659 pub fn input_files(&self) -> Vec<PathBuf> {
662 self.input_files_iter().collect()
663 }
664
665 pub fn read_input_files(&self) -> Result<Sources> {
667 Ok(Source::read_all(self.input_files_iter())?)
668 }
669}
670
671impl fmt::Display for ProjectPathsConfig {
672 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
673 writeln!(f, "root: {}", self.root.display())?;
674 writeln!(f, "contracts: {}", self.sources.display())?;
675 writeln!(f, "artifacts: {}", self.artifacts.display())?;
676 writeln!(f, "tests: {}", self.tests.display())?;
677 writeln!(f, "scripts: {}", self.scripts.display())?;
678 writeln!(f, "libs:")?;
679 for lib in &self.libraries {
680 writeln!(f, " {}", lib.display())?;
681 }
682 writeln!(f, "remappings:")?;
683 for remapping in &self.remappings {
684 writeln!(f, " {remapping}")?;
685 }
686 Ok(())
687 }
688}
689
690#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
692pub struct ProjectPaths {
693 pub artifacts: PathBuf,
694 pub build_infos: PathBuf,
695 pub sources: PathBuf,
696 pub tests: PathBuf,
697 pub scripts: PathBuf,
698 pub libraries: BTreeSet<PathBuf>,
699}
700
701impl ProjectPaths {
702 pub fn join_all(&mut self, root: &Path) -> &mut Self {
704 self.artifacts = root.join(&self.artifacts);
705 self.build_infos = root.join(&self.build_infos);
706 self.sources = root.join(&self.sources);
707 self.tests = root.join(&self.tests);
708 self.scripts = root.join(&self.scripts);
709 let libraries = std::mem::take(&mut self.libraries);
710 self.libraries.extend(libraries.into_iter().map(|p| root.join(p)));
711 self
712 }
713
714 pub fn strip_prefix_all(&mut self, base: &Path) -> &mut Self {
716 if let Ok(stripped) = self.artifacts.strip_prefix(base) {
717 self.artifacts = stripped.to_path_buf();
718 }
719 if let Ok(stripped) = self.build_infos.strip_prefix(base) {
720 self.build_infos = stripped.to_path_buf();
721 }
722 if let Ok(stripped) = self.sources.strip_prefix(base) {
723 self.sources = stripped.to_path_buf();
724 }
725 if let Ok(stripped) = self.tests.strip_prefix(base) {
726 self.tests = stripped.to_path_buf();
727 }
728 if let Ok(stripped) = self.scripts.strip_prefix(base) {
729 self.scripts = stripped.to_path_buf();
730 }
731 self.libraries = std::mem::take(&mut self.libraries)
732 .into_iter()
733 .map(|path| strip_prefix_owned(path, base))
734 .collect();
735 self
736 }
737
738 pub fn is_test_or_script(&self, path: &Path) -> bool {
740 self.is_test(path) || self.is_script(path)
741 }
742
743 pub fn is_test(&self, path: &Path) -> bool {
745 path.starts_with(&self.tests)
746 }
747
748 pub fn is_script(&self, path: &Path) -> bool {
750 path.starts_with(&self.scripts)
751 }
752
753 pub fn is_source_file(&self, path: &Path) -> bool {
755 !self.is_test_or_script(path)
756 }
757}
758
759impl Default for ProjectPaths {
760 fn default() -> Self {
761 Self {
762 artifacts: "out".into(),
763 build_infos: ["out", "build-info"].iter().collect::<PathBuf>(),
764 sources: "src".into(),
765 tests: "test".into(),
766 scripts: "script".into(),
767 libraries: Default::default(),
768 }
769 }
770}
771
772#[derive(Clone, Debug, PartialEq, Eq)]
773pub enum PathStyle {
774 HardHat,
775 Dapptools,
776}
777
778impl PathStyle {
779 pub fn paths<C>(&self, root: &Path) -> Result<ProjectPathsConfig<C>> {
781 let root = utils::canonicalize(root)?;
782
783 Ok(match self {
784 Self::Dapptools => ProjectPathsConfig::builder()
785 .sources(root.join("src"))
786 .artifacts(root.join("out"))
787 .build_infos(root.join("out").join("build-info"))
788 .lib(root.join("lib"))
789 .remappings(Remapping::find_many(&root.join("lib")))
790 .root(root)
791 .build()?,
792 Self::HardHat => ProjectPathsConfig::builder()
793 .sources(root.join("contracts"))
794 .artifacts(root.join("artifacts"))
795 .build_infos(root.join("artifacts").join("build-info"))
796 .lib(root.join("node_modules"))
797 .root(root)
798 .build()?,
799 })
800 }
801}
802
803#[derive(Clone, Debug, Default)]
804pub struct ProjectPathsConfigBuilder {
805 root: Option<PathBuf>,
806 cache: Option<PathBuf>,
807 artifacts: Option<PathBuf>,
808 build_infos: Option<PathBuf>,
809 sources: Option<PathBuf>,
810 tests: Option<PathBuf>,
811 scripts: Option<PathBuf>,
812 libraries: Option<Vec<PathBuf>>,
813 remappings: Option<Vec<Remapping>>,
814 include_paths: BTreeSet<PathBuf>,
815 allowed_paths: BTreeSet<PathBuf>,
816}
817
818impl ProjectPathsConfigBuilder {
819 pub fn root(mut self, root: impl Into<PathBuf>) -> Self {
820 self.root = Some(utils::canonicalized(root));
821 self
822 }
823
824 pub fn cache(mut self, cache: impl Into<PathBuf>) -> Self {
825 self.cache = Some(utils::canonicalized(cache));
826 self
827 }
828
829 pub fn artifacts(mut self, artifacts: impl Into<PathBuf>) -> Self {
830 self.artifacts = Some(utils::canonicalized(artifacts));
831 self
832 }
833
834 pub fn build_infos(mut self, build_infos: impl Into<PathBuf>) -> Self {
835 self.build_infos = Some(utils::canonicalized(build_infos));
836 self
837 }
838
839 pub fn sources(mut self, sources: impl Into<PathBuf>) -> Self {
840 self.sources = Some(utils::canonicalized(sources));
841 self
842 }
843
844 pub fn tests(mut self, tests: impl Into<PathBuf>) -> Self {
845 self.tests = Some(utils::canonicalized(tests));
846 self
847 }
848
849 pub fn scripts(mut self, scripts: impl Into<PathBuf>) -> Self {
850 self.scripts = Some(utils::canonicalized(scripts));
851 self
852 }
853
854 pub fn no_libs(mut self) -> Self {
856 self.libraries = Some(Vec::new());
857 self
858 }
859
860 pub fn lib(mut self, lib: impl Into<PathBuf>) -> Self {
861 self.libraries.get_or_insert_with(Vec::new).push(utils::canonicalized(lib));
862 self
863 }
864
865 pub fn libs(mut self, libs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
866 let libraries = self.libraries.get_or_insert_with(Vec::new);
867 for lib in libs {
868 libraries.push(utils::canonicalized(lib));
869 }
870 self
871 }
872
873 pub fn remapping(mut self, remapping: Remapping) -> Self {
874 self.remappings.get_or_insert_with(Vec::new).push(remapping);
875 self
876 }
877
878 pub fn remappings(mut self, remappings: impl IntoIterator<Item = Remapping>) -> Self {
879 let our_remappings = self.remappings.get_or_insert_with(Vec::new);
880 for remapping in remappings {
881 our_remappings.push(remapping);
882 }
883 self
884 }
885
886 pub fn allowed_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
888 self.allowed_paths.insert(path.into());
889 self
890 }
891
892 pub fn allowed_paths<I, S>(mut self, args: I) -> Self
894 where
895 I: IntoIterator<Item = S>,
896 S: Into<PathBuf>,
897 {
898 for arg in args {
899 self = self.allowed_path(arg);
900 }
901 self
902 }
903
904 pub fn include_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
906 self.include_paths.insert(path.into());
907 self
908 }
909
910 pub fn include_paths<I, S>(mut self, args: I) -> Self
912 where
913 I: IntoIterator<Item = S>,
914 S: Into<PathBuf>,
915 {
916 for arg in args {
917 self = self.include_path(arg);
918 }
919 self
920 }
921
922 pub fn build_with_root<C>(self, root: impl Into<PathBuf>) -> ProjectPathsConfig<C> {
923 let root = utils::canonicalized(root);
924
925 let libraries = self.libraries.unwrap_or_else(|| ProjectPathsConfig::find_libs(&root));
926 let artifacts =
927 self.artifacts.unwrap_or_else(|| ProjectPathsConfig::find_artifacts_dir(&root));
928
929 let mut allowed_paths = self.allowed_paths;
930 allowed_paths.insert(root.clone());
932
933 ProjectPathsConfig {
934 cache: self
935 .cache
936 .unwrap_or_else(|| root.join("cache").join(SOLIDITY_FILES_CACHE_FILENAME)),
937 build_infos: self.build_infos.unwrap_or_else(|| artifacts.join("build-info")),
938 artifacts,
939 sources: self.sources.unwrap_or_else(|| ProjectPathsConfig::find_source_dir(&root)),
940 tests: self.tests.unwrap_or_else(|| root.join("test")),
941 scripts: self.scripts.unwrap_or_else(|| root.join("script")),
942 remappings: self.remappings.unwrap_or_else(|| {
943 libraries.iter().flat_map(|p| Remapping::find_many(p)).collect()
944 }),
945 libraries,
946 root,
947 include_paths: self.include_paths,
948 allowed_paths,
949 _l: PhantomData,
950 }
951 }
952
953 pub fn build<C>(self) -> std::result::Result<ProjectPathsConfig<C>, SolcIoError> {
954 let root = self
955 .root
956 .clone()
957 .map(Ok)
958 .unwrap_or_else(std::env::current_dir)
959 .map_err(|err| SolcIoError::new(err, "."))?;
960 Ok(self.build_with_root(root))
961 }
962}
963
964#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
966pub struct SolcConfig {
967 pub settings: Settings,
969}
970
971impl SolcConfig {
972 pub fn builder() -> SolcConfigBuilder {
984 SolcConfigBuilder::default()
985 }
986}
987
988impl From<SolcConfig> for Settings {
989 fn from(config: SolcConfig) -> Self {
990 config.settings
991 }
992}
993
994#[derive(Default)]
995pub struct SolcConfigBuilder {
996 settings: Option<Settings>,
997
998 output_selection: Vec<ContractOutputSelection>,
1000
1001 ast: bool,
1003}
1004
1005impl SolcConfigBuilder {
1006 pub fn settings(mut self, settings: Settings) -> Self {
1007 self.settings = Some(settings);
1008 self
1009 }
1010
1011 #[must_use]
1013 pub fn additional_output(mut self, output: impl Into<ContractOutputSelection>) -> Self {
1014 self.output_selection.push(output.into());
1015 self
1016 }
1017
1018 #[must_use]
1020 pub fn additional_outputs<I, S>(mut self, outputs: I) -> Self
1021 where
1022 I: IntoIterator<Item = S>,
1023 S: Into<ContractOutputSelection>,
1024 {
1025 for out in outputs {
1026 self = self.additional_output(out);
1027 }
1028 self
1029 }
1030
1031 pub const fn ast(mut self, yes: bool) -> Self {
1032 self.ast = yes;
1033 self
1034 }
1035
1036 pub fn build(self) -> Settings {
1038 let Self { settings, output_selection, ast } = self;
1039 let mut settings = settings.unwrap_or_default();
1040 settings.push_all(output_selection);
1041 if ast {
1042 settings = settings.with_ast();
1043 }
1044 settings
1045 }
1046}
1047
1048fn path_starts_with_rooted(a: &Path, b: &Path, root: &Path) -> bool {
1050 if a.starts_with(b) {
1051 return true;
1052 }
1053 if let Ok(b) = b.strip_prefix(root) {
1054 return a.starts_with(b);
1055 }
1056 false
1057}
1058
1059#[cfg(test)]
1060mod tests {
1061 use super::*;
1062
1063 #[test]
1064 fn can_autodetect_dirs() {
1065 let root = utils::tempdir("root").unwrap();
1066 let out = root.path().join("out");
1067 let artifacts = root.path().join("artifacts");
1068 let build_infos = artifacts.join("build-info");
1069 let contracts = root.path().join("contracts");
1070 let src = root.path().join("src");
1071 let lib = root.path().join("lib");
1072 let node_modules = root.path().join("node_modules");
1073
1074 let root = root.path();
1075 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
1076 std::fs::create_dir_all(&contracts).unwrap();
1077 assert_eq!(ProjectPathsConfig::find_source_dir(root), contracts,);
1078 assert_eq!(
1079 ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
1080 utils::canonicalized(contracts),
1081 );
1082 std::fs::create_dir_all(&src).unwrap();
1083 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
1084 assert_eq!(
1085 ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
1086 utils::canonicalized(src),
1087 );
1088
1089 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
1090 std::fs::create_dir_all(&artifacts).unwrap();
1091 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), artifacts,);
1092 assert_eq!(
1093 ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
1094 utils::canonicalized(artifacts),
1095 );
1096 std::fs::create_dir_all(&build_infos).unwrap();
1097 assert_eq!(
1098 ProjectPathsConfig::builder().build_with_root::<()>(root).build_infos,
1099 utils::canonicalized(build_infos)
1100 );
1101
1102 std::fs::create_dir_all(&out).unwrap();
1103 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
1104 assert_eq!(
1105 ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
1106 utils::canonicalized(out),
1107 );
1108
1109 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
1110 std::fs::create_dir_all(&node_modules).unwrap();
1111 assert_eq!(ProjectPathsConfig::find_libs(root), vec![node_modules.clone()],);
1112 assert_eq!(
1113 ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
1114 vec![utils::canonicalized(node_modules)],
1115 );
1116 std::fs::create_dir_all(&lib).unwrap();
1117 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
1118 assert_eq!(
1119 ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
1120 vec![utils::canonicalized(lib)],
1121 );
1122 }
1123
1124 #[test]
1125 fn can_have_sane_build_info_default() {
1126 let root = utils::tempdir("root").unwrap();
1127 let root = root.path();
1128 let artifacts = root.join("forge-artifacts");
1129
1130 let paths = ProjectPathsConfig::builder().artifacts(&artifacts).build_with_root::<()>(root);
1133
1134 assert_eq!(paths.artifacts, utils::canonicalized(artifacts));
1136
1137 assert_eq!(paths.build_infos, utils::canonicalized(paths.artifacts.join("build-info")));
1139 }
1140
1141 #[test]
1142 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
1143 fn can_find_library_ancestor() {
1144 let mut config = ProjectPathsConfig::builder().lib("lib").build::<()>().unwrap();
1145 config.root = "/root/".into();
1146
1147 assert_eq!(
1148 config.find_library_ancestor("lib/src/Greeter.sol".as_ref()).unwrap(),
1149 Path::new("lib")
1150 );
1151
1152 assert_eq!(
1153 config.find_library_ancestor("/root/lib/src/Greeter.sol".as_ref()).unwrap(),
1154 Path::new("lib")
1155 );
1156
1157 config.libraries.push("/root/test/".into());
1158
1159 assert_eq!(
1160 config.find_library_ancestor("test/src/Greeter.sol".as_ref()).unwrap(),
1161 Path::new("/root/test/")
1162 );
1163
1164 assert_eq!(
1165 config.find_library_ancestor("/root/test/src/Greeter.sol".as_ref()).unwrap(),
1166 Path::new("/root/test/")
1167 );
1168 }
1169
1170 #[test]
1171 fn can_resolve_import() {
1172 let dir = tempfile::tempdir().unwrap();
1173 let config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1174 config.create_all().unwrap();
1175
1176 fs::write(config.sources.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
1177
1178 assert_eq!(
1180 config
1181 .resolve_import_and_include_paths(
1182 &config.sources,
1183 Path::new("./A.sol"),
1184 &mut Default::default(),
1185 )
1186 .unwrap(),
1187 config.sources.join("A.sol")
1188 );
1189
1190 assert_eq!(
1192 config
1193 .resolve_import_and_include_paths(
1194 &config.sources,
1195 Path::new("src/A.sol"),
1196 &mut Default::default(),
1197 )
1198 .unwrap(),
1199 config.sources.join("A.sol")
1200 );
1201 }
1202
1203 #[test]
1204 fn can_resolve_remapped_import() {
1205 let dir = tempfile::tempdir().unwrap();
1206 let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1207 config.create_all().unwrap();
1208
1209 let dependency = config.root.join("dependency");
1210 fs::create_dir(&dependency).unwrap();
1211 fs::write(dependency.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
1212
1213 config.remappings.push(Remapping {
1214 context: None,
1215 name: "@dependency/".into(),
1216 path: "dependency/".into(),
1217 });
1218
1219 assert_eq!(
1220 config
1221 .resolve_import_and_include_paths(
1222 &config.sources,
1223 Path::new("@dependency/A.sol"),
1224 &mut Default::default(),
1225 )
1226 .unwrap(),
1227 dependency.join("A.sol")
1228 );
1229 }
1230
1231 #[test]
1232 fn can_resolve_single_file_mapped_import() {
1233 let dir = tempfile::tempdir().unwrap();
1234 let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1235 config.create_all().unwrap();
1236
1237 fs::write(
1238 config.sources.join("A.sol"),
1239 r#"pragma solidity ^0.8.0; import "@my-lib/B.sol"; contract A is B {}"#,
1240 )
1241 .unwrap();
1242
1243 let dependency = config.root.join("my-lib");
1244 fs::create_dir(&dependency).unwrap();
1245 fs::write(dependency.join("B.sol"), r"pragma solidity ^0.8.0; contract B {}").unwrap();
1246
1247 config.remappings.push(Remapping {
1248 context: None,
1249 name: "@my-lib/B.sol".into(),
1250 path: "my-lib/B.sol".into(),
1251 });
1252
1253 assert!(
1255 config
1256 .resolve_import_and_include_paths(
1257 &config.sources,
1258 Path::new("@my-lib/B.sol"),
1259 &mut Default::default(),
1260 )
1261 .unwrap()
1262 .to_str()
1263 .unwrap()
1264 .ends_with("my-lib/B.sol")
1265 );
1266 }
1267
1268 #[test]
1269 fn can_apply_remapping_to_relative_import() {
1270 let dir = tempfile::tempdir().unwrap();
1274 let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1275 config.create_all().unwrap();
1276
1277 let lib_dir = config.root.join("lib/external/utils/LibMem");
1279 fs::create_dir_all(&lib_dir).unwrap();
1280 fs::write(
1281 lib_dir.join("LibMem.sol"),
1282 r"pragma solidity ^0.8.0; library LibMem { bool constant EXTERNAL = true; }",
1283 )
1284 .unwrap();
1285
1286 let local_dir = config.root.join("src/utils");
1288 fs::create_dir_all(&local_dir).unwrap();
1289 fs::write(
1290 local_dir.join("LibMem.sol"),
1291 r"pragma solidity ^0.8.0; library LibMem { bool constant LOCAL = true; }",
1292 )
1293 .unwrap();
1294
1295 let importing_file = config.root.join("lib/external/utils/BytesUtils.sol");
1297 fs::write(&importing_file, r#"pragma solidity ^0.8.0; import "./LibMem/LibMem.sol";"#)
1298 .unwrap();
1299
1300 config.remappings.push(Remapping {
1302 context: None,
1303 name: "lib/external/utils/LibMem/".into(),
1304 path: "src/utils/".into(),
1305 });
1306
1307 let cwd = importing_file.parent().unwrap();
1309 let resolved = config
1310 .resolve_import_and_include_paths(
1311 cwd,
1312 Path::new("./LibMem/LibMem.sol"),
1313 &mut Default::default(),
1314 )
1315 .unwrap();
1316
1317 assert_eq!(resolved, local_dir.join("LibMem.sol"));
1319 }
1320}