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/>
|
/// <https://blog.soliditylang.org/2021/08/11/solidity-0.8.7-release-announcement/>
|
||||||
pub const LONDON_SOLC: Version = Version::new(0, 8, 7);
|
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"))]
|
#[cfg(any(test, feature = "tests"))]
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
@ -527,6 +535,7 @@ impl Solc {
|
||||||
let mut cmd = Command::new(&self.solc);
|
let mut cmd = Command::new(&self.solc);
|
||||||
if let Some(ref base_path) = self.base_path {
|
if let Some(ref base_path) = self.base_path {
|
||||||
cmd.current_dir(base_path);
|
cmd.current_dir(base_path);
|
||||||
|
cmd.arg("--base-path").arg(base_path);
|
||||||
}
|
}
|
||||||
let mut child = cmd
|
let mut child = cmd
|
||||||
.args(&self.args)
|
.args(&self.args)
|
||||||
|
|
|
@ -156,8 +156,7 @@ impl<'a, T: ArtifactOutput> ProjectCompiler<'a, T> {
|
||||||
let graph = Graph::resolve_sources(&project.paths, sources)?;
|
let graph = Graph::resolve_sources(&project.paths, sources)?;
|
||||||
let (versions, edges) = graph.into_sources_by_version(project.offline)?;
|
let (versions, edges) = graph.into_sources_by_version(project.offline)?;
|
||||||
|
|
||||||
let base_path = project.root();
|
let sources_by_version = versions.get(project)?;
|
||||||
let sources_by_version = versions.get(&project.allowed_lib_paths, base_path)?;
|
|
||||||
|
|
||||||
let sources = if project.solc_jobs > 1 && sources_by_version.len() > 1 {
|
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
|
// 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> {
|
) -> Result<Self> {
|
||||||
let version = solc.version()?;
|
let version = solc.version()?;
|
||||||
let (sources, edges) = Graph::resolve_sources(&project.paths, sources)?.into_sources();
|
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_by_version = BTreeMap::from([(solc, (version, sources))]);
|
||||||
let sources = CompilerSources::Sequential(sources_by_version);
|
let sources = CompilerSources::Sequential(sources_by_version);
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
artifacts::Settings,
|
artifacts::{output_selection::ContractOutputSelection, Settings},
|
||||||
cache::SOLIDITY_FILES_CACHE_FILENAME,
|
cache::SOLIDITY_FILES_CACHE_FILENAME,
|
||||||
error::{Result, SolcError, SolcIoError},
|
error::{Result, SolcError, SolcIoError},
|
||||||
remappings::Remapping,
|
remappings::Remapping,
|
||||||
resolver::{Graph, SolImportAlias},
|
resolver::{Graph, SolImportAlias},
|
||||||
utils, Source, Sources,
|
utils, Source, Sources,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::artifacts::output_selection::ContractOutputSelection;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeSet, HashSet},
|
collections::{BTreeSet, HashSet},
|
||||||
fmt::{self, Formatter},
|
fmt::{self, Formatter},
|
||||||
fs,
|
fs,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
path::{Component, Path, PathBuf},
|
path::{Component, Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -86,6 +84,15 @@ impl ProjectPathsConfig {
|
||||||
paths
|
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
|
/// Creates all configured dirs and files
|
||||||
pub fn create_all(&self) -> std::result::Result<(), SolcIoError> {
|
pub fn create_all(&self) -> std::result::Result<(), SolcIoError> {
|
||||||
if let Some(parent) = self.cache.parent() {
|
if let Some(parent) = self.cache.parent() {
|
||||||
|
@ -214,11 +221,20 @@ impl ProjectPathsConfig {
|
||||||
/// Attempts to resolve an `import` from the given working directory.
|
/// Attempts to resolve an `import` from the given working directory.
|
||||||
///
|
///
|
||||||
/// The `cwd` path is the parent dir of the file that includes the `import`
|
/// 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
|
let component = import
|
||||||
.components()
|
.components()
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| SolcError::msg(format!("Empty import path {}", import.display())))?;
|
.ok_or_else(|| SolcError::msg(format!("Empty import path {}", import.display())))?;
|
||||||
|
|
||||||
if component == Component::CurDir || component == Component::ParentDir {
|
if component == Component::CurDir || component == Component::ParentDir {
|
||||||
// if the import is relative we assume it's already part of the processed input
|
// if the import is relative we assume it's already part of the processed input
|
||||||
// file set
|
// file set
|
||||||
|
@ -227,7 +243,32 @@ impl ProjectPathsConfig {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// resolve library file
|
// 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!(
|
SolcError::msg(format!(
|
||||||
"failed to resolve library import \"{:?}\"",
|
"failed to resolve library import \"{:?}\"",
|
||||||
import.display()
|
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`
|
/// 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
|
/// 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
|
/// 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):
|
/// 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.
|
/// can be allowed via the --allow-paths /sample/path,/another/sample/path switch.
|
||||||
/// Everything inside the path specified via --base-path is always allowed.
|
/// Everything inside the path specified via --base-path is always allowed.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct AllowedLibPaths(pub(crate) Vec<PathBuf>);
|
pub struct AllowedLibPaths(pub(crate) BTreeSet<PathBuf>);
|
||||||
|
|
||||||
|
// === impl AllowedLibPaths ===
|
||||||
|
|
||||||
impl AllowedLibPaths {
|
impl AllowedLibPaths {
|
||||||
pub fn is_empty(&self) -> bool {
|
/// Returns the [Command](std::process::Command) arguments for this type
|
||||||
self.0.is_empty()
|
///
|
||||||
|
/// `--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 {
|
impl fmt::Display for AllowedLibPaths {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let lib_paths = self
|
let lib_paths =
|
||||||
.0
|
self.paths().map(|path| format!("{}", path.display())).collect::<Vec<_>>().join(",");
|
||||||
.iter()
|
|
||||||
.filter(|path| path.exists())
|
|
||||||
.map(|path| format!("{}", path.display()))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(",");
|
|
||||||
write!(f, "{}", lib_paths)
|
write!(f, "{}", lib_paths)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ pub use filter::{FileFilter, TestFileFilter};
|
||||||
use crate::{
|
use crate::{
|
||||||
artifacts::Sources,
|
artifacts::Sources,
|
||||||
cache::SolFilesCache,
|
cache::SolFilesCache,
|
||||||
|
config::IncludePaths,
|
||||||
error::{SolcError, SolcIoError},
|
error::{SolcError, SolcIoError},
|
||||||
sources::{VersionedSourceFile, VersionedSourceFiles},
|
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
|
/// Errors/Warnings which match these error codes are not going to be logged
|
||||||
pub ignored_error_codes: Vec<u64>,
|
pub ignored_error_codes: Vec<u64>,
|
||||||
/// The paths which will be allowed for library inclusion
|
/// 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.
|
/// Maximum number of `solc` processes to run simultaneously.
|
||||||
solc_jobs: usize,
|
solc_jobs: usize,
|
||||||
/// Offline mode, if set, network access (download solc) is disallowed
|
/// Offline mode, if set, network access (download solc) is disallowed
|
||||||
|
@ -151,16 +154,48 @@ impl<T: ArtifactOutput> Project<T> {
|
||||||
SolFilesCache::read_joined(&self.paths)
|
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`
|
/// Applies the configured arguments to the given `Solc`
|
||||||
///
|
///
|
||||||
/// This will set the `--allow-paths` to the paths configured for the `Project`, if any.
|
/// 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() &&
|
/// If a version is provided and it is applicable it will also set `--base-path` and
|
||||||
!solc.args.iter().any(|arg| arg == "--allow-paths")
|
/// `--include-path` This will set the `--allow-paths` to the paths configured for the
|
||||||
{
|
/// `Project`, if any.
|
||||||
solc = solc.arg("--allow-paths").arg(self.allowed_lib_paths.to_string());
|
/// 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.
|
/// 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)
|
return self.svm_compile(sources)
|
||||||
}
|
}
|
||||||
|
|
||||||
let solc = self.configure_solc(self.solc.clone());
|
self.compile_with_version(&self.solc, sources)
|
||||||
|
|
||||||
self.compile_with_version(&solc, sources)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compiles a set of contracts using `svm` managed solc installs
|
/// Compiles a set of contracts using `svm` managed solc installs
|
||||||
|
@ -360,13 +393,9 @@ impl<T: ArtifactOutput> Project<T> {
|
||||||
.compile()
|
.compile()
|
||||||
}
|
}
|
||||||
|
|
||||||
project::ProjectCompiler::with_sources_and_solc(
|
project::ProjectCompiler::with_sources_and_solc(self, sources, self.solc.clone())?
|
||||||
self,
|
.with_sparse_output(filter)
|
||||||
sources,
|
.compile()
|
||||||
self.configure_solc(self.solc.clone()),
|
|
||||||
)?
|
|
||||||
.with_sparse_output(filter)
|
|
||||||
.compile()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compiles the given source files with the exact `Solc` executable
|
/// Compiles the given source files with the exact `Solc` executable
|
||||||
|
@ -397,12 +426,7 @@ impl<T: ArtifactOutput> Project<T> {
|
||||||
solc: &Solc,
|
solc: &Solc,
|
||||||
sources: Sources,
|
sources: Sources,
|
||||||
) -> Result<ProjectCompileOutput<T>> {
|
) -> Result<ProjectCompileOutput<T>> {
|
||||||
project::ProjectCompiler::with_sources_and_solc(
|
project::ProjectCompiler::with_sources_and_solc(self, sources, solc.clone())?.compile()
|
||||||
self,
|
|
||||||
sources,
|
|
||||||
self.configure_solc(solc.clone()),
|
|
||||||
)?
|
|
||||||
.compile()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the project's artifacts and cache file
|
/// Removes the project's artifacts and cache file
|
||||||
|
@ -541,8 +565,10 @@ pub struct ProjectBuilder<T: ArtifactOutput = ConfigurableArtifacts> {
|
||||||
artifacts: T,
|
artifacts: T,
|
||||||
/// Which error codes to ignore
|
/// Which error codes to ignore
|
||||||
pub ignored_error_codes: Vec<u64>,
|
pub ignored_error_codes: Vec<u64>,
|
||||||
/// All allowed paths
|
/// All allowed paths for solc's `--allowed-paths`
|
||||||
pub allowed_paths: Vec<PathBuf>,
|
allowed_paths: AllowedLibPaths,
|
||||||
|
/// Paths to use for solc's `--include-path`
|
||||||
|
include_paths: IncludePaths,
|
||||||
solc_jobs: Option<usize>,
|
solc_jobs: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,7 +587,8 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
||||||
slash_paths: true,
|
slash_paths: true,
|
||||||
artifacts,
|
artifacts,
|
||||||
ignored_error_codes: Vec::new(),
|
ignored_error_codes: Vec::new(),
|
||||||
allowed_paths: vec![],
|
allowed_paths: Default::default(),
|
||||||
|
include_paths: Default::default(),
|
||||||
solc_jobs: None,
|
solc_jobs: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -697,6 +724,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
||||||
auto_detect,
|
auto_detect,
|
||||||
ignored_error_codes,
|
ignored_error_codes,
|
||||||
allowed_paths,
|
allowed_paths,
|
||||||
|
include_paths,
|
||||||
solc_jobs,
|
solc_jobs,
|
||||||
offline,
|
offline,
|
||||||
build_info,
|
build_info,
|
||||||
|
@ -715,6 +743,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
||||||
artifacts,
|
artifacts,
|
||||||
ignored_error_codes,
|
ignored_error_codes,
|
||||||
allowed_paths,
|
allowed_paths,
|
||||||
|
include_paths,
|
||||||
solc_jobs,
|
solc_jobs,
|
||||||
build_info,
|
build_info,
|
||||||
}
|
}
|
||||||
|
@ -723,7 +752,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
||||||
/// Adds an allowed-path to the solc executable
|
/// Adds an allowed-path to the solc executable
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn allowed_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
|
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -740,6 +769,26 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
||||||
self
|
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>> {
|
pub fn build(self) -> Result<Project<T>> {
|
||||||
let Self {
|
let Self {
|
||||||
paths,
|
paths,
|
||||||
|
@ -751,6 +800,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
||||||
artifacts,
|
artifacts,
|
||||||
ignored_error_codes,
|
ignored_error_codes,
|
||||||
mut allowed_paths,
|
mut allowed_paths,
|
||||||
|
mut include_paths,
|
||||||
solc_jobs,
|
solc_jobs,
|
||||||
offline,
|
offline,
|
||||||
build_info,
|
build_info,
|
||||||
|
@ -767,10 +817,10 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
||||||
let solc = solc.unwrap_or_default();
|
let solc = solc.unwrap_or_default();
|
||||||
let solc_config = solc_config.unwrap_or_else(|| SolcConfig::builder().build());
|
let solc_config = solc_config.unwrap_or_else(|| SolcConfig::builder().build());
|
||||||
|
|
||||||
if allowed_paths.is_empty() {
|
// allow every contract under root by default
|
||||||
// allow every contract under root by default
|
allowed_paths.insert(paths.root.clone());
|
||||||
allowed_paths.push(paths.root.clone())
|
// allow paths where contracts are stored by default
|
||||||
}
|
include_paths.extend(paths.include_paths());
|
||||||
|
|
||||||
Ok(Project {
|
Ok(Project {
|
||||||
paths,
|
paths,
|
||||||
|
@ -782,8 +832,9 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
|
||||||
auto_detect,
|
auto_detect,
|
||||||
artifacts,
|
artifacts,
|
||||||
ignored_error_codes,
|
ignored_error_codes,
|
||||||
allowed_lib_paths: allowed_paths.into(),
|
allowed_paths,
|
||||||
solc_jobs: solc_jobs.unwrap_or_else(::num_cpus::get),
|
include_paths,
|
||||||
|
solc_jobs: solc_jobs.unwrap_or_else(num_cpus::get),
|
||||||
offline,
|
offline,
|
||||||
slash_paths,
|
slash_paths,
|
||||||
})
|
})
|
||||||
|
|
|
@ -46,19 +46,16 @@
|
||||||
//! [version pragma](https://docs.soliditylang.org/en/develop/layout-of-source-files.html#version-pragma),
|
//! [version pragma](https://docs.soliditylang.org/en/develop/layout-of-source-files.html#version-pragma),
|
||||||
//! which is defined on a per source file basis.
|
//! 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::{
|
use std::{
|
||||||
collections::{HashMap, HashSet, VecDeque},
|
collections::{HashMap, HashSet, VecDeque},
|
||||||
fmt, io,
|
fmt, io,
|
||||||
path::{Path, PathBuf},
|
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 parse;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
||||||
|
@ -90,6 +87,13 @@ pub struct GraphEdges {
|
||||||
num_input_files: usize,
|
num_input_files: usize,
|
||||||
/// tracks all imports that we failed to resolve for a file
|
/// tracks all imports that we failed to resolve for a file
|
||||||
unresolved_imports: HashSet<(PathBuf, PathBuf)>,
|
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 {
|
impl GraphEdges {
|
||||||
|
@ -113,6 +117,11 @@ impl GraphEdges {
|
||||||
self.files().skip(self.num_input_files)
|
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
|
/// Returns all imports that we failed to resolve
|
||||||
pub fn unresolved_imports(&self) -> &HashSet<(PathBuf, PathBuf)> {
|
pub fn unresolved_imports(&self) -> &HashSet<(PathBuf, PathBuf)> {
|
||||||
&self.unresolved_imports
|
&self.unresolved_imports
|
||||||
|
@ -333,6 +342,10 @@ impl Graph {
|
||||||
let mut nodes = Vec::with_capacity(unresolved.len());
|
let mut nodes = Vec::with_capacity(unresolved.len());
|
||||||
let mut edges = 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
|
// keep track of all unique paths that we failed to resolve to not spam the reporter with
|
||||||
// the same path
|
// the same path
|
||||||
let mut unresolved_imports = HashSet::new();
|
let mut unresolved_imports = HashSet::new();
|
||||||
|
@ -349,7 +362,11 @@ impl Graph {
|
||||||
|
|
||||||
for import in node.data.imports.iter() {
|
for import in node.data.imports.iter() {
|
||||||
let import_path = import.data().path();
|
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) => {
|
Ok(import) => {
|
||||||
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)?;
|
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)?;
|
||||||
}
|
}
|
||||||
|
@ -391,6 +408,7 @@ impl Graph {
|
||||||
.collect(),
|
.collect(),
|
||||||
data: Default::default(),
|
data: Default::default(),
|
||||||
unresolved_imports,
|
unresolved_imports,
|
||||||
|
resolved_solc_include_paths,
|
||||||
};
|
};
|
||||||
Ok(Graph { nodes, edges, root: paths.root.clone() })
|
Ok(Graph { nodes, edges, root: paths.root.clone() })
|
||||||
}
|
}
|
||||||
|
@ -461,7 +479,14 @@ impl Graph {
|
||||||
}
|
}
|
||||||
versioned_sources.insert(version, sources);
|
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:
|
/// 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"))]
|
#[cfg(all(feature = "svm-solc"))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct VersionedSources {
|
pub struct VersionedSources {
|
||||||
|
resolved_solc_include_paths: IncludePaths,
|
||||||
inner: HashMap<crate::SolcVersion, Sources>,
|
inner: HashMap<crate::SolcVersion, Sources>,
|
||||||
offline: bool,
|
offline: bool,
|
||||||
}
|
}
|
||||||
|
@ -724,17 +750,11 @@ impl VersionedSources {
|
||||||
/// This will also configure following solc arguments:
|
/// This will also configure following solc arguments:
|
||||||
/// - `allowed_paths`
|
/// - `allowed_paths`
|
||||||
/// - `base_path`
|
/// - `base_path`
|
||||||
pub fn get(
|
pub fn get<T: crate::ArtifactOutput>(
|
||||||
self,
|
self,
|
||||||
allowed_lib_paths: &crate::AllowedLibPaths,
|
project: &crate::Project<T>,
|
||||||
base_path: impl AsRef<Path>,
|
|
||||||
) -> Result<std::collections::BTreeMap<crate::Solc, (semver::Version, Sources)>> {
|
) -> Result<std::collections::BTreeMap<crate::Solc, (semver::Version, Sources)>> {
|
||||||
use crate::Solc;
|
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
|
// we take the installer lock here to ensure installation checking is done in sync
|
||||||
#[cfg(any(test, feature = "tests"))]
|
#[cfg(any(test, feature = "tests"))]
|
||||||
let _lock = crate::compile::take_solc_installer_lock();
|
let _lock = crate::compile::take_solc_installer_lock();
|
||||||
|
@ -771,16 +791,15 @@ impl VersionedSources {
|
||||||
tracing::trace!("reinstalled solc: \"{}\"", version);
|
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()?;
|
let version = solc.version()?;
|
||||||
|
|
||||||
if SUPPORTS_BASE_PATH.matches(&version) {
|
// this will configure the `Solc` executable and its arguments
|
||||||
solc = solc.arg("--base-path").arg(format!("{}", base_path.as_ref().display()));
|
let solc = project.configure_solc_with_version(
|
||||||
}
|
solc,
|
||||||
|
Some(version.clone()),
|
||||||
|
self.resolved_solc_include_paths.clone(),
|
||||||
|
);
|
||||||
sources_by_version.insert(solc, (version, sources));
|
sources_by_version.insert(solc, (version, sources));
|
||||||
}
|
}
|
||||||
Ok(sources_by_version)
|
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
|
/// Reads the list of Solc versions that have been installed in the machine. The version list is
|
||||||
/// sorted in ascending order.
|
/// sorted in ascending order.
|
||||||
/// Checks for installed solc versions under the given path as
|
/// Checks for installed solc versions under the given path as
|
||||||
|
|
|
@ -1202,7 +1202,7 @@ library MyLib {
|
||||||
#[test]
|
#[test]
|
||||||
fn can_recompile_with_changes() {
|
fn can_recompile_with_changes() {
|
||||||
let mut tmp = TempProject::dapptools().unwrap();
|
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#"
|
let content = r#"
|
||||||
pragma solidity ^0.8.10;
|
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("Foo").is_some());
|
||||||
assert!(compiled.find_first("Bar").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