feat(solc): use relative paths and --base-path option (#1317)
* feat(solc): use relative paths and --base-path option * chore: update CHANGELOG * strip lib paths
This commit is contained in:
parent
e3e810cef9
commit
54f1b9dee8
|
@ -98,6 +98,8 @@
|
||||||
|
|
||||||
### Unreleased
|
### Unreleased
|
||||||
|
|
||||||
|
- Use relative source paths and `solc --base-path`
|
||||||
|
[#1317](https://github.com/gakonst/ethers-rs/pull/1317)
|
||||||
- Save cache entry objects with relative paths
|
- Save cache entry objects with relative paths
|
||||||
[#1307](https://github.com/gakonst/ethers-rs/pull/1307)
|
[#1307](https://github.com/gakonst/ethers-rs/pull/1307)
|
||||||
- Bundle svm, svm-builds and sha2 dependencies in new `svm-solc` feature
|
- Bundle svm, svm-builds and sha2 dependencies in new `svm-solc` feature
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
//! Solc artifact types
|
//! Solc artifact types
|
||||||
use ethers_core::abi::Abi;
|
use crate::{
|
||||||
|
compile::*, error::SolcIoError, remappings::Remapping, utils, ProjectPathsConfig, SolcError,
|
||||||
|
};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
use ethers_core::abi::Abi;
|
||||||
use md5::Digest;
|
use md5::Digest;
|
||||||
use semver::{Version, VersionReq};
|
use semver::{Version, VersionReq};
|
||||||
|
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashSet},
|
collections::{BTreeMap, HashSet},
|
||||||
fmt, fs,
|
fmt, fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
compile::*, error::SolcIoError, remappings::Remapping, utils, ProjectPathsConfig, SolcError,
|
|
||||||
};
|
|
||||||
|
|
||||||
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
|
@ -189,10 +186,20 @@ impl CompilerInput {
|
||||||
self.sources = self
|
self.sources = self
|
||||||
.sources
|
.sources
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(path, s)| (path.strip_prefix(base).map(|p| p.to_path_buf()).unwrap_or(path), s))
|
.map(|(path, s)| (path.strip_prefix(base).map(Into::into).unwrap_or(path), s))
|
||||||
.collect();
|
.collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Similar to `Self::strip_prefix()`. Remove a base path from all
|
||||||
|
/// sources _and_ all paths in solc settings such as remappings
|
||||||
|
///
|
||||||
|
/// See also `solc --base-path`
|
||||||
|
pub fn with_base_path(mut self, base: impl AsRef<Path>) -> Self {
|
||||||
|
let base = base.as_ref();
|
||||||
|
self.settings = self.settings.with_base_path(base);
|
||||||
|
self.strip_prefix(base)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `CompilerInput` representation used for verify
|
/// A `CompilerInput` representation used for verify
|
||||||
|
@ -385,6 +392,38 @@ impl Settings {
|
||||||
output.insert("".to_string(), vec!["ast".to_string()]);
|
output.insert("".to_string(), vec!["ast".to_string()]);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Strips `base` from all paths
|
||||||
|
pub fn with_base_path(mut self, base: impl AsRef<Path>) -> Self {
|
||||||
|
let base = base.as_ref();
|
||||||
|
self.remappings.iter_mut().for_each(|r| {
|
||||||
|
r.strip_prefix(base);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.libraries.libs = self
|
||||||
|
.libraries
|
||||||
|
.libs
|
||||||
|
.into_iter()
|
||||||
|
.map(|(file, libs)| (file.strip_prefix(base).map(Into::into).unwrap_or(file), libs))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
self.output_selection = OutputSelection(
|
||||||
|
self.output_selection
|
||||||
|
.0
|
||||||
|
.into_iter()
|
||||||
|
.map(|(file, selection)| {
|
||||||
|
(
|
||||||
|
Path::new(&file)
|
||||||
|
.strip_prefix(base)
|
||||||
|
.map(|p| format!("{}", p.display()))
|
||||||
|
.unwrap_or(file),
|
||||||
|
selection,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
|
|
|
@ -127,6 +127,8 @@ impl fmt::Display for SolcVersion {
|
||||||
pub struct Solc {
|
pub struct Solc {
|
||||||
/// Path to the `solc` executable
|
/// Path to the `solc` executable
|
||||||
pub solc: PathBuf,
|
pub solc: PathBuf,
|
||||||
|
/// The base path to set when invoking solc, see also <https://docs.soliditylang.org/en/v0.8.11/path-resolution.html#base-path-and-include-paths>
|
||||||
|
pub base_path: Option<PathBuf>,
|
||||||
/// Additional arguments passed to the `solc` exectuable
|
/// Additional arguments passed to the `solc` exectuable
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
@ -163,7 +165,15 @@ impl fmt::Display for Solc {
|
||||||
impl Solc {
|
impl Solc {
|
||||||
/// A new instance which points to `solc`
|
/// A new instance which points to `solc`
|
||||||
pub fn new(path: impl Into<PathBuf>) -> Self {
|
pub fn new(path: impl Into<PathBuf>) -> Self {
|
||||||
Solc { solc: path.into(), args: Vec::new() }
|
Solc { solc: path.into(), base_path: None, args: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets solc's base path
|
||||||
|
///
|
||||||
|
/// Ref: <https://docs.soliditylang.org/en/v0.8.11/path-resolution.html#base-path-and-include-paths>
|
||||||
|
pub fn with_base_path(mut self, base_path: impl Into<PathBuf>) -> Self {
|
||||||
|
self.base_path = Some(base_path.into());
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds an argument to pass to the `solc` command.
|
/// Adds an argument to pass to the `solc` command.
|
||||||
|
@ -513,6 +523,9 @@ impl Solc {
|
||||||
|
|
||||||
pub fn compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
|
pub fn compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
|
||||||
let mut cmd = Command::new(&self.solc);
|
let mut cmd = Command::new(&self.solc);
|
||||||
|
if let Some(ref base_path) = self.base_path {
|
||||||
|
cmd.current_dir(base_path);
|
||||||
|
}
|
||||||
let mut child = cmd
|
let mut child = cmd
|
||||||
.args(&self.args)
|
.args(&self.args)
|
||||||
.arg("--standard-json")
|
.arg("--standard-json")
|
||||||
|
@ -575,7 +588,11 @@ impl Solc {
|
||||||
pub async fn async_compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
|
pub async fn async_compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
let content = serde_json::to_vec(input)?;
|
let content = serde_json::to_vec(input)?;
|
||||||
let mut child = tokio::process::Command::new(&self.solc)
|
let mut cmd = tokio::process::Command::new(&self.solc);
|
||||||
|
if let Some(ref base_path) = self.base_path {
|
||||||
|
cmd.current_dir(base_path);
|
||||||
|
}
|
||||||
|
let mut child = cmd
|
||||||
.args(&self.args)
|
.args(&self.args)
|
||||||
.arg("--standard-json")
|
.arg("--standard-json")
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
|
|
|
@ -136,7 +136,7 @@ impl VersionedContracts {
|
||||||
self.0 = std::mem::take(&mut self.0)
|
self.0 = std::mem::take(&mut self.0)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(contract_path, contracts)| {
|
.map(|(contract_path, contracts)| {
|
||||||
(root.join(contract_path).to_string_lossy().to_string(), contracts)
|
(format!("{}", root.join(contract_path).display()), contracts)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
self
|
self
|
||||||
|
|
|
@ -366,6 +366,14 @@ impl AggregatedCompilerOutput {
|
||||||
(self.sources, self.contracts)
|
(self.sources, self.contracts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Joins all file path with `root`
|
||||||
|
pub fn join_all(&mut self, root: impl AsRef<Path>) -> &mut Self {
|
||||||
|
let root = root.as_ref();
|
||||||
|
self.contracts.join_all(root);
|
||||||
|
self.sources.join_all(root);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Strips the given prefix from all file paths to make them relative to the given
|
/// Strips the given prefix from all file paths to make them relative to the given
|
||||||
/// `base` argument.
|
/// `base` argument.
|
||||||
///
|
///
|
||||||
|
|
|
@ -106,6 +106,7 @@ use crate::{
|
||||||
artifacts::{Settings, VersionedFilteredSources, VersionedSources},
|
artifacts::{Settings, VersionedFilteredSources, VersionedSources},
|
||||||
cache::ArtifactsCache,
|
cache::ArtifactsCache,
|
||||||
error::Result,
|
error::Result,
|
||||||
|
filter::SparseOutputFilter,
|
||||||
output::AggregatedCompilerOutput,
|
output::AggregatedCompilerOutput,
|
||||||
report,
|
report,
|
||||||
resolver::GraphEdges,
|
resolver::GraphEdges,
|
||||||
|
@ -113,8 +114,6 @@ use crate::{
|
||||||
Sources,
|
Sources,
|
||||||
};
|
};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
use crate::filter::SparseOutputFilter;
|
|
||||||
use std::{collections::btree_map::BTreeMap, path::PathBuf, time::Instant};
|
use std::{collections::btree_map::BTreeMap, path::PathBuf, time::Instant};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -155,7 +154,9 @@ impl<'a, T: ArtifactOutput> ProjectCompiler<'a, T> {
|
||||||
pub fn with_sources(project: &'a Project<T>, sources: Sources) -> Result<Self> {
|
pub fn with_sources(project: &'a Project<T>, sources: Sources) -> Result<Self> {
|
||||||
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 sources_by_version = versions.get(&project.allowed_lib_paths)?;
|
|
||||||
|
let base_path = project.root();
|
||||||
|
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
|
||||||
|
@ -239,13 +240,20 @@ impl<'a, T: ArtifactOutput> PreprocessedState<'a, T> {
|
||||||
/// advance to the next state by compiling all sources
|
/// advance to the next state by compiling all sources
|
||||||
fn compile(self) -> Result<CompiledState<'a, T>> {
|
fn compile(self) -> Result<CompiledState<'a, T>> {
|
||||||
let PreprocessedState { sources, cache, sparse_output } = self;
|
let PreprocessedState { sources, cache, sparse_output } = self;
|
||||||
let output = sources.compile(
|
let mut output = sources.compile(
|
||||||
&cache.project().solc_config.settings,
|
&cache.project().solc_config.settings,
|
||||||
&cache.project().paths,
|
&cache.project().paths,
|
||||||
sparse_output,
|
sparse_output,
|
||||||
cache.graph(),
|
cache.graph(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// source paths get stripped before handing them over to solc, so solc never uses absolute
|
||||||
|
// paths, instead `--base-path <root dir>` is set. this way any metadata that's derived from
|
||||||
|
// data (paths) is relative to the project dir and should be independent of the current OS
|
||||||
|
// disk. However internally we still want to keep absolute paths, so we join the
|
||||||
|
// contracts again
|
||||||
|
output.join_all(cache.project().root());
|
||||||
|
|
||||||
Ok(CompiledState { output, cache })
|
Ok(CompiledState { output, cache })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -457,9 +465,10 @@ fn compile_sequential(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let input = input
|
let input = input
|
||||||
.settings(opt_settings.clone())
|
.settings(opt_settings.clone().with_base_path(&paths.root))
|
||||||
.normalize_evm_version(&version)
|
.normalize_evm_version(&version)
|
||||||
.with_remappings(paths.remappings.clone())
|
.with_remappings(paths.remappings.clone())
|
||||||
|
.with_base_path(&paths.root)
|
||||||
.sanitized(&version);
|
.sanitized(&version);
|
||||||
|
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
|
@ -539,6 +548,7 @@ fn compile_parallel(
|
||||||
.settings(settings.clone())
|
.settings(settings.clone())
|
||||||
.normalize_evm_version(&version)
|
.normalize_evm_version(&version)
|
||||||
.with_remappings(paths.remappings.clone())
|
.with_remappings(paths.remappings.clone())
|
||||||
|
.with_base_path(&paths.root)
|
||||||
.sanitized(&version);
|
.sanitized(&version);
|
||||||
|
|
||||||
jobs.push((solc.clone(), version.clone(), job, actually_dirty))
|
jobs.push((solc.clone(), version.clone(), job, actually_dirty))
|
||||||
|
@ -740,7 +750,6 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let project = Project::builder().paths(paths).build().unwrap();
|
let project = Project::builder().paths(paths).build().unwrap();
|
||||||
let compiler = ProjectCompiler::new(&project).unwrap();
|
let compiler = ProjectCompiler::new(&project).unwrap();
|
||||||
let out = compiler.compile().unwrap();
|
let _out = compiler.compile().unwrap();
|
||||||
println!("{}", out);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,10 +141,12 @@ impl<T: ArtifactOutput> Project<T> {
|
||||||
///
|
///
|
||||||
/// 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 {
|
fn configure_solc(&self, mut solc: Solc) -> Solc {
|
||||||
if solc.args.is_empty() && !self.allowed_lib_paths.0.is_empty() {
|
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());
|
solc = solc.arg("--allow-paths").arg(self.allowed_lib_paths.to_string());
|
||||||
}
|
}
|
||||||
solc
|
solc.with_base_path(self.root())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the maximum number of parallel `solc` processes to run simultaneously.
|
/// Sets the maximum number of parallel `solc` processes to run simultaneously.
|
||||||
|
|
|
@ -55,6 +55,14 @@ impl Remapping {
|
||||||
pub fn into_relative(self, root: impl AsRef<Path>) -> RelativeRemapping {
|
pub fn into_relative(self, root: impl AsRef<Path>) -> RelativeRemapping {
|
||||||
RelativeRemapping::new(self, root)
|
RelativeRemapping::new(self, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the `base` path from the remapping
|
||||||
|
pub fn strip_prefix(&mut self, base: impl AsRef<Path>) -> &mut Self {
|
||||||
|
if let Ok(stripped) = Path::new(&self.path).strip_prefix(base.as_ref()) {
|
||||||
|
self.path = format!("{}", stripped.display());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, PartialEq, Eq, PartialOrd)]
|
#[derive(thiserror::Error, Debug, PartialEq, Eq, PartialOrd)]
|
||||||
|
|
|
@ -701,12 +701,21 @@ pub struct VersionedSources {
|
||||||
#[cfg(all(feature = "svm-solc"))]
|
#[cfg(all(feature = "svm-solc"))]
|
||||||
impl VersionedSources {
|
impl VersionedSources {
|
||||||
/// Resolves or installs the corresponding `Solc` installation.
|
/// Resolves or installs the corresponding `Solc` installation.
|
||||||
|
///
|
||||||
|
/// This will also configure following solc arguments:
|
||||||
|
/// - `allowed_paths`
|
||||||
|
/// - `base_path`
|
||||||
pub fn get(
|
pub fn get(
|
||||||
self,
|
self,
|
||||||
allowed_lib_paths: &crate::AllowedLibPaths,
|
allowed_lib_paths: &crate::AllowedLibPaths,
|
||||||
|
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();
|
||||||
|
@ -743,8 +752,16 @@ impl VersionedSources {
|
||||||
tracing::trace!("reinstalled solc: \"{}\"", version);
|
tracing::trace!("reinstalled solc: \"{}\"", version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let solc = solc.arg("--allow-paths").arg(allowed_lib_paths.to_string());
|
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) {
|
||||||
|
solc = solc.arg("--base-path").arg(format!("{}", base_path.as_ref().display()));
|
||||||
|
}
|
||||||
|
|
||||||
sources_by_version.insert(solc, (version, sources));
|
sources_by_version.insert(solc, (version, sources));
|
||||||
}
|
}
|
||||||
Ok(sources_by_version)
|
Ok(sources_by_version)
|
||||||
|
|
|
@ -1434,7 +1434,6 @@ fn can_detect_contract_def_source_files() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let compiled = tmp.compile().unwrap();
|
let compiled = tmp.compile().unwrap();
|
||||||
println!("{}", compiled);
|
|
||||||
assert!(!compiled.has_compiler_errors());
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
|
||||||
let mut sources = compiled.output().sources;
|
let mut sources = compiled.output().sources;
|
||||||
|
@ -1750,7 +1749,6 @@ contract D { }
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let compiled = project.compile().unwrap();
|
let compiled = project.compile().unwrap();
|
||||||
println!("{}", compiled);
|
|
||||||
assert!(!compiled.has_compiler_errors());
|
assert!(!compiled.has_compiler_errors());
|
||||||
|
|
||||||
let cache = SolFilesCache::read(project.cache_path()).unwrap();
|
let cache = SolFilesCache::read(project.cache_path()).unwrap();
|
||||||
|
|
Loading…
Reference in New Issue