Skip to content

Commit

Permalink
feat(forge): add more init options (#541)
Browse files Browse the repository at this point in the history
* feat: add more init options

* feat: add no git mode

* rustmft

* feat: check if dir is non empty

* feat: add no-deps alias

* fix: generate file before git

* fix failing tests
  • Loading branch information
mattsse authored Jan 24, 2022
1 parent 58fdbc9 commit 6be75fa
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 93 deletions.
132 changes: 102 additions & 30 deletions cli/src/cmd/init.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
//! init command
use super::install;
use crate::{cmd::Cmd, opts::forge::Dependency};
use crate::{
cmd::{install::install, Cmd},
opts::forge::Dependency,
utils::p_println,
};
use clap::{Parser, ValueHint};
use foundry_config::Config;

use std::{path::PathBuf, process::Command, str::FromStr};
use crate::cmd::install::DependencyInstallOpts;
use ansi_term::Colour;
use ethers::solc::remappings::Remapping;
use std::{
path::{Path, PathBuf},
process::{Command, Stdio},
str::FromStr,
};

/// Command to initialize a new forge project
#[derive(Debug, Clone, Parser)]
Expand All @@ -17,13 +27,32 @@ pub struct InitArgs {
root: Option<PathBuf>,
#[clap(help = "optional solidity template to start from", long, short)]
template: Option<String>,
#[clap(
help = "initialize without creating a git repository",
conflicts_with = "template",
long
)]
no_git: bool,
#[clap(help = "do not create initial commit", conflicts_with = "template", long)]
no_commit: bool,
#[clap(help = "do not print messages", short, long)]
quiet: bool,
#[clap(
help = "run without installing libs from the network",
conflicts_with = "template",
long,
alias = "no-deps"
)]
offline: bool,
#[clap(help = "force init if project dir is not empty", conflicts_with = "template", long)]
force: bool,
}

