Skip to content

Commit 1da9080

Browse files
grandizzyzerosnacks
authored andcommitted
feat(forge): add max supported EVM version in compiler -vv (foundry-rs#9129)
* feat(forge): add max supported EVM version in compiler -v * shorter message, displayed on -vv verbosity * match on verbosity * Respect verbosity in json, nicer json output * Redact default EVM version in tests * make --json output not output paths in verbosity mode 0, equivalent of non-verbose text mode --------- Co-authored-by: zerosnacks <[email protected]>
1 parent a160287 commit 1da9080

File tree

2 files changed

+160
-35
lines changed

2 files changed

+160
-35
lines changed

crates/forge/bin/cmd/compiler.rs

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use clap::{ArgAction, Parser, Subcommand, ValueHint};
22
use eyre::Result;
3-
use foundry_compilers::Graph;
3+
use foundry_compilers::{artifacts::EvmVersion, Graph};
44
use foundry_config::Config;
55
use semver::Version;
6+
use serde::Serialize;
67
use std::{collections::BTreeMap, path::PathBuf};
78

89
/// CLI arguments for `forge compiler`.
@@ -27,6 +28,19 @@ pub enum CompilerSubcommands {
2728
Resolve(ResolveArgs),
2829
}
2930

31+
/// Resolved compiler within the project.
32+
#[derive(Serialize)]
33+
struct ResolvedCompiler {
34+
/// Compiler version.
35+
version: Version,
36+
/// Max supported EVM version of compiler.
37+
#[serde(skip_serializing_if = "Option::is_none")]
38+
evm_version: Option<EvmVersion>,
39+
/// Source paths.
40+
#[serde(skip_serializing_if = "Vec::is_empty")]
41+
paths: Vec<String>,
42+
}
43+
3044
/// CLI arguments for `forge compiler resolve`.
3145
#[derive(Debug, Parser)]
3246
pub struct ResolveArgs {
@@ -43,7 +57,9 @@ pub struct ResolveArgs {
4357
/// Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv).
4458
///
4559
/// Verbosity levels:
46-
/// - 2: Print source paths.
60+
/// - 0: Print compiler versions.
61+
/// - 1: Print compiler version and source paths.
62+
/// - 2: Print compiler version, source paths and max supported EVM version of the compiler.
4763
#[arg(long, short, verbatim_doc_comment, action = ArgAction::Count, help_heading = "Display options")]
4864
pub verbosity: u8,
4965

@@ -67,10 +83,10 @@ impl ResolveArgs {
6783
&project.compiler,
6884
)?;
6985

70-
let mut output: BTreeMap<String, Vec<(Version, Vec<String>)>> = BTreeMap::new();
86+
let mut output: BTreeMap<String, Vec<ResolvedCompiler>> = BTreeMap::new();
7187

7288
for (language, sources) in sources {
73-
let mut versions_with_paths: Vec<(Version, Vec<String>)> = sources
89+
let mut versions_with_paths: Vec<ResolvedCompiler> = sources
7490
.iter()
7591
.map(|(version, sources)| {
7692
let paths: Vec<String> = sources
@@ -94,16 +110,32 @@ impl ResolveArgs {
94110
})
95111
.collect();
96112

97-
(version.clone(), paths)
113+
let evm_version = if verbosity > 1 {
114+
Some(
115+
EvmVersion::default()
116+
.normalize_version_solc(version)
117+
.unwrap_or_default(),
118+
)
119+
} else {
120+
None
121+
};
122+
123+
ResolvedCompiler { version: version.clone(), evm_version, paths }
98124
})
99-
.filter(|(_, paths)| !paths.is_empty())
125+
.filter(|version| !version.paths.is_empty())
100126
.collect();
101127

102128
// Sort by SemVer version.
103-
versions_with_paths.sort_by(|(v1, _), (v2, _)| Version::cmp(v1, v2));
129+
versions_with_paths.sort_by(|v1, v2| Version::cmp(&v1.version, &v2.version));
104130

105131
// Skip language if no paths are found after filtering.
106132
if !versions_with_paths.is_empty() {
133+
// Clear paths if verbosity is 0, performed only after filtering to avoid being
134+
// skipped.
135+
if verbosity == 0 {
136+
versions_with_paths.iter_mut().for_each(|version| version.paths.clear());
137+
}
138+
107139
output.insert(language.to_string(), versions_with_paths);
108140
}
109141
}
@@ -113,29 +145,38 @@ impl ResolveArgs {
113145
return Ok(());
114146
}
115147

116-
for (language, versions) in &output {
117-
if verbosity < 1 {
118-
println!("{language}:");
119-
} else {
120-
println!("{language}:\n");
148+
for (language, compilers) in &output {
149+
match verbosity {
150+
0 => println!("{language}:"),
151+
_ => println!("{language}:\n"),
121152
}
122153

123-
for (version, paths) in versions {
124-
if verbosity >= 1 {
125-
println!("{version}:");
154+
for resolved_compiler in compilers {
155+
let version = &resolved_compiler.version;
156+
match verbosity {
157+
0 => println!("- {version}"),
158+
_ => {
159+
if let Some(evm) = &resolved_compiler.evm_version {
160+
println!("{version} (<= {evm}):")
161+
} else {
162+
println!("{version}:")
163+
}
164+
}
165+
}
166+
167+
if verbosity > 0 {
168+
let paths = &resolved_compiler.paths;
126169
for (idx, path) in paths.iter().enumerate() {
127170
if idx == paths.len() - 1 {
128171
println!("└── {path}\n");
129172
} else {
130173
println!("├── {path}");
131174
}
132175
}
133-
} else {
134-
println!("- {version}");
135176
}
136177
}
137178

138-
if verbosity < 1 {
179+
if verbosity == 0 {
139180
println!();
140181
}
141182
}

crates/forge/tests/cli/compiler.rs

Lines changed: 101 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ contract ContractD {}
3131
"#;
3232

3333
const VYPER_INTERFACE: &str = r#"
34-
# pragma version 0.4.0
34+
# pragma version >=0.4.0
3535
3636
@external
3737
@view
@@ -87,6 +87,23 @@ Solidity:
8787
"#]]);
8888
});
8989

90+
forgetest!(can_list_resolved_compiler_versions_json, |prj, cmd| {
91+
prj.add_source("ContractA", CONTRACT_A).unwrap();
92+
93+
cmd.args(["compiler", "resolve", "--json"]).assert_success().stdout_eq(
94+
str![[r#"
95+
{
96+
"Solidity":[
97+
{
98+
"version":"0.8.4"
99+
}
100+
]
101+
}
102+
"#]]
103+
.is_json(),
104+
);
105+
});
106+
90107
forgetest!(can_list_resolved_compiler_versions_verbose, |prj, cmd| {
91108
prj.add_source("ContractC", CONTRACT_C).unwrap();
92109
prj.add_source("ContractD", CONTRACT_D).unwrap();
@@ -102,13 +119,24 @@ Solidity:
102119
"#]]);
103120
});
104121

105-
forgetest!(can_list_resolved_compiler_versions_json, |prj, cmd| {
122+
forgetest!(can_list_resolved_compiler_versions_verbose_json, |prj, cmd| {
106123
prj.add_source("ContractC", CONTRACT_C).unwrap();
107124
prj.add_source("ContractD", CONTRACT_D).unwrap();
108125

109-
cmd.args(["compiler", "resolve", "--json"]).assert_success().stdout_eq(
126+
cmd.args(["compiler", "resolve", "--json", "-v"]).assert_success().stdout_eq(
110127
str![[r#"
111-
{"Solidity":[["0.8.27",["src/ContractC.sol","src/ContractD.sol"]]]}"#]]
128+
{
129+
"Solidity": [
130+
{
131+
"version": "0.8.27",
132+
"paths": [
133+
"src/ContractC.sol",
134+
"src/ContractD.sol"
135+
]
136+
}
137+
]
138+
}
139+
"#]]
112140
.is_json(),
113141
);
114142
});
@@ -163,11 +191,32 @@ forgetest!(can_list_resolved_multiple_compiler_versions_skipped_json, |prj, cmd|
163191
prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap();
164192
prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap();
165193

166-
cmd.args(["compiler", "resolve", "--skip", "Contract(A|B|C)", "--json"])
167-
.assert_success()
168-
.stdout_eq(str![[r#"
169-
{"Solidity":[["0.8.27",["src/ContractD.sol"]]],"Vyper":[["0.4.0",["src/Counter.vy","src/ICounter.vyi"]]]}
170-
"#]].is_json());
194+
cmd.args(["compiler", "resolve", "--skip", "Contract(A|B|C)", "--json", "-v"])
195+
.assert_success()
196+
.stdout_eq(
197+
str![[r#"
198+
{
199+
"Solidity": [
200+
{
201+
"version": "0.8.27",
202+
"paths": [
203+
"src/ContractD.sol"
204+
]
205+
}
206+
],
207+
"Vyper": [
208+
{
209+
"version": "0.4.0",
210+
"paths": [
211+
"src/Counter.vy",
212+
"src/ICounter.vyi"
213+
]
214+
}
215+
]
216+
}
217+
"#]]
218+
.is_json(),
219+
);
171220
});
172221

173222
forgetest!(can_list_resolved_multiple_compiler_versions_verbose, |prj, cmd| {
@@ -178,40 +227,75 @@ forgetest!(can_list_resolved_multiple_compiler_versions_verbose, |prj, cmd| {
178227
prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap();
179228
prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap();
180229

181-
cmd.args(["compiler", "resolve", "-v"]).assert_success().stdout_eq(str![[r#"
230+
cmd.args(["compiler", "resolve", "-vv"]).assert_success().stdout_eq(str![[r#"
182231
Solidity:
183232
184-
0.8.4:
233+
0.8.4 (<= istanbul):
185234
└── src/ContractA.sol
186235
187-
0.8.11:
236+
0.8.11 (<= london):
188237
└── src/ContractB.sol
189238
190-
0.8.27:
239+
0.8.27 (<= [..]):
191240
├── src/ContractC.sol
192241
└── src/ContractD.sol
193242
194243
Vyper:
195244
196-
0.4.0:
245+
0.4.0 (<= [..]):
197246
├── src/Counter.vy
198247
└── src/ICounter.vyi
199248
200249
201250
"#]]);
202251
});
203252

204-
forgetest!(can_list_resolved_multiple_compiler_versions_json, |prj, cmd| {
253+
forgetest!(can_list_resolved_multiple_compiler_versions_verbose_json, |prj, cmd| {
205254
prj.add_source("ContractA", CONTRACT_A).unwrap();
206255
prj.add_source("ContractB", CONTRACT_B).unwrap();
207256
prj.add_source("ContractC", CONTRACT_C).unwrap();
208257
prj.add_source("ContractD", CONTRACT_D).unwrap();
209258
prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap();
210259
prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap();
211260

212-
cmd.args(["compiler", "resolve", "--json"]).assert_success().stdout_eq(
261+
cmd.args(["compiler", "resolve", "--json", "-vv"]).assert_success().stdout_eq(
213262
str![[r#"
214-
{"Solidity":[["0.8.4",["src/ContractA.sol"]],["0.8.11",["src/ContractB.sol"]],["0.8.27",["src/ContractC.sol","src/ContractD.sol"]]],"Vyper":[["0.4.0",["src/Counter.vy","src/ICounter.vyi"]]]}
263+
{
264+
"Solidity": [
265+
{
266+
"version": "0.8.4",
267+
"evm_version": "Istanbul",
268+
"paths": [
269+
"src/ContractA.sol"
270+
]
271+
},
272+
{
273+
"version": "0.8.11",
274+
"evm_version": "London",
275+
"paths": [
276+
"src/ContractB.sol"
277+
]
278+
},
279+
{
280+
"version": "0.8.27",
281+
"evm_version": "[..]",
282+
"paths": [
283+
"src/ContractC.sol",
284+
"src/ContractD.sol"
285+
]
286+
}
287+
],
288+
"Vyper": [
289+
{
290+
"version": "0.4.0",
291+
"evm_version": "[..]",
292+
"paths": [
293+
"src/Counter.vy",
294+
"src/ICounter.vyi"
295+
]
296+
}
297+
]
298+
}
215299
"#]]
216300
.is_json(),
217301
);

0 commit comments

Comments
 (0)