forge/cmd/
compiler.rs
1use clap::{Parser, Subcommand, ValueHint};
2use eyre::Result;
3use foundry_common::shell;
4use foundry_compilers::{artifacts::EvmVersion, Graph};
5use foundry_config::{normalize_evm_version_vyper, Config};
6use semver::Version;
7use serde::Serialize;
8use std::{collections::BTreeMap, path::PathBuf};
9
10#[derive(Debug, Parser)]
12pub struct CompilerArgs {
13 #[command(subcommand)]
14 pub sub: CompilerSubcommands,
15}
16
17impl CompilerArgs {
18 pub fn run(self) -> Result<()> {
19 match self.sub {
20 CompilerSubcommands::Resolve(args) => args.run(),
21 }
22 }
23}
24
25#[derive(Debug, Subcommand)]
26pub enum CompilerSubcommands {
27 #[command(visible_alias = "r")]
29 Resolve(ResolveArgs),
30}
31
32#[derive(Serialize)]
34struct ResolvedCompiler {
35 version: Version,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 evm_version: Option<EvmVersion>,
40 #[serde(skip_serializing_if = "Vec::is_empty")]
42 paths: Vec<String>,
43}
44
45#[derive(Debug, Parser)]
47pub struct ResolveArgs {
48 #[arg(long, short, value_hint = ValueHint::DirPath, value_name = "PATH")]
50 root: Option<PathBuf>,
51
52 #[arg(long, short, value_name = "REGEX")]
54 skip: Option<regex::Regex>,
55}
56
57impl ResolveArgs {
58 pub fn run(self) -> Result<()> {
59 let Self { root, skip } = self;
60
61 let root = root.unwrap_or_else(|| PathBuf::from("."));
62 let config = Config::load_with_root(&root)?;
63 let project = config.project()?;
64
65 let graph = Graph::resolve(&project.paths)?;
66 let sources = graph.into_sources_by_version(&project)?.sources;
67
68 let mut output: BTreeMap<String, Vec<ResolvedCompiler>> = BTreeMap::new();
69
70 for (language, sources) in sources {
71 let mut versions_with_paths: Vec<ResolvedCompiler> = sources
72 .iter()
73 .map(|(version, sources, _)| {
74 let paths: Vec<String> = sources
75 .iter()
76 .filter_map(|(path_file, _)| {
77 let path_str = path_file
78 .strip_prefix(&project.paths.root)
79 .unwrap_or(path_file)
80 .to_path_buf()
81 .display()
82 .to_string();
83
84 if let Some(ref regex) = skip {
86 if regex.is_match(&path_str) {
87 return None;
88 }
89 }
90
91 Some(path_str)
92 })
93 .collect();
94
95 let evm_version = if shell::verbosity() > 1 {
96 let evm = EvmVersion::default()
97 .normalize_version_solc(version)
98 .unwrap_or_default();
99
100 if language.is_vyper() {
102 Some(normalize_evm_version_vyper(evm))
103 } else {
104 Some(evm)
105 }
106 } else {
107 None
108 };
109
110 ResolvedCompiler { version: version.clone(), evm_version, paths }
111 })
112 .filter(|version| !version.paths.is_empty())
113 .collect();
114
115 versions_with_paths.sort_by(|v1, v2| Version::cmp(&v1.version, &v2.version));
117
118 if !versions_with_paths.is_empty() {
120 if shell::verbosity() == 0 {
123 versions_with_paths.iter_mut().for_each(|version| version.paths.clear());
124 }
125
126 output.insert(language.to_string(), versions_with_paths);
127 }
128 }
129
130 if shell::is_json() {
131 sh_println!("{}", serde_json::to_string(&output)?)?;
132 return Ok(());
133 }
134
135 for (language, compilers) in &output {
136 match shell::verbosity() {
137 0 => sh_println!("{language}:")?,
138 _ => sh_println!("{language}:\n")?,
139 }
140
141 for resolved_compiler in compilers {
142 let version = &resolved_compiler.version;
143 match shell::verbosity() {
144 0 => sh_println!("- {version}")?,
145 _ => {
146 if let Some(evm) = &resolved_compiler.evm_version {
147 sh_println!("{version} (<= {evm}):")?
148 } else {
149 sh_println!("{version}:")?
150 }
151 }
152 }
153
154 if shell::verbosity() > 0 {
155 let paths = &resolved_compiler.paths;
156 for (idx, path) in paths.iter().enumerate() {
157 if idx == paths.len() - 1 {
158 sh_println!("└── {path}\n")?
159 } else {
160 sh_println!("├── {path}")?
161 }
162 }
163 }
164 }
165
166 if shell::verbosity() == 0 {
167 sh_println!()?
168 }
169 }
170
171 Ok(())
172 }
173}