impl Cmd for InitArgs {
type Output = ();

fn run(self) -> eyre::Result<Self::Output> {
let InitArgs { root, template } = self;
let InitArgs { root, template, no_git, no_commit, quiet, offline, force } = self;

let root = root.unwrap_or_else(|| std::env::current_dir().unwrap());
// create the root dir if it does not exist
Expand All @@ -35,20 +64,30 @@ impl Cmd for InitArgs {
// if a template is provided, then this command is just an alias to `git clone <url>
// <path>`
if let Some(ref template) = template {
println!("Initializing {} from {}...", root.display(), template);
p_println!(!quiet => "Initializing {} from {}...", root.display(), template);
Command::new("git")
.args(&["clone", template, &root.display().to_string()])
.stdout(Stdio::piped())
.spawn()?
.wait()?;
} else {
println!("Initializing {}...", root.display());
// check if target is empty
if !force && root.read_dir().map(|mut i| i.next().is_some()).unwrap_or(false) {
eprintln!(
r#"{}: `forge init` cannot be run on a non-empty directory.
run `forge init --force` to initialize regardless."#,
Colour::Red.paint("error")
);
std::process::exit(1);
}

p_println!(!quiet => "Initializing {}...", root.display());

// make the dirs
let src = root.join("src");
let test = src.join("test");
std::fs::create_dir_all(&test)?;
let lib = root.join("lib");
std::fs::create_dir_all(&lib)?;

// write the contract file
let contract_path = src.join("Contract.sol");
Expand All @@ -57,35 +96,68 @@ impl Cmd for InitArgs {
let contract_path = test.join("Contract.t.sol");
std::fs::write(contract_path, include_str!("../../../assets/ContractTemplate.t.sol"))?;

// sets up git
let is_git = Command::new("git")
.args(&["rev-parse", "--is-inside-work-tree"])
.current_dir(&root)
.spawn()?
.wait()?;
if !is_git.success() {
let gitignore_path = root.join(".gitignore");
std::fs::write(gitignore_path, include_str!("../../../assets/.gitignoreTemplate"))?;
Command::new("git").arg("init").current_dir(&root).spawn()?.wait()?;
Command::new("git").args(&["add", "."]).current_dir(&root).spawn()?.wait()?;
Command::new("git")
.args(&["commit", "-m", "chore: forge init"])
.current_dir(&root)
.spawn()?
.wait()?;
}
Dependency::from_str("https://github.com/dapphub/ds-test")
.and_then(|dependency| install(&root, vec![dependency]))?;

let dest = root.join(Config::FILE_NAME);
if !dest.exists() {
// write foundry.toml
let config = Config::load_with_root(&root).into_basic();
let mut config = Config::load_with_root(&root).into_basic();
// add the ds-test remapping manually because we initialize before installing it
if !offline {
config
.remappings
.push("ds-test/=lib/ds-test/src/".parse::<Remapping>().unwrap().into());
}
std::fs::write(dest, config.to_string_pretty()?)?;
}

// sets up git
if !no_git {
init_git_repo(&root, no_commit)?;
}

if !offline {
let opts = DependencyInstallOpts { no_git, no_commit, quiet };
Dependency::from_str("https://github.com/dapphub/ds-test")
.and_then(|dependency| install(&root, vec![dependency], opts))?;
}
}

println!("Done.");
p_println!(!quiet => " {} forge project.", Colour::Green.paint("Initialized"));
Ok(())
}
}

/// initializes the root dir
fn init_git_repo(root: &Path, no_commit: bool) -> eyre::Result<()> {
let is_git = Command::new("git")
.args(&["rev-parse", "--is-inside-work-tree"])
.current_dir(&root)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?
.wait()?;

if !is_git.success() {
let gitignore_path = root.join(".gitignore");
std::fs::write(gitignore_path, include_str!("../../../assets/.gitignoreTemplate"))?;

Command::new("git")
.arg("init")
.current_dir(&root)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?
.wait()?;

if !no_commit {
Command::new("git").args(&["add", "."]).current_dir(&root).spawn()?.wait()?;
Command::new("git")
.args(&["commit", "-m", "chore: forge init"])
.current_dir(&root)
.stdout(Stdio::piped())
.spawn()?
.wait()?;
}
}

Ok(())
}
140 changes: 140 additions & 0 deletions cli/src/cmd/install.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//! Create command
use crate::{cmd::Cmd, opts::forge::Dependency, utils::p_println};
use ansi_term::Colour;
use clap::Parser;
use foundry_config::find_project_root_path;
use std::{
path::Path,
process::{Command, Stdio},
};

/// Command to install dependencies
#[derive(Debug, Clone, Parser)]
pub struct InstallArgs {
#[clap(help = "the submodule name of the library you want to install")]
dependencies: Vec<Dependency>,
#[clap(flatten)]
opts: DependencyInstallOpts,
}

impl Cmd for InstallArgs {
type Output = ();

fn run(self) -> eyre::Result<Self::Output> {
install(find_project_root_path()?, self.dependencies, self.opts)
}
}

#[derive(Debug, Clone, Copy, Default, Parser)]
pub struct DependencyInstallOpts {
#[clap(help = "install without creating a submodule repository", long)]
pub no_git: bool,
#[clap(help = "do not create a commit", long)]
pub no_commit: bool,
#[clap(help = "do not print messages", short, long)]
pub quiet: bool,
}

/// Installs all dependencies
pub(crate) fn install(
root: impl AsRef<Path>,
dependencies: Vec<Dependency>,
opts: DependencyInstallOpts,
) -> eyre::Result<()> {
let root = root.as_ref();
let libs = root.join("libs");
std::fs::create_dir_all(&libs)?;

for dep in dependencies {
let DependencyInstallOpts { no_git, no_commit, quiet } = opts;
let path = libs.join(&dep.name);
p_println!(!quiet => "Installing {} in {:?}, (url: {}, tag: {:?})", dep.name, path, dep.url, dep.tag);
if no_git {
install_as_folder(&dep, &path)?;
} else {
install_as_submodule(&dep, root, &path, no_commit)?;
}

p_println!(!quiet => " {} {}", Colour::Green.paint("Installed"), dep.name);
}
Ok(())
}

/// installs the dependency as an ordinary folder instead of a submodule
fn install_as_folder(dep: &Dependency, path: &Path) -> eyre::Result<()> {
Command::new("git")
.args(&["clone", &dep.url, &path.display().to_string()])
.stdout(Stdio::piped())
.spawn()?
.wait()?;

if let Some(ref tag) = dep.tag {
Command::new("git")
.args(&["checkout", tag])
.current_dir(&path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?
.wait()?;
}

// rm git artifacts
std::fs::remove_dir_all(path.join(".git"))?;

Ok(())
}

/// installs the dependency as new submodule
fn install_as_submodule(
dep: &Dependency,
root: &Path,
path: &Path,
no_commit: bool,
) -> eyre::Result<()> {
// install the dep
Command::new("git")
.args(&["submodule", "add", &dep.url, &path.display().to_string()])
.current_dir(&root)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?
.wait()?;
// call update on it
Command::new("git")
.args(&["submodule", "update", "--init", "--recursive", &path.display().to_string()])
.current_dir(&root)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?
.wait()?;

// checkout the tag if necessary
let message = if let Some(ref tag) = dep.tag {
Command::new("git")
.args(&["checkout", "--recurse-submodules", tag])
.current_dir(&path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?
.wait()?;

if !no_commit {
Command::new("git").args(&["add", &path.display().to_string()]).spawn()?.wait()?;
}
format!("forge install: {}\n\n{}", dep.name, tag)
} else {
format!("forge install: {}", dep.name)
};

if !no_commit {
Command::new("git")
.args(&["commit", "-m", &message])
.current_dir(&root)
.stdout(Stdio::piped())
.spawn()?
.wait()?;
}

Ok(())
}
50 changes: 2 additions & 48 deletions cli/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ pub mod config;
pub mod create;
pub mod flatten;
pub mod init;
pub mod install;
pub mod remappings;
pub mod run;
pub mod snapshot;
pub mod test;
pub mod verify;

use crate::opts::forge::{ContractInfo, Dependency};
use crate::opts::forge::ContractInfo;
use ethers::{
abi::Abi,
prelude::Graph,
Expand All @@ -69,8 +70,6 @@ use ethers::solc::{
artifacts::BytecodeObject, MinimalCombinedArtifacts, Project, ProjectCompileOutput,
};

use std::process::Command;

/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether
/// compilation was successful or if there was a cache hit.
// TODO: Move this to ethers-solc.
Expand Down Expand Up @@ -226,48 +225,3 @@ fn get_artifact_from_path(
.ok_or_else(|| eyre::Error::msg(format!("bytecode not found for {}", name)))?,
))
}

pub(crate) fn install(
root: impl AsRef<std::path::Path>,
dependencies: Vec<Dependency>,
) -> eyre::Result<()> {
let libs = std::path::Path::new("lib");

dependencies.iter().try_for_each(|dep| -> eyre::Result<_> {
let path = libs.join(&dep.name);
println!("Installing {} in {:?}, (url: {}, tag: {:?})", dep.name, path, dep.url, dep.tag);

// install the dep
Command::new("git")
.args(&["submodule", "add", &dep.url, &path.display().to_string()])
.current_dir(&root)
.spawn()?
.wait()?;

// call update on it
Command::new("git")
.args(&["submodule", "update", "--init", "--recursive", &path.display().to_string()])
.current_dir(&root)
.spawn()?
.wait()?;

// checkout the tag if necessary
let message = if let Some(ref tag) = dep.tag {
Command::new("git")
.args(&["checkout", "--recurse-submodules", tag])
.current_dir(&path)
.spawn()?
.wait()?;

Command::new("git").args(&["add", &path.display().to_string()]).spawn()?.wait()?;

format!("forge install: {}\n\n{}", dep.name, tag)
} else {
format!("forge install: {}", dep.name)
};

Command::new("git").args(&["commit", "-m", &message]).current_dir(&root).spawn()?.wait()?;

Ok(())
})
}
Loading

0 comments on commit 6be75fa

Please sign in to comment.