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 6 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
2 changes: 1 addition & 1 deletion ethers-solc/src/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ impl Source {

/// Returns all import statements of the file
pub fn parse_imports(&self) -> Vec<&str> {
utils::find_import_paths(self.as_ref())
utils::find_import_paths(self.as_ref()).map(|m| m.as_str()).collect()
}
}

Expand Down
4 changes: 2 additions & 2 deletions ethers-solc/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,10 @@ impl SolFilesCacheBuilder {
.map_err(|err| SolcError::solc(err.to_string()))?
.as_millis() as u64;
let imports =
utils::find_import_paths(source.as_ref()).into_iter().map(str::to_string).collect();
utils::find_import_paths(source.as_ref()).map(|m| m.as_str().to_owned()).collect();

let version_pragmas = utils::find_version_pragma(source.as_ref())
.map(|v| vec![v.to_string()])
.map(|v| vec![v.as_str().to_string()])
.unwrap_or_default();

let entry = CacheEntry {
Expand Down
2 changes: 1 addition & 1 deletion ethers-solc/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ impl Solc {
pub fn source_version_req(source: &Source) -> Result<VersionReq> {
let version =
utils::find_version_pragma(&source.content).ok_or(SolcError::PragmaNotFound)?;
Self::version_req(version)
Self::version_req(version.as_str())
}

/// Returns the corresponding SemVer version requirement for the solidity version
Expand Down
65 changes: 63 additions & 2 deletions ethers-solc/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ use crate::{
error::{Result, SolcError, SolcIoError},
hh::HardhatArtifact,
remappings::Remapping,
resolver::Graph,
utils, CompilerOutput, Source, Sources,
};
use ethers_core::{abi::Abi, types::Bytes};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
collections::BTreeMap,
convert::TryFrom,
fmt,
fmt::Formatter,
fmt::{self, Formatter},
fs, io,
path::{Path, PathBuf},
};
Expand Down Expand Up @@ -146,6 +146,67 @@ impl ProjectPathsConfig {
pub fn find_libs(root: impl AsRef<Path>) -> Vec<PathBuf> {
vec![utils::find_fave_or_alt_path(root, "lib", "node_modules")]
}

/// Flatten all file imports into a single string
pub fn flatten(&self, target: &Path) -> Result<String> {
tracing::trace!("flattening file");
let graph = Graph::resolve(self)?;

struct Flattener<'a> {
f: &'a dyn Fn(&Flattener, &Path) -> Result<String>,
}
rkrasiuk marked this conversation as resolved.
Show resolved Hide resolved
let flatten = Flattener {
f: &|flattener, target| {
let target_dir = target.parent().ok_or_else(|| {
SolcError::msg(format!(
"failed to get parent directory for \"{:?}\"",
target.display()
))
})?;
let target_index = graph.files().get(target).ok_or_else(|| {
SolcError::msg(format!("cannot resolve file at \"{:?}\"", target.display()))
})?;
let target_node = graph.node(*target_index);

let mut imports = target_node.imports().clone();
imports.sort_by(|a, b| b.loc().0.cmp(&a.loc().0));

let content = target_node.content().bytes().collect::<Vec<_>>();
let mut curr_import = imports.pop();
let mut extended = vec![];

let mut i = 0;
while i < content.len() {
rkrasiuk marked this conversation as resolved.
Show resolved Hide resolved
if let Some(ref import) = curr_import {
let (start, end) = import.loc();
if i == start {
let import_path =
utils::resolve_import_component(import.path(), target_dir, self)?;
rkrasiuk marked this conversation as resolved.
Show resolved Hide resolved
let import_content = (flattener.f)(flattener, &import_path)?;
let import_content =
utils::RE_SOL_PRAGMA_VERSION.replace_all(&import_content, "");
rkrasiuk marked this conversation as resolved.
Show resolved Hide resolved
extended.extend(import_content.trim().as_bytes());
i = end;
curr_import = imports.pop();
continue
}
}

extended.push(content[i]);
i += 1;
}

let result = String::from_utf8(extended).map_err(|err| {
SolcError::msg(format!("failed to convert extended bytes to string: {}", err))
})?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can definitely do to_string_lossy here?


Ok(result)
},
};

let flattened = (flatten.f)(&flatten, target)?;
Ok(flattened)
}
}

impl fmt::Display for ProjectPathsConfig {
Expand Down
12 changes: 10 additions & 2 deletions ethers-solc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@ use crate::{
};
use error::Result;
use std::{
borrow::Cow, collections::BTreeMap, convert::TryInto, fmt, fs, marker::PhantomData,
path::PathBuf,
borrow::Cow,
collections::BTreeMap,
convert::TryInto,
fmt, fs,
marker::PhantomData,
path::{Path, PathBuf},
};

/// Utilities for creating, mocking and testing of (temporary) projects
Expand Down Expand Up @@ -481,6 +485,10 @@ impl<Artifacts: ArtifactOutput> Project<Artifacts> {
}
Ok(())
}

rkrasiuk marked this conversation as resolved.
Show resolved Hide resolved
pub fn flatten(&self, target: &Path) -> Result<String> {
self.paths.flatten(target)
}
}

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: &Path) -> Result<String> {
self.project().flatten(target)
}

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

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

use rayon::prelude::*;
use regex::Match;
use semver::VersionReq;
use solang_parser::pt::{Import, SourceUnitPart};
use solang_parser::pt::{Import, Loc, SourceUnitPart};

use crate::{error::Result, utils, ProjectPathsConfig, Solc, Source, Sources};

Expand Down Expand Up @@ -139,33 +140,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.path(), node_dir, paths) {
Ok(result) => {
add_node(&mut unresolved, &mut index, &mut resolved_imports, result)?;
rkrasiuk marked this conversation as resolved.
Show resolved Hide resolved
}
}
Err(err) => tracing::trace!("failed to resolve import component \"{:?}\"", err),
};
}
nodes.push(node);
edges.push(resolved_imports);
Expand Down Expand Up @@ -417,12 +397,56 @@ pub struct Node {
data: SolData,
}

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

pub fn imports(&self) -> &Vec<SolImport> {
&self.data.imports
}
}

#[derive(Debug, Clone)]
#[allow(unused)]
struct SolData {
version: Option<String>,
version_req: Option<VersionReq>,
imports: Vec<PathBuf>,
imports: Vec<SolImport>,
}

#[derive(Debug, Clone)]
pub struct SolImport {
path: PathBuf,
loc: Location,
}

#[derive(Debug, Clone)]
pub struct Location {
pub start: usize,
pub end: usize,
}

impl SolImport {
pub fn path(&self) -> &PathBuf {
&self.path
}

pub fn loc(&self) -> (usize, usize) {
(self.loc.start, self.loc.end)
}
}

impl From<Match<'_>> for Location {
fn from(src: Match) -> Self {
Location { start: src.start(), end: src.end() }
}
}

impl From<Loc> for Location {
fn from(src: Loc) -> Self {
Location { start: src.1, end: src.2 }
}
}

fn read_node(file: impl AsRef<Path>) -> Result<Node> {
Expand All @@ -438,7 +462,7 @@ fn read_node(file: impl AsRef<Path>) -> Result<Node> {
/// parsing fails, we'll fall back to extract that info via regex
fn parse_data(content: &str) -> SolData {
let mut version = None;
let mut imports = Vec::new();
let mut imports = Vec::<SolImport>::new();
match solang_parser::parse(content, 0) {
Ok(units) => {
for unit in units.0 {
Expand All @@ -450,12 +474,15 @@ fn parse_data(content: &str) -> SolData {
}
}
SourceUnitPart::ImportDirective(_, import) => {
let import = match import {
Import::Plain(s, _) => s,
Import::GlobalSymbol(s, _, _) => s,
Import::Rename(s, _, _) => s,
let (import, loc) = match import {
Import::Plain(s, l) => (s, l),
Import::GlobalSymbol(s, _, l) => (s, l),
Import::Rename(s, _, l) => (s, l),
};
imports.push(PathBuf::from(import.string));
imports.push(SolImport {
path: PathBuf::from(import.string),
loc: loc.into(),
});
}
_ => {}
}
Expand All @@ -466,21 +493,19 @@ fn parse_data(content: &str) -> SolData {
"failed to parse solidity ast: \"{:?}\". Falling back to regex to extract data",
err
);
version = utils::find_version_pragma(content).map(str::to_string);
version = utils::find_version_pragma(content).map(|m| m.as_str().to_owned());
imports = utils::find_import_paths(content)
.into_iter()
.map(|p| Path::new(p).to_path_buf())
.collect()
.map(|m| SolImport { path: PathBuf::from(m.as_str()), loc: m.into() })
.collect();
}
};
let version_req = if let Some(ref v) = version { Solc::version_req(v).ok() } else { None };
let version_req = version.as_ref().and_then(|v| Solc::version_req(v).ok());
SolData { version_req, version, imports }
}

#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;

#[test]
fn can_resolve_hardhat_dependency_graph() {
Expand Down Expand Up @@ -522,11 +547,8 @@ mod tests {
let dapp_test = graph.node(1);
assert_eq!(dapp_test.path, paths.sources.join("Dapp.t.sol"));
assert_eq!(
dapp_test.data.imports,
vec![
Path::new("ds-test/test.sol").to_path_buf(),
Path::new("./Dapp.sol").to_path_buf()
]
dapp_test.data.imports.iter().map(|i| i.path()).collect::<Vec<&PathBuf>>(),
vec![&PathBuf::from("ds-test/test.sol"), &PathBuf::from("./Dapp.sol")]
);
assert_eq!(graph.imported_nodes(1).to_vec(), vec![2, 0]);
}
Expand Down
Loading