1use clap::{Parser, Subcommand, ValueHint};
2use eyre::Result;
3use foundry_common::shell;
4use foundry_compilers::{
5 Compiler, CompilerInput, Graph, artifacts::EvmVersion, multi::MultiCompilerInput,
6};
7use foundry_config::Config;
8use semver::Version;
9use serde::Serialize;
10use std::{collections::BTreeMap, path::PathBuf};
11
12#[derive(Debug, Parser)]
14pub struct CompilerArgs {
15 #[command(subcommand)]
16 pub sub: CompilerSubcommands,
17}
18
19impl CompilerArgs {
20 pub fn run(self) -> Result<()> {
21 match self.sub {
22 CompilerSubcommands::Resolve(args) => args.run(),
23 }
24 }
25}
26
27#[derive(Debug, Subcommand)]
28pub enum CompilerSubcommands {
29 #[command(visible_alias = "r")]
31 Resolve(ResolveArgs),
32}
33
34#[derive(Serialize)]
36struct Dependency {
37 name: String,
38 version: Version,
39}
40
41#[derive(Serialize)]
43struct ResolvedCompiler {
44 name: String,
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 #[serde(skip_serializing_if = "Option::is_none")]
55 dependency: Option<Dependency>,
57}
58
59#[derive(Debug, Parser)]
61pub struct ResolveArgs {
62 #[arg(long, short, value_hint = ValueHint::DirPath, value_name = "PATH")]
64 root: Option<PathBuf>,
65
66 #[arg(long, short, value_name = "REGEX")]
68 skip: Option<regex::Regex>,
69
70 #[arg(
72 value_name = "RESOLC_COMPILE",
73 help = "Enable compiling with resolc",
74 long = "resolc-compile",
75 visible_alias = "resolc",
76 action = clap::ArgAction::SetTrue,
77 default_value = "false"
78 )]
79 resolc_compile: bool,
80}
81
82impl ResolveArgs {
83 pub fn run(self) -> Result<()> {
84 let Self { root, skip, resolc_compile } = self;
85
86 let root = root.unwrap_or_else(|| PathBuf::from("."));
87
88 let config = {
89 let mut config = Config::load_with_root(&root)?.canonic_at(root);
90
91 if resolc_compile {
92 config.polkadot.resolc_compile = true;
93 }
94 config
95 };
96
97 let project = config.project()?;
98
99 let graph = Graph::resolve(&project.paths)?;
100 let sources = graph.into_sources_by_version(&project)?.sources;
101 let mut output: BTreeMap<String, Vec<ResolvedCompiler>> = BTreeMap::new();
102
103 for (language, sources) in sources {
104 let mut versions_with_paths: Vec<ResolvedCompiler> = sources
105 .iter()
106 .map(|(version, sources, (_, settings))| {
107 let paths: Vec<String> = sources
108 .iter()
109 .filter_map(|(path_file, _)| {
110 let path_str = path_file
111 .strip_prefix(&project.paths.root)
112 .unwrap_or(path_file)
113 .to_path_buf()
114 .display()
115 .to_string();
116
117 if let Some(ref regex) = skip
119 && regex.is_match(&path_str)
120 {
121 return None;
122 }
123
124 Some(path_str)
125 })
126 .collect();
127
128 let evm_version = if shell::verbosity() > 1 {
129 let evm = EvmVersion::default()
130 .normalize_version_solc(version)
131 .unwrap_or_default();
132
133 Some(evm)
134 } else {
135 None
136 };
137 let input = MultiCompilerInput::build(
138 sources.clone(),
139 settings.to_owned().clone(),
140 language,
141 version.clone(),
142 );
143 let compiler_version = project.compiler.compiler_version(&input);
144 let mut compiler_name = project.compiler.compiler_name(&input).into_owned();
145
146 let dependency = {
147 if config.polkadot.resolc_compile {
149 let names = compiler_name;
150 let mut names = names.split_whitespace();
151 compiler_name =
152 names.next().expect("Malformed compiler name").to_owned();
153 names
154 .last()
155 .map(|item| item.to_owned())
156 .zip(Some(version.clone()))
157 .map(|(name, version)| Dependency { name, version })
158 } else {
159 None
160 }
161 };
162
163 ResolvedCompiler {
164 name: compiler_name,
165 version: compiler_version,
166 evm_version,
167 paths,
168 dependency,
169 }
170 })
171 .filter(|version| !version.paths.is_empty())
172 .collect();
173
174 versions_with_paths.sort_by(|v1, v2| {
176 (&v1.version, &v1.dependency.as_ref().map(|x| &x.version))
177 .cmp(&(&v2.version, &v2.dependency.as_ref().map(|x| &x.version)))
178 });
179
180 if !versions_with_paths.is_empty() {
182 if shell::verbosity() == 0 {
185 versions_with_paths.iter_mut().for_each(|version| version.paths.clear());
186 }
187
188 output.insert(language.to_string(), versions_with_paths);
189 }
190 }
191
192 if shell::is_json() {
193 sh_println!("{}", serde_json::to_string(&output)?)?;
194 return Ok(());
195 }
196
197 for (language, compilers) in &output {
198 match shell::verbosity() {
199 0 => sh_println!("{language}:")?,
200 _ => sh_println!("{language}:\n")?,
201 }
202
203 for resolved_compiler in compilers {
204 let version = &resolved_compiler.version;
205 let extras =
206 if let Some(Dependency { name, version }) = &resolved_compiler.dependency {
207 format!(", {name} v{version}")
208 } else {
209 String::new()
210 };
211 match shell::verbosity() {
212 0 => sh_println!("- {} v{version}{}", resolved_compiler.name, extras)?,
213 _ => {
214 if let Some(evm) = &resolved_compiler.evm_version {
215 sh_println!(
216 "{} v{version}{} (<= {evm}):",
217 resolved_compiler.name,
218 extras
219 )?
220 } else {
221 sh_println!("{} v{version}{}:", resolved_compiler.name, extras)?
222 }
223 }
224 }
225
226 if shell::verbosity() > 0 {
227 let paths = &resolved_compiler.paths;
228 for (idx, path) in paths.iter().enumerate() {
229 if idx == paths.len() - 1 {
230 sh_println!("└── {path}\n")?
231 } else {
232 sh_println!("├── {path}")?
233 }
234 }
235 }
236 }
237
238 if shell::verbosity() == 0 {
239 sh_println!()?
240 }
241 }
242
243 Ok(())
244 }
245}