Skip to content

Commit

Permalink
feat: add --skip <filter> to forge build (foundry-rs#3370)
Browse files Browse the repository at this point in the history
* feat: add --skip <filter> to forge build

* feat: add skip filter

* integrate filter

* chore: bump ethers

* test: pin version

Co-authored-by: Georgios Konstantopoulos <[email protected]>
  • Loading branch information
2 people authored and iFrostizz committed Nov 9, 2022
1 parent 22a17c8 commit 5134cbb
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 104 deletions.
48 changes: 24 additions & 24 deletions Cargo.lock

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

87 changes: 87 additions & 0 deletions cli/src/cmd/forge/build/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! Filter for excluding contracts in `forge build`
use ethers::solc::FileFilter;
use std::{convert::Infallible, path::Path, str::FromStr};

/// Bundles multiple `SkipBuildFilter` into a single `FileFilter`
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SkipBuildFilters(pub Vec<SkipBuildFilter>);

impl FileFilter for SkipBuildFilters {
/// Only returns a match if no filter a
fn is_match(&self, file: &Path) -> bool {
self.0.iter().all(|filter| filter.is_match(file))
}
}

/// A filter that excludes matching contracts from the build
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum SkipBuildFilter {
/// Exclude all `.t.sol` contracts
Tests,
/// Exclude all `.s.sol` contracts
Scripts,
/// Exclude if the file matches
Custom(String),
}

impl SkipBuildFilter {
/// Returns the pattern to match against a file
fn file_pattern(&self) -> &str {
match self {
SkipBuildFilter::Tests => ".t.sol",
SkipBuildFilter::Scripts => ".s.sol",
SkipBuildFilter::Custom(s) => s.as_str(),
}
}
}

impl<T: AsRef<str>> From<T> for SkipBuildFilter {
fn from(s: T) -> Self {
match s.as_ref() {
"tests" => SkipBuildFilter::Tests,
"scripts" => SkipBuildFilter::Scripts,
s => SkipBuildFilter::Custom(s.to_string()),
}
}
}

impl FromStr for SkipBuildFilter {
type Err = Infallible;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(s.into())
}
}

impl FileFilter for SkipBuildFilter {
/// Matches file only if the filter does not apply
///
/// This is returns the inverse of `file.name.contains(pattern)`
fn is_match(&self, file: &Path) -> bool {
fn exclude(file: &Path, pattern: &str) -> Option<bool> {
let file_name = file.file_name()?.to_str()?;
Some(file_name.contains(pattern))
}

!exclude(file, self.file_pattern()).unwrap_or_default()
}
}

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

#[test]
fn test_build_filter() {
let file = Path::new("A.t.sol");
assert!(!SkipBuildFilter::Tests.is_match(file));
assert!(SkipBuildFilter::Scripts.is_match(file));
assert!(!SkipBuildFilter::Custom("A.t".to_string()).is_match(file));

let file = Path::new("A.s.sol");
assert!(SkipBuildFilter::Tests.is_match(file));
assert!(!SkipBuildFilter::Scripts.is_match(file));
assert!(!SkipBuildFilter::Custom("A.s".to_string()).is_match(file));
}
}
54 changes: 50 additions & 4 deletions cli/src/cmd/forge/build/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
//! Build command
use crate::cmd::{
forge::{
build::filter::{SkipBuildFilter, SkipBuildFilters},
install::{self},
watch::WatchArgs,
},
Cmd, LoadConfig,
};
use clap::Parser;
use clap::{ArgAction, Parser};
use ethers::solc::{Project, ProjectCompileOutput};
use foundry_common::compile;
use foundry_common::{compile, compile::ProjectCompiler};
use foundry_config::{
figment::{
self,
Expand All @@ -19,6 +20,7 @@ use foundry_config::{
Config,
};
use serde::Serialize;
use tracing::trace;
use watchexec::config::{InitConfig, RuntimeConfig};

mod core;
Expand All @@ -27,6 +29,8 @@ pub use self::core::CoreBuildArgs;
mod paths;
pub use self::paths::ProjectPathsArgs;

mod filter;

foundry_config::merge_impl_figment_convert!(BuildArgs, args);

/// All `forge build` related arguments
Expand Down Expand Up @@ -64,6 +68,14 @@ pub struct BuildArgs {
#[serde(skip)]
pub sizes: bool,

#[clap(
long,
multiple_values = true,
action = ArgAction::Append,
help = "Skip building whose names contain FILTER. `tests` and `scripts` are aliases for `.t.sol` and `.s.sol`. (this flag can be used multiple times)")]
#[serde(skip)]
pub skip: Option<Vec<SkipBuildFilter>>,

#[clap(flatten, next_help_heading = "WATCH OPTIONS")]
#[serde(skip)]
pub watch: WatchArgs,
Expand All @@ -83,10 +95,23 @@ impl Cmd for BuildArgs {
project = config.project()?;
}

let filters = self.skip.unwrap_or_default();

if self.args.silent {
compile::suppress_compile(&project)
if filters.is_empty() {
compile::suppress_compile(&project)
} else {
trace!(?filters, "compile with filters suppressed");
compile::suppress_compile_sparse(&project, SkipBuildFilters(filters))
}
} else {
compile::compile(&project, self.names, self.sizes)
let compiler = ProjectCompiler::new(self.names, self.sizes);
if filters.is_empty() {
compiler.compile(&project)
} else {
trace!(?filters, "compile with filters");
compiler.compile_sparse(&project, SkipBuildFilters(filters))
}
}
}
}
Expand Down Expand Up @@ -139,3 +164,24 @@ impl Provider for BuildArgs {
Ok(Map::from([(Config::selected_profile(), dict)]))
}
}

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

#[test]
fn can_parse_build_filters() {
let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "tests"]);
assert_eq!(args.skip, Some(vec![SkipBuildFilter::Tests]));

let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "scripts"]);
assert_eq!(args.skip, Some(vec![SkipBuildFilter::Scripts]));

let args: BuildArgs =
BuildArgs::parse_from(["foundry-cli", "--skip", "tests", "--skip", "scripts"]);
assert_eq!(args.skip, Some(vec![SkipBuildFilter::Tests, SkipBuildFilter::Scripts]));

let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "tests", "scripts"]);
assert_eq!(args.skip, Some(vec![SkipBuildFilter::Tests, SkipBuildFilter::Scripts]));
}
}
3 changes: 3 additions & 0 deletions cli/tests/fixtures/can_build_skip_contracts.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Compiling 1 files with 0.8.17
Solc 0.8.17 finished in 34.45ms
Compiler run successful
20 changes: 20 additions & 0 deletions cli/tests/it/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1341,3 +1341,23 @@ forgetest_init!(can_install_missing_deps_build, |prj: TestProject, mut cmd: Test
assert!(output.contains("Missing dependencies found. Installing now"), "{}", output);
assert!(output.contains("Compiler run successful"), "{}", output);
});

// checks that extra output works
forgetest_init!(can_build_skip_contracts, |prj: TestProject, mut cmd: TestCommand| {
// explicitly set to run with 0.8.17 for consistent output
let config = Config { solc: Some("0.8.17".into()), ..Default::default() };
prj.write_config(config);

// only builds the single template contract `src/*`
cmd.args(["build", "--skip", "tests", "--skip", "scripts"]);

cmd.unchecked_output().stdout_matches_path(
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/can_build_skip_contracts.stdout"),
);
// re-run command
let out = cmd.stdout();

// unchanged
assert!(out.trim().contains("No files changed, compilation skipped"), "{}", out);
});
Loading

0 comments on commit 5134cbb

Please sign in to comment.