Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(forge): added --json argument to forge build command #6465

Merged
merged 7 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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": {}
}