Skip to content

Commit 8b7500b

Browse files
feat(forge): added --json argument to forge build command (#6465)
* 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>
1 parent 96bc0dc commit 8b7500b

File tree

6 files changed

+107
-10
lines changed

6 files changed

+107
-10
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,4 @@ revm = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" }
200200
revm-interpreter = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" }
201201
revm-precompile = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" }
202202
revm-primitives = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" }
203+

crates/common/src/compile.rs

+39-9
Original file line numberDiff line numberDiff line change
@@ -284,14 +284,20 @@ pub fn compile_with_filter(
284284
ProjectCompiler::with_filter(print_names, print_sizes, skip).compile(project)
285285
}
286286

287+
/// Compiles the provided [`Project`] and does not throw if there's any compiler error
288+
/// Doesn't print anything to stdout, thus is "suppressed".
289+
pub fn try_suppress_compile(project: &Project) -> Result<ProjectCompileOutput> {
290+
Ok(foundry_compilers::report::with_scoped(
291+
&foundry_compilers::report::Report::new(NoReporter::default()),
292+
|| project.compile(),
293+
)?)
294+
}
295+
287296
/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether
288297
/// compilation was successful or if there was a cache hit.
289298
/// Doesn't print anything to stdout, thus is "suppressed".
290299
pub fn suppress_compile(project: &Project) -> Result<ProjectCompileOutput> {
291-
let output = foundry_compilers::report::with_scoped(
292-
&foundry_compilers::report::Report::new(NoReporter::default()),
293-
|| project.compile(),
294-
)?;
300+
let output = try_suppress_compile(project)?;
295301

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

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

322+
/// Depending on whether the `skip` is empty this will [`suppress_compile_sparse`] or
323+
/// [`suppress_compile`] and does not throw if there's any compiler error
324+
pub fn suppress_compile_with_filter_json(
325+
project: &Project,
326+
skip: Vec<SkipBuildFilter>,
327+
) -> Result<ProjectCompileOutput> {
328+
if skip.is_empty() {
329+
try_suppress_compile(project)
330+
} else {
331+
try_suppress_compile_sparse(project, SkipBuildFilters(skip))
332+
}
333+
}
334+
335+
/// Compiles the provided [`Project`],
336+
/// Doesn't print anything to stdout, thus is "suppressed".
337+
///
338+
/// See [`Project::compile_sparse`]
339+
pub fn try_suppress_compile_sparse<F: FileFilter + 'static>(
340+
project: &Project,
341+
filter: F,
342+
) -> Result<ProjectCompileOutput> {
343+
Ok(foundry_compilers::report::with_scoped(
344+
&foundry_compilers::report::Report::new(NoReporter::default()),
345+
|| project.compile_sparse(filter),
346+
)?)
347+
}
348+
316349
/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether
317350
/// compilation was successful or if there was a cache hit.
318351
/// Doesn't print anything to stdout, thus is "suppressed".
@@ -322,10 +355,7 @@ pub fn suppress_compile_sparse<F: FileFilter + 'static>(
322355
project: &Project,
323356
filter: F,
324357
) -> Result<ProjectCompileOutput> {
325-
let output = foundry_compilers::report::with_scoped(
326-
&foundry_compilers::report::Report::new(NoReporter::default()),
327-
|| project.compile_sparse(filter),
328-
)?;
358+
let output = try_suppress_compile_sparse(project, filter)?;
329359

330360
if output.has_compiler_errors() {
331361
eyre::bail!(output.to_string())

crates/forge/bin/cmd/build.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ pub struct BuildArgs {
6969
#[clap(flatten)]
7070
#[serde(skip)]
7171
pub watch: WatchArgs,
72+
73+
/// Output the compilation errors in the json format.
74+
/// This is useful when you want to use the output in other tools.
75+
#[clap(long, conflicts_with = "silent")]
76+
#[serde(skip)]
77+
pub format_json: bool,
7278
}
7379

7480
impl BuildArgs {
@@ -86,7 +92,12 @@ impl BuildArgs {
8692

8793
let filters = self.skip.unwrap_or_default();
8894

89-
if self.args.silent {
95+
if self.format_json {
96+
let output = compile::suppress_compile_with_filter_json(&project, filters)?;
97+
let json = serde_json::to_string_pretty(&output.clone().output())?;
98+
println!("{}", json);
99+
Ok(output)
100+
} else if self.args.silent {
90101
compile::suppress_compile_with_filter(&project, filters)
91102
} else {
92103
let compiler = ProjectCompiler::with_filter(self.names, self.sizes, filters);
@@ -161,4 +172,12 @@ mod tests {
161172
let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "tests", "scripts"]);
162173
assert_eq!(args.skip, Some(vec![SkipBuildFilter::Tests, SkipBuildFilter::Scripts]));
163174
}
175+
176+
#[test]
177+
fn check_conflicts() {
178+
let args: std::result::Result<BuildArgs, clap::Error> =
179+
BuildArgs::try_parse_from(["foundry-cli", "--format-json", "--silent"]);
180+
assert!(args.is_err());
181+
assert!(args.unwrap_err().kind() == clap::error::ErrorKind::ArgumentConflict);
182+
}
164183
}

crates/forge/tests/cli/build.rs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use foundry_test_utils::{forgetest, util::OutputExt};
2+
use std::path::PathBuf;
3+
4+
// tests that json is printed when --json is passed
5+
forgetest!(compile_json, |prj, cmd| {
6+
prj.add_source(
7+
"jsonError",
8+
r"
9+
contract Dummy {
10+
uint256 public number;
11+
function something(uint256 newNumber) public {
12+
number = newnumber; // error here
13+
}
14+
}
15+
",
16+
)
17+
.unwrap();
18+
19+
// set up command
20+
cmd.args(["compile", "--format-json"]);
21+
22+
// run command and assert
23+
cmd.unchecked_output().stdout_matches_path(
24+
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/compile_json.stdout"),
25+
);
26+
});

crates/forge/tests/cli/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ extern crate foundry_test_utils;
44
pub mod constants;
55
pub mod utils;
66

7+
mod build;
78
mod cache;
89
mod cmd;
910
mod config;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"errors": [
3+
{
4+
"sourceLocation": {
5+
"file": "src/jsonError.sol",
6+
"start": 184,
7+
"end": 193
8+
},
9+
"type": "DeclarationError",
10+
"component": "general",
11+
"severity": "error",
12+
"errorCode": "7576",
13+
"message": "Undeclared identifier. Did you mean \"newNumber\"?",
14+
"formattedMessage": "DeclarationError: Undeclared identifier. Did you mean \"newNumber\"?\n --> src/dummy.sol:7:18:\n |\n7 | number = newnumber; // error here\n | ^^^^^^^^^\n\n"
15+
}
16+
],
17+
"sources": {},
18+
"contracts": {},
19+
"build_infos": {}
20+
}

0 commit comments

Comments
 (0)