1use crate::{
2 artifacts::{output_selection::ContractOutputSelection, Settings},
3 cache::SOLIDITY_FILES_CACHE_FILENAME,
4 error::{Result, SolcError, SolcIoError},
5 remappings::Remapping,
6 resolver::{Graph, SolImportAlias},
7 utils, Source, Sources,
8};
9use serde::{Deserialize, Serialize};
10use std::{
11 collections::{BTreeSet, HashSet},
12 fmt::{self, Formatter},
13 fs,
14 ops::{Deref, DerefMut},
15 path::{Component, Path, PathBuf},
16};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ProjectPathsConfig {
21 pub root: PathBuf,
23 pub cache: PathBuf,
25 pub artifacts: PathBuf,
27 pub build_infos: PathBuf,
29 pub sources: PathBuf,
31 pub tests: PathBuf,
33 pub scripts: PathBuf,
35 pub libraries: Vec<PathBuf>,
37 pub remappings: Vec<Remapping>,
39}
40
41impl ProjectPathsConfig {
42 pub fn builder() -> ProjectPathsConfigBuilder {
43 ProjectPathsConfigBuilder::default()
44 }
45
46 pub fn hardhat(root: impl AsRef<Path>) -> Result<Self> {
48 PathStyle::HardHat.paths(root)
49 }
50
51 pub fn dapptools(root: impl AsRef<Path>) -> Result<Self> {
53 PathStyle::Dapptools.paths(root)
54 }
55
56 pub fn current_hardhat() -> Result<Self> {
58 Self::hardhat(std::env::current_dir().map_err(|err| SolcError::io(err, "."))?)
59 }
60
61 pub fn current_dapptools() -> Result<Self> {
63 Self::dapptools(std::env::current_dir().map_err(|err| SolcError::io(err, "."))?)
64 }
65
66 pub fn paths(&self) -> ProjectPaths {
69 ProjectPaths {
70 artifacts: self.artifacts.clone(),
71 build_infos: self.build_infos.clone(),
72 sources: self.sources.clone(),
73 tests: self.tests.clone(),
74 scripts: self.scripts.clone(),
75 libraries: self.libraries.iter().cloned().collect(),
76 }
77 }
78
79 pub fn paths_relative(&self) -> ProjectPaths {
83 let mut paths = self.paths();
84 paths.strip_prefix_all(&self.root);
85 paths
86 }
87
88 pub fn create_all(&self) -> std::result::Result<(), SolcIoError> {
90 if let Some(parent) = self.cache.parent() {
91 fs::create_dir_all(parent).map_err(|err| SolcIoError::new(err, parent))?;
92 }
93 fs::create_dir_all(&self.artifacts)
94 .map_err(|err| SolcIoError::new(err, &self.artifacts))?;
95 fs::create_dir_all(&self.sources).map_err(|err| SolcIoError::new(err, &self.sources))?;
96 fs::create_dir_all(&self.tests).map_err(|err| SolcIoError::new(err, &self.tests))?;
97 fs::create_dir_all(&self.scripts).map_err(|err| SolcIoError::new(err, &self.scripts))?;
98 for lib in &self.libraries {
99 fs::create_dir_all(lib).map_err(|err| SolcIoError::new(err, lib))?;
100 }
101 Ok(())
102 }
103
104 pub fn read_sources(&self) -> Result<Sources> {
106 tracing::trace!("reading all sources from \"{}\"", self.sources.display());
107 Ok(Source::read_all_from(&self.sources)?)
108 }
109
110 pub fn read_tests(&self) -> Result<Sources> {
112 tracing::trace!("reading all tests from \"{}\"", self.tests.display());
113 Ok(Source::read_all_from(&self.tests)?)
114 }
115
116 pub fn read_scripts(&self) -> Result<Sources> {
118 tracing::trace!("reading all scripts from \"{}\"", self.scripts.display());
119 Ok(Source::read_all_from(&self.scripts)?)
120 }
121
122 pub fn has_input_files(&self) -> bool {
126 self.input_files_iter().next().is_some()
127 }
128
129 pub fn input_files_iter(&self) -> impl Iterator<Item = PathBuf> + '_ {
132 utils::source_files_iter(&self.sources)
133 .chain(utils::source_files_iter(&self.tests))
134 .chain(utils::source_files_iter(&self.scripts))
135 }
136
137 pub fn input_files(&self) -> Vec<PathBuf> {
140 self.input_files_iter().collect()
141 }
142
143 pub fn read_input_files(&self) -> Result<Sources> {
145 Ok(Source::read_all_files(self.input_files())?)
146 }
147
148 pub fn slash_paths(&mut self) {
150 #[cfg(windows)]
151 {
152 use path_slash::PathBufExt;
153
154 let slashed = |p: &mut PathBuf| {
155 *p = p.to_slash_lossy().as_ref().into();
156 };
157 slashed(&mut self.root);
158 slashed(&mut self.cache);
159 slashed(&mut self.artifacts);
160 slashed(&mut self.build_infos);
161 slashed(&mut self.sources);
162 slashed(&mut self.tests);
163 slashed(&mut self.scripts);
164
165 self.libraries.iter_mut().for_each(slashed);
166 self.remappings.iter_mut().for_each(Remapping::slash_path);
167 }
168 }
169
170 pub fn has_library_ancestor(&self, file: impl AsRef<Path>) -> bool {
172 self.find_library_ancestor(file).is_some()
173 }
174
175 pub fn find_library_ancestor(&self, file: impl AsRef<Path>) -> Option<&PathBuf> {
191 let file = file.as_ref();
192
193 for lib in &self.libraries {
194 if lib.is_relative() &&
195 file.is_absolute() &&
196 file.starts_with(&self.root) &&
197 file.starts_with(self.root.join(lib)) ||
198 file.is_relative() &&
199 lib.is_absolute() &&
200 lib.starts_with(&self.root) &&
201 self.root.join(file).starts_with(lib)
202 {
203 return Some(lib)
204 }
205 if file.starts_with(lib) {
206 return Some(lib)
207 }
208 }
209
210 None
211 }
212
213 pub fn resolve_import_and_include_paths(
220 &self,
221 cwd: &Path,
222 import: &Path,
223 include_paths: &mut IncludePaths,
224 ) -> Result<PathBuf> {
225 let component = import
226 .components()
227 .next()
228 .ok_or_else(|| SolcError::msg(format!("Empty import path {}", import.display())))?;
229
230 if component == Component::CurDir || component == Component::ParentDir {
231 utils::canonicalize(cwd.join(import)).map_err(|err| {
234 SolcError::msg(format!("failed to resolve relative import \"{err:?}\""))
235 })
236 } else {
237 let resolved = self.resolve_library_import(cwd.as_ref(), import.as_ref());
239
240 if resolved.is_none() {
241 if let Some(lib) = self.find_library_ancestor(cwd) {
245 if let Some((include_path, import)) =
246 utils::resolve_absolute_library(lib, cwd, import)
247 {
248 include_paths.insert(include_path);
250 return Ok(import)
251 }
252 }
253 for path in [&self.root, &self.sources, &self.tests, &self.scripts] {
255 if cwd.starts_with(path) {
256 if let Ok(import) = utils::canonicalize(path.join(import)) {
257 return Ok(import)
258 }
259 }
260 }
261 }
262
263 resolved.ok_or_else(|| {
264 SolcError::msg(format!(
265 "failed to resolve library import \"{:?}\"",
266 import.display()
267 ))
268 })
269 }
270 }
271
272 pub fn resolve_import(&self, cwd: &Path, import: &Path) -> Result<PathBuf> {
276 self.resolve_import_and_include_paths(cwd, import, &mut Default::default())
277 }
278
279 pub fn resolve_library_import(&self, cwd: &Path, import: &Path) -> Option<PathBuf> {
329 let cwd = cwd.strip_prefix(&self.root).unwrap_or(cwd);
332 if let Some(path) = self
333 .remappings
334 .iter()
335 .filter(|r| {
336 if let Some(ctx) = r.context.as_ref() {
338 cwd.starts_with(ctx)
339 } else {
340 true
341 }
342 })
343 .find_map(|r| {
344 import.strip_prefix(&r.name).ok().map(|stripped_import| {
345 let lib_path = Path::new(&r.path).join(stripped_import);
346
347 if let Ok(adjusted_import) = stripped_import.strip_prefix("contracts/") {
351 if r.path.ends_with("/contracts/") && !lib_path.exists() {
354 return Path::new(&r.path).join(adjusted_import)
355 }
356 }
357 lib_path
358 })
359 })
360 {
361 Some(self.root.join(path))
362 } else {
363 utils::resolve_library(&self.libraries, import)
364 }
365 }
366
367 pub fn find_artifacts_dir(root: impl AsRef<Path>) -> PathBuf {
374 utils::find_fave_or_alt_path(root, "out", "artifacts")
375 }
376
377 pub fn find_source_dir(root: impl AsRef<Path>) -> PathBuf {
384 utils::find_fave_or_alt_path(root, "src", "contracts")
385 }
386
387 pub fn find_libs(root: impl AsRef<Path>) -> Vec<PathBuf> {
394 vec![utils::find_fave_or_alt_path(root, "lib", "node_modules")]
395 }
396
397 pub fn flatten(&self, target: &Path) -> Result<String> {
399 tracing::trace!("flattening file");
400 let mut input_files = self.input_files();
401
402 let flatten_target = target.to_path_buf();
405 if !input_files.contains(&flatten_target) {
406 input_files.push(flatten_target);
407 }
408
409 let sources = Source::read_all_files(input_files)?;
410 let graph = Graph::resolve_sources(self, sources)?;
411 self.flatten_node(target, &graph, &mut Default::default(), false, false, false).map(|x| {
412 format!("{}\n", utils::RE_THREE_OR_MORE_NEWLINES.replace_all(&x, "\n\n").trim())
413 })
414 }
415
416 fn flatten_node(
418 &self,
419 target: &Path,
420 graph: &Graph,
421 imported: &mut HashSet<usize>,
422 strip_version_pragma: bool,
423 strip_experimental_pragma: bool,
424 strip_license: bool,
425 ) -> Result<String> {
426 let target_dir = target.parent().ok_or_else(|| {
427 SolcError::msg(format!("failed to get parent directory for \"{:?}\"", target.display()))
428 })?;
429 let target_index = graph.files().get(target).ok_or_else(|| {
430 SolcError::msg(format!("cannot resolve file at {:?}", target.display()))
431 })?;
432
433 if imported.contains(target_index) {
434 return Ok(String::new())
436 }
437 imported.insert(*target_index);
438
439 let target_node = graph.node(*target_index);
440
441 let mut imports = target_node.imports().clone();
442 imports.sort_by_key(|x| x.loc().start);
443
444 let mut content = target_node.content().to_owned();
445
446 for alias in imports.iter().flat_map(|i| i.data().aliases()) {
447 let (alias, target) = match alias {
448 SolImportAlias::Contract(alias, target) => (alias.clone(), target.clone()),
449 _ => continue,
450 };
451 let name_regex = utils::create_contract_or_lib_name_regex(&alias);
452 let target_len = target.len() as isize;
453 let mut replace_offset = 0;
454 for cap in name_regex.captures_iter(&content.clone()) {
455 if cap.name("ignore").is_some() {
456 continue
457 }
458 if let Some(name_match) = ["n1", "n2", "n3"].iter().find_map(|name| cap.name(name))
459 {
460 let name_match_range =
461 utils::range_by_offset(&name_match.range(), replace_offset);
462 replace_offset += target_len - (name_match_range.len() as isize);
463 content.replace_range(name_match_range, &target);
464 }
465 }
466 }
467
468 let mut content = content.as_bytes().to_vec();
469 let mut offset = 0_isize;
470
471 let mut statements = [
472 (target_node.license(), strip_license),
473 (target_node.version(), strip_version_pragma),
474 (target_node.experimental(), strip_experimental_pragma),
475 ]
476 .iter()
477 .filter_map(|(data, condition)| if *condition { data.to_owned().as_ref() } else { None })
478 .collect::<Vec<_>>();
479 statements.sort_by_key(|x| x.loc().start);
480
481 let (mut imports, mut statements) =
482 (imports.iter().peekable(), statements.iter().peekable());
483 while imports.peek().is_some() || statements.peek().is_some() {
484 let (next_import_start, next_statement_start) = (
485 imports.peek().map_or(usize::max_value(), |x| x.loc().start),
486 statements.peek().map_or(usize::max_value(), |x| x.loc().start),
487 );
488 if next_statement_start < next_import_start {
489 let repl_range = statements.next().unwrap().loc_by_offset(offset);
490 offset -= repl_range.len() as isize;
491 content.splice(repl_range, std::iter::empty());
492 } else {
493 let import = imports.next().unwrap();
494 let import_path = self.resolve_import(target_dir, import.data().path())?;
495 let s = self.flatten_node(&import_path, graph, imported, true, true, true)?;
496
497 let import_content = s.as_bytes();
498 let import_content_len = import_content.len() as isize;
499 let import_range = import.loc_by_offset(offset);
500 offset += import_content_len - (import_range.len() as isize);
501 content.splice(import_range, import_content.iter().copied());
502 }
503 }
504
505 let result = String::from_utf8(content).map_err(|err| {
506 SolcError::msg(format!("failed to convert extended bytes to string: {err}"))
507 })?;
508
509 Ok(result)
510 }
511}
512
513impl fmt::Display for ProjectPathsConfig {
514 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
515 writeln!(f, "root: {}", self.root.display())?;
516 writeln!(f, "contracts: {}", self.sources.display())?;
517 writeln!(f, "artifacts: {}", self.artifacts.display())?;
518 writeln!(f, "tests: {}", self.tests.display())?;
519 writeln!(f, "scripts: {}", self.scripts.display())?;
520 writeln!(f, "libs:")?;
521 for lib in &self.libraries {
522 writeln!(f, " {}", lib.display())?;
523 }
524 writeln!(f, "remappings:")?;
525 for remapping in &self.remappings {
526 writeln!(f, " {remapping}")?;
527 }
528 Ok(())
529 }
530}
531
532#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
534pub struct ProjectPaths {
535 pub artifacts: PathBuf,
536 pub build_infos: PathBuf,
537 pub sources: PathBuf,
538 pub tests: PathBuf,
539 pub scripts: PathBuf,
540 pub libraries: BTreeSet<PathBuf>,
541}
542
543impl ProjectPaths {
544 pub fn join_all(&mut self, root: impl AsRef<Path>) -> &mut Self {
546 let root = root.as_ref();
547 self.artifacts = root.join(&self.artifacts);
548 self.build_infos = root.join(&self.build_infos);
549 self.sources = root.join(&self.sources);
550 self.tests = root.join(&self.tests);
551 self.scripts = root.join(&self.scripts);
552 let libraries = std::mem::take(&mut self.libraries);
553 self.libraries.extend(libraries.into_iter().map(|p| root.join(p)));
554 self
555 }
556
557 pub fn strip_prefix_all(&mut self, base: impl AsRef<Path>) -> &mut Self {
559 let base = base.as_ref();
560
561 if let Ok(prefix) = self.artifacts.strip_prefix(base) {
562 self.artifacts = prefix.to_path_buf();
563 }
564 if let Ok(prefix) = self.build_infos.strip_prefix(base) {
565 self.build_infos = prefix.to_path_buf();
566 }
567 if let Ok(prefix) = self.sources.strip_prefix(base) {
568 self.sources = prefix.to_path_buf();
569 }
570 if let Ok(prefix) = self.tests.strip_prefix(base) {
571 self.tests = prefix.to_path_buf();
572 }
573 if let Ok(prefix) = self.scripts.strip_prefix(base) {
574 self.scripts = prefix.to_path_buf();
575 }
576 let libraries = std::mem::take(&mut self.libraries);
577 self.libraries.extend(
578 libraries
579 .into_iter()
580 .map(|p| p.strip_prefix(base).map(|p| p.to_path_buf()).unwrap_or(p)),
581 );
582 self
583 }
584}
585
586impl Default for ProjectPaths {
587 fn default() -> Self {
588 Self {
589 artifacts: "out".into(),
590 build_infos: ["out", "build-info"].iter().collect::<PathBuf>(),
591 sources: "src".into(),
592 tests: "test".into(),
593 scripts: "script".into(),
594 libraries: Default::default(),
595 }
596 }
597}
598
599#[derive(Debug, Clone, Eq, PartialEq)]
600pub enum PathStyle {
601 HardHat,
602 Dapptools,
603}
604
605impl PathStyle {
606 pub fn paths(&self, root: impl AsRef<Path>) -> Result<ProjectPathsConfig> {
608 let root = root.as_ref();
609 let root = utils::canonicalize(root)?;
610
611 Ok(match self {
612 PathStyle::Dapptools => ProjectPathsConfig::builder()
613 .sources(root.join("src"))
614 .artifacts(root.join("out"))
615 .build_infos(root.join("out").join("build-info"))
616 .lib(root.join("lib"))
617 .remappings(Remapping::find_many(root.join("lib")))
618 .root(root)
619 .build()?,
620 PathStyle::HardHat => ProjectPathsConfig::builder()
621 .sources(root.join("contracts"))
622 .artifacts(root.join("artifacts"))
623 .build_infos(root.join("artifacts").join("build-info"))
624 .lib(root.join("node_modules"))
625 .root(root)
626 .build()?,
627 })
628 }
629}
630
631#[derive(Debug, Clone, Default)]
632pub struct ProjectPathsConfigBuilder {
633 root: Option<PathBuf>,
634 cache: Option<PathBuf>,
635 artifacts: Option<PathBuf>,
636 build_infos: Option<PathBuf>,
637 sources: Option<PathBuf>,
638 tests: Option<PathBuf>,
639 scripts: Option<PathBuf>,
640 libraries: Option<Vec<PathBuf>>,
641 remappings: Option<Vec<Remapping>>,
642}
643
644impl ProjectPathsConfigBuilder {
645 pub fn root(mut self, root: impl Into<PathBuf>) -> Self {
646 self.root = Some(utils::canonicalized(root));
647 self
648 }
649
650 pub fn cache(mut self, cache: impl Into<PathBuf>) -> Self {
651 self.cache = Some(utils::canonicalized(cache));
652 self
653 }
654
655 pub fn artifacts(mut self, artifacts: impl Into<PathBuf>) -> Self {
656 self.artifacts = Some(utils::canonicalized(artifacts));
657 self
658 }
659
660 pub fn build_infos(mut self, build_infos: impl Into<PathBuf>) -> Self {
661 self.build_infos = Some(utils::canonicalized(build_infos));
662 self
663 }
664
665 pub fn sources(mut self, sources: impl Into<PathBuf>) -> Self {
666 self.sources = Some(utils::canonicalized(sources));
667 self
668 }
669
670 pub fn tests(mut self, tests: impl Into<PathBuf>) -> Self {
671 self.tests = Some(utils::canonicalized(tests));
672 self
673 }
674
675 pub fn scripts(mut self, scripts: impl Into<PathBuf>) -> Self {
676 self.scripts = Some(utils::canonicalized(scripts));
677 self
678 }
679
680 pub fn no_libs(mut self) -> Self {
682 self.libraries = Some(Vec::new());
683 self
684 }
685
686 pub fn lib(mut self, lib: impl Into<PathBuf>) -> Self {
687 self.libraries.get_or_insert_with(Vec::new).push(utils::canonicalized(lib));
688 self
689 }
690
691 pub fn libs(mut self, libs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
692 let libraries = self.libraries.get_or_insert_with(Vec::new);
693 for lib in libs.into_iter() {
694 libraries.push(utils::canonicalized(lib));
695 }
696 self
697 }
698
699 pub fn remapping(mut self, remapping: Remapping) -> Self {
700 self.remappings.get_or_insert_with(Vec::new).push(remapping);
701 self
702 }
703
704 pub fn remappings(mut self, remappings: impl IntoIterator<Item = Remapping>) -> Self {
705 let our_remappings = self.remappings.get_or_insert_with(Vec::new);
706 for remapping in remappings.into_iter() {
707 our_remappings.push(remapping);
708 }
709 self
710 }
711
712 pub fn build_with_root(self, root: impl Into<PathBuf>) -> ProjectPathsConfig {
713 let root = utils::canonicalized(root);
714
715 let libraries = self.libraries.unwrap_or_else(|| ProjectPathsConfig::find_libs(&root));
716 let artifacts =
717 self.artifacts.unwrap_or_else(|| ProjectPathsConfig::find_artifacts_dir(&root));
718
719 ProjectPathsConfig {
720 cache: self
721 .cache
722 .unwrap_or_else(|| root.join("cache").join(SOLIDITY_FILES_CACHE_FILENAME)),
723 build_infos: self.build_infos.unwrap_or_else(|| artifacts.join("build-info")),
724 artifacts,
725 sources: self.sources.unwrap_or_else(|| ProjectPathsConfig::find_source_dir(&root)),
726 tests: self.tests.unwrap_or_else(|| root.join("test")),
727 scripts: self.scripts.unwrap_or_else(|| root.join("script")),
728 remappings: self
729 .remappings
730 .unwrap_or_else(|| libraries.iter().flat_map(Remapping::find_many).collect()),
731 libraries,
732 root,
733 }
734 }
735
736 pub fn build(self) -> std::result::Result<ProjectPathsConfig, SolcIoError> {
737 let root = self
738 .root
739 .clone()
740 .map(Ok)
741 .unwrap_or_else(std::env::current_dir)
742 .map_err(|err| SolcIoError::new(err, "."))?;
743 Ok(self.build_with_root(root))
744 }
745}
746
747#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
749pub struct SolcConfig {
750 pub settings: Settings,
752}
753
754impl SolcConfig {
755 pub fn builder() -> SolcConfigBuilder {
764 SolcConfigBuilder::default()
765 }
766}
767
768impl From<SolcConfig> for Settings {
769 fn from(config: SolcConfig) -> Self {
770 config.settings
771 }
772}
773
774#[derive(Default)]
775pub struct SolcConfigBuilder {
776 settings: Option<Settings>,
777
778 output_selection: Vec<ContractOutputSelection>,
780}
781
782impl SolcConfigBuilder {
783 pub fn settings(mut self, settings: Settings) -> Self {
784 self.settings = Some(settings);
785 self
786 }
787
788 #[must_use]
790 pub fn additional_output(mut self, output: impl Into<ContractOutputSelection>) -> Self {
791 self.output_selection.push(output.into());
792 self
793 }
794
795 #[must_use]
797 pub fn additional_outputs<I, S>(mut self, outputs: I) -> Self
798 where
799 I: IntoIterator<Item = S>,
800 S: Into<ContractOutputSelection>,
801 {
802 for out in outputs {
803 self = self.additional_output(out);
804 }
805 self
806 }
807
808 pub fn build(self) -> SolcConfig {
812 let Self { settings, output_selection } = self;
813 let mut settings = settings.unwrap_or_default();
814 settings.push_all(output_selection);
815 SolcConfig { settings }
816 }
817}
818
819#[derive(Clone, Debug, Default)]
831pub struct IncludePaths(pub(crate) BTreeSet<PathBuf>);
832
833impl IncludePaths {
836 pub fn args(&self) -> impl Iterator<Item = String> + '_ {
840 self.paths().flat_map(|path| ["--include-path".to_string(), format!("{}", path.display())])
841 }
842
843 pub fn paths(&self) -> impl Iterator<Item = &PathBuf> + '_ {
845 self.0.iter().filter(|path| path.exists())
846 }
847}
848
849impl Deref for IncludePaths {
850 type Target = BTreeSet<PathBuf>;
851
852 fn deref(&self) -> &Self::Target {
853 &self.0
854 }
855}
856
857impl DerefMut for IncludePaths {
858 fn deref_mut(&mut self) -> &mut Self::Target {
859 &mut self.0
860 }
861}
862
863#[derive(Clone, Debug, Default)]
873pub struct AllowedLibPaths(pub(crate) BTreeSet<PathBuf>);
874
875impl AllowedLibPaths {
878 pub fn args(&self) -> Option<[String; 2]> {
882 let args = self.to_string();
883 if args.is_empty() {
884 return None
885 }
886 Some(["--allow-paths".to_string(), args])
887 }
888
889 pub fn paths(&self) -> impl Iterator<Item = &PathBuf> + '_ {
891 self.0.iter().filter(|path| path.exists())
892 }
893}
894
895impl Deref for AllowedLibPaths {
896 type Target = BTreeSet<PathBuf>;
897
898 fn deref(&self) -> &Self::Target {
899 &self.0
900 }
901}
902
903impl DerefMut for AllowedLibPaths {
904 fn deref_mut(&mut self) -> &mut Self::Target {
905 &mut self.0
906 }
907}
908
909impl fmt::Display for AllowedLibPaths {
910 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
911 let lib_paths =
912 self.paths().map(|path| format!("{}", path.display())).collect::<Vec<_>>().join(",");
913 write!(f, "{lib_paths}")
914 }
915}
916
917impl<T: Into<PathBuf>> From<Vec<T>> for AllowedLibPaths {
918 fn from(libs: Vec<T>) -> Self {
919 let libs = libs.into_iter().map(utils::canonicalized).collect();
920 AllowedLibPaths(libs)
921 }
922}
923
924#[cfg(test)]
925mod tests {
926 use super::*;
927
928 #[test]
929 fn can_autodetect_dirs() {
930 let root = utils::tempdir("root").unwrap();
931 let out = root.path().join("out");
932 let artifacts = root.path().join("artifacts");
933 let build_infos = artifacts.join("build-info");
934 let contracts = root.path().join("contracts");
935 let src = root.path().join("src");
936 let lib = root.path().join("lib");
937 let node_modules = root.path().join("node_modules");
938
939 let root = root.path();
940 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
941 std::fs::create_dir_all(&contracts).unwrap();
942 assert_eq!(ProjectPathsConfig::find_source_dir(root), contracts,);
943 assert_eq!(
944 ProjectPathsConfig::builder().build_with_root(root).sources,
945 utils::canonicalized(contracts),
946 );
947 std::fs::create_dir_all(&src).unwrap();
948 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
949 assert_eq!(
950 ProjectPathsConfig::builder().build_with_root(root).sources,
951 utils::canonicalized(src),
952 );
953
954 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
955 std::fs::create_dir_all(&artifacts).unwrap();
956 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), artifacts,);
957 assert_eq!(
958 ProjectPathsConfig::builder().build_with_root(root).artifacts,
959 utils::canonicalized(artifacts),
960 );
961 std::fs::create_dir_all(&build_infos).unwrap();
962 assert_eq!(
963 ProjectPathsConfig::builder().build_with_root(root).build_infos,
964 utils::canonicalized(build_infos)
965 );
966
967 std::fs::create_dir_all(&out).unwrap();
968 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
969 assert_eq!(
970 ProjectPathsConfig::builder().build_with_root(root).artifacts,
971 utils::canonicalized(out),
972 );
973
974 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
975 std::fs::create_dir_all(&node_modules).unwrap();
976 assert_eq!(ProjectPathsConfig::find_libs(root), vec![node_modules.clone()],);
977 assert_eq!(
978 ProjectPathsConfig::builder().build_with_root(root).libraries,
979 vec![utils::canonicalized(node_modules)],
980 );
981 std::fs::create_dir_all(&lib).unwrap();
982 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
983 assert_eq!(
984 ProjectPathsConfig::builder().build_with_root(root).libraries,
985 vec![utils::canonicalized(lib)],
986 );
987 }
988
989 #[test]
990 fn can_have_sane_build_info_default() {
991 let root = utils::tempdir("root").unwrap();
992 let root = root.path();
993 let artifacts = root.join("forge-artifacts");
994
995 let project = ProjectPathsConfig::builder().artifacts(&artifacts).build_with_root(root);
998
999 assert_eq!(project.artifacts, utils::canonicalized(artifacts));
1001
1002 assert_eq!(project.build_infos, utils::canonicalized(project.artifacts.join("build-info")));
1004 }
1005
1006 #[test]
1007 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
1008 fn can_find_library_ancestor() {
1009 let mut config = ProjectPathsConfig::builder().lib("lib").build().unwrap();
1010 config.root = "/root/".into();
1011
1012 assert_eq!(config.find_library_ancestor("lib/src/Greeter.sol").unwrap(), Path::new("lib"));
1013
1014 assert_eq!(
1015 config.find_library_ancestor("/root/lib/src/Greeter.sol").unwrap(),
1016 Path::new("lib")
1017 );
1018
1019 config.libraries.push("/root/test/".into());
1020
1021 assert_eq!(
1022 config.find_library_ancestor("test/src/Greeter.sol").unwrap(),
1023 Path::new("/root/test/")
1024 );
1025
1026 assert_eq!(
1027 config.find_library_ancestor("/root/test/src/Greeter.sol").unwrap(),
1028 Path::new("/root/test/")
1029 );
1030 }
1031}