Skip to content

Commit

Permalink
feat(forge): added --json argument to forge build command (#6465)
Browse files Browse the repository at this point in the history
* feat(forge): added --json argument to `forge build` command

* refactor(forge): added standalone functions for suppress_compile* instead of an additional parameter

* fix(forge): addec conflict constraint and renamed argument to format-json to avoid conflict with the test args

* test(forge): added test for conflicts with silent argument

* test(forge): added cli compile command with json argument test

* rustfmt

* lock

---------

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
  • Loading branch information
0xmemorygrinder and mattsse authored Dec 1, 2023
1 parent 96bc0dc commit 8b7500b
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 10 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,4 @@ revm = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" }
revm-interpreter = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" }
revm-precompile = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" }
revm-primitives = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" }

48 changes: 39 additions & 9 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,14 +284,20 @@ pub fn compile_with_filter(
ProjectCompiler::with_filter(print_names, print_sizes, skip).compile(project)
}

/// Compiles the provided [`Project`] and does not throw if there's any compiler error
/// Doesn't print anything to stdout, thus is "suppressed".
pub fn try_suppress_compile(project: &Project) -> Result<ProjectCompileOutput> {
Ok(foundry_compilers::report::with_scoped(
&foundry_compilers::report::Report::new(NoReporter::default()),
|| project.compile(),
)?)
}

/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether
/// compilation was successful or if there was a cache hit.
/// Doesn't print anything to stdout, thus is "suppressed".
pub fn suppress_compile(project: &Project) -> Result<ProjectCompileOutput> {
let output = foundry_compilers::report::with_scoped(
&foundry_compilers::report::Report::new(NoReporter::default()),
|| project.compile(),
)?;
let output = try_suppress_compile(project)?;

if output.has_compiler_errors() {
eyre::bail!(output.to_string())
Expand All @@ -301,7 +307,7 @@ pub fn suppress_compile(project: &Project) -> Result<ProjectCompileOutput> {
}

/// Depending on whether the `skip` is empty this will [`suppress_compile_sparse`] or
/// [`suppress_compile`]
/// [`suppress_compile`] and throw if there's any compiler error
pub fn suppress_compile_with_filter(
project: &Project,
skip: Vec<SkipBuildFilter>,
Expand All @@ -313,6 +319,33 @@ pub fn suppress_compile_with_filter(
}
}

/// Depending on whether the `skip` is empty this will [`suppress_compile_sparse`] or
/// [`suppress_compile`] and does not throw if there's any compiler error
pub fn suppress_compile_with_filter_json(
project: &Project,
skip: Vec<SkipBuildFilter>,
) -> Result<ProjectCompileOutput> {
if skip.is_empty() {
try_suppress_compile(project)
} else {
try_suppress_compile_sparse(project, SkipBuildFilters(skip))
}
}

/// Compiles the provided [`Project`],
/// Doesn't print anything to stdout, thus is "suppressed".
///
/// See [`Project::compile_sparse`]
pub fn try_suppress_compile_sparse<F: FileFilter + 'static>(
project: &Project,
filter: F,
) -> Result<ProjectCompileOutput> {
Ok(foundry_compilers::report::with_scoped(
&foundry_compilers::report::Report::new(NoReporter::default()),
|| project.compile_sparse(filter),
)?)
}

/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether
/// compilation was successful or if there was a cache hit.
/// Doesn't print anything to stdout, thus is "suppressed".
Expand All @@ -322,10 +355,7 @@ pub fn suppress_compile_sparse<F: FileFilter + 'static>(
project: &Project,
filter: F,
) -> Result<ProjectCompileOutput> {
let output = foundry_compilers::report::with_scoped(
&foundry_compilers::report::Report::new(NoReporter::default()),
|| project.compile_sparse(filter),
)?;
let output = try_suppress_compile_sparse(project, filter)?;

if output.has_compiler_errors() {
eyre::bail!(output.to_string())
Expand Down
21 changes: 20 additions & 1 deletion crates/forge/bin/cmd/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ pub struct BuildArgs {
#[clap(flatten)]
#[serde(skip)]
pub watch: WatchArgs,

/// Output the compilation errors in the json format.
/// This is useful when you want to use the output in other tools.
#[clap(long, conflicts_with = "silent")]
#[serde(skip)]
pub format_json: bool,
}

impl BuildArgs {
Expand All @@ -86,7 +92,12 @@ impl BuildArgs {

let filters = self.skip.unwrap_or_default();

if self.args.silent {
if self.format_json {
let output = compile::suppress_compile_with_filter_json(&project, filters)?;
let json = serde_json::to_string_pretty(&output.clone().output())?;
println!("{}", json);
Ok(output)
} else if self.args.silent {
compile::suppress_compile_with_filter(&project, filters)
} else {
let compiler = ProjectCompiler::with_filter(self.names, self.sizes, filters);
Expand Down Expand Up @@ -161,4 +172,12 @@ mod tests {
let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "tests", "scripts"]);
assert_eq!(args.skip, Some(vec![SkipBuildFilter::Tests, SkipBuildFilter::Scripts]));
}

#[test]
fn check_conflicts() {
let args: std::result::Result<BuildArgs, clap::Error> =
BuildArgs::try_parse_from(["foundry-cli", "--format-json", "--silent"]);
assert!(args.is_err());
assert!(args.unwrap_err().kind() == clap::error::ErrorKind::ArgumentConflict);
}
}
26 changes: 26 additions & 0 deletions crates/forge/tests/cli/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use foundry_test_utils::{forgetest, util::OutputExt};
use std::path::PathBuf;

// tests that json is printed when --json is passed
forgetest!(compile_json, |prj, cmd| {
prj.add_source(
"jsonError",
r"
contract Dummy {
uint256 public number;
function something(uint256 newNumber) public {
number = newnumber; // error here
}
}
",
)
.unwrap();

// set up command
cmd.args(["compile", "--format-json"]);

// run command and assert
cmd.unchecked_output().stdout_matches_path(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/compile_json.stdout"),
);
});
1 change: 1 addition & 0 deletions crates/forge/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ extern crate foundry_test_utils;
pub mod constants;
pub mod utils;

mod build;
mod cache;
mod cmd;
mod config;
Expand Down
20 changes: 20 additions & 0 deletions crates/forge/tests/fixtures/compile_json.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"errors": [
{
"sourceLocation": {
"file": "src/jsonError.sol",
"start": 184,
"end": 193
},
"type": "DeclarationError",
"component": "general",
"severity": "error",
"errorCode": "7576",
"message": "Undeclared identifier. Did you mean \"newNumber\"?",
"formattedMessage": "DeclarationError: Undeclared identifier. Did you mean \"newNumber\"?\n --> src/dummy.sol:7:18:\n |\n7 | number = newnumber; // error here\n | ^^^^^^^^^\n\n"
}
],
"sources": {},
"contracts": {},
"build_infos": {}
}

0 comments on commit 8b7500b

Please sign in to comment.