Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

feat(solc): flatten #774

Merged
merged 24 commits into from
Jan 17, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

### Unreleased

- Add ability to flatten file imports
[#774](https://github.com/gakonst/ethers-rs/pull/774)
- Add dependency graph and resolve all imported libraryfiles
[#750](https://github.com/gakonst/ethers-rs/pull/750)
- `Remapping::find_many` does not return a `Result` anymore
Expand Down
43 changes: 43 additions & 0 deletions ethers-solc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,49 @@ impl<Artifacts: ArtifactOutput> Project<Artifacts> {
}
Ok(())
}

rkrasiuk marked this conversation as resolved.
Show resolved Hide resolved
/// Flatten all file imports into a single string
pub fn flatten(&self, target: &PathBuf) -> Result<String> {
rkrasiuk marked this conversation as resolved.
Show resolved Hide resolved
tracing::trace!("flattening file");
let graph = Graph::resolve(&self.paths)?;

struct Flattener<'a> {
f: &'a dyn Fn(&Flattener, &PathBuf) -> Result<Cow<'a, str>>,
}
let flatten = Flattener {
f: &|flattener, target| {
let target_index = graph.files().get(target).ok_or(SolcError::msg(format!(
"cannot resolve file at \"{:?}\"",
target.display()
)))?;
let target_node = graph.node(*target_index);
let node_dir = target.parent().ok_or(SolcError::msg(format!(
"failed to get parent directory for \"{:?}\"",
target.display()
)))?;
let flattened = utils::RE_SOL_IMPORT.replace_all(
target_node.content(),
|cap: &regex::Captures<'_>| {
let import = cap.name("p1").or(cap.name("p2")).or(cap.name("p3")).unwrap(); // one of the name patterns must match

let import_path = utils::resolve_import_component(
&PathBuf::from(import.as_str()),
node_dir,
&self.paths,
)
.expect("failed to resolve import component {}");

let result = (flattener.f)(flattener, &import_path)
.expect("failed to flatten the import file");
utils::RE_SOL_PRAGMA_VERSION.replace_all(&result, "").trim().to_owned()
},
);
Ok(flattened)
},
};

Ok((flatten.f)(&flatten, target)?.to_string())
}
}

enum PreprocessedJob<T: ArtifactOutput> {
Expand Down
4 changes: 4 additions & 0 deletions ethers-solc/src/project_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ impl<T: ArtifactOutput> TempProject<T> {
self.project().compile()
}

pub fn flatten(&self, target: &PathBuf) -> Result<String> {
self.project().flatten(target)
}

pub fn project_mut(&mut self) -> &mut Project<T> {
&mut self.inner
}
Expand Down
39 changes: 12 additions & 27 deletions ethers-solc/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

use std::{
collections::{HashMap, VecDeque},
path::{Component, Path, PathBuf},
path::{Path, PathBuf},
};

use rayon::prelude::*;
Expand Down Expand Up @@ -139,33 +139,12 @@ impl Graph {
};

for import in node.data.imports.iter() {
let component = match import.components().next() {
Some(inner) => inner,
None => continue,
};
if component == Component::CurDir || component == Component::ParentDir {
// if the import is relative we assume it's already part of the processed input
// file set
match utils::canonicalize(node_dir.join(import)) {
Ok(target) => {
// the file at least exists,
add_node(&mut unresolved, &mut index, &mut resolved_imports, target)?;
}
Err(err) => {
tracing::trace!("failed to resolve relative import \"{:?}\"", err);
}
}
} else {
// resolve library file
if let Some(lib) = paths.resolve_library_import(import.as_ref()) {
add_node(&mut unresolved, &mut index, &mut resolved_imports, lib)?;
} else {
tracing::trace!(
"failed to resolve library import \"{:?}\"",
import.display()
);
match utils::resolve_import_component(import, node_dir, paths) {
Ok(result) => {
add_node(&mut unresolved, &mut index, &mut resolved_imports, result)?;
}
}
Err(err) => tracing::trace!("failed to resolve import component \"{:?}\"", err),
};
}
nodes.push(node);
edges.push(resolved_imports);
Expand Down Expand Up @@ -409,6 +388,12 @@ pub struct Node {
data: SolData,
}

impl Node {
pub fn content(&self) -> &str {
&self.source.content
}
}

#[derive(Debug, Clone)]
#[allow(unused)]
struct SolData {
Expand Down
36 changes: 35 additions & 1 deletion ethers-solc/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

use std::path::{Component, Path, PathBuf};

use crate::{error::SolcError, SolcIoError};
use crate::{
error::{self, SolcError},
ProjectPathsConfig, SolcIoError,
};
use once_cell::sync::Lazy;
use regex::Regex;
use semver::Version;
Expand Down Expand Up @@ -79,6 +82,37 @@ pub fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf, SolcIoError> {
dunce::canonicalize(&path).map_err(|err| SolcIoError::new(err, path))
}

/// Try to resolve import to a local file or library path
pub fn resolve_import_component(
import: &PathBuf,
node_dir: &Path,
paths: &ProjectPathsConfig,
) -> error::Result<PathBuf> {
let component = match import.components().next() {
Some(inner) => inner,
None => {
return Err(SolcError::msg(format!(
"failed to resolve import at \"{:?}\"",
import.display()
)))
}
};

if component == Component::CurDir || component == Component::ParentDir {
// if the import is relative we assume it's already part of the processed input file set
canonicalize(node_dir.join(import)).map_err(|err| err.into())
} else {
// resolve library file
match paths.resolve_library_import(import.as_ref()) {
Some(lib) => Ok(lib),
None => Err(SolcError::msg(format!(
"failed to resolve library import \"{:?}\"",
import.display()
))),
}
}
}

/// Returns the path to the library if the source path is in fact determined to be a library path,
/// and it exists.
/// Note: this does not handle relative imports or remappings.
Expand Down
25 changes: 25 additions & 0 deletions ethers-solc/tests/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,28 @@ fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()>
}
Ok(())
}

#[test]
fn can_flatten_file() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/test-contract-libs");
let target = root.join("src").join("Foo.sol");
let paths = ProjectPathsConfig::builder()
.sources(root.join("src"))
.lib(root.join("lib1"))
.lib(root.join("lib2"));
let project = TempProject::<MinimalCombinedArtifacts>::new(paths).unwrap();

assert!(project.flatten(&target).is_ok());
}

#[test]
fn can_flatten_file_with_external_lib() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/hardhat-sample");
let target = root.join("contracts").join("Greeter.sol");
let paths = ProjectPathsConfig::builder()
.sources(root.join("contracts"))
.lib(root.join("node_modules"));
let project = TempProject::<MinimalCombinedArtifacts>::new(paths).unwrap();

assert!(project.flatten(&target).is_ok());
}