feat(solc): resolve absolute imports in libraries (#1590)
* feat(solc): resolve absolute imports in libraries * feat(solc): support --include-path * update test * only add base path if not empty * simplify solc config * include root in include paths * test: add test for absolute imports * fix: bad predicate * cleanup * fix: use base-path directly * fix: exclude root from include set
This commit is contained in:
parent
8105d8be9b
commit
2c33acb3ad
|
@ -40,6 +40,14 @@ pub const BERLIN_SOLC: Version = Version::new(0, 8, 5);
|
|||
/// <https://blog.soliditylang.org/2021/08/11/solidity-0.8.7-release-announcement/>
|
||||
pub const LONDON_SOLC: Version = Version::new(0, 8, 7);
|
||||
|
||||
// `--base-path` was introduced in 0.6.9 <https://github.com/ethereum/solidity/releases/tag/v0.6.9>
|
||||
pub static SUPPORTS_BASE_PATH: once_cell::sync::Lazy<VersionReq> =
|
||||
once_cell::sync::Lazy::new(|| VersionReq::parse(">=0.6.9").unwrap());
|
||||
|
||||
// `--include-path` was introduced in 0.8.8 <https://github.com/ethereum/solidity/releases/tag/v0.8.8>
|
||||
pub static SUPPORTS_INCLUDE_PATH: once_cell::sync::Lazy<VersionReq> =
|
||||
once_cell::sync::Lazy::new(|| VersionReq::parse(">=0.8.8").unwrap());
|
||||
|
||||
#[cfg(any(test, feature = "tests"))]
|
||||
use std::sync::Mutex;
|
||||
|
||||
|
@ -527,6 +535,7 @@ impl Solc {
|
|||
let mut cmd = Command::new(&self.solc);
|
||||
if let Some(ref base_path) = self.base_path {
|
||||
cmd.current_dir(base_path);
|
||||
cmd.arg("--base-path").arg(base_path);
|
||||
}
|
||||
let mut child = cmd
|
||||
.args(&self.args)
|
||||
|
|
|
@ -156,8 +156,7 @@ impl<'a, T: ArtifactOutput> ProjectCompiler<'a, T> {
|
|||
let graph = Graph::resolve_sources(&project.paths, sources)?;
|
||||
let (versions, edges) = graph.into_sources_by_version(project.offline)?;
|
||||
|
||||
let base_path = project.root();
|
||||
let sources_by_version = versions.get(&project.allowed_lib_paths, base_path)?;
|
||||
let sources_by_version = versions.get(project)?;
|
||||
|
||||
let sources = if project.solc_jobs > 1 && sources_by_version.len() > 1 {
|
||||
// if there are multiple different versions, and we can use multiple jobs we can compile
|
||||
|
@ -178,6 +177,14 @@ impl<'a, T: ArtifactOutput> ProjectCompiler<'a, T> {
|
|||
) -> Result<Self> {
|
||||
let version = solc.version()?;
|
||||
let (sources, edges) = Graph::resolve_sources(&project.paths, sources)?.into_sources();
|
||||
|
||||
// make sure `solc` has all required arguments
|
||||
let solc = project.configure_solc_with_version(
|
||||
solc,
|
||||
Some(version.clone()),
|
||||
edges.include_paths().clone(),
|
||||
);
|
||||
|
||||
let sources_by_version = BTreeMap::from([(solc, (version, sources))]);
|
||||
let sources = CompilerSources::Sequential(sources_by_version);
|
||||
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
use crate::{
|
||||
artifacts::Settings,
|
||||
artifacts::{output_selection::ContractOutputSelection, Settings},
|
||||
cache::SOLIDITY_FILES_CACHE_FILENAME,
|
||||
error::{Result, SolcError, SolcIoError},
|
||||
remappings::Remapping,
|
||||
resolver::{Graph, SolImportAlias},
|
||||
utils, Source, Sources,
|
||||
};
|
||||
|
||||
use crate::artifacts::output_selection::ContractOutputSelection;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::{BTreeSet, HashSet},
|
||||
fmt::{self, Formatter},
|
||||
fs,
|
||||
ops::{Deref, DerefMut},
|
||||
path::{Component, Path, PathBuf},
|
||||
};
|
||||
|
||||
|
@ -86,6 +84,15 @@ impl ProjectPathsConfig {
|
|||
paths
|
||||
}
|
||||
|
||||
/// Returns all `--include-path` paths that should be used for this project
|
||||
///
|
||||
/// See [IncludePaths]
|
||||
pub fn include_paths(&self) -> Vec<PathBuf> {
|
||||
// Note: root must not be included, since it will be used as base-path, which would be a
|
||||
// conflict
|
||||
vec![self.sources.clone(), self.tests.clone(), self.scripts.clone()]
|
||||
}
|
||||
|
||||
/// Creates all configured dirs and files
|
||||
pub fn create_all(&self) -> std::result::Result<(), SolcIoError> {
|
||||
if let Some(parent) = self.cache.parent() {
|
||||
|
@ -214,11 +221,20 @@ impl ProjectPathsConfig {
|
|||
/// Attempts to resolve an `import` from the given working directory.
|
||||
///
|
||||
/// The `cwd` path is the parent dir of the file that includes the `import`
|
||||
pub fn resolve_import(&self, cwd: &Path, import: &Path) -> Result<PathBuf> {
|
||||
///
|
||||
/// This will also populate the `include_paths` with any nested library root paths that should
|
||||
/// be provided to solc via `--include-path` because it uses absolute imports.
|
||||
pub fn resolve_import_and_include_paths(
|
||||
&self,
|
||||
cwd: &Path,
|
||||
import: &Path,
|
||||
include_paths: &mut IncludePaths,
|
||||
) -> 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 {
|
||||
// if the import is relative we assume it's already part of the processed input
|
||||
// file set
|
||||
|
@ -227,7 +243,32 @@ impl ProjectPathsConfig {
|
|||
})
|
||||
} else {
|
||||
// resolve library file
|
||||
self.resolve_library_import(import.as_ref()).ok_or_else(|| {
|
||||
let resolved = self.resolve_library_import(import.as_ref());
|
||||
|
||||
if resolved.is_none() {
|
||||
// absolute paths in solidity are a thing for example `import
|
||||
// "src/interfaces/IConfig.sol"` which could either point to `cwd +
|
||||
// src/interfaces/IConfig.sol`, or make use of a remapping (`src/=....`)
|
||||
if let Some(lib) = self.find_library_ancestor(cwd) {
|
||||
if let Some((include_path, import)) =
|
||||
utils::resolve_absolute_library(lib, cwd, import)
|
||||
{
|
||||
// track the path for this absolute import inside a nested library
|
||||
include_paths.insert(include_path);
|
||||
return Ok(import)
|
||||
}
|
||||
}
|
||||
// also try to resolve absolute imports from the project paths
|
||||
for path in [&self.root, &self.sources, &self.tests, &self.scripts] {
|
||||
if cwd.starts_with(path) {
|
||||
if let Ok(import) = utils::canonicalize(path.join(import)) {
|
||||
return Ok(import)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolved.ok_or_else(|| {
|
||||
SolcError::msg(format!(
|
||||
"failed to resolve library import \"{:?}\"",
|
||||
import.display()
|
||||
|
@ -236,6 +277,13 @@ impl ProjectPathsConfig {
|
|||
}
|
||||
}
|
||||
|
||||
/// Attempts to resolve an `import` from the given working directory.
|
||||
///
|
||||
/// The `cwd` path is the parent dir of the file that includes the `import`
|
||||
pub fn resolve_import(&self, cwd: &Path, import: &Path) -> Result<PathBuf> {
|
||||
self.resolve_import_and_include_paths(cwd, import, &mut Default::default())
|
||||
}
|
||||
|
||||
/// Attempts to find the path to the real solidity file that's imported via the given `import`
|
||||
/// path by applying the configured remappings and checking the library dirs
|
||||
///
|
||||
|
@ -751,6 +799,49 @@ impl SolcConfigBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Container for all `--include-path` arguments for Solc, se also [Solc docs](https://docs.soliditylang.org/en/v0.8.9/using-the-compiler.html#base-path-and-import-remapping
|
||||
///
|
||||
/// The `--include--path` flag:
|
||||
/// > Makes an additional source directory available to the default import callback. Use this option
|
||||
/// > if you want to import contracts whose location is not fixed in relation to your main source
|
||||
/// > tree, e.g. third-party libraries installed using a package manager. Can be used multiple
|
||||
/// > times. Can only be used if base path has a non-empty value.
|
||||
///
|
||||
/// In contrast to `--allow-paths` [`AllowedLibPaths`], which takes multiple arguments,
|
||||
/// `--include-path` only takes a single path argument.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct IncludePaths(pub(crate) BTreeSet<PathBuf>);
|
||||
|
||||
// === impl IncludePaths ===
|
||||
|
||||
impl IncludePaths {
|
||||
/// Returns the [Command](std::process::Command) arguments for this type
|
||||
///
|
||||
/// For each entry in the set, it will return `--include-path` + `<entry>`
|
||||
pub fn args(&self) -> impl Iterator<Item = String> + '_ {
|
||||
self.paths().flat_map(|path| ["--include-path".to_string(), format!("{}", path.display())])
|
||||
}
|
||||
|
||||
/// Returns all paths that exist
|
||||
pub fn paths(&self) -> impl Iterator<Item = &PathBuf> + '_ {
|
||||
self.0.iter().filter(|path| path.exists())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for IncludePaths {
|
||||
type Target = BTreeSet<PathBuf>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for IncludePaths {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct for serializing `--allow-paths` arguments to Solc
|
||||
///
|
||||
/// From the [Solc docs](https://docs.soliditylang.org/en/v0.8.9/using-the-compiler.html#base-path-and-import-remapping):
|
||||
|
@ -761,23 +852,46 @@ impl SolcConfigBuilder {
|
|||
/// can be allowed via the --allow-paths /sample/path,/another/sample/path switch.
|
||||
/// Everything inside the path specified via --base-path is always allowed.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct AllowedLibPaths(pub(crate) Vec<PathBuf>);
|
||||
pub struct AllowedLibPaths(pub(crate) BTreeSet<PathBuf>);
|
||||
|
||||
// === impl AllowedLibPaths ===
|
||||
|
||||
impl AllowedLibPaths {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
/// Returns the [Command](std::process::Command) arguments for this type
|
||||
///
|
||||
/// `--allow-paths` takes a single value: all comma separated paths
|
||||
pub fn args(&self) -> Option<[String; 2]> {
|
||||
let args = self.to_string();
|
||||
if args.is_empty() {
|
||||
return None
|
||||
}
|
||||
Some(["--allow-paths".to_string(), args])
|
||||
}
|
||||
|
||||
/// Returns all paths that exist
|
||||
pub fn paths(&self) -> impl Iterator<Item = &PathBuf> + '_ {
|
||||
self.0.iter().filter(|path| path.exists())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AllowedLibPaths {
|
||||
type Target = BTreeSet<PathBuf>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for AllowedLibPaths {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AllowedLibPaths {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let lib_paths = self
|
||||
.0
|
||||
.iter()
|
||||
.filter(|path| path.exists())
|
||||
.map(|path| format!("{}", path.display()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
let lib_paths =
|
||||
self.paths().map(|path| format!("{}", path.display())).collect::<Vec<_>>().join(",");
|
||||
write!(f, "{}", lib_paths)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ pub use filter::{FileFilter, TestFileFilter};
|
|||
use crate::{
|
||||
artifacts::Sources,
|
||||
cache::SolFilesCache,
|
||||
config::IncludePaths,
|
||||
error::{SolcError, SolcIoError},
|
||||
sources::{VersionedSourceFile, VersionedSourceFiles},
|
||||
};
|
||||
|
@ -73,7 +74,9 @@ pub struct Project<T: ArtifactOutput = ConfigurableArtifacts> {
|
|||
/// Errors/Warnings which match these error codes are not going to be logged
|
||||
pub ignored_error_codes: Vec<u64>,
|
||||
/// The paths which will be allowed for library inclusion
|
||||
pub allowed_lib_paths: AllowedLibPaths,
|
||||
pub allowed_paths: AllowedLibPaths,
|
||||
/// The paths which will be used with solc's `--include-path` attribute
|
||||
pub include_paths: IncludePaths,
|
||||
/// Maximum number of `solc` processes to run simultaneously.
|
||||
solc_jobs: usize,
|
||||
/// Offline mode, if set, network access (download solc) is disallowed
|
||||
|
@ -151,16 +154,48 @@ impl<T: ArtifactOutput> Project<T> {
|
|||
SolFilesCache::read_joined(&self.paths)
|
||||
}
|
||||
|
||||
/// Applies the configured arguments to the given `Solc`
|
||||
///
|
||||
/// See [Self::configure_solc_with_version()]
|
||||
pub(crate) fn configure_solc(&self, solc: Solc) -> Solc {
|
||||
let version = solc.version().ok();
|
||||
self.configure_solc_with_version(solc, version, Default::default())
|
||||
}
|
||||
|
||||
/// Applies the configured arguments to the given `Solc`
|
||||
///
|
||||
/// This will set the `--allow-paths` to the paths configured for the `Project`, if any.
|
||||
fn configure_solc(&self, mut solc: Solc) -> Solc {
|
||||
if !self.allowed_lib_paths.0.is_empty() &&
|
||||
!solc.args.iter().any(|arg| arg == "--allow-paths")
|
||||
{
|
||||
solc = solc.arg("--allow-paths").arg(self.allowed_lib_paths.to_string());
|
||||
///
|
||||
/// If a version is provided and it is applicable it will also set `--base-path` and
|
||||
/// `--include-path` This will set the `--allow-paths` to the paths configured for the
|
||||
/// `Project`, if any.
|
||||
/// This also accepts additional `include_paths`
|
||||
pub(crate) fn configure_solc_with_version(
|
||||
&self,
|
||||
mut solc: Solc,
|
||||
version: Option<Version>,
|
||||
mut include_paths: IncludePaths,
|
||||
) -> Solc {
|
||||
if !solc.args.iter().any(|arg| arg == "--allow-paths") {
|
||||
if let Some([allow, libs]) = self.allowed_paths.args() {
|
||||
solc = solc.arg(allow).arg(libs);
|
||||
}
|
||||
}
|
||||
solc.with_base_path(self.root())
|
||||
if let Some(version) = version {
|
||||
if SUPPORTS_BASE_PATH.matches(&version) {
|
||||
let base_path = format!("{}", self.root().display());
|
||||
if !base_path.is_empty() {
|
||||
solc = solc.with_base_path(self.root());
|
||||
if SUPPORTS_INCLUDE_PATH.matches(&version) {
|
||||
include_paths.extend(self.include_paths.paths().cloned());
|
||||
solc = solc.args(include_paths.args());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
solc.base_path.take();
|
||||
}
|
||||
}
|
||||
solc
|
||||
}
|
||||
|
||||
/// Sets the maximum number of parallel `solc` processes to run simultaneously.
|
||||
|
@ -232,9 +267,7 @@ impl<T: ArtifactOutput> Project<T> {
|
|||
return self.svm_compile(sources)
|
||||
}
|
||||
|
||||
let solc = self.configure_solc(self.solc.clone());
|
||||
|
||||
self.compile_with_version(&solc, sources)
|
||||
self.compile_with_version(&self.solc, sources)
|
||||
}
|
||||
|
||||
/// Compiles a set of contracts using `svm` managed solc installs
|
||||
|
@ -360,13 +393,9 @@ impl<T: ArtifactOutput> Project<T> {
|
|||
.compile()
|
||||
}
|
||||
|
||||
project::ProjectCompiler::with_sources_and_solc(
|
||||
self,
|
||||
sources,
|
||||
self.configure_solc(self.solc.clone()),
|
||||
)?
|
||||
.with_sparse_output(filter)
|
||||
.compile()
|
||||
project::ProjectCompiler::with_sources_and_solc(self, sources, self.solc.clone())?
|
||||
.with_sparse_output(filter)
|
||||
.compile()
|
||||
}
|
||||
|
||||
/// Compiles the given source files with the exact `Solc` executable
|
||||
|
@ -397,12 +426,7 @@ impl<T: ArtifactOutput> Project<T> {
|
|||
solc: &Solc,
|
||||
sources: Sources,
|
||||
) -> Result<ProjectCompileOutput<T>> {
|
||||
project::ProjectCompiler::with_sources_and_solc(
|
||||
self,
|
||||
sources,
|
||||
self.configure_solc(solc.clone()),
|
||||
)?
|
||||
.compile()
|
||||
project::ProjectCompiler::with_sources_and_solc(self, sources, solc.clone())?.compile()
|
||||
}
|
||||
|
||||
/// Removes the project's artifacts and cache file
|
||||
|
@ -541,8 +565,10 @@ pub struct ProjectBuilder<T: ArtifactOutput = ConfigurableArtifacts> {
|
|||
artifacts: T,
|
||||
/// Which error codes to ignore
|
||||
pub ignored_error_codes: Vec<u64>,
|
||||
/// All allowed paths
|
||||
pub allowed_paths: Vec<PathBuf>,
|
||||
/// All allowed paths for solc's `--allowed-paths`
|
||||
allowed_paths: AllowedLibPaths,
|
||||
/// Paths to use for solc's `--include-path`
|
||||
include_paths: IncludePaths,
|
||||
solc_jobs: Option<usize>,
|
||||
}
|
||||
|
||||
|
@ -561,7 +587,8 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
|||
slash_paths: true,
|
||||
artifacts,
|
||||
ignored_error_codes: Vec::new(),
|
||||
allowed_paths: vec![],
|
||||
allowed_paths: Default::default(),
|
||||
include_paths: Default::default(),
|
||||
solc_jobs: None,
|
||||
}
|
||||
}
|
||||
|
@ -697,6 +724,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
|||
auto_detect,
|
||||
ignored_error_codes,
|
||||
allowed_paths,
|
||||
include_paths,
|
||||
solc_jobs,
|
||||
offline,
|
||||
build_info,
|
||||
|
@ -715,6 +743,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
|||
artifacts,
|
||||
ignored_error_codes,
|
||||
allowed_paths,
|
||||
include_paths,
|
||||
solc_jobs,
|
||||
build_info,
|
||||
}
|
||||
|
@ -723,7 +752,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
|||
/// Adds an allowed-path to the solc executable
|
||||
#[must_use]
|
||||
pub fn allowed_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
|
||||
self.allowed_paths.push(path.into());
|
||||
self.allowed_paths.insert(path.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -740,6 +769,26 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Adds an `--include-path` to the solc executable
|
||||
#[must_use]
|
||||
pub fn include_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
|
||||
self.include_paths.insert(path.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds multiple include-path to the solc executable
|
||||
#[must_use]
|
||||
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(self) -> Result<Project<T>> {
|
||||
let Self {
|
||||
paths,
|
||||
|
@ -751,6 +800,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
|||
artifacts,
|
||||
ignored_error_codes,
|
||||
mut allowed_paths,
|
||||
mut include_paths,
|
||||
solc_jobs,
|
||||
offline,
|
||||
build_info,
|
||||
|
@ -767,10 +817,10 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
|||
let solc = solc.unwrap_or_default();
|
||||
let solc_config = solc_config.unwrap_or_else(|| SolcConfig::builder().build());
|
||||
|
||||
if allowed_paths.is_empty() {
|
||||
// allow every contract under root by default
|
||||
allowed_paths.push(paths.root.clone())
|
||||
}
|
||||
// allow every contract under root by default
|
||||
allowed_paths.insert(paths.root.clone());
|
||||
// allow paths where contracts are stored by default
|
||||
include_paths.extend(paths.include_paths());
|
||||
|
||||
Ok(Project {
|
||||
paths,
|
||||
|
@ -782,8 +832,9 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
|||
auto_detect,
|
||||
artifacts,
|
||||
ignored_error_codes,
|
||||
allowed_lib_paths: allowed_paths.into(),
|
||||
solc_jobs: solc_jobs.unwrap_or_else(::num_cpus::get),
|
||||
allowed_paths,
|
||||
include_paths,
|
||||
solc_jobs: solc_jobs.unwrap_or_else(num_cpus::get),
|
||||
offline,
|
||||
slash_paths,
|
||||
})
|
||||
|
|
|
@ -46,19 +46,16 @@
|
|||
//! [version pragma](https://docs.soliditylang.org/en/develop/layout-of-source-files.html#version-pragma),
|
||||
//! which is defined on a per source file basis.
|
||||
|
||||
use crate::{error::Result, utils, IncludePaths, ProjectPathsConfig, SolcError, Source, Sources};
|
||||
use parse::{SolData, SolDataUnit, SolImport};
|
||||
use rayon::prelude::*;
|
||||
use semver::VersionReq;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
fmt, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use parse::{SolData, SolDataUnit, SolImport};
|
||||
use rayon::prelude::*;
|
||||
|
||||
use semver::VersionReq;
|
||||
|
||||
use crate::{error::Result, utils, ProjectPathsConfig, SolcError, Source, Sources};
|
||||
|
||||
mod parse;
|
||||
mod tree;
|
||||
|
||||
|
@ -90,6 +87,13 @@ pub struct GraphEdges {
|
|||
num_input_files: usize,
|
||||
/// tracks all imports that we failed to resolve for a file
|
||||
unresolved_imports: HashSet<(PathBuf, PathBuf)>,
|
||||
/// tracks additional include paths resolved by scanning all imports of the graph
|
||||
///
|
||||
/// Absolute imports, like `import "src/Contract.sol"` are possible, but this does not play
|
||||
/// nice with the standard-json import format, since the VFS won't be able to resolve
|
||||
/// "src/Contract.sol" without help via `--include-path`
|
||||
#[allow(unused)]
|
||||
resolved_solc_include_paths: IncludePaths,
|
||||
}
|
||||
|
||||
impl GraphEdges {
|
||||
|
@ -113,6 +117,11 @@ impl GraphEdges {
|
|||
self.files().skip(self.num_input_files)
|
||||
}
|
||||
|
||||
/// Returns all additional `--include-paths`
|
||||
pub fn include_paths(&self) -> &IncludePaths {
|
||||
&self.resolved_solc_include_paths
|
||||
}
|
||||
|
||||
/// Returns all imports that we failed to resolve
|
||||
pub fn unresolved_imports(&self) -> &HashSet<(PathBuf, PathBuf)> {
|
||||
&self.unresolved_imports
|
||||
|
@ -333,6 +342,10 @@ impl Graph {
|
|||
let mut nodes = Vec::with_capacity(unresolved.len());
|
||||
let mut edges = Vec::with_capacity(unresolved.len());
|
||||
|
||||
// tracks additional paths that should be used with `--include-path`, these are libraries
|
||||
// that use absolute imports like `import "src/Contract.sol"`
|
||||
let mut resolved_solc_include_paths = IncludePaths::default();
|
||||
|
||||
// keep track of all unique paths that we failed to resolve to not spam the reporter with
|
||||
// the same path
|
||||
let mut unresolved_imports = HashSet::new();
|
||||
|
@ -349,7 +362,11 @@ impl Graph {
|
|||
|
||||
for import in node.data.imports.iter() {
|
||||
let import_path = import.data().path();
|
||||
match paths.resolve_import(cwd, import_path) {
|
||||
match paths.resolve_import_and_include_paths(
|
||||
cwd,
|
||||
import_path,
|
||||
&mut resolved_solc_include_paths,
|
||||
) {
|
||||
Ok(import) => {
|
||||
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)?;
|
||||
}
|
||||
|
@ -391,6 +408,7 @@ impl Graph {
|
|||
.collect(),
|
||||
data: Default::default(),
|
||||
unresolved_imports,
|
||||
resolved_solc_include_paths,
|
||||
};
|
||||
Ok(Graph { nodes, edges, root: paths.root.clone() })
|
||||
}
|
||||
|
@ -461,7 +479,14 @@ impl Graph {
|
|||
}
|
||||
versioned_sources.insert(version, sources);
|
||||
}
|
||||
Ok((VersionedSources { inner: versioned_sources, offline }, edges))
|
||||
Ok((
|
||||
VersionedSources {
|
||||
inner: versioned_sources,
|
||||
offline,
|
||||
resolved_solc_include_paths: edges.resolved_solc_include_paths.clone(),
|
||||
},
|
||||
edges,
|
||||
))
|
||||
}
|
||||
|
||||
/// Writes the list of imported files into the given formatter:
|
||||
|
@ -713,6 +738,7 @@ impl<'a> Iterator for NodesIter<'a> {
|
|||
#[cfg(all(feature = "svm-solc"))]
|
||||
#[derive(Debug)]
|
||||
pub struct VersionedSources {
|
||||
resolved_solc_include_paths: IncludePaths,
|
||||
inner: HashMap<crate::SolcVersion, Sources>,
|
||||
offline: bool,
|
||||
}
|
||||
|
@ -724,17 +750,11 @@ impl VersionedSources {
|
|||
/// This will also configure following solc arguments:
|
||||
/// - `allowed_paths`
|
||||
/// - `base_path`
|
||||
pub fn get(
|
||||
pub fn get<T: crate::ArtifactOutput>(
|
||||
self,
|
||||
allowed_lib_paths: &crate::AllowedLibPaths,
|
||||
base_path: impl AsRef<Path>,
|
||||
project: &crate::Project<T>,
|
||||
) -> Result<std::collections::BTreeMap<crate::Solc, (semver::Version, Sources)>> {
|
||||
use crate::Solc;
|
||||
|
||||
// `--base-path` was introduced in 0.6.9 <https://github.com/ethereum/solidity/releases/tag/v0.6.9>
|
||||
static SUPPORTS_BASE_PATH: once_cell::sync::Lazy<VersionReq> =
|
||||
once_cell::sync::Lazy::new(|| VersionReq::parse(">=0.6.9").unwrap());
|
||||
|
||||
// we take the installer lock here to ensure installation checking is done in sync
|
||||
#[cfg(any(test, feature = "tests"))]
|
||||
let _lock = crate::compile::take_solc_installer_lock();
|
||||
|
@ -771,16 +791,15 @@ impl VersionedSources {
|
|||
tracing::trace!("reinstalled solc: \"{}\"", version);
|
||||
}
|
||||
}
|
||||
let mut solc = solc
|
||||
.arg("--allow-paths")
|
||||
.arg(allowed_lib_paths.to_string())
|
||||
.with_base_path(base_path.as_ref());
|
||||
|
||||
let version = solc.version()?;
|
||||
|
||||
if SUPPORTS_BASE_PATH.matches(&version) {
|
||||
solc = solc.arg("--base-path").arg(format!("{}", base_path.as_ref().display()));
|
||||
}
|
||||
|
||||
// this will configure the `Solc` executable and its arguments
|
||||
let solc = project.configure_solc_with_version(
|
||||
solc,
|
||||
Some(version.clone()),
|
||||
self.resolved_solc_include_paths.clone(),
|
||||
);
|
||||
sources_by_version.insert(solc, (version, sources));
|
||||
}
|
||||
Ok(sources_by_version)
|
||||
|
|
|
@ -223,6 +223,37 @@ pub fn resolve_library(libs: &[impl AsRef<Path>], source: impl AsRef<Path>) -> O
|
|||
}
|
||||
}
|
||||
|
||||
/// Tries to find an absolute import like `src/interfaces/IConfig.sol` in `cwd`, moving up the path
|
||||
/// until the `root` is reached.
|
||||
///
|
||||
/// If an existing file under `root` is found, this returns the path up to the `import` path and the
|
||||
/// canonicalized `import` path itself:
|
||||
///
|
||||
/// For example for following layout:
|
||||
///
|
||||
/// ```text
|
||||
/// <root>/mydependency/
|
||||
/// ├── src (`cwd`)
|
||||
/// │ ├── interfaces
|
||||
/// │ │ ├── IConfig.sol
|
||||
/// ```
|
||||
/// and `import` as `src/interfaces/IConfig.sol` and `cwd` as `src` this will return
|
||||
/// (`<root>/mydependency/`, `<root>/mydependency/src/interfaces/IConfig.sol`)
|
||||
pub fn resolve_absolute_library(
|
||||
root: &Path,
|
||||
cwd: &Path,
|
||||
import: &Path,
|
||||
) -> Option<(PathBuf, PathBuf)> {
|
||||
let mut parent = cwd.parent()?;
|
||||
while parent != root {
|
||||
if let Ok(import) = canonicalize(parent.join(import)) {
|
||||
return Some((parent.to_path_buf(), import))
|
||||
}
|
||||
parent = parent.parent()?;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Reads the list of Solc versions that have been installed in the machine. The version list is
|
||||
/// sorted in ascending order.
|
||||
/// Checks for installed solc versions under the given path as
|
||||
|
|
|
@ -1202,7 +1202,7 @@ library MyLib {
|
|||
#[test]
|
||||
fn can_recompile_with_changes() {
|
||||
let mut tmp = TempProject::dapptools().unwrap();
|
||||
tmp.project_mut().allowed_lib_paths = vec![tmp.root().join("modules")].into();
|
||||
tmp.project_mut().allowed_paths = vec![tmp.root().join("modules")].into();
|
||||
|
||||
let content = r#"
|
||||
pragma solidity ^0.8.10;
|
||||
|
@ -2211,3 +2211,110 @@ fn can_add_basic_contract_and_library() {
|
|||
assert!(compiled.find_first("Foo").is_some());
|
||||
assert!(compiled.find_first("Bar").is_some());
|
||||
}
|
||||
|
||||
// <https://github.com/foundry-rs/foundry/issues/2706>
|
||||
#[test]
|
||||
fn can_handle_nested_absolute_imports() {
|
||||
let mut project = TempProject::dapptools().unwrap();
|
||||
|
||||
let remapping = project.paths().libraries[0].join("myDepdendency");
|
||||
project
|
||||
.paths_mut()
|
||||
.remappings
|
||||
.push(Remapping::from_str(&format!("myDepdendency/={}/", remapping.display())).unwrap());
|
||||
|
||||
project
|
||||
.add_lib(
|
||||
"myDepdendency/src/interfaces/IConfig.sol",
|
||||
r#"
|
||||
pragma solidity ^0.8.10;
|
||||
|
||||
interface IConfig {}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
project
|
||||
.add_lib(
|
||||
"myDepdendency/src/Config.sol",
|
||||
r#"
|
||||
pragma solidity ^0.8.10;
|
||||
import "src/interfaces/IConfig.sol";
|
||||
|
||||
contract Config {}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
project
|
||||
.add_source(
|
||||
"Greeter",
|
||||
r#"
|
||||
pragma solidity ^0.8.10;
|
||||
import "myDepdendency/src/Config.sol";
|
||||
|
||||
contract Greeter {}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let compiled = project.compile().unwrap();
|
||||
assert!(!compiled.has_compiler_errors());
|
||||
assert!(compiled.find_first("Greeter").is_some());
|
||||
assert!(compiled.find_first("Config").is_some());
|
||||
assert!(compiled.find_first("IConfig").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_handle_nested_test_absolute_imports() {
|
||||
let project = TempProject::dapptools().unwrap();
|
||||
|
||||
project
|
||||
.add_source(
|
||||
"Contract.sol",
|
||||
r#"
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity =0.8.13;
|
||||
|
||||
library Library {
|
||||
function f(uint256 a, uint256 b) public pure returns (uint256) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
|
||||
contract Contract {
|
||||
uint256 c;
|
||||
|
||||
constructor() {
|
||||
c = Library.f(1, 2);
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
project
|
||||
.add_test(
|
||||
"Contract.t.sol",
|
||||
r#"
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity =0.8.13;
|
||||
|
||||
import "src/Contract.sol";
|
||||
|
||||
contract ContractTest {
|
||||
function setUp() public {
|
||||
}
|
||||
|
||||
function test() public {
|
||||
new Contract();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let compiled = project.compile().unwrap();
|
||||
assert!(!compiled.has_compiler_errors());
|
||||
assert!(compiled.find_first("Contract").is_some());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue