Skip to content

Commit

Permalink
feat(forge): flatten (#506)
Browse files Browse the repository at this point in the history
* add flatten support

* remove linked libs opt

* address PR comments

* chore: bump ethers to fix flatten bug

gakonst/ethers-rs#813

Co-authored-by: Georgios Konstantopoulos <[email protected]>
  • Loading branch information
0129general and gakonst committed Jan 19, 2022
1 parent b962ade commit 11968ea
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 65 deletions.
22 changes: 11 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 8 additions & 51 deletions cli/src/cmd/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
use ethers::solc::{
artifacts::{Optimizer, Settings},
remappings::Remapping,
MinimalCombinedArtifacts, Project, ProjectCompileOutput, ProjectPathsConfig, SolcConfig,
};
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
str::FromStr,
};

use crate::{cmd::Cmd, opts::forge::CompilerArgs, utils};
Expand Down Expand Up @@ -118,26 +116,6 @@ impl BuildArgs {
}
}

/// Determines the libraries
fn libs(&self, root: impl AsRef<Path>) -> Vec<PathBuf> {
let root = root.as_ref();
if self.lib_paths.is_empty() {
if self.hardhat {
vec![root.join("node_modules")]
} else {
// no libs directories provided
ProjectPathsConfig::find_libs(&root)
}
} else {
let mut libs = self.lib_paths.clone();
if self.hardhat && !self.lib_paths.iter().any(|lib| lib.ends_with("node_modules")) {
// if --hardhat was set, ensure it is present in the lib set
libs.push(root.join("node_modules"));
}
libs
}
}

/// Converts all build arguments to the corresponding project config
///
/// Defaults to DAppTools-style repo layout, but can be customized.
Expand All @@ -156,35 +134,14 @@ impl BuildArgs {

// 4. Set where the libraries are going to be read from
// default to the lib path being the `lib/` dir
let lib_paths = self.libs(&root);

// get all the remappings corresponding to the lib paths
let mut remappings: Vec<_> = lib_paths.iter().flat_map(Remapping::find_many).collect();

// extend them with the once manually provided in the opts
remappings.extend_from_slice(&self.remappings);

// extend them with the one via the env vars
if let Some(ref env) = self.remappings_env {
remappings.extend(remappings_from_newline(env))
}

// extend them with the one via the requirements.txt
if let Ok(ref remap) = std::fs::read_to_string(root.join("remappings.txt")) {
remappings.extend(remappings_from_newline(remap))
}

// helper function for parsing newline-separated remappings
fn remappings_from_newline(remappings: &str) -> impl Iterator<Item = Remapping> + '_ {
remappings.split('\n').filter(|x| !x.is_empty()).map(|x| {
Remapping::from_str(x)
.unwrap_or_else(|_| panic!("could not parse remapping: {}", x))
})
}

// remove any potential duplicates
remappings.sort_unstable();
remappings.dedup();
let lib_paths = utils::find_libs(&root, &self.lib_paths, self.hardhat);

let remappings = utils::find_remappings(
&lib_paths,
&self.remappings,
&root.join("remappings.txt"),
&self.remappings_env,
);

// build the path
let mut paths_builder =
Expand Down
106 changes: 106 additions & 0 deletions cli/src/cmd/flatten.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use std::path::PathBuf;

use ethers::solc::{remappings::Remapping, ProjectPathsConfig};

use crate::{cmd::Cmd, utils};
use clap::{Parser, ValueHint};

#[derive(Debug, Clone, Parser)]
pub struct FlattenArgs {
#[clap(help = "the path to the contract to flatten", value_hint = ValueHint::FilePath)]
pub target_path: PathBuf,

#[clap(long, short, help = "output path for the flattened contract", value_hint = ValueHint::FilePath)]
pub output: Option<PathBuf>,

#[clap(
help = "the project's root path. By default, this is the root directory of the current Git repository or the current working directory if it is not part of a Git repository",
long,
value_hint = ValueHint::DirPath
)]
pub root: Option<PathBuf>,

