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