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 paths(&self) -> ProjectPaths {
256 ProjectPaths {
257 artifacts: self.artifacts.clone(),
258 build_infos: self.build_infos.clone(),
259 sources: self.sources.clone(),
260 tests: self.tests.clone(),
261 scripts: self.scripts.clone(),
262 libraries: self.libraries.iter().cloned().collect(),
263 }
264 }
265
266 pub fn paths_relative(&self) -> ProjectPaths {
270 let mut paths = self.paths();
271 paths.strip_prefix_all(&self.root);
272 paths
273 }
274
275 pub fn create_all(&self) -> std::result::Result<(), SolcIoError> {
277 if let Some(parent) = self.cache.parent() {
278 fs::create_dir_all(parent).map_err(|err| SolcIoError::new(err, parent))?;
279 }
280 fs::create_dir_all(&self.artifacts)
281 .map_err(|err| SolcIoError::new(err, &self.artifacts))?;
282 fs::create_dir_all(&self.sources).map_err(|err| SolcIoError::new(err, &self.sources))?;
283 fs::create_dir_all(&self.tests).map_err(|err| SolcIoError::new(err, &self.tests))?;
284 fs::create_dir_all(&self.scripts).map_err(|err| SolcIoError::new(err, &self.scripts))?;
285 for lib in &self.libraries {
286 fs::create_dir_all(lib).map_err(|err| SolcIoError::new(err, lib))?;
287 }
288 Ok(())
289 }
290
291 pub fn slash_paths(&mut self) {
293 #[cfg(windows)]
294 {
295 use path_slash::PathBufExt;
296
297 let slashed = |p: &mut PathBuf| {
298 *p = p.to_slash_lossy().as_ref().into();
299 };
300 slashed(&mut self.root);
301 slashed(&mut self.cache);
302 slashed(&mut self.artifacts);
303 slashed(&mut self.build_infos);
304 slashed(&mut self.sources);
305 slashed(&mut self.tests);
306 slashed(&mut self.scripts);
307
308 self.libraries.iter_mut().for_each(slashed);
309 self.remappings.iter_mut().for_each(Remapping::slash_path);
310
311 self.include_paths = std::mem::take(&mut self.include_paths)
312 .into_iter()
313 .map(|mut p| {
314 slashed(&mut p);
315 p
316 })
317 .collect();
318 self.allowed_paths = std::mem::take(&mut self.allowed_paths)
319 .into_iter()
320 .map(|mut p| {
321 slashed(&mut p);
322 p
323 })
324 .collect();
325 }
326 }
327
328 pub fn has_library_ancestor(&self, file: &Path) -> bool {
330 self.find_library_ancestor(file).is_some()
331 }
332
333 pub fn find_library_ancestor(&self, file: &Path) -> Option<&Path> {
354 for lib in &self.libraries {
355 if lib.is_relative()
356 && file.is_absolute()
357 && file.starts_with(&self.root)
358 && file.starts_with(self.root.join(lib))
359 || file.is_relative()
360 && lib.is_absolute()
361 && lib.starts_with(&self.root)
362 && self.root.join(file).starts_with(lib)
363 {
364 return Some(lib);
365 }
366 if file.starts_with(lib) {
367 return Some(lib);
368 }
369 }
370
371 None
372 }
373
374 pub fn resolve_import_and_include_paths(
381 &self,
382 cwd: &Path,
383 import: &Path,
384 include_paths: &mut BTreeSet<PathBuf>,
385 ) -> Result<PathBuf> {
386 let component = import
387 .components()
388 .next()
389 .ok_or_else(|| SolcError::msg(format!("Empty import path {}", import.display())))?;
390
391 if component == Component::CurDir || component == Component::ParentDir {
392 utils::normalize_solidity_import_path(cwd, import).map_err(|err| {
395 SolcError::msg(format!("failed to resolve relative import \"{err:?}\""))
396 })
397 } else {
398 let resolved = self.resolve_library_import(cwd.as_ref(), import.as_ref());
400
401 if resolved.is_none() {
402 if let Some(lib) = self.find_library_ancestor(cwd) {
406 if let Some((include_path, import)) =
407 utils::resolve_absolute_library(lib, cwd, import)
408 {
409 include_paths.insert(include_path);
411 return Ok(import);
412 }
413 }
414 for path in [&self.root, &self.sources, &self.tests, &self.scripts] {
416 if cwd.starts_with(path) {
417 if let Ok(import) = utils::normalize_solidity_import_path(path, import) {
418 return Ok(import);
419 }
420 }
421 }
422 }
423
424 resolved.ok_or_else(|| {
425 SolcError::msg(format!(
426 "failed to resolve library import \"{:?}\"",
427 import.display()
428 ))
429 })
430 }
431 }
432
433 pub fn resolve_import(&self, cwd: &Path, import: &Path) -> Result<PathBuf> {
437 self.resolve_import_and_include_paths(cwd, import, &mut Default::default())
438 }
439
440 pub fn resolve_library_import(&self, cwd: &Path, import: &Path) -> Option<PathBuf> {
491 let cwd = cwd.strip_prefix(&self.root).unwrap_or(cwd);
494 if let Some(path) = self
495 .remappings
496 .iter()
497 .filter(|r| {
498 if let Some(ctx) = r.context.as_ref() {
500 cwd.starts_with(ctx)
501 } else {
502 true
503 }
504 })
505 .find_map(|r| {
506 import.strip_prefix(&r.name).ok().map(|stripped_import| {
507 let lib_path = Path::new(&r.path).join(stripped_import);
508
509 if let Ok(adjusted_import) = stripped_import.strip_prefix("contracts/") {
513 if r.path.ends_with("contracts/") && !lib_path.exists() {
514 return Path::new(&r.path).join(adjusted_import);
515 }
516 }
517 lib_path
518 })
519 })
520 {
521 Some(self.root.join(path))
522 } else {
523 utils::resolve_library(&self.libraries, import)
524 }
525 }
526
527 pub fn with_language<Lang>(self) -> ProjectPathsConfig<Lang> {
528 let Self {
529 root,
530 cache,
531 artifacts,
532 build_infos,
533 sources,
534 tests,
535 scripts,
536 libraries,
537 remappings,
538 include_paths,
539 allowed_paths,
540 _l,
541 } = self;
542
543 ProjectPathsConfig {
544 root,
545 cache,
546 artifacts,
547 build_infos,
548 sources,
549 tests,
550 scripts,
551 libraries,
552 remappings,
553 include_paths,
554 allowed_paths,
555 _l: PhantomData,
556 }
557 }
558
559 pub fn apply_lib_remappings(&self, mut libraries: Libraries) -> Libraries {
560 libraries.libs = libraries.libs
561 .into_iter()
562 .map(|(file, target)| {
563 let file = self.resolve_import(&self.root, &file).unwrap_or_else(|err| {
564 warn!(target: "libs", "Failed to resolve library `{}` for linking: {:?}", file.display(), err);
565 file
566 });
567 (file, target)
568 })
569 .collect();
570 libraries
571 }
572}
573
574impl<L: Language> ProjectPathsConfig<L> {
575 pub fn read_sources(&self) -> Result<Sources> {
577 trace!("reading all sources from \"{}\"", self.sources.display());
578 Ok(Source::read_all_from(&self.sources, L::FILE_EXTENSIONS)?)
579 }
580
581 pub fn read_tests(&self) -> Result<Sources> {
583 trace!("reading all tests from \"{}\"", self.tests.display());
584 Ok(Source::read_all_from(&self.tests, L::FILE_EXTENSIONS)?)
585 }
586
587 pub fn read_scripts(&self) -> Result<Sources> {
589 trace!("reading all scripts from \"{}\"", self.scripts.display());
590 Ok(Source::read_all_from(&self.scripts, L::FILE_EXTENSIONS)?)
591 }
592
593 pub fn has_input_files(&self) -> bool {
597 self.input_files_iter().next().is_some()
598 }
599
600 pub fn input_files_iter(&self) -> impl Iterator<Item = PathBuf> + '_ {
603 utils::source_files_iter(&self.sources, L::FILE_EXTENSIONS)
604 .chain(utils::source_files_iter(&self.tests, L::FILE_EXTENSIONS))
605 .chain(utils::source_files_iter(&self.scripts, L::FILE_EXTENSIONS))
606 }
607
608 pub fn input_files(&self) -> Vec<PathBuf> {
611 self.input_files_iter().collect()
612 }
613
614 pub fn read_input_files(&self) -> Result<Sources> {
616 Ok(Source::read_all_files(self.input_files())?)
617 }
618}
619
620impl fmt::Display for ProjectPathsConfig {
621 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
622 writeln!(f, "root: {}", self.root.display())?;
623 writeln!(f, "contracts: {}", self.sources.display())?;
624 writeln!(f, "artifacts: {}", self.artifacts.display())?;
625 writeln!(f, "tests: {}", self.tests.display())?;
626 writeln!(f, "scripts: {}", self.scripts.display())?;
627 writeln!(f, "libs:")?;
628 for lib in &self.libraries {
629 writeln!(f, " {}", lib.display())?;
630 }
631 writeln!(f, "remappings:")?;
632 for remapping in &self.remappings {
633 writeln!(f, " {remapping}")?;
634 }
635 Ok(())
636 }
637}
638
639#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
641pub struct ProjectPaths {
642 pub artifacts: PathBuf,
643 pub build_infos: PathBuf,
644 pub sources: PathBuf,
645 pub tests: PathBuf,
646 pub scripts: PathBuf,
647 pub libraries: BTreeSet<PathBuf>,
648}
649
650impl ProjectPaths {
651 pub fn join_all(&mut self, root: &Path) -> &mut Self {
653 self.artifacts = root.join(&self.artifacts);
654 self.build_infos = root.join(&self.build_infos);
655 self.sources = root.join(&self.sources);
656 self.tests = root.join(&self.tests);
657 self.scripts = root.join(&self.scripts);
658 let libraries = std::mem::take(&mut self.libraries);
659 self.libraries.extend(libraries.into_iter().map(|p| root.join(p)));
660 self
661 }
662
663 pub fn strip_prefix_all(&mut self, base: &Path) -> &mut Self {
665 if let Ok(stripped) = self.artifacts.strip_prefix(base) {
666 self.artifacts = stripped.to_path_buf();
667 }
668 if let Ok(stripped) = self.build_infos.strip_prefix(base) {
669 self.build_infos = stripped.to_path_buf();
670 }
671 if let Ok(stripped) = self.sources.strip_prefix(base) {
672 self.sources = stripped.to_path_buf();
673 }
674 if let Ok(stripped) = self.tests.strip_prefix(base) {
675 self.tests = stripped.to_path_buf();
676 }
677 if let Ok(stripped) = self.scripts.strip_prefix(base) {
678 self.scripts = stripped.to_path_buf();
679 }
680 self.libraries = std::mem::take(&mut self.libraries)
681 .into_iter()
682 .map(|path| strip_prefix_owned(path, base))
683 .collect();
684 self
685 }
686}
687
688impl Default for ProjectPaths {
689 fn default() -> Self {
690 Self {
691 artifacts: "out".into(),
692 build_infos: ["out", "build-info"].iter().collect::<PathBuf>(),
693 sources: "src".into(),
694 tests: "test".into(),
695 scripts: "script".into(),
696 libraries: Default::default(),
697 }
698 }
699}
700
701#[derive(Clone, Debug, PartialEq, Eq)]
702pub enum PathStyle {
703 HardHat,
704 Dapptools,
705}
706
707impl PathStyle {
708 pub fn paths<C>(&self, root: &Path) -> Result<ProjectPathsConfig<C>> {
710 let root = utils::canonicalize(root)?;
711
712 Ok(match self {
713 Self::Dapptools => ProjectPathsConfig::builder()
714 .sources(root.join("src"))
715 .artifacts(root.join("out"))
716 .build_infos(root.join("out").join("build-info"))
717 .lib(root.join("lib"))
718 .remappings(Remapping::find_many(&root.join("lib")))
719 .root(root)
720 .build()?,
721 Self::HardHat => ProjectPathsConfig::builder()
722 .sources(root.join("contracts"))
723 .artifacts(root.join("artifacts"))
724 .build_infos(root.join("artifacts").join("build-info"))
725 .lib(root.join("node_modules"))
726 .root(root)
727 .build()?,
728 })
729 }
730}
731
732#[derive(Clone, Debug, Default)]
733pub struct ProjectPathsConfigBuilder {
734 root: Option<PathBuf>,
735 cache: Option<PathBuf>,
736 artifacts: Option<PathBuf>,
737 build_infos: Option<PathBuf>,
738 sources: Option<PathBuf>,
739 tests: Option<PathBuf>,
740 scripts: Option<PathBuf>,
741 libraries: Option<Vec<PathBuf>>,
742 remappings: Option<Vec<Remapping>>,
743 include_paths: BTreeSet<PathBuf>,
744 allowed_paths: BTreeSet<PathBuf>,
745}
746
747impl ProjectPathsConfigBuilder {
748 pub fn root(mut self, root: impl Into<PathBuf>) -> Self {
749 self.root = Some(utils::canonicalized(root));
750 self
751 }
752
753 pub fn cache(mut self, cache: impl Into<PathBuf>) -> Self {
754 self.cache = Some(utils::canonicalized(cache));
755 self
756 }
757
758 pub fn artifacts(mut self, artifacts: impl Into<PathBuf>) -> Self {
759 self.artifacts = Some(utils::canonicalized(artifacts));
760 self
761 }
762
763 pub fn build_infos(mut self, build_infos: impl Into<PathBuf>) -> Self {
764 self.build_infos = Some(utils::canonicalized(build_infos));
765 self
766 }
767
768 pub fn sources(mut self, sources: impl Into<PathBuf>) -> Self {
769 self.sources = Some(utils::canonicalized(sources));
770 self
771 }
772
773 pub fn tests(mut self, tests: impl Into<PathBuf>) -> Self {
774 self.tests = Some(utils::canonicalized(tests));
775 self
776 }
777
778 pub fn scripts(mut self, scripts: impl Into<PathBuf>) -> Self {
779 self.scripts = Some(utils::canonicalized(scripts));
780 self
781 }
782
783 pub fn no_libs(mut self) -> Self {
785 self.libraries = Some(Vec::new());
786 self
787 }
788
789 pub fn lib(mut self, lib: impl Into<PathBuf>) -> Self {
790 self.libraries.get_or_insert_with(Vec::new).push(utils::canonicalized(lib));
791 self
792 }
793
794 pub fn libs(mut self, libs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
795 let libraries = self.libraries.get_or_insert_with(Vec::new);
796 for lib in libs.into_iter() {
797 libraries.push(utils::canonicalized(lib));
798 }
799 self
800 }
801
802 pub fn remapping(mut self, remapping: Remapping) -> Self {
803 self.remappings.get_or_insert_with(Vec::new).push(remapping);
804 self
805 }
806
807 pub fn remappings(mut self, remappings: impl IntoIterator<Item = Remapping>) -> Self {
808 let our_remappings = self.remappings.get_or_insert_with(Vec::new);
809 for remapping in remappings.into_iter() {
810 our_remappings.push(remapping);
811 }
812 self
813 }
814
815 pub fn allowed_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
817 self.allowed_paths.insert(path.into());
818 self
819 }
820
821 pub fn allowed_paths<I, S>(mut self, args: I) -> Self
823 where
824 I: IntoIterator<Item = S>,
825 S: Into<PathBuf>,
826 {
827 for arg in args {
828 self = self.allowed_path(arg);
829 }
830 self
831 }
832
833 pub fn include_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
835 self.include_paths.insert(path.into());
836 self
837 }
838
839 pub fn include_paths<I, S>(mut self, args: I) -> Self
841 where
842 I: IntoIterator<Item = S>,
843 S: Into<PathBuf>,
844 {
845 for arg in args {
846 self = self.include_path(arg);
847 }
848 self
849 }
850
851 pub fn build_with_root<C>(self, root: impl Into<PathBuf>) -> ProjectPathsConfig<C> {
852 let root = utils::canonicalized(root);
853
854 let libraries = self.libraries.unwrap_or_else(|| ProjectPathsConfig::find_libs(&root));
855 let artifacts =
856 self.artifacts.unwrap_or_else(|| ProjectPathsConfig::find_artifacts_dir(&root));
857
858 let mut allowed_paths = self.allowed_paths;
859 allowed_paths.insert(root.clone());
861
862 ProjectPathsConfig {
863 cache: self
864 .cache
865 .unwrap_or_else(|| root.join("cache").join(SOLIDITY_FILES_CACHE_FILENAME)),
866 build_infos: self.build_infos.unwrap_or_else(|| artifacts.join("build-info")),
867 artifacts,
868 sources: self.sources.unwrap_or_else(|| ProjectPathsConfig::find_source_dir(&root)),
869 tests: self.tests.unwrap_or_else(|| root.join("test")),
870 scripts: self.scripts.unwrap_or_else(|| root.join("script")),
871 remappings: self.remappings.unwrap_or_else(|| {
872 libraries.iter().flat_map(|p| Remapping::find_many(p)).collect()
873 }),
874 libraries,
875 root,
876 include_paths: self.include_paths,
877 allowed_paths,
878 _l: PhantomData,
879 }
880 }
881
882 pub fn build<C>(self) -> std::result::Result<ProjectPathsConfig<C>, SolcIoError> {
883 let root = self
884 .root
885 .clone()
886 .map(Ok)
887 .unwrap_or_else(std::env::current_dir)
888 .map_err(|err| SolcIoError::new(err, "."))?;
889 Ok(self.build_with_root(root))
890 }
891}
892
893#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
895pub struct SolcConfig {
896 pub settings: Settings,
898}
899
900impl SolcConfig {
901 pub fn builder() -> SolcConfigBuilder {
913 SolcConfigBuilder::default()
914 }
915}
916
917impl From<SolcConfig> for Settings {
918 fn from(config: SolcConfig) -> Self {
919 config.settings
920 }
921}
922
923#[derive(Default)]
924pub struct SolcConfigBuilder {
925 settings: Option<Settings>,
926
927 output_selection: Vec<ContractOutputSelection>,
929
930 ast: bool,
932}
933
934impl SolcConfigBuilder {
935 pub fn settings(mut self, settings: Settings) -> Self {
936 self.settings = Some(settings);
937 self
938 }
939
940 #[must_use]
942 pub fn additional_output(mut self, output: impl Into<ContractOutputSelection>) -> Self {
943 self.output_selection.push(output.into());
944 self
945 }
946
947 #[must_use]
949 pub fn additional_outputs<I, S>(mut self, outputs: I) -> Self
950 where
951 I: IntoIterator<Item = S>,
952 S: Into<ContractOutputSelection>,
953 {
954 for out in outputs {
955 self = self.additional_output(out);
956 }
957 self
958 }
959
960 pub fn ast(mut self, yes: bool) -> Self {
961 self.ast = yes;
962 self
963 }
964
965 pub fn build(self) -> Settings {
967 let Self { settings, output_selection, ast } = self;
968 let mut settings = settings.unwrap_or_default();
969 settings.push_all(output_selection);
970 if ast {
971 settings = settings.with_ast();
972 }
973 settings
974 }
975}
976
977#[cfg(test)]
978mod tests {
979 use super::*;
980
981 #[test]
982 fn can_autodetect_dirs() {
983 let root = utils::tempdir("root").unwrap();
984 let out = root.path().join("out");
985 let artifacts = root.path().join("artifacts");
986 let build_infos = artifacts.join("build-info");
987 let contracts = root.path().join("contracts");
988 let src = root.path().join("src");
989 let lib = root.path().join("lib");
990 let node_modules = root.path().join("node_modules");
991
992 let root = root.path();
993 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
994 std::fs::create_dir_all(&contracts).unwrap();
995 assert_eq!(ProjectPathsConfig::find_source_dir(root), contracts,);
996 assert_eq!(
997 ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
998 utils::canonicalized(contracts),
999 );
1000 std::fs::create_dir_all(&src).unwrap();
1001 assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
1002 assert_eq!(
1003 ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
1004 utils::canonicalized(src),
1005 );
1006
1007 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
1008 std::fs::create_dir_all(&artifacts).unwrap();
1009 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), artifacts,);
1010 assert_eq!(
1011 ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
1012 utils::canonicalized(artifacts),
1013 );
1014 std::fs::create_dir_all(&build_infos).unwrap();
1015 assert_eq!(
1016 ProjectPathsConfig::builder().build_with_root::<()>(root).build_infos,
1017 utils::canonicalized(build_infos)
1018 );
1019
1020 std::fs::create_dir_all(&out).unwrap();
1021 assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
1022 assert_eq!(
1023 ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
1024 utils::canonicalized(out),
1025 );
1026
1027 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
1028 std::fs::create_dir_all(&node_modules).unwrap();
1029 assert_eq!(ProjectPathsConfig::find_libs(root), vec![node_modules.clone()],);
1030 assert_eq!(
1031 ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
1032 vec![utils::canonicalized(node_modules)],
1033 );
1034 std::fs::create_dir_all(&lib).unwrap();
1035 assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
1036 assert_eq!(
1037 ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
1038 vec![utils::canonicalized(lib)],
1039 );
1040 }
1041
1042 #[test]
1043 fn can_have_sane_build_info_default() {
1044 let root = utils::tempdir("root").unwrap();
1045 let root = root.path();
1046 let artifacts = root.join("forge-artifacts");
1047
1048 let paths = ProjectPathsConfig::builder().artifacts(&artifacts).build_with_root::<()>(root);
1051
1052 assert_eq!(paths.artifacts, utils::canonicalized(artifacts));
1054
1055 assert_eq!(paths.build_infos, utils::canonicalized(paths.artifacts.join("build-info")));
1057 }
1058
1059 #[test]
1060 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
1061 fn can_find_library_ancestor() {
1062 let mut config = ProjectPathsConfig::builder().lib("lib").build::<()>().unwrap();
1063 config.root = "/root/".into();
1064
1065 assert_eq!(
1066 config.find_library_ancestor("lib/src/Greeter.sol".as_ref()).unwrap(),
1067 Path::new("lib")
1068 );
1069
1070 assert_eq!(
1071 config.find_library_ancestor("/root/lib/src/Greeter.sol".as_ref()).unwrap(),
1072 Path::new("lib")
1073 );
1074
1075 config.libraries.push("/root/test/".into());
1076
1077 assert_eq!(
1078 config.find_library_ancestor("test/src/Greeter.sol".as_ref()).unwrap(),
1079 Path::new("/root/test/")
1080 );
1081
1082 assert_eq!(
1083 config.find_library_ancestor("/root/test/src/Greeter.sol".as_ref()).unwrap(),
1084 Path::new("/root/test/")
1085 );
1086 }
1087
1088 #[test]
1089 fn can_resolve_import() {
1090 let dir = tempfile::tempdir().unwrap();
1091 let config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1092 config.create_all().unwrap();
1093
1094 fs::write(config.sources.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
1095
1096 assert_eq!(
1098 config
1099 .resolve_import_and_include_paths(
1100 &config.sources,
1101 Path::new("./A.sol"),
1102 &mut Default::default(),
1103 )
1104 .unwrap(),
1105 config.sources.join("A.sol")
1106 );
1107
1108 assert_eq!(
1110 config
1111 .resolve_import_and_include_paths(
1112 &config.sources,
1113 Path::new("src/A.sol"),
1114 &mut Default::default(),
1115 )
1116 .unwrap(),
1117 config.sources.join("A.sol")
1118 );
1119 }
1120
1121 #[test]
1122 fn can_resolve_remapped_import() {
1123 let dir = tempfile::tempdir().unwrap();
1124 let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1125 config.create_all().unwrap();
1126
1127 let dependency = config.root.join("dependency");
1128 fs::create_dir(&dependency).unwrap();
1129 fs::write(dependency.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
1130
1131 config.remappings.push(Remapping {
1132 context: None,
1133 name: "@dependency/".into(),
1134 path: "dependency/".into(),
1135 });
1136
1137 assert_eq!(
1138 config
1139 .resolve_import_and_include_paths(
1140 &config.sources,
1141 Path::new("@dependency/A.sol"),
1142 &mut Default::default(),
1143 )
1144 .unwrap(),
1145 dependency.join("A.sol")
1146 );
1147 }
1148}