Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@

### 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
[#1491](https://github.com/gakonst/ethers-rs/pull/1491)
- Make `ethers-solc` optional dependency of `ethers`, needs `ethers-solc` feature to activate
Expand Down
31 changes: 30 additions & 1 deletion ethers-solc/src/artifact_output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ pub struct 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
///
/// Note: This identifier is not necessarily unique. If two contracts have the same name, they
Expand Down Expand Up @@ -178,6 +194,18 @@ impl<T: Serialize> 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> {
self.0
}
Expand Down Expand Up @@ -250,7 +278,8 @@ impl<T> Artifacts<T> {
name,
source: source.clone(),
version: artifact.version,
},
}
.with_slashed_paths(),
artifact.artifact,
)
})
Expand Down
11 changes: 11 additions & 0 deletions ethers-solc/src/compile/output/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ use std::{collections::BTreeMap, ops::Deref, path::Path};
pub struct VersionedContracts(pub FileToContractsMap<Vec<VersionedContract>>);

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 {
self.0.is_empty()
}
Expand Down
19 changes: 19 additions & 0 deletions ethers-solc/src/compile/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ pub struct ProjectCompileOutput<T: ArtifactOutput = ConfigurableArtifacts> {
}

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>`
///
/// This returns a chained iterator of both cached and recompiled contract artifacts
Expand Down Expand Up @@ -386,6 +399,12 @@ pub struct 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
pub fn has_error(&self) -> bool {
self.errors.iter().any(|err| err.severity.is_error())
Expand Down
12 changes: 12 additions & 0 deletions ethers-solc/src/compile/output/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ use std::{collections::BTreeMap, path::Path};
pub struct VersionedSourceFiles(pub BTreeMap<String, Vec<VersionedSourceFile>>);

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 {
self.0.is_empty()
}
Expand Down
43 changes: 41 additions & 2 deletions ethers-solc/src/compile/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,28 @@ impl<'a, T: ArtifactOutput> ProjectCompiler<'a, T> {
/// let output = project.compile().unwrap();
/// ```
pub fn compile(self) -> Result<ProjectCompileOutput<T>> {
let slash_paths = self.project.slash_paths;

// 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
/// - sets proper source unit names
/// - check cache
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)?;
// retain and compile only dirty sources and all their imports
Expand Down Expand Up @@ -344,6 +357,32 @@ enum 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`]
fn filtered<T: ArtifactOutput>(self, cache: &mut ArtifactsCache<T>) -> FilteredCompilerSources {
fn filtered_sources<T: ArtifactOutput>(
Expand Down
23 changes: 23 additions & 0 deletions ethers-solc/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
};

use crate::artifacts::output_selection::ContractOutputSelection;

use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeSet, HashSet},
Expand Down Expand Up @@ -145,6 +146,28 @@ impl ProjectPathsConfig {
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.
///
/// The `cwd` path is the parent dir of the file that includes the `import`
Expand Down
27 changes: 26 additions & 1 deletion ethers-solc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ pub struct Project<T: ArtifactOutput = ConfigurableArtifacts> {
solc_jobs: usize,
/// Offline mode, if set, network access (download solc) is disallowed
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 {
Expand Down Expand Up @@ -531,6 +535,8 @@ pub struct ProjectBuilder<T: ArtifactOutput = ConfigurableArtifacts> {
auto_detect: bool,
/// Use offline mode
offline: bool,
/// Whether to slash paths of the `ProjectCompilerOutput`
slash_paths: bool,
/// handles all artifacts related tasks
artifacts: T,
/// Which error codes to ignore
Expand All @@ -552,6 +558,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
no_artifacts: false,
auto_detect: true,
offline: false,
slash_paths: true,
artifacts,
ignored_error_codes: Vec::new(),
allowed_paths: vec![],
Expand Down Expand Up @@ -626,6 +633,15 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
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
#[must_use]
pub fn no_artifacts(self) -> Self {
Expand Down Expand Up @@ -684,6 +700,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
solc_jobs,
offline,
build_info,
slash_paths,
..
} = self;
ProjectBuilder {
Expand All @@ -694,6 +711,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
no_artifacts,
auto_detect,
offline,
slash_paths,
artifacts,
ignored_error_codes,
allowed_paths,
Expand Down Expand Up @@ -736,9 +754,15 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
solc_jobs,
offline,
build_info,
slash_paths,
} = 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_config = solc_config.unwrap_or_else(|| SolcConfig::builder().build());
Expand All @@ -761,6 +785,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
allowed_lib_paths: allowed_paths.into(),
solc_jobs: solc_jobs.unwrap_or_else(::num_cpus::get),
offline,
slash_paths,
})
}
}
Expand Down
12 changes: 11 additions & 1 deletion ethers-solc/src/remappings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::utils;

use serde::{Deserialize, Serialize};
use std::{
collections::{hash_map::Entry, HashMap},
Expand Down Expand Up @@ -224,6 +225,15 @@ impl Remapping {
.map(|(name, path)| Remapping { name, path: format!("{}/", path.display()) })
.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
Expand Down Expand Up @@ -263,7 +273,7 @@ impl RelativeRemapping {
impl fmt::Display for RelativeRemapping {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = {
#[cfg(target_os = "windows")]
#[cfg(windows)]
{
// ensure we have `/` slashes on windows
use path_slash::PathExt;
Expand Down
16 changes: 15 additions & 1 deletion ethers-solc/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Utility functions

use cfg_if::cfg_if;
use std::{
collections::HashSet,
ops::Range,
Expand Down Expand Up @@ -157,9 +158,22 @@ pub fn is_local_source_name(libs: &[impl AsRef<Path>], source: impl AsRef<Path>)
}

/// 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> {
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.
Expand Down