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): add max supported EVM version in compiler -vv #9129

Merged
merged 6 commits into from
Oct 17, 2024
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
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
Loading