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:
Matthias Seitz 2022-05-27 22:31:11 +02:00 committed by GitHub
parent e3e810cef9
commit 54f1b9dee8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 124 additions and 24 deletions

View File

@ -98,6 +98,8 @@
### 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
[#1307](https://github.com/gakonst/ethers-rs/pull/1307)
- Bundle svm, svm-builds and sha2 dependencies in new `svm-solc` feature

View File

@ -1,21 +1,18 @@
//! Solc artifact types
use ethers_core::abi::Abi;
use crate::{
compile::*, error::SolcIoError, remappings::Remapping, utils, ProjectPathsConfig, SolcError,
};
use colored::Colorize;
use ethers_core::abi::Abi;
use md5::Digest;
use semver::{Version, VersionReq};
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::{
collections::{BTreeMap, HashSet},
fmt, fs,
path::{Path, PathBuf},
str::FromStr,
};
use crate::{
compile::*, error::SolcIoError, remappings::Remapping, utils, ProjectPathsConfig, SolcError,
};
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use tracing::warn;
pub mod ast;
@ -189,10 +186,20 @@ impl CompilerInput {
self.sources = self
.sources
.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();
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
@ -385,6 +392,38 @@ impl Settings {
output.insert("".to_string(), vec!["ast".to_string()]);
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 {

View File

@ -127,6 +127,8 @@ impl fmt::Display for SolcVersion {
pub struct Solc {
/// Path to the `solc` executable
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
pub args: Vec<String>,
}
@ -163,7 +165,15 @@ impl fmt::Display for Solc {
impl Solc {
/// A new instance which points to `solc`
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.
@ -513,6 +523,9 @@ impl Solc {
pub fn compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
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
.args(&self.args)
.arg("--standard-json")
@ -575,7 +588,11 @@ impl Solc {
pub async fn async_compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
use tokio::io::AsyncWriteExt;
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)
.arg("--standard-json")
.stdin(Stdio::piped())

View File

@ -136,7 +136,7 @@ impl VersionedContracts {
self.0 = std::mem::take(&mut self.0)
.into_iter()
.map(|(contract_path, contracts)| {
(root.join(contract_path).to_string_lossy().to_string(), contracts)
(format!("{}", root.join(contract_path).display()), contracts)
})
.collect();
self

View File

@ -366,6 +366,14 @@ impl AggregatedCompilerOutput {
(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
/// `base` argument.
///

View File

@ -106,6 +106,7 @@ use crate::{
artifacts::{Settings, VersionedFilteredSources, VersionedSources},
cache::ArtifactsCache,
error::Result,
filter::SparseOutputFilter,
output::AggregatedCompilerOutput,
report,
resolver::GraphEdges,
@ -113,8 +114,6 @@ use crate::{
Sources,
};
use rayon::prelude::*;
use crate::filter::SparseOutputFilter;
use std::{collections::btree_map::BTreeMap, path::PathBuf, time::Instant};
#[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> {
let graph = Graph::resolve_sources(&project.paths, sources)?;
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 {
// 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
fn compile(self) -> Result<CompiledState<'a, T>> {
let PreprocessedState { sources, cache, sparse_output } = self;
let output = sources.compile(
let mut output = sources.compile(
&cache.project().solc_config.settings,
&cache.project().paths,
sparse_output,
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 })
}
}
@ -457,9 +465,10 @@ fn compile_sequential(
continue
}
let input = input
.settings(opt_settings.clone())
.settings(opt_settings.clone().with_base_path(&paths.root))
.normalize_evm_version(&version)
.with_remappings(paths.remappings.clone())
.with_base_path(&paths.root)
.sanitized(&version);
tracing::trace!(
@ -539,6 +548,7 @@ fn compile_parallel(
.settings(settings.clone())
.normalize_evm_version(&version)
.with_remappings(paths.remappings.clone())
.with_base_path(&paths.root)
.sanitized(&version);
jobs.push((solc.clone(), version.clone(), job, actually_dirty))
@ -740,7 +750,6 @@ mod tests {
.unwrap();
let project = Project::builder().paths(paths).build().unwrap();
let compiler = ProjectCompiler::new(&project).unwrap();
let out = compiler.compile().unwrap();
println!("{}", out);
let _out = compiler.compile().unwrap();
}
}

View File

@ -141,10 +141,12 @@ impl<T: ArtifactOutput> Project<T> {
///
/// This will set the `--allow-paths` to the paths configured for the `Project`, if any.
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.with_base_path(self.root())
}
/// Sets the maximum number of parallel `solc` processes to run simultaneously.

View File

@ -55,6 +55,14 @@ impl Remapping {
pub fn into_relative(self, root: impl AsRef<Path>) -> RelativeRemapping {
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)]

View File

@ -701,12 +701,21 @@ pub struct VersionedSources {
#[cfg(all(feature = "svm-solc"))]
impl VersionedSources {
/// Resolves or installs the corresponding `Solc` installation.
///
/// This will also configure following solc arguments:
/// - `allowed_paths`
/// - `base_path`
pub fn get(
self,
allowed_lib_paths: &crate::AllowedLibPaths,
base_path: impl AsRef<Path>,
) -> 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();
@ -743,8 +752,16 @@ impl VersionedSources {
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()?;
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));
}
Ok(sources_by_version)

View File

@ -1434,7 +1434,6 @@ fn can_detect_contract_def_source_files() {
.unwrap();
let compiled = tmp.compile().unwrap();
println!("{}", compiled);
assert!(!compiled.has_compiler_errors());
let mut sources = compiled.output().sources;
@ -1750,7 +1749,6 @@ contract D { }
.unwrap();
let compiled = project.compile().unwrap();
println!("{}", compiled);
assert!(!compiled.has_compiler_errors());
let cache = SolFilesCache::read(project.cache_path()).unwrap();