use crate::{
cache::SOLIDITY_FILES_CACHE_FILENAME,
compilers::{multi::MultiCompilerLanguage, Language},
flatten::{collect_ordered_deps, combine_version_pragmas},
resolver::{parse::SolData, SolImportAlias},
Graph,
};
use foundry_compilers_artifacts::{
output_selection::ContractOutputSelection,
remappings::Remapping,
sources::{Source, Sources},
Libraries, Settings, SolcLanguage,
};
use foundry_compilers_core::{
error::{Result, SolcError, SolcIoError},
utils::{self, strip_prefix_owned},
};
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeSet,
fmt::{self, Formatter},
fs,
marker::PhantomData,
path::{Component, Path, PathBuf},
};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProjectPathsConfig<L = MultiCompilerLanguage> {
pub root: PathBuf,
pub cache: PathBuf,
pub artifacts: PathBuf,
pub build_infos: PathBuf,
pub sources: PathBuf,
pub tests: PathBuf,
pub scripts: PathBuf,
pub libraries: Vec<PathBuf>,
pub remappings: Vec<Remapping>,
pub include_paths: BTreeSet<PathBuf>,
pub allowed_paths: BTreeSet<PathBuf>,
pub _l: PhantomData<L>,
}
impl ProjectPathsConfig {
pub fn builder() -> ProjectPathsConfigBuilder {
ProjectPathsConfigBuilder::default()
}
pub fn find_artifacts_dir(root: &Path) -> PathBuf {
utils::find_fave_or_alt_path(root, "out", "artifacts")
}
pub fn find_source_dir(root: &Path) -> PathBuf {
utils::find_fave_or_alt_path(root, "src", "contracts")
}
pub fn find_libs(root: &Path) -> Vec<PathBuf> {
vec![utils::find_fave_or_alt_path(root, "lib", "node_modules")]
}
}
impl ProjectPathsConfig<SolcLanguage> {
pub fn flatten(&self, target: &Path) -> Result<String> {
trace!("flattening file");
let mut input_files = self.input_files();
let flatten_target = target.to_path_buf();
if !input_files.contains(&flatten_target) {
input_files.push(flatten_target.clone());
}
let sources = Source::read_all_files(input_files)?;
let graph = Graph::<SolData>::resolve_sources(self, sources)?;
let ordered_deps = collect_ordered_deps(&flatten_target, self, &graph)?;
#[cfg(windows)]
let ordered_deps = {
use path_slash::PathBufExt;
let mut deps = ordered_deps;
for p in &mut deps {
*p = PathBuf::from(p.to_slash_lossy().to_string());
}
deps
};
let mut sources = Vec::new();
let mut experimental_pragma = None;
let mut version_pragmas = Vec::new();
let mut result = String::new();
for path in ordered_deps.iter() {
let node_id = graph.files().get(path).ok_or_else(|| {
SolcError::msg(format!("cannot resolve file at {}", path.display()))
})?;
let node = graph.node(*node_id);
let content = node.content();
let mut ranges_to_remove = Vec::new();
if let Some(license) = &node.data.license {
ranges_to_remove.push(license.loc());
if *path == flatten_target {
result.push_str(&content[license.loc()]);
result.push('\n');
}
}
if let Some(version) = &node.data.version {
let content = &content[version.loc()];
ranges_to_remove.push(version.loc());
version_pragmas.push(content);
}
if let Some(experimental) = &node.data.experimental {
ranges_to_remove.push(experimental.loc());
if experimental_pragma.is_none() {
experimental_pragma = Some(content[experimental.loc()].to_owned());
}
}
for import in &node.data.imports {
ranges_to_remove.push(import.loc());
}
ranges_to_remove.sort_by_key(|loc| loc.start);
let mut content = content.as_bytes().to_vec();
let mut offset = 0_isize;
for range in ranges_to_remove {
let repl_range = utils::range_by_offset(&range, offset);
offset -= repl_range.len() as isize;
content.splice(repl_range, std::iter::empty());
}
let mut content = String::from_utf8(content).map_err(|err| {
SolcError::msg(format!("failed to convert extended bytes to string: {err}"))
})?;
for alias in node.data.imports.iter().flat_map(|i| i.data().aliases()) {
let (alias, target) = match alias {
SolImportAlias::Contract(alias, target) => (alias.clone(), target.clone()),
_ => continue,
};
let name_regex = utils::create_contract_or_lib_name_regex(&alias);
let target_len = target.len() as isize;
let mut replace_offset = 0;
for cap in name_regex.captures_iter(&content.clone()) {
if cap.name("ignore").is_some() {
continue;
}
if let Some(name_match) =
["n1", "n2", "n3"].iter().find_map(|name| cap.name(name))
{
let name_match_range =
utils::range_by_offset(&name_match.range(), replace_offset);
replace_offset += target_len - (name_match_range.len() as isize);
content.replace_range(name_match_range, &target);
}
}
}
let content = format!(
"// {}\n{}",
path.strip_prefix(&self.root).unwrap_or(path).display(),
content
);
sources.push(content);
}
if let Some(version) = combine_version_pragmas(version_pragmas) {
result.push_str(&version);
result.push('\n');
}
if let Some(experimental) = experimental_pragma {
result.push_str(&experimental);
result.push('\n');
}
for source in sources {
result.push_str("\n\n");
result.push_str(&source);
}
Ok(format!("{}\n", utils::RE_THREE_OR_MORE_NEWLINES.replace_all(&result, "\n\n").trim()))
}
}
impl<L> ProjectPathsConfig<L> {
pub fn hardhat(root: &Path) -> Result<Self> {
PathStyle::HardHat.paths(root)
}
pub fn dapptools(root: &Path) -> Result<Self> {
PathStyle::Dapptools.paths(root)
}
pub fn current_hardhat() -> Result<Self> {
Self::hardhat(&std::env::current_dir().map_err(|err| SolcError::io(err, "."))?)
}
pub fn current_dapptools() -> Result<Self> {
Self::dapptools(&std::env::current_dir().map_err(|err| SolcError::io(err, "."))?)
}
pub fn paths(&self) -> ProjectPaths {
ProjectPaths {
artifacts: self.artifacts.clone(),
build_infos: self.build_infos.clone(),
sources: self.sources.clone(),
tests: self.tests.clone(),
scripts: self.scripts.clone(),
libraries: self.libraries.iter().cloned().collect(),
}
}
pub fn paths_relative(&self) -> ProjectPaths {
let mut paths = self.paths();
paths.strip_prefix_all(&self.root);
paths
}
pub fn create_all(&self) -> std::result::Result<(), SolcIoError> {
if let Some(parent) = self.cache.parent() {
fs::create_dir_all(parent).map_err(|err| SolcIoError::new(err, parent))?;
}
fs::create_dir_all(&self.artifacts)
.map_err(|err| SolcIoError::new(err, &self.artifacts))?;
fs::create_dir_all(&self.sources).map_err(|err| SolcIoError::new(err, &self.sources))?;
fs::create_dir_all(&self.tests).map_err(|err| SolcIoError::new(err, &self.tests))?;
fs::create_dir_all(&self.scripts).map_err(|err| SolcIoError::new(err, &self.scripts))?;
for lib in &self.libraries {
fs::create_dir_all(lib).map_err(|err| SolcIoError::new(err, lib))?;
}
Ok(())
}
pub fn slash_paths(&mut self) {
#[cfg(windows)]
{
use path_slash::PathBufExt;
let slashed = |p: &mut PathBuf| {
*p = p.to_slash_lossy().as_ref().into();
};
slashed(&mut self.root);
slashed(&mut self.cache);
slashed(&mut self.artifacts);
slashed(&mut self.build_infos);
slashed(&mut self.sources);
slashed(&mut self.tests);
slashed(&mut self.scripts);
self.libraries.iter_mut().for_each(slashed);
self.remappings.iter_mut().for_each(Remapping::slash_path);
self.include_paths = std::mem::take(&mut self.include_paths)
.into_iter()
.map(|mut p| {
slashed(&mut p);
p
})
.collect();
self.allowed_paths = std::mem::take(&mut self.allowed_paths)
.into_iter()
.map(|mut p| {
slashed(&mut p);
p
})
.collect();
}
}
pub fn has_library_ancestor(&self, file: &Path) -> bool {
self.find_library_ancestor(file).is_some()
}
pub fn find_library_ancestor(&self, file: &Path) -> Option<&Path> {
for lib in &self.libraries {
if lib.is_relative()
&& file.is_absolute()
&& file.starts_with(&self.root)
&& file.starts_with(self.root.join(lib))
|| file.is_relative()
&& lib.is_absolute()
&& lib.starts_with(&self.root)
&& self.root.join(file).starts_with(lib)
{
return Some(lib);
}
if file.starts_with(lib) {
return Some(lib);
}
}
None
}
pub fn resolve_import_and_include_paths(
&self,
cwd: &Path,
import: &Path,
include_paths: &mut BTreeSet<PathBuf>,
) -> Result<PathBuf> {
let component = import
.components()
.next()
.ok_or_else(|| SolcError::msg(format!("Empty import path {}", import.display())))?;
if component == Component::CurDir || component == Component::ParentDir {
utils::normalize_solidity_import_path(cwd, import).map_err(|err| {
SolcError::msg(format!("failed to resolve relative import \"{err:?}\""))
})
} else {
let resolved = self.resolve_library_import(cwd.as_ref(), import.as_ref());
if resolved.is_none() {
if let Some(lib) = self.find_library_ancestor(cwd) {
if let Some((include_path, import)) =
utils::resolve_absolute_library(lib, cwd, import)
{
include_paths.insert(include_path);
return Ok(import);
}
}
for path in [&self.root, &self.sources, &self.tests, &self.scripts] {
if cwd.starts_with(path) {
if let Ok(import) = utils::normalize_solidity_import_path(path, import) {
return Ok(import);
}
}
}
}
resolved.ok_or_else(|| {
SolcError::msg(format!(
"failed to resolve library import \"{:?}\"",
import.display()
))
})
}
}
pub fn resolve_import(&self, cwd: &Path, import: &Path) -> Result<PathBuf> {
self.resolve_import_and_include_paths(cwd, import, &mut Default::default())
}
pub fn resolve_library_import(&self, cwd: &Path, import: &Path) -> Option<PathBuf> {
let cwd = cwd.strip_prefix(&self.root).unwrap_or(cwd);
if let Some(path) = self
.remappings
.iter()
.filter(|r| {
if let Some(ctx) = r.context.as_ref() {
cwd.starts_with(ctx)
} else {
true
}
})
.find_map(|r| {
import.strip_prefix(&r.name).ok().map(|stripped_import| {
let lib_path = Path::new(&r.path).join(stripped_import);
if let Ok(adjusted_import) = stripped_import.strip_prefix("contracts/") {
if r.path.ends_with("contracts/") && !lib_path.exists() {
return Path::new(&r.path).join(adjusted_import);
}
}
lib_path
})
})
{
Some(self.root.join(path))
} else {
utils::resolve_library(&self.libraries, import)
}
}
pub fn with_language<Lang>(self) -> ProjectPathsConfig<Lang> {
let Self {
root,
cache,
artifacts,
build_infos,
sources,
tests,
scripts,
libraries,
remappings,
include_paths,
allowed_paths,
_l,
} = self;
ProjectPathsConfig {
root,
cache,
artifacts,
build_infos,
sources,
tests,
scripts,
libraries,
remappings,
include_paths,
allowed_paths,
_l: PhantomData,
}
}
pub fn apply_lib_remappings(&self, mut libraries: Libraries) -> Libraries {
libraries.libs = libraries.libs
.into_iter()
.map(|(file, target)| {
let file = self.resolve_import(&self.root, &file).unwrap_or_else(|err| {
warn!(target: "libs", "Failed to resolve library `{}` for linking: {:?}", file.display(), err);
file
});
(file, target)
})
.collect();
libraries
}
}
impl<L: Language> ProjectPathsConfig<L> {
pub fn read_sources(&self) -> Result<Sources> {
trace!("reading all sources from \"{}\"", self.sources.display());
Ok(Source::read_all_from(&self.sources, L::FILE_EXTENSIONS)?)
}
pub fn read_tests(&self) -> Result<Sources> {
trace!("reading all tests from \"{}\"", self.tests.display());
Ok(Source::read_all_from(&self.tests, L::FILE_EXTENSIONS)?)
}
pub fn read_scripts(&self) -> Result<Sources> {
trace!("reading all scripts from \"{}\"", self.scripts.display());
Ok(Source::read_all_from(&self.scripts, L::FILE_EXTENSIONS)?)
}
pub fn has_input_files(&self) -> bool {
self.input_files_iter().next().is_some()
}
pub fn input_files_iter(&self) -> impl Iterator<Item = PathBuf> + '_ {
utils::source_files_iter(&self.sources, L::FILE_EXTENSIONS)
.chain(utils::source_files_iter(&self.tests, L::FILE_EXTENSIONS))
.chain(utils::source_files_iter(&self.scripts, L::FILE_EXTENSIONS))
}
pub fn input_files(&self) -> Vec<PathBuf> {
self.input_files_iter().collect()
}
pub fn read_input_files(&self) -> Result<Sources> {
Ok(Source::read_all_files(self.input_files())?)
}
}
impl fmt::Display for ProjectPathsConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f, "root: {}", self.root.display())?;
writeln!(f, "contracts: {}", self.sources.display())?;
writeln!(f, "artifacts: {}", self.artifacts.display())?;
writeln!(f, "tests: {}", self.tests.display())?;
writeln!(f, "scripts: {}", self.scripts.display())?;
writeln!(f, "libs:")?;
for lib in &self.libraries {
writeln!(f, " {}", lib.display())?;
}
writeln!(f, "remappings:")?;
for remapping in &self.remappings {
writeln!(f, " {remapping}")?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProjectPaths {
pub artifacts: PathBuf,
pub build_infos: PathBuf,
pub sources: PathBuf,
pub tests: PathBuf,
pub scripts: PathBuf,
pub libraries: BTreeSet<PathBuf>,
}
impl ProjectPaths {
pub fn join_all(&mut self, root: &Path) -> &mut Self {
self.artifacts = root.join(&self.artifacts);
self.build_infos = root.join(&self.build_infos);
self.sources = root.join(&self.sources);
self.tests = root.join(&self.tests);
self.scripts = root.join(&self.scripts);
let libraries = std::mem::take(&mut self.libraries);
self.libraries.extend(libraries.into_iter().map(|p| root.join(p)));
self
}
pub fn strip_prefix_all(&mut self, base: &Path) -> &mut Self {
if let Ok(stripped) = self.artifacts.strip_prefix(base) {
self.artifacts = stripped.to_path_buf();
}
if let Ok(stripped) = self.build_infos.strip_prefix(base) {
self.build_infos = stripped.to_path_buf();
}
if let Ok(stripped) = self.sources.strip_prefix(base) {
self.sources = stripped.to_path_buf();
}
if let Ok(stripped) = self.tests.strip_prefix(base) {
self.tests = stripped.to_path_buf();
}
if let Ok(stripped) = self.scripts.strip_prefix(base) {
self.scripts = stripped.to_path_buf();
}
self.libraries = std::mem::take(&mut self.libraries)
.into_iter()
.map(|path| strip_prefix_owned(path, base))
.collect();
self
}
}
impl Default for ProjectPaths {
fn default() -> Self {
Self {
artifacts: "out".into(),
build_infos: ["out", "build-info"].iter().collect::<PathBuf>(),
sources: "src".into(),
tests: "test".into(),
scripts: "script".into(),
libraries: Default::default(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PathStyle {
HardHat,
Dapptools,
}
impl PathStyle {
pub fn paths<C>(&self, root: &Path) -> Result<ProjectPathsConfig<C>> {
let root = utils::canonicalize(root)?;
Ok(match self {
Self::Dapptools => ProjectPathsConfig::builder()
.sources(root.join("src"))
.artifacts(root.join("out"))
.build_infos(root.join("out").join("build-info"))
.lib(root.join("lib"))
.remappings(Remapping::find_many(&root.join("lib")))
.root(root)
.build()?,
Self::HardHat => ProjectPathsConfig::builder()
.sources(root.join("contracts"))
.artifacts(root.join("artifacts"))
.build_infos(root.join("artifacts").join("build-info"))
.lib(root.join("node_modules"))
.root(root)
.build()?,
})
}
}
#[derive(Clone, Debug, Default)]
pub struct ProjectPathsConfigBuilder {
root: Option<PathBuf>,
cache: Option<PathBuf>,
artifacts: Option<PathBuf>,
build_infos: Option<PathBuf>,
sources: Option<PathBuf>,
tests: Option<PathBuf>,
scripts: Option<PathBuf>,
libraries: Option<Vec<PathBuf>>,
remappings: Option<Vec<Remapping>>,
include_paths: BTreeSet<PathBuf>,
allowed_paths: BTreeSet<PathBuf>,
}
impl ProjectPathsConfigBuilder {
pub fn root(mut self, root: impl Into<PathBuf>) -> Self {
self.root = Some(utils::canonicalized(root));
self
}
pub fn cache(mut self, cache: impl Into<PathBuf>) -> Self {
self.cache = Some(utils::canonicalized(cache));
self
}
pub fn artifacts(mut self, artifacts: impl Into<PathBuf>) -> Self {
self.artifacts = Some(utils::canonicalized(artifacts));
self
}
pub fn build_infos(mut self, build_infos: impl Into<PathBuf>) -> Self {
self.build_infos = Some(utils::canonicalized(build_infos));
self
}
pub fn sources(mut self, sources: impl Into<PathBuf>) -> Self {
self.sources = Some(utils::canonicalized(sources));
self
}
pub fn tests(mut self, tests: impl Into<PathBuf>) -> Self {
self.tests = Some(utils::canonicalized(tests));
self
}
pub fn scripts(mut self, scripts: impl Into<PathBuf>) -> Self {
self.scripts = Some(utils::canonicalized(scripts));
self
}
pub fn no_libs(mut self) -> Self {
self.libraries = Some(Vec::new());
self
}
pub fn lib(mut self, lib: impl Into<PathBuf>) -> Self {
self.libraries.get_or_insert_with(Vec::new).push(utils::canonicalized(lib));
self
}
pub fn libs(mut self, libs: impl IntoIterator<Item = impl Into<PathBuf>>) -> Self {
let libraries = self.libraries.get_or_insert_with(Vec::new);
for lib in libs.into_iter() {
libraries.push(utils::canonicalized(lib));
}
self
}
pub fn remapping(mut self, remapping: Remapping) -> Self {
self.remappings.get_or_insert_with(Vec::new).push(remapping);
self
}
pub fn remappings(mut self, remappings: impl IntoIterator<Item = Remapping>) -> Self {
let our_remappings = self.remappings.get_or_insert_with(Vec::new);
for remapping in remappings.into_iter() {
our_remappings.push(remapping);
}
self
}
pub fn allowed_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.allowed_paths.insert(path.into());
self
}
pub fn allowed_paths<I, S>(mut self, args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<PathBuf>,
{
for arg in args {
self = self.allowed_path(arg);
}
self
}
pub fn include_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.include_paths.insert(path.into());
self
}
pub fn include_paths<I, S>(mut self, args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<PathBuf>,
{
for arg in args {
self = self.include_path(arg);
}
self
}
pub fn build_with_root<C>(self, root: impl Into<PathBuf>) -> ProjectPathsConfig<C> {
let root = utils::canonicalized(root);
let libraries = self.libraries.unwrap_or_else(|| ProjectPathsConfig::find_libs(&root));
let artifacts =
self.artifacts.unwrap_or_else(|| ProjectPathsConfig::find_artifacts_dir(&root));
let mut allowed_paths = self.allowed_paths;
allowed_paths.insert(root.clone());
ProjectPathsConfig {
cache: self
.cache
.unwrap_or_else(|| root.join("cache").join(SOLIDITY_FILES_CACHE_FILENAME)),
build_infos: self.build_infos.unwrap_or_else(|| artifacts.join("build-info")),
artifacts,
sources: self.sources.unwrap_or_else(|| ProjectPathsConfig::find_source_dir(&root)),
tests: self.tests.unwrap_or_else(|| root.join("test")),
scripts: self.scripts.unwrap_or_else(|| root.join("script")),
remappings: self.remappings.unwrap_or_else(|| {
libraries.iter().flat_map(|p| Remapping::find_many(p)).collect()
}),
libraries,
root,
include_paths: self.include_paths,
allowed_paths,
_l: PhantomData,
}
}
pub fn build<C>(self) -> std::result::Result<ProjectPathsConfig<C>, SolcIoError> {
let root = self
.root
.clone()
.map(Ok)
.unwrap_or_else(std::env::current_dir)
.map_err(|err| SolcIoError::new(err, "."))?;
Ok(self.build_with_root(root))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SolcConfig {
pub settings: Settings,
}
impl SolcConfig {
pub fn builder() -> SolcConfigBuilder {
SolcConfigBuilder::default()
}
}
impl From<SolcConfig> for Settings {
fn from(config: SolcConfig) -> Self {
config.settings
}
}
#[derive(Default)]
pub struct SolcConfigBuilder {
settings: Option<Settings>,
output_selection: Vec<ContractOutputSelection>,
}
impl SolcConfigBuilder {
pub fn settings(mut self, settings: Settings) -> Self {
self.settings = Some(settings);
self
}
#[must_use]
pub fn additional_output(mut self, output: impl Into<ContractOutputSelection>) -> Self {
self.output_selection.push(output.into());
self
}
#[must_use]
pub fn additional_outputs<I, S>(mut self, outputs: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<ContractOutputSelection>,
{
for out in outputs {
self = self.additional_output(out);
}
self
}
pub fn build(self) -> SolcConfig {
let Self { settings, output_selection } = self;
let mut settings = settings.unwrap_or_default();
settings.push_all(output_selection);
SolcConfig { settings }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_autodetect_dirs() {
let root = utils::tempdir("root").unwrap();
let out = root.path().join("out");
let artifacts = root.path().join("artifacts");
let build_infos = artifacts.join("build-info");
let contracts = root.path().join("contracts");
let src = root.path().join("src");
let lib = root.path().join("lib");
let node_modules = root.path().join("node_modules");
let root = root.path();
assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
std::fs::create_dir_all(&contracts).unwrap();
assert_eq!(ProjectPathsConfig::find_source_dir(root), contracts,);
assert_eq!(
ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
utils::canonicalized(contracts),
);
std::fs::create_dir_all(&src).unwrap();
assert_eq!(ProjectPathsConfig::find_source_dir(root), src,);
assert_eq!(
ProjectPathsConfig::builder().build_with_root::<()>(root).sources,
utils::canonicalized(src),
);
assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
std::fs::create_dir_all(&artifacts).unwrap();
assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), artifacts,);
assert_eq!(
ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
utils::canonicalized(artifacts),
);
std::fs::create_dir_all(&build_infos).unwrap();
assert_eq!(
ProjectPathsConfig::builder().build_with_root::<()>(root).build_infos,
utils::canonicalized(build_infos)
);
std::fs::create_dir_all(&out).unwrap();
assert_eq!(ProjectPathsConfig::find_artifacts_dir(root), out,);
assert_eq!(
ProjectPathsConfig::builder().build_with_root::<()>(root).artifacts,
utils::canonicalized(out),
);
assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
std::fs::create_dir_all(&node_modules).unwrap();
assert_eq!(ProjectPathsConfig::find_libs(root), vec![node_modules.clone()],);
assert_eq!(
ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
vec![utils::canonicalized(node_modules)],
);
std::fs::create_dir_all(&lib).unwrap();
assert_eq!(ProjectPathsConfig::find_libs(root), vec![lib.clone()],);
assert_eq!(
ProjectPathsConfig::builder().build_with_root::<()>(root).libraries,
vec![utils::canonicalized(lib)],
);
}
#[test]
fn can_have_sane_build_info_default() {
let root = utils::tempdir("root").unwrap();
let root = root.path();
let artifacts = root.join("forge-artifacts");
let paths = ProjectPathsConfig::builder().artifacts(&artifacts).build_with_root::<()>(root);
assert_eq!(paths.artifacts, utils::canonicalized(artifacts));
assert_eq!(paths.build_infos, utils::canonicalized(paths.artifacts.join("build-info")));
}
#[test]
#[cfg_attr(windows, ignore = "Windows remappings #2347")]
fn can_find_library_ancestor() {
let mut config = ProjectPathsConfig::builder().lib("lib").build::<()>().unwrap();
config.root = "/root/".into();
assert_eq!(
config.find_library_ancestor("lib/src/Greeter.sol".as_ref()).unwrap(),
Path::new("lib")
);
assert_eq!(
config.find_library_ancestor("/root/lib/src/Greeter.sol".as_ref()).unwrap(),
Path::new("lib")
);
config.libraries.push("/root/test/".into());
assert_eq!(
config.find_library_ancestor("test/src/Greeter.sol".as_ref()).unwrap(),
Path::new("/root/test/")
);
assert_eq!(
config.find_library_ancestor("/root/test/src/Greeter.sol".as_ref()).unwrap(),
Path::new("/root/test/")
);
}
#[test]
fn can_resolve_import() {
let dir = tempfile::tempdir().unwrap();
let config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
config.create_all().unwrap();
fs::write(config.sources.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
assert_eq!(
config
.resolve_import_and_include_paths(
&config.sources,
Path::new("./A.sol"),
&mut Default::default(),
)
.unwrap(),
config.sources.join("A.sol")
);
assert_eq!(
config
.resolve_import_and_include_paths(
&config.sources,
Path::new("src/A.sol"),
&mut Default::default(),
)
.unwrap(),
config.sources.join("A.sol")
);
}
#[test]
fn can_resolve_remapped_import() {
let dir = tempfile::tempdir().unwrap();
let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
config.create_all().unwrap();
let dependency = config.root.join("dependency");
fs::create_dir(&dependency).unwrap();
fs::write(dependency.join("A.sol"), r"pragma solidity ^0.8.0; contract A {}").unwrap();
config.remappings.push(Remapping {
context: None,
name: "@dependency/".into(),
path: "dependency/".into(),
});
assert_eq!(
config
.resolve_import_and_include_paths(
&config.sources,
Path::new("@dependency/A.sol"),
&mut Default::default(),
)
.unwrap(),
dependency.join("A.sol")
);
}
}