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::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 Some(
97 EvmVersion::default()
98 .normalize_version_solc(version)
99 .unwrap_or_default(),
100 )
101 } else {
102 None
103 };
104
105 ResolvedCompiler { version: version.clone(), evm_version, paths }
106 })
107 .filter(|version| !version.paths.is_empty())
108 .collect();
109
110 versions_with_paths.sort_by(|v1, v2| Version::cmp(&v1.version, &v2.version));
112
113 if !versions_with_paths.is_empty() {
115 if shell::verbosity() == 0 {
118 versions_with_paths.iter_mut().for_each(|version| version.paths.clear());
119 }
120
121 output.insert(language.to_string(), versions_with_paths);
122 }
123 }
124
125 if shell::is_json() {
126 sh_println!("{}", serde_json::to_string(&output)?)?;
127 return Ok(());
128 }
129
130 for (language, compilers) in &output {
131 match shell::verbosity() {
132 0 => sh_println!("{language}:")?,
133 _ => sh_println!("{language}:\n")?,
134 }
135
136 for resolved_compiler in compilers {
137 let version = &resolved_compiler.version;
138 match shell::verbosity() {
139 0 => sh_println!("- {version}")?,
140 _ => {
141 if let Some(evm) = &resolved_compiler.evm_version {
142 sh_println!("{version} (<= {evm}):")?
143 } else {
144 sh_println!("{version}:")?
145 }
146 }
147 }
148
149 if shell::verbosity() > 0 {
150 let paths = &resolved_compiler.paths;
151 for (idx, path) in paths.iter().enumerate() {
152 if idx == paths.len() - 1 {
153 sh_println!("└── {path}\n")?
154 } else {
155 sh_println!("├── {path}")?
156 }
157 }
158 }
159 }
160
161 if shell::verbosity() == 0 {
162 sh_println!()?
163 }
164 }
165
166 Ok(())
167 }
168}