1use clap::{Parser, Subcommand, ValueHint};
2use eyre::Result;
3use foundry_common::shell;
4use foundry_compilers::{Graph, artifacts::EvmVersion};
5use foundry_config::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 .keys()
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 && regex.is_match(&path_str)
87 {
88 return None;
89 }
90
91 Some(path_str)
92 })
93 .collect();
94
95 let evm_version = (shell::verbosity() > 1).then(|| {
96 EvmVersion::default().normalize_version_solc(version).unwrap_or_default()
97 });
98
99 ResolvedCompiler { version: version.clone(), evm_version, paths }
100 })
101 .filter(|version| !version.paths.is_empty())
102 .collect();
103
104 versions_with_paths.sort_by(|v1, v2| Version::cmp(&v1.version, &v2.version));
106
107 if !versions_with_paths.is_empty() {
109 if shell::verbosity() == 0 {
112 for version in &mut versions_with_paths {
113 version.paths.clear();
114 }
115 }
116
117 output.insert(language.to_string(), versions_with_paths);
118 }
119 }
120
121 if shell::is_json() {
122 sh_println!("{}", serde_json::to_string(&output)?)?;
123 return Ok(());
124 }
125
126 for (language, compilers) in &output {
127 match shell::verbosity() {
128 0 => sh_println!("{language}:")?,
129 _ => sh_println!("{language}:\n")?,
130 }
131
132 for resolved_compiler in compilers {
133 let version = &resolved_compiler.version;
134 match shell::verbosity() {
135 0 => sh_println!("- {version}")?,
136 _ => {
137 if let Some(evm) = &resolved_compiler.evm_version {
138 sh_println!("{version} (<= {evm}):")?
139 } else {
140 sh_println!("{version}:")?
141 }
142 }
143 }
144
145 if shell::verbosity() > 0 {
146 let paths = &resolved_compiler.paths;
147 for (idx, path) in paths.iter().enumerate() {
148 if idx == paths.len() - 1 {
149 sh_println!("└── {path}\n")?
150 } else {
151 sh_println!("├── {path}")?
152 }
153 }
154 }
155 }
156
157 if shell::verbosity() == 0 {
158 sh_println!()?
159 }
160 }
161
162 Ok(())
163 }
164}