Skip to content

Commit

Permalink
feat(forge): add max supported EVM version in compiler -vv (foundry-r…
Browse files Browse the repository at this point in the history
…s#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 <zerosnacks@protonmail.com>
  • Loading branch information
2 people authored and rplusq committed Nov 29, 2024
1 parent a160287 commit 1da9080
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 35 deletions.
77 changes: 59 additions & 18 deletions crates/forge/bin/cmd/compiler.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use clap::{ArgAction, Parser, Subcommand, ValueHint};
use eyre::Result;
use foundry_compilers::Graph;
use foundry_compilers::{artifacts::EvmVersion, Graph};
use foundry_config::Config;
use semver::Version;
use serde::Serialize;
use std::{collections::BTreeMap, path::PathBuf};

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

/// Resolved compiler within the project.
#[derive(Serialize)]
struct ResolvedCompiler {
/// Compiler version.
version: Version,
/// Max supported EVM version of compiler.
#[serde(skip_serializing_if = "Option::is_none")]
evm_version: Option<EvmVersion>,
/// Source paths.
#[serde(skip_serializing_if = "Vec::is_empty")]
paths: Vec<String>,
}

/// CLI arguments for `forge compiler resolve`.
#[derive(Debug, Parser)]
pub struct ResolveArgs {
Expand All @@ -43,7 +57,9 @@ pub struct ResolveArgs {
/// Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv).
///
/// Verbosity levels:
/// - 2: Print source paths.
/// - 0: Print compiler versions.
/// - 1: Print compiler version and source paths.
/// - 2: Print compiler version, source paths and max supported EVM version of the compiler.
#[arg(long, short, verbatim_doc_comment, action = ArgAction::Count, help_heading = "Display options")]
pub verbosity: u8,

Expand All @@ -67,10 +83,10 @@ impl ResolveArgs {
&project.compiler,
)?;

let mut output: BTreeMap<String, Vec<(Version, Vec<String>)>> = BTreeMap::new();
let mut output: BTreeMap<String, Vec<ResolvedCompiler>> = BTreeMap::new();

for (language, sources) in sources {
let mut versions_with_paths: Vec<(Version, Vec<String>)> = sources
let mut versions_with_paths: Vec<ResolvedCompiler> = sources
.iter()
.map(|(version, sources)| {
let paths: Vec<String> = sources
Expand All @@ -94,16 +110,32 @@ impl ResolveArgs {
})
.collect();

(version.clone(), paths)
let evm_version = if verbosity > 1 {
Some(
EvmVersion::default()
.normalize_version_solc(version)
.unwrap_or_default(),
)
} else {
None
};

ResolvedCompiler { version: version.clone(), evm_version, paths }
})
.filter(|(_, paths)| !paths.is_empty())
.filter(|version| !version.paths.is_empty())
.collect();

// Sort by SemVer version.
versions_with_paths.sort_by(|(v1, _), (v2, _)| Version::cmp(v1, v2));
versions_with_paths.sort_by(|v1, v2| Version::cmp(&v1.version, &v2.version));

// Skip language if no paths are found after filtering.
if !versions_with_paths.is_empty() {
// Clear paths if verbosity is 0, performed only after filtering to avoid being
// skipped.
if verbosity == 0 {
versions_with_paths.iter_mut().for_each(|version| version.paths.clear());
}

output.insert(language.to_string(), versions_with_paths);
}
}
Expand All @@ -113,29 +145,38 @@ impl ResolveArgs {
return Ok(());
}

for (language, versions) in &output {
if verbosity < 1 {
println!("{language}:");
} else {
println!("{language}:\n");
for (language, compilers) in &output {
match verbosity {
0 => println!("{language}:"),
_ => println!("{language}:\n"),
}

for (version, paths) in versions {
if verbosity >= 1 {
println!("{version}:");
for resolved_compiler in compilers {
let version = &resolved_compiler.version;
match verbosity {
0 => println!("- {version}"),
_ => {
if let Some(evm) = &resolved_compiler.evm_version {
println!("{version} (<= {evm}):")
} else {
println!("{version}:")
}
}
}

if verbosity > 0 {
let paths = &resolved_compiler.paths;
for (idx, path) in paths.iter().enumerate() {
if idx == paths.len() - 1 {
println!("└── {path}\n");
} else {
println!("├── {path}");
}
}
} else {
println!("- {version}");
}
}

if verbosity < 1 {
if verbosity == 0 {
println!();
}
}
Expand Down
118 changes: 101 additions & 17 deletions crates/forge/tests/cli/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract ContractD {}
"#;

const VYPER_INTERFACE: &str = r#"
# pragma version 0.4.0
# pragma version >=0.4.0
@external
@view
Expand Down Expand Up @@ -87,6 +87,23 @@ Solidity:
"#]]);
});

forgetest!(can_list_resolved_compiler_versions_json, |prj, cmd| {
prj.add_source("ContractA", CONTRACT_A).unwrap();

cmd.args(["compiler", "resolve", "--json"]).assert_success().stdout_eq(
str![[r#"
{
"Solidity":[
{
"version":"0.8.4"
}
]
}
"#]]
.is_json(),
);
});

forgetest!(can_list_resolved_compiler_versions_verbose, |prj, cmd| {
prj.add_source("ContractC", CONTRACT_C).unwrap();
prj.add_source("ContractD", CONTRACT_D).unwrap();
Expand All @@ -102,13 +119,24 @@ Solidity:
"#]]);
});

forgetest!(can_list_resolved_compiler_versions_json, |prj, cmd| {
forgetest!(can_list_resolved_compiler_versions_verbose_json, |prj, cmd| {
prj.add_source("ContractC", CONTRACT_C).unwrap();
prj.add_source("ContractD", CONTRACT_D).unwrap();

cmd.args(["compiler", "resolve", "--json"]).assert_success().stdout_eq(
cmd.args(["compiler", "resolve", "--json", "-v"]).assert_success().stdout_eq(
str![[r#"
{"Solidity":[["0.8.27",["src/ContractC.sol","src/ContractD.sol"]]]}"#]]
{
"Solidity": [
{
"version": "0.8.27",
"paths": [
"src/ContractC.sol",
"src/ContractD.sol"
]
}
]
}
"#]]
.is_json(),
);
});
Expand Down Expand Up @@ -163,11 +191,32 @@ forgetest!(can_list_resolved_multiple_compiler_versions_skipped_json, |prj, cmd|
prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap();
prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap();

cmd.args(["compiler", "resolve", "--skip", "Contract(A|B|C)", "--json"])
.assert_success()
.stdout_eq(str![[r#"
{"Solidity":[["0.8.27",["src/ContractD.sol"]]],"Vyper":[["0.4.0",["src/Counter.vy","src/ICounter.vyi"]]]}
"#]].is_json());
cmd.args(["compiler", "resolve", "--skip", "Contract(A|B|C)", "--json", "-v"])
.assert_success()
.stdout_eq(
str![[r#"
{
"Solidity": [
{
"version": "0.8.27",
"paths": [
"src/ContractD.sol"
]
}
],
"Vyper": [
{
"version": "0.4.0",
"paths": [
"src/Counter.vy",
"src/ICounter.vyi"
]
}
]
}
"#]]
.is_json(),
);
});

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

cmd.args(["compiler", "resolve", "-v"]).assert_success().stdout_eq(str![[r#"
cmd.args(["compiler", "resolve", "-vv"]).assert_success().stdout_eq(str![[r#"
Solidity:
0.8.4:
0.8.4 (<= istanbul):
└── src/ContractA.sol
0.8.11:
0.8.11 (<= london):
└── src/ContractB.sol
0.8.27:
0.8.27 (<= [..]):
├── src/ContractC.sol
└── src/ContractD.sol
Vyper:
0.4.0:
0.4.0 (<= [..]):
├── src/Counter.vy
└── src/ICounter.vyi
"#]]);
});

forgetest!(can_list_resolved_multiple_compiler_versions_json, |prj, cmd| {
forgetest!(can_list_resolved_multiple_compiler_versions_verbose_json, |prj, cmd| {
prj.add_source("ContractA", CONTRACT_A).unwrap();
prj.add_source("ContractB", CONTRACT_B).unwrap();
prj.add_source("ContractC", CONTRACT_C).unwrap();
prj.add_source("ContractD", CONTRACT_D).unwrap();
prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap();
prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap();

cmd.args(["compiler", "resolve", "--json"]).assert_success().stdout_eq(
cmd.args(["compiler", "resolve", "--json", "-vv"]).assert_success().stdout_eq(
str![[r#"
{"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"]]]}
{
"Solidity": [
{
"version": "0.8.4",
"evm_version": "Istanbul",
"paths": [
"src/ContractA.sol"
]
},
{
"version": "0.8.11",
"evm_version": "London",
"paths": [
"src/ContractB.sol"
]
},
{
"version": "0.8.27",
"evm_version": "[..]",
"paths": [
"src/ContractC.sol",
"src/ContractD.sol"
]
}
],
"Vyper": [
{
"version": "0.4.0",
"evm_version": "[..]",
"paths": [
"src/Counter.vy",
"src/ICounter.vyi"
]
}
]
}
"#]]
.is_json(),
);
Expand Down

0 comments on commit 1da9080

Please sign in to comment.