diff --git a/crates/forge/bin/cmd/compiler.rs b/crates/forge/bin/cmd/compiler.rs index badc053c6c4d..06823c0c60b0 100644 --- a/crates/forge/bin/cmd/compiler.rs +++ b/crates/forge/bin/cmd/compiler.rs @@ -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`. @@ -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, + /// Source paths. + #[serde(skip_serializing_if = "Vec::is_empty")] + paths: Vec, +} + /// CLI arguments for `forge compiler resolve`. #[derive(Debug, Parser)] pub struct ResolveArgs { @@ -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, @@ -67,10 +83,10 @@ impl ResolveArgs { &project.compiler, )?; - let mut output: BTreeMap)>> = BTreeMap::new(); + let mut output: BTreeMap> = BTreeMap::new(); for (language, sources) in sources { - let mut versions_with_paths: Vec<(Version, Vec)> = sources + let mut versions_with_paths: Vec = sources .iter() .map(|(version, sources)| { let paths: Vec = sources @@ -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); } } @@ -113,16 +145,27 @@ 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"); @@ -130,12 +173,10 @@ impl ResolveArgs { println!("├── {path}"); } } - } else { - println!("- {version}"); } } - if verbosity < 1 { + if verbosity == 0 { println!(); } } diff --git a/crates/forge/tests/cli/compiler.rs b/crates/forge/tests/cli/compiler.rs index 665356632320..b8453b67b944 100644 --- a/crates/forge/tests/cli/compiler.rs +++ b/crates/forge/tests/cli/compiler.rs @@ -31,7 +31,7 @@ contract ContractD {} "#; const VYPER_INTERFACE: &str = r#" -# pragma version 0.4.0 +# pragma version >=0.4.0 @external @view @@ -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(); @@ -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(), ); }); @@ -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| { @@ -178,22 +227,22 @@ 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 @@ -201,7 +250,7 @@ Vyper: "#]]); }); -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(); @@ -209,9 +258,44 @@ forgetest!(can_list_resolved_multiple_compiler_versions_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", "--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(), );