#[clap(
env = "DAPP_SRC",
help = "the directory relative to the root under which the smart contracts are",
long,
short,
value_hint = ValueHint::DirPath
)]
pub contracts: Option<PathBuf>,

#[clap(help = "the remappings", long, short)]
pub remappings: Vec<Remapping>,
#[clap(long = "remappings-env", env = "DAPP_REMAPPINGS")]
pub remappings_env: Option<String>,

#[clap(
help = "the paths where your libraries are installed",
long,
value_hint = ValueHint::DirPath
)]
pub lib_paths: Vec<PathBuf>,

#[clap(
help = "uses hardhat style project layout. This a convenience flag and is the same as `--contracts contracts --lib-paths node_modules`",
long,
conflicts_with = "contracts",
alias = "hh"
)]
pub hardhat: bool,
}

impl Cmd for FlattenArgs {
type Output = ();
fn run(self) -> eyre::Result<Self::Output> {
let root = self.root.clone().unwrap_or_else(|| {
utils::find_git_root_path().unwrap_or_else(|_| std::env::current_dir().unwrap())
});
let root = dunce::canonicalize(&root)?;

let contracts = match self.contracts {
Some(ref contracts) => root.join(contracts),
None => {
if self.hardhat {
root.join("contracts")
} else {
// no contract source directory was provided, determine the source directory
ProjectPathsConfig::find_source_dir(&root)
}
}
};

let lib_paths = utils::find_libs(&root, &self.lib_paths, self.hardhat);

let remappings = utils::find_remappings(
&lib_paths,
&self.remappings,
&root.join("remappings.txt"),
&self.remappings_env,
);

// build the path
let mut paths_builder = ProjectPathsConfig::builder().root(&root).sources(contracts);

if !remappings.is_empty() {
paths_builder = paths_builder.remappings(remappings);
}

let paths = paths_builder.build()?;
let target_path = dunce::canonicalize(self.target_path)?;
let flattened = paths
.flatten(&target_path)
.map_err(|err| eyre::Error::msg(format!("failed to flatten the file: {}", err)))?;

match self.output {
Some(output) => {
std::fs::create_dir_all(&output.parent().unwrap())?;
std::fs::write(&output, flattened)?;
println!("Flattened file written at {}", output.display());
}
None => println!("{}", flattened),
};

Ok(())
}
}
1 change: 1 addition & 0 deletions cli/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub mod build;
pub mod create;
pub mod flatten;
pub mod remappings;
pub mod run;
pub mod snapshot;
Expand Down
3 changes: 3 additions & 0 deletions cli/src/forge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ fn main() -> eyre::Result<()> {
Subcommands::Snapshot(cmd) => {
cmd.run()?;
}
Subcommands::Flatten(cmd) => {
cmd.run()?;
}
}

Ok(())
Expand Down
6 changes: 5 additions & 1 deletion cli/src/opts/forge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use ethers::{solc::EvmVersion, types::Address};
use std::{path::PathBuf, str::FromStr};

use crate::cmd::{
build::BuildArgs, create::CreateArgs, remappings::RemappingArgs, run::RunArgs, snapshot, test,
build::BuildArgs, create::CreateArgs, flatten, remappings::RemappingArgs, run::RunArgs,
snapshot, test,
};

#[derive(Debug, Parser)]
Expand Down Expand Up @@ -98,6 +99,9 @@ pub enum Subcommands {

#[clap(about = "creates a snapshot of each test's gas usage")]
Snapshot(snapshot::SnapshotArgs),

#[clap(about = "concats a file with all of its imports")]
Flatten(flatten::FlattenArgs),
}

#[derive(Debug, Clone, Parser)]
Expand Down
Loading

0 comments on commit 11968ea

Please sign in to comment.