1use clap::{Parser, Subcommand, ValueHint};
2use eyre::Result;
3use foundry_common::shell;
4use foundry_compilers::{
5 Graph, Project,
6 artifacts::EvmVersion,
7 compilers::{
8 multi::{MultiCompiler, MultiCompilerLanguage},
9 solc::{Solc, SolcCompiler},
10 },
11};
12use foundry_config::Config;
13use semver::Version;
14use serde::Serialize;
15use std::{collections::BTreeMap, path::PathBuf};
16
17#[derive(Debug, Parser)]
19pub struct CompilerArgs {
20 #[command(subcommand)]
21 pub sub: CompilerSubcommands,
22}
23
24impl CompilerArgs {
25 pub fn run(self) -> Result<()> {
26 match self.sub {
27 CompilerSubcommands::Resolve(args) => args.run(),
28 }
29 }
30}
31
32#[derive(Debug, Subcommand)]
33pub enum CompilerSubcommands {
34 #[command(visible_alias = "r")]
36 Resolve(ResolveArgs),
37}
38
39#[derive(Serialize)]
41struct ResolvedCompiler {
42 #[serde(skip)]
44 language: MultiCompilerLanguage,
45 version: Version,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 evm_version: Option<EvmVersion>,
50 #[serde(skip_serializing_if = "Vec::is_empty")]
52 paths: Vec<String>,
53}
54
55#[derive(Debug, Parser)]
57pub struct ResolveArgs {
58 #[arg(long, short, value_hint = ValueHint::DirPath, value_name = "PATH")]
60 root: Option<PathBuf>,
61
62 #[arg(long, short, value_name = "REGEX")]
64 skip: Option<regex::Regex>,
65
66 #[arg(long)]
68 path: bool,
69}
70
71impl ResolveArgs {
72 pub fn run(self) -> Result<()> {
73 let Self { root, skip, path } = self;
74
75 let root = root.unwrap_or_else(|| PathBuf::from("."));
76 let config = Config::load_with_root(&root)?;
77 let project = config.project()?;
78
79 let graph = Graph::resolve(&project.paths)?;
80 let sources = graph.into_sources_by_version(&project)?.sources;
81
82 let mut output: BTreeMap<String, Vec<ResolvedCompiler>> = BTreeMap::new();
83
84 for (language, sources) in sources {
85 let mut versions_with_paths: Vec<ResolvedCompiler> = sources
86 .iter()
87 .map(|(version, sources, _)| {
88 let paths: Vec<String> = sources
89 .keys()
90 .filter_map(|path_file| {
91 let path_str = path_file
92 .strip_prefix(&project.paths.root)
93 .unwrap_or(path_file)
94 .to_path_buf()
95 .display()
96 .to_string();
97
98 if let Some(ref regex) = skip
100 && regex.is_match(&path_str)
101 {
102 return None;
103 }
104
105 Some(path_str)
106 })
107 .collect();
108
109 let evm_version = (shell::verbosity() > 1).then(|| {
110 EvmVersion::default().normalize_version_solc(version).unwrap_or_default()
111 });
112
113 ResolvedCompiler { language, version: version.clone(), evm_version, paths }
114 })
115 .filter(|version| !version.paths.is_empty())
116 .collect();
117
118 versions_with_paths.sort_by(|v1, v2| Version::cmp(&v1.version, &v2.version));
120
121 if !versions_with_paths.is_empty() {
123 if shell::verbosity() == 0 {
126 for version in &mut versions_with_paths {
127 version.paths.clear();
128 }
129 }
130
131 output.insert(language.to_string(), versions_with_paths);
132 }
133 }
134
135 if path {
136 let mut compilers = output.values().flatten();
137 let Some(compiler) = compilers.next() else {
138 eyre::bail!("no compiler resolved");
139 };
140 eyre::ensure!(
141 compilers.next().is_none(),
142 "multiple compilers resolved; use `forge compiler resolve` to inspect them"
143 );
144
145 let path = resolved_compiler_path(&project, compiler)?;
146 if shell::is_json() {
147 sh_println!("{}", serde_json::to_string(&path)?)?;
148 } else {
149 sh_println!("{}", path.display())?;
150 }
151 return Ok(());
152 }
153
154 if shell::is_json() {
155 sh_println!("{}", serde_json::to_string(&output)?)?;
156 return Ok(());
157 }
158
159 for (language, compilers) in &output {
160 match shell::verbosity() {
161 0 => sh_println!("{language}:")?,
162 _ => sh_println!("{language}:\n")?,
163 }
164
165 for resolved_compiler in compilers {
166 let version = &resolved_compiler.version;
167 match shell::verbosity() {
168 0 => sh_println!("- {version}")?,
169 _ => {
170 if let Some(evm) = &resolved_compiler.evm_version {
171 sh_println!("{version} (<= {evm}):")?
172 } else {
173 sh_println!("{version}:")?
174 }
175 }
176 }
177
178 if shell::verbosity() > 0 {
179 let paths = &resolved_compiler.paths;
180 for (idx, path) in paths.iter().enumerate() {
181 if idx == paths.len() - 1 {
182 sh_println!("└── {path}\n")?
183 } else {
184 sh_println!("├── {path}")?
185 }
186 }
187 }
188 }
189
190 if shell::verbosity() == 0 {
191 sh_println!()?
192 }
193 }
194
195 Ok(())
196 }
197}
198
199fn resolved_compiler_path(
200 project: &Project<MultiCompiler>,
201 compiler: &ResolvedCompiler,
202) -> Result<PathBuf> {
203 match compiler.language {
204 MultiCompilerLanguage::Solc(_) => {
205 let solc = project
206 .compiler
207 .solc
208 .as_ref()
209 .ok_or_else(|| eyre::eyre!("Solidity compiler is not available"))?;
210 match solc {
211 SolcCompiler::AutoDetect => Solc::find_svm_installed_version(&compiler.version)?
212 .map(|solc| solc.solc)
213 .ok_or_else(|| {
214 eyre::eyre!("Solidity compiler {} is not installed", compiler.version)
215 }),
216 SolcCompiler::Specific(solc) => Ok(solc.solc.clone()),
217 }
218 }
219 MultiCompilerLanguage::Vyper(_) => project
220 .compiler
221 .vyper
222 .as_ref()
223 .map(|vyper| vyper.path.clone())
224 .ok_or_else(|| eyre::eyre!("Vyper compiler is not available")),
225 }
226}