fix(solc): convert source paths on windows (#1540)

* fix(solc): convert source paths on windows

* refactor: slash all paths

* fix: use correct import

* feat: slash compiler output

* add util function

* slash by default

* slash artifact id

* typo

* updat cfg

* unify cfg

* update changelog
This commit is contained in:
Matthias Seitz 2022-08-01 18:47:41 +02:00 committed by GitHub
parent e817073f8e
commit e62c84d650
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 190 additions and 6 deletions

View File

@ -115,6 +115,8 @@
### Unreleased ### Unreleased
- On windows all paths in the `ProjectCompilerOutput` are now slashed by default
[#1540](https://github.com/gakonst/ethers-rs/pull/1540)
- `ArtifactOutput::write_extras` now takes the `Artifacts` directly - `ArtifactOutput::write_extras` now takes the `Artifacts` directly
[#1491](https://github.com/gakonst/ethers-rs/pull/1491) [#1491](https://github.com/gakonst/ethers-rs/pull/1491)
- Make `ethers-solc` optional dependency of `ethers`, needs `ethers-solc` feature to activate - Make `ethers-solc` optional dependency of `ethers`, needs `ethers-solc` feature to activate

View File

@ -42,6 +42,22 @@ pub struct ArtifactId {
} }
impl ArtifactId { impl ArtifactId {
/// Converts any `\\` separators in the `path` to `/`
pub fn slash_paths(&mut self) {
#[cfg(windows)]
{
use path_slash::PathBufExt;
self.path = self.path.to_slash_lossy().as_ref().into();
self.source = self.source.to_slash_lossy().as_ref().into();
}
}
/// Convenience function fo [`Self::slash_paths()`]
pub fn with_slashed_paths(mut self) -> Self {
self.slash_paths();
self
}
/// Returns a <filename>:<name> slug that identifies an artifact /// Returns a <filename>:<name> slug that identifies an artifact
/// ///
/// Note: This identifier is not necessarily unique. If two contracts have the same name, they /// Note: This identifier is not necessarily unique. If two contracts have the same name, they
@ -178,6 +194,18 @@ impl<T: Serialize> Artifacts<T> {
} }
impl<T> Artifacts<T> { impl<T> Artifacts<T> {
/// Converts all `\\` separators in _all_ paths to `/`
pub fn slash_paths(&mut self) {
#[cfg(windows)]
{
use path_slash::PathExt;
self.0 = std::mem::take(&mut self.0)
.into_iter()
.map(|(path, files)| (Path::new(&path).to_slash_lossy().to_string(), files))
.collect()
}
}
pub fn into_inner(self) -> ArtifactsMap<T> { pub fn into_inner(self) -> ArtifactsMap<T> {
self.0 self.0
} }
@ -250,7 +278,8 @@ impl<T> Artifacts<T> {
name, name,
source: source.clone(), source: source.clone(),
version: artifact.version, version: artifact.version,
}, }
.with_slashed_paths(),
artifact.artifact, artifact.artifact,
) )
}) })

View File

@ -15,6 +15,17 @@ use std::{collections::BTreeMap, ops::Deref, path::Path};
pub struct VersionedContracts(pub FileToContractsMap<Vec<VersionedContract>>); pub struct VersionedContracts(pub FileToContractsMap<Vec<VersionedContract>>);
impl VersionedContracts { impl VersionedContracts {
/// Converts all `\\` separators in _all_ paths to `/`
pub fn slash_paths(&mut self) {
#[cfg(windows)]
{
use path_slash::PathExt;
self.0 = std::mem::take(&mut self.0)
.into_iter()
.map(|(path, files)| (Path::new(&path).to_slash_lossy().to_string(), files))
.collect()
}
}
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.is_empty() self.0.is_empty()
} }

View File

@ -34,6 +34,19 @@ pub struct ProjectCompileOutput<T: ArtifactOutput = ConfigurableArtifacts> {
} }
impl<T: ArtifactOutput> ProjectCompileOutput<T> { impl<T: ArtifactOutput> ProjectCompileOutput<T> {
/// Converts all `\\` separators in _all_ paths to `/`
pub fn slash_paths(&mut self) {
self.compiler_output.slash_paths();
self.compiled_artifacts.slash_paths();
self.cached_artifacts.slash_paths();
}
/// Convenience function fo [`Self::slash_paths()`]
pub fn with_slashed_paths(mut self) -> Self {
self.slash_paths();
self
}
/// All artifacts together with their contract file name and name `<file name>:<name>` /// All artifacts together with their contract file name and name `<file name>:<name>`
/// ///
/// This returns a chained iterator of both cached and recompiled contract artifacts /// This returns a chained iterator of both cached and recompiled contract artifacts
@ -386,6 +399,12 @@ pub struct AggregatedCompilerOutput {
} }
impl AggregatedCompilerOutput { impl AggregatedCompilerOutput {
/// Converts all `\\` separators in _all_ paths to `/`
pub fn slash_paths(&mut self) {
self.sources.slash_paths();
self.contracts.slash_paths();
}
/// Whether the output contains a compiler error /// Whether the output contains a compiler error
pub fn has_error(&self) -> bool { pub fn has_error(&self) -> bool {
self.errors.iter().any(|err| err.severity.is_error()) self.errors.iter().any(|err| err.severity.is_error())

View File

@ -9,6 +9,18 @@ use std::{collections::BTreeMap, path::Path};
pub struct VersionedSourceFiles(pub BTreeMap<String, Vec<VersionedSourceFile>>); pub struct VersionedSourceFiles(pub BTreeMap<String, Vec<VersionedSourceFile>>);
impl VersionedSourceFiles { impl VersionedSourceFiles {
/// Converts all `\\` separators in _all_ paths to `/`
pub fn slash_paths(&mut self) {
#[cfg(windows)]
{
use path_slash::PathExt;
self.0 = std::mem::take(&mut self.0)
.into_iter()
.map(|(path, files)| (Path::new(&path).to_slash_lossy().to_string(), files))
.collect()
}
}
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.is_empty() self.0.is_empty()
} }

View File

@ -207,15 +207,28 @@ impl<'a, T: ArtifactOutput> ProjectCompiler<'a, T> {
/// let output = project.compile().unwrap(); /// let output = project.compile().unwrap();
/// ``` /// ```
pub fn compile(self) -> Result<ProjectCompileOutput<T>> { pub fn compile(self) -> Result<ProjectCompileOutput<T>> {
let slash_paths = self.project.slash_paths;
// drive the compiler statemachine to completion // drive the compiler statemachine to completion
self.preprocess()?.compile()?.write_artifacts()?.write_cache() let mut output = self.preprocess()?.compile()?.write_artifacts()?.write_cache()?;
if slash_paths {
// ensures we always use `/` paths
output.slash_paths();
}
Ok(output)
} }
/// Does basic preprocessing /// Does basic preprocessing
/// - sets proper source unit names /// - sets proper source unit names
/// - check cache /// - check cache
fn preprocess(self) -> Result<PreprocessedState<'a, T>> { fn preprocess(self) -> Result<PreprocessedState<'a, T>> {
let Self { edges, project, sources, sparse_output } = self; let Self { edges, project, mut sources, sparse_output } = self;
// convert paths on windows to ensure consistency with the `CompilerOutput` `solc` emits,
// which is unix style `/`
sources.slash_paths();
let mut cache = ArtifactsCache::new(project, edges)?; let mut cache = ArtifactsCache::new(project, edges)?;
// retain and compile only dirty sources and all their imports // retain and compile only dirty sources and all their imports
@ -344,6 +357,32 @@ enum CompilerSources {
} }
impl CompilerSources { impl CompilerSources {
/// Converts all `\\` separators to `/`
///
/// This effectively ensures that `solc` can find imported files like `/src/Cheats.sol` in the
/// VFS (the `CompilerInput` as json) under `src/Cheats.sol`.
fn slash_paths(&mut self) {
#[cfg(windows)]
{
use path_slash::PathBufExt;
fn slash_versioned_sources(v: &mut VersionedSources) {
for (_, (_, sources)) in v {
*sources = std::mem::take(sources)
.into_iter()
.map(|(path, source)| {
(PathBuf::from(path.to_slash_lossy().as_ref()), source)
})
.collect()
}
}
match self {
CompilerSources::Sequential(v) => slash_versioned_sources(v),
CompilerSources::Parallel(v, _) => slash_versioned_sources(v),
};
}
}
/// Filters out all sources that don't need to be compiled, see [`ArtifactsCache::filter`] /// Filters out all sources that don't need to be compiled, see [`ArtifactsCache::filter`]
fn filtered<T: ArtifactOutput>(self, cache: &mut ArtifactsCache<T>) -> FilteredCompilerSources { fn filtered<T: ArtifactOutput>(self, cache: &mut ArtifactsCache<T>) -> FilteredCompilerSources {
fn filtered_sources<T: ArtifactOutput>( fn filtered_sources<T: ArtifactOutput>(

View File

@ -8,6 +8,7 @@ use crate::{
}; };
use crate::artifacts::output_selection::ContractOutputSelection; use crate::artifacts::output_selection::ContractOutputSelection;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
collections::{BTreeSet, HashSet}, collections::{BTreeSet, HashSet},
@ -145,6 +146,28 @@ impl ProjectPathsConfig {
Ok(Source::read_all_files(self.input_files())?) Ok(Source::read_all_files(self.input_files())?)
} }
/// Converts all `\\` separators in _all_ paths to `/`
pub fn slash_paths(&mut self) {
#[cfg(windows)]
{
use path_slash::PathBufExt;
let slashed = |p: &mut PathBuf| {
*p = p.to_slash_lossy().as_ref().into();
};
slashed(&mut self.root);
slashed(&mut self.cache);
slashed(&mut self.artifacts);
slashed(&mut self.build_infos);
slashed(&mut self.sources);
slashed(&mut self.tests);
slashed(&mut self.scripts);
self.libraries.iter_mut().for_each(slashed);
self.remappings.iter_mut().for_each(Remapping::slash_path);
}
}
/// 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`

View File

@ -78,6 +78,10 @@ pub struct Project<T: ArtifactOutput = ConfigurableArtifacts> {
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
pub offline: bool, pub offline: bool,
/// Windows only config value to ensure the all paths use `/` instead of `\\`, same as `solc`
///
/// This is a noop on other platforms
pub slash_paths: bool,
} }
impl Project { impl Project {
@ -531,6 +535,8 @@ pub struct ProjectBuilder<T: ArtifactOutput = ConfigurableArtifacts> {
auto_detect: bool, auto_detect: bool,
/// Use offline mode /// Use offline mode
offline: bool, offline: bool,
/// Whether to slash paths of the `ProjectCompilerOutput`
slash_paths: bool,
/// handles all artifacts related tasks /// handles all artifacts related tasks
artifacts: T, artifacts: T,
/// Which error codes to ignore /// Which error codes to ignore
@ -552,6 +558,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
no_artifacts: false, no_artifacts: false,
auto_detect: true, auto_detect: true,
offline: false, offline: false,
slash_paths: true,
artifacts, artifacts,
ignored_error_codes: Vec::new(), ignored_error_codes: Vec::new(),
allowed_paths: vec![], allowed_paths: vec![],
@ -626,6 +633,15 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
self self
} }
/// Sets whether to slash all paths on windows
///
/// If set to `true` all `\\` separators are replaced with `/`, same as solc
#[must_use]
pub fn set_slashed_paths(mut self, slashed_paths: bool) -> Self {
self.slash_paths = slashed_paths;
self
}
/// Disables writing artifacts to disk /// Disables writing artifacts to disk
#[must_use] #[must_use]
pub fn no_artifacts(self) -> Self { pub fn no_artifacts(self) -> Self {
@ -684,6 +700,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
solc_jobs, solc_jobs,
offline, offline,
build_info, build_info,
slash_paths,
.. ..
} = self; } = self;
ProjectBuilder { ProjectBuilder {
@ -694,6 +711,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
no_artifacts, no_artifacts,
auto_detect, auto_detect,
offline, offline,
slash_paths,
artifacts, artifacts,
ignored_error_codes, ignored_error_codes,
allowed_paths, allowed_paths,
@ -736,9 +754,15 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
solc_jobs, solc_jobs,
offline, offline,
build_info, build_info,
slash_paths,
} = self; } = self;
let paths = paths.map(Ok).unwrap_or_else(ProjectPathsConfig::current_hardhat)?; let mut paths = paths.map(Ok).unwrap_or_else(ProjectPathsConfig::current_hardhat)?;
if slash_paths {
// ensures we always use `/` paths
paths.slash_paths();
}
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());
@ -761,6 +785,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
allowed_lib_paths: allowed_paths.into(), allowed_lib_paths: allowed_paths.into(),
solc_jobs: solc_jobs.unwrap_or_else(::num_cpus::get), solc_jobs: solc_jobs.unwrap_or_else(::num_cpus::get),
offline, offline,
slash_paths,
}) })
} }
} }

View File

@ -1,4 +1,5 @@
use crate::utils; use crate::utils;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
collections::{hash_map::Entry, HashMap}, collections::{hash_map::Entry, HashMap},
@ -224,6 +225,15 @@ impl Remapping {
.map(|(name, path)| Remapping { name, path: format!("{}/", path.display()) }) .map(|(name, path)| Remapping { name, path: format!("{}/", path.display()) })
.collect() .collect()
} }
/// Converts any `\\` separators in the `path` to `/`
pub fn slash_path(&mut self) {
#[cfg(windows)]
{
use path_slash::PathExt;
self.path = Path::new(&self.path).to_slash_lossy().to_string();
}
}
} }
/// A relative [`Remapping`] that's aware of the current location /// A relative [`Remapping`] that's aware of the current location
@ -263,7 +273,7 @@ impl RelativeRemapping {
impl fmt::Display for RelativeRemapping { impl fmt::Display for RelativeRemapping {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = { let mut s = {
#[cfg(target_os = "windows")] #[cfg(windows)]
{ {
// ensure we have `/` slashes on windows // ensure we have `/` slashes on windows
use path_slash::PathExt; use path_slash::PathExt;

View File

@ -1,5 +1,6 @@
//! Utility functions //! Utility functions
use cfg_if::cfg_if;
use std::{ use std::{
collections::HashSet, collections::HashSet,
ops::Range, ops::Range,
@ -157,9 +158,22 @@ pub fn is_local_source_name(libs: &[impl AsRef<Path>], source: impl AsRef<Path>)
} }
/// Canonicalize the path, platform-agnostic /// Canonicalize the path, platform-agnostic
///
/// On windows this will ensure the path only consists of `/` separators
pub fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf, SolcIoError> { pub fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf, SolcIoError> {
let path = path.as_ref(); let path = path.as_ref();
dunce::canonicalize(&path).map_err(|err| SolcIoError::new(err, path)) cfg_if! {
if #[cfg(windows)] {
let res = dunce::canonicalize(path).map(|p| {
use path_slash::PathBufExt;
PathBuf::from(p.to_slash_lossy().as_ref())
});
} else {
let res = dunce::canonicalize(path);
}
};
res.map_err(|err| SolcIoError::new(err, path))
} }
/// Returns the same path config but with canonicalized paths. /// Returns the same path config but with canonicalized paths.