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 let resolved = utils::normalize_solidity_import_path(cwd, import).map_err(|err| {
415 SolcError::msg(format!("failed to resolve relative import \"{err:?}\""))
416 })?;
417 Ok(self.apply_remapping_to_path(cwd, &resolved))
419 } else {
420 let resolved = self.resolve_library_import(cwd.as_ref(), import.as_ref());
422
423 if resolved.is_none() {
424 if let Some(lib) = self.find_library_ancestor(cwd) {
428 if let Some((include_path, import)) =
429 utils::resolve_absolute_library(lib, cwd, import)
430 {
431 include_paths.insert(include_path);
433 return Ok(import);
434 }
435 }
436 for path in [&self.root, &self.sources, &self.tests, &self.scripts] {
438 if cwd.starts_with(path) {
439 if let Ok(import) = utils::normalize_solidity_import_path(path, import) {
440 return Ok(import);
441 }
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() {
522 cwd.starts_with(ctx)
523 } else {
524 true
525 }
526 })
527 .find_map(|r| {
528 import.strip_prefix(&r.name).ok().map(|stripped_import| {
529 let lib_path =
530 if stripped_import.as_os_str().is_empty() && r.path.ends_with(".sol") {
531 r.path.clone().into()
532 } else {
533 Path::new(&r.path).join(stripped_import)
534 };
535
536 if let Ok(adjusted_import) = stripped_import.strip_prefix("contracts/") {
540 if r.path.ends_with("contracts/") && !self.root.join(&lib_path).exists() {
541 return Path::new(&r.path).join(adjusted_import);
542 }
543 }
544 lib_path
545 })
546 })
547 {
548 Some(self.root.join(path))
549 } else {
550 utils::resolve_library(&self.libraries, import)
551 }
552 }
553
554 pub fn apply_remapping_to_path(&self, cwd: &Path, resolved_path: &Path) -> PathBuf {
571 let relative_path = match resolved_path.strip_prefix(&self.root) {
573 Ok(p) => p,
574 Err(_) => return resolved_path.to_path_buf(),
575 };
576
577 let cwd_relative = cwd.strip_prefix(&self.root).unwrap_or(cwd);
578
579 for r in &self.remappings {
581 if let Some(ctx) = r.context.as_ref() {
583 if !cwd_relative.starts_with(ctx) {
584 continue;
585 }
586 }
587
588 if let Ok(stripped) = relative_path.strip_prefix(&r.name) {
590 let remapped_relative = Path::new(&r.path).join(stripped);
591 let remapped_absolute = self.root.join(&remapped_relative);
592
593 if remapped_absolute.exists() {
595 return remapped_absolute;
596 }
597 }
598 }
599
600 resolved_path.to_path_buf()
601 }
602
603 pub fn with_language_ref<Lang>(&self) -> &ProjectPathsConfig<Lang> {
604 unsafe { std::mem::transmute(self) }
606 }
607
608 pub fn with_language<Lang>(self) -> ProjectPathsConfig<Lang> {
609 unsafe { std::mem::transmute(self) }
611 }
612
613 pub fn apply_lib_remappings(&self, mut libraries: Libraries) -> Libraries {
614 libraries.libs = libraries.libs
615 .into_iter()
616 .map(|(file, target)| {
617 let file = self.resolve_import(&self.root, &file).unwrap_or_else(|err| {
618 warn!(target: "libs", "Failed to resolve library `{}` for linking: {:?}", file.display(), err);
619 file
620 });
621 (file, target)
622 })
623 .collect();
624 libraries
625 }
626}
627
628impl<L: Language> ProjectPathsConfig<L> {
629 pub fn read_sources(&self) -> Result<Sources> {
631 trace!("reading all sources from \"{}\"", self.sources.display());
632 Ok(Source::read_all_from(&self.sources, L::FILE_EXTENSIONS)?)
633 }
634
635 pub fn read_tests(&self) -> Result<Sources> {
637 trace!("reading all tests from \"{}\"", self.tests.display());
638 Ok(Source::read_all_from(&self.tests, L::FILE_EXTENSIONS)?)
639 }
640
641 pub fn read_scripts(&self) -> Result<Sources> {
643 trace!("reading all scripts from \"{}\"", self.scripts.display());
644 Ok(Source::read_all_from(&self.scripts, L::FILE_EXTENSIONS)?)
645 }
646
647 pub fn has_input_files(&self) -> bool {
651 self.input_files_iter().next().is_some()
652 }
653
654 pub fn input_files_iter(&self) -> impl Iterator<Item = PathBuf> + '_ {
657 utils::source_files_iter(&self.sources, L::FILE_EXTENSIONS)
658 .chain(utils::source_files_iter(&self.tests, L::FILE_EXTENSIONS))
659 .chain(utils::source_files_iter(&self.scripts, L::FILE_EXTENSIONS))
660 }
661
662 pub fn input_files(&self) -> Vec<PathBuf> {
665 self.input_files_iter().collect()
666 }
667
668 pub fn read_input_files(&self) -> Result<Sources> {
670 Ok(Source::read_all(self.input_files_iter())?)
671 }
672}
673
674impl fmt::Display for ProjectPathsConfig {
675 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
676 writeln!(f, "root: {}", self.root.display())?;
677 writeln!(f, "contracts: {}", self.sources.display())?;
678 writeln!(f, "artifacts: {}", self.artifacts.display())?;
679 writeln!(f, "tests: {}", self.tests.display())?;
680 writeln!(f, "scripts: {}", self.scripts.display())?;
681 writeln!(f, "libs:")?;
682 for lib in &self.libraries {
683 writeln!(f, " {}", lib.display())?;
684 }
685 writeln!(f, "remappings:")?;
686 for remapping in &self.remappings {
687 writeln!(f, " {remapping}")?;
688 }
689 Ok(())
690 }
691}
692
693#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
695pub struct ProjectPaths {
696 pub artifacts: PathBuf,
697 pub build_infos: PathBuf,
698 pub sources: PathBuf,
699 pub tests: PathBuf,
700 pub scripts: PathBuf,
701 pub libraries: BTreeSet<PathBuf>,
702}
703
704impl ProjectPaths {
705 pub fn join_all(&mut self, root: &Path) -> &mut Self {
707 self.artifacts = root.join(&self.artifacts);
708 self.build_infos = root.join(&self.build_infos);
709 self.sources = root.join(&self.sources);
710 self.tests = root.join(&self.tests);
711 self.scripts = root.join(&self.scripts);
712 let libraries = std::mem::take(&mut self.libraries);
713 self.libraries.extend(libraries.into_iter().map(|p| root.join(p)));
714 self
715 }
716
717 pub fn strip_prefix_all(&mut self, base: &Path) -> &mut Self {
719 if let Ok(stripped) = self.artifacts.strip_prefix(base) {
720 self.artifacts = stripped.to_path_buf();
721 }
722 if let Ok(stripped) = self.build_infos.strip_prefix(base) {
723 self.build_infos = stripped.to_path_buf();
724 }
725 if let Ok(stripped) = self.sources.strip_prefix(base) {
726 self.sources = stripped.to_path_buf();
727 }
728 if let Ok(stripped) = self.tests.strip_prefix(base) {
729 self.tests = stripped.to_path_buf();
730 }
731 if let Ok(stripped) = self.scripts.strip_prefix(base) {
732 self.scripts = stripped.to_path_buf();
733 }
734 self.libraries = std::mem::take(&mut self.libraries)
735 .into_iter()
736 .map(|path| strip_prefix_owned(path, base))
737 .collect();
738 self
739 }
740
741 pub fn is_test_or_script(&self, path: &Path) -> bool {
743 self.is_test(path) || self.is_script(path)
744 }
745
746 pub fn is_test(&self, path: &Path) -> bool {
748 path.starts_with(&self.tests)
749 }
750
751 pub fn is_script(&self, path: &Path) -> bool {
753 path.starts_with(&self.scripts)
754 }
755
756 pub fn is_source_file(&self, path: &Path) -> bool {
758 !self.is_test_or_script(path)
759 }
760}
761
762impl Default for ProjectPaths {
763 fn default() -> Self {
764 Self {
765 artifacts: "out".into(),
766 build_infos: ["out", "build-info"].iter().collect::<PathBuf>(),
767 sources: "src".into(),
768 tests: "test".into(),
769 scripts: "script".into(),
770 libraries: Default::default(),
771 }
772 }
773}
774
775#[derive(Clone, Debug, PartialEq, Eq)]
776pub enum PathStyle {
777 HardHat,
778 Dapptools,
779}
780
781impl PathStyle {
782 pub fn paths<C>(&self, root: &Path) -> Result<ProjectPathsConfig<C>> {
784 let root = utils::canonicalize(root)?;
785
786 Ok(match self {
787 Self::Dapptools => ProjectPathsConfig::builder()
788 .sources(root.join("src"))
789 .artifacts(root.join("out"))
790 .build_infos(root.join("out").join("build-info"))
791 .lib(root.join("lib"))
792 .remappings(Remapping::find_many(&root.join("lib")))
793 .root(root)
794 .build()?,
795 Self::HardHat => ProjectPathsConfig::builder()
796 .sources(root.join("contracts"))
797 .artifacts(root.join("artifacts"))
798 .build_infos(root.join("artifacts").join("build-info"))
799 .lib(root.join("node_modules"))
800 .root(root)
801 .build()?,
802 })
803 }
804}
805
806#[derive(Clone, Debug, Default)]
807pub struct ProjectPathsConfigBuilder {
808 root: Option<PathBuf>,
809 cache: Option<PathBuf>,
810 artifacts: Option<PathBuf>,
811 build_infos: Option<PathBuf>,
812 sources: Option<PathBuf>,
813 tests: Option<PathBuf>,
814 scripts: Option<PathBuf>,
815 libraries: Option<Vec<PathBuf>>,
816 remappings: Option<Vec<Remapping>>,
817 include_paths: BTreeSet<PathBuf>,
818 allowed_paths: BTreeSet<PathBuf>,
819}
820
821impl ProjectPathsConfigBuilder {
822 pub fn root(mut self, root: impl Into<PathBuf>) -> Self {
823 self.root = Some(utils::canonicalized(root));
824 self
825 }
826
827 pub fn cache(mut self, cache: impl Into<PathBuf>) -> Self {
828 self.cache = Some(utils::canonicalized(cache));
829 self
830 }
831
832 pub fn artifacts(mut self, artifacts: impl Into<PathBuf>) -> Self {
833 self.artifacts = Some(utils::canonicalized(artifacts));
834 self
835 }
836
837 pub fn build_infos(mut self, build_infos: impl Into<PathBuf>) -> Self {
838 self.build_infos = Some(utils::canonicalized(build_infos));
839 self
840 }
841
842 pub fn sources(mut self, sources: impl Into<PathBuf>) -> Self {
843 self.sources = Some(utils::canonicalized(sources));
844 self
845 }
846
847 pub fn tests(mut self, tests: impl Into<PathBuf>) -> Self {
848 self.tests = Some(utils::canonicalized(tests));
849 self
850 }
851
852 pub fn scripts(mut self, scripts: impl Into<PathBuf>) -> Self {
853 self.scripts = Some(utils::canonicalized(scripts));
854 self
855 }
856
857 pub fn no_libs(mut self) -> Self {
859 self.libraries = Some(Vec::new());
860 self
861 }
862
863 pub fn lib(mut self, lib: impl Into<PathBuf>) -> Self {
864 self.libraries.get_or_insert_with(Vec::new).push(utils::canonicalized(lib));
865 self
866 }
867
868 pub fn libs(mut self, libs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
869 let libraries = self.libraries.get_or_insert_with(Vec::new);
870 for lib in libs.into_iter() {
871 libraries.push(utils::canonicalized(lib));
872 }
873 self
874 }
875
876 pub fn remapping(mut self, remapping: Remapping) -> Self {
877 self.remappings.get_or_insert_with(Vec::new).push(remapping);
878 self
879 }
880
881 pub fn remappings(mut self, remappings: impl IntoIterator<Item = Remapping>) -> Self {
882 let our_remappings = self.remappings.get_or_insert_with(Vec::new);
883 for remapping in remappings.into_iter() {
884 our_remappings.push(remapping);
885 }
886 self
887 }
888
889 pub fn allowed_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
891 self.allowed_paths.insert(path.into());
892 self
893 }
894
895 pub fn allowed_paths<I, S>(mut self, args: I) -> Self
897 where
898 I: IntoIterator<Item = S>,
899 S: Into<PathBuf>,
900 {
901 for arg in args {
902 self = self.allowed_path(arg);
903 }
904 self
905 }
906
907 pub fn include_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
909 self.include_paths.insert(path.into());
910 self
911 }
912
913 pub fn include_paths<I, S>(mut self, args: I) -> Self
915 where
916 I: IntoIterator<Item = S>,
917 S: Into<PathBuf>,
918 {
919 for arg in args {
920 self = self.include_path(arg);
921 }
922 self
923 }
924
925 pub fn build_with_root<C>(self, root: impl Into<PathBuf>) -> ProjectPathsConfig<C> {
926 let root = utils::canonicalized(root);
927
928 let libraries = self.libraries.unwrap_or_else(|| ProjectPathsConfig::find_libs(&root));
929 let artifacts =
930 self.artifacts.unwrap_or_else(|| ProjectPathsConfig::find_artifacts_dir(&root));
931
932 let mut allowed_paths = self.allowed_paths;
933 allowed_paths.insert(root.clone());
935
936 ProjectPathsConfig {
937 cache: self
938 .cache
939 .unwrap_or_else(|| root.join("cache").join(SOLIDITY_FILES_CACHE_FILENAME)),
940 build_infos: self.build_infos.unwrap_or_else(|| artifacts.join("build-info")),
941 artifacts,
942 sources: self.sources.unwrap_or_else(|| ProjectPathsConfig::find_source_dir(&root)),
943 tests: self.tests.unwrap_or_else(|| root.join("test")),
944 scripts: self.scripts.unwrap_or_else(|| root.join("script")),
945 remappings: self.remappings.unwrap_or_else(|| {
946 libraries.iter().flat_map(|p| Remapping::find_many(p)).collect()
947 }),
948 libraries,
949 root,
950 include_paths: self.include_paths,
951 allowed_paths,
952 _l: PhantomData,
953 }
954 }
955
956 pub fn build<C>(self) -> std::result::Result<ProjectPathsConfig<C>, SolcIoError> {
957 let root = self
958 .root
959 .clone()
960 .map(Ok)
961 .unwrap_or_else(std::env::current_dir)
962 .map_err(|err| SolcIoError::new(err, "."))?;
963 Ok(self.build_with_root(root))
964 }
965}
966
967#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
969pub struct SolcConfig {
970 pub settings: Settings,
972}
973
974impl SolcConfig {
975 pub fn builder() -> SolcConfigBuilder {
987 SolcConfigBuilder::default()
988 }
989}
990
991impl From<SolcConfig> for Settings {
992 fn from(config: SolcConfig) -> Self {
993 config.settings
994 }
995}
996
997#[derive(Default)]
998pub struct SolcConfigBuilder {
999 settings: Option<Settings>,
1000
1001 output_selection: Vec<ContractOutputSelection>,
1003
1004 ast: bool,
1006}
1007
1008impl SolcConfigBuilder {
1009 pub fn settings(mut self, settings: Settings) -> Self {
1010 self.settings = Some(settings);
1011 self
1012 }
1013
1014 #[must_use]
1016 pub fn additional_output(mut self, output: impl Into<ContractOutputSelection>) -> Self {
1017 self.output_selection.push(output.into());
1018 self
1019 }
1020
1021 #[must_use]
1023 pub fn additional_outputs<I, S>(mut self, outputs: I) -> Self
1024 where
1025 I: IntoIterator<Item = S>,
1026 S: Into<ContractOutputSelection>,
1027 {
1028 for out in outputs {
1029 self = self.additional_output(out);
1030 }
1031 self
1032 }
1033
1034 pub fn ast(mut self, yes: bool) -> Self {
1035 self.ast = yes;
1036 self
1037 }
1038
1039 pub fn build(self) -> Settings {
1041 let Self { settings, output_selection, ast } = self;
1042 let mut settings = settings.unwrap_or_default();
1043 settings.push_all(output_selection);
1044 if ast {
1045 settings = settings.with_ast();
1046 }
1047 settings
1048 }
1049}
1050
1051fn path_starts_with_rooted(a: &Path, b: &Path, root: &Path) -> bool {
1053 if a.starts_with(b) {
1054 return true;
1055 }
1056 if let Ok(b) = b.strip_prefix(root) {
1057 return a.starts_with(b);
1058 }
1059 false
1060}
1061
1062#[cfg(test)]
1063mod tests {
1064 use super::*;
1065
1066 #[test]
1067 fn can_autodetect_dirs() {
1068 let root = utils::tempdir("root").unwrap();
1069 let out = root.path().join("out");
1070 let artifacts = root.path().join("artifacts");
1071 let build_infos = artifacts.join("build-info");
1072 let contracts = root.path().join("contracts");
1073 let src = root.path().join("src");
1074 let lib = root.path().join("lib");
1075 let node_modules = root.path().join("node_modules");
1076
1077 let root = root.path();
1078 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
1079 std::fs::create_dir_all(&contracts).unwrap();
1080 assert_eq!(ProjectPathsConfig::find_source_dir(root), contracts,);
1081 assert_eq!(
1082 ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
1083 utils::canonicalized(contracts),
1084 );
1085 std::fs::create_dir_all(&src).unwrap();
1086 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
1087 assert_eq!(
1088 ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
1089 utils::canonicalized(src),
1090 );
1091
1092 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
1093 std::fs::create_dir_all(&artifacts).unwrap();
1094 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), artifacts,);
1095 assert_eq!(
1096 ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
1097 utils::canonicalized(artifacts),
1098 );
1099 std::fs::create_dir_all(&build_infos).unwrap();
1100 assert_eq!(
1101 ProjectPathsConfig::builder().build_with_root::<()>(root).build_infos,
1102 utils::canonicalized(build_infos)
1103 );
1104
1105 std::fs::create_dir_all(&out).unwrap();
1106 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
1107 assert_eq!(
1108 ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
1109 utils::canonicalized(out),
1110 );
1111
1112 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
1113 std::fs::create_dir_all(&node_modules).unwrap();
1114 assert_eq!(ProjectPathsConfig::find_libs(root), vec![node_modules.clone()],);
1115 assert_eq!(
1116 ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
1117 vec![utils::canonicalized(node_modules)],
1118 );
1119 std::fs::create_dir_all(&lib).unwrap();
1120 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
1121 assert_eq!(
1122 ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
1123 vec![utils::canonicalized(lib)],
1124 );
1125 }
1126
1127 #[test]
1128 fn can_have_sane_build_info_default() {
1129 let root = utils::tempdir("root").unwrap();
1130 let root = root.path();
1131 let artifacts = root.join("forge-artifacts");
1132
1133 let paths = ProjectPathsConfig::builder().artifacts(&artifacts).build_with_root::<()>(root);
1136
1137 assert_eq!(paths.artifacts, utils::canonicalized(artifacts));
1139
1140 assert_eq!(paths.build_infos, utils::canonicalized(paths.artifacts.join("build-info")));
1142 }
1143
1144 #[test]
1145 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
1146 fn can_find_library_ancestor() {
1147 let mut config = ProjectPathsConfig::builder().lib("lib").build::<()>().unwrap();
1148 config.root = "/root/".into();
1149
1150 assert_eq!(
1151 config.find_library_ancestor("lib/src/Greeter.sol".as_ref()).unwrap(),
1152 Path::new("lib")
1153 );
1154
1155 assert_eq!(
1156 config.find_library_ancestor("/root/lib/src/Greeter.sol".as_ref()).unwrap(),
1157 Path::new("lib")
1158 );
1159
1160 config.libraries.push("/root/test/".into());
1161
1162 assert_eq!(
1163 config.find_library_ancestor("test/src/Greeter.sol".as_ref()).unwrap(),
1164 Path::new("/root/test/")
1165 );
1166
1167 assert_eq!(
1168 config.find_library_ancestor("/root/test/src/Greeter.sol".as_ref()).unwrap(),
1169 Path::new("/root/test/")
1170 );
1171 }
1172
1173 #[test]
1174 fn can_resolve_import() {
1175 let dir = tempfile::tempdir().unwrap();
1176 let config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1177 config.create_all().unwrap();
1178
1179 fs::write(config.sources.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
1180
1181 assert_eq!(
1183 config
1184 .resolve_import_and_include_paths(
1185 &config.sources,
1186 Path::new("./A.sol"),
1187 &mut Default::default(),
1188 )
1189 .unwrap(),
1190 config.sources.join("A.sol")
1191 );
1192
1193 assert_eq!(
1195 config
1196 .resolve_import_and_include_paths(
1197 &config.sources,
1198 Path::new("src/A.sol"),
1199 &mut Default::default(),
1200 )
1201 .unwrap(),
1202 config.sources.join("A.sol")
1203 );
1204 }
1205
1206 #[test]
1207 fn can_resolve_remapped_import() {
1208 let dir = tempfile::tempdir().unwrap();
1209 let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1210 config.create_all().unwrap();
1211
1212 let dependency = config.root.join("dependency");
1213 fs::create_dir(&dependency).unwrap();
1214 fs::write(dependency.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
1215
1216 config.remappings.push(Remapping {
1217 context: None,
1218 name: "@dependency/".into(),
1219 path: "dependency/".into(),
1220 });
1221
1222 assert_eq!(
1223 config
1224 .resolve_import_and_include_paths(
1225 &config.sources,
1226 Path::new("@dependency/A.sol"),
1227 &mut Default::default(),
1228 )
1229 .unwrap(),
1230 dependency.join("A.sol")
1231 );
1232 }
1233
1234 #[test]
1235 fn can_resolve_single_file_mapped_import() {
1236 let dir = tempfile::tempdir().unwrap();
1237 let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1238 config.create_all().unwrap();
1239
1240 fs::write(
1241 config.sources.join("A.sol"),
1242 r#"pragma solidity ^0.8.0; import "@my-lib/B.sol"; contract A is B {}"#,
1243 )
1244 .unwrap();
1245
1246 let dependency = config.root.join("my-lib");
1247 fs::create_dir(&dependency).unwrap();
1248 fs::write(dependency.join("B.sol"), r"pragma solidity ^0.8.0; contract B {}").unwrap();
1249
1250 config.remappings.push(Remapping {
1251 context: None,
1252 name: "@my-lib/B.sol".into(),
1253 path: "my-lib/B.sol".into(),
1254 });
1255
1256 assert!(config
1258 .resolve_import_and_include_paths(
1259 &config.sources,
1260 Path::new("@my-lib/B.sol"),
1261 &mut Default::default(),
1262 )
1263 .unwrap()
1264 .to_str()
1265 .unwrap()
1266 .ends_with("my-lib/B.sol"));
1267 }
1268
1269 #[test]
1270 fn can_apply_remapping_to_relative_import() {
1271 let dir = tempfile::tempdir().unwrap();
1275 let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1276 config.create_all().unwrap();
1277
1278 let lib_dir = config.root.join("lib/external/utils/LibMem");
1280 fs::create_dir_all(&lib_dir).unwrap();
1281 fs::write(
1282 lib_dir.join("LibMem.sol"),
1283 r"pragma solidity ^0.8.0; library LibMem { bool constant EXTERNAL = true; }",
1284 )
1285 .unwrap();
1286
1287 let local_dir = config.root.join("src/utils");
1289 fs::create_dir_all(&local_dir).unwrap();
1290 fs::write(
1291 local_dir.join("LibMem.sol"),
1292 r"pragma solidity ^0.8.0; library LibMem { bool constant LOCAL = true; }",
1293 )
1294 .unwrap();
1295
1296 let importing_file = config.root.join("lib/external/utils/BytesUtils.sol");
1298 fs::write(&importing_file, r#"pragma solidity ^0.8.0; import "./LibMem/LibMem.sol";"#)
1299 .unwrap();
1300
1301 config.remappings.push(Remapping {
1303 context: None,
1304 name: "lib/external/utils/LibMem/".into(),
1305 path: "src/utils/".into(),
1306 });
1307
1308 let cwd = importing_file.parent().unwrap();
1310 let resolved = config
1311 .resolve_import_and_include_paths(
1312 cwd,
1313 Path::new("./LibMem/LibMem.sol"),
1314 &mut Default::default(),
1315 )
1316 .unwrap();
1317
1318 assert_eq!(resolved, local_dir.join("LibMem.sol"));
1320 }
1321}