Skip to main content

forge/cmd/
compiler.rs

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/// CLI arguments for `forge compiler`.
13#[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    /// Retrieves the resolved version(s) of the compiler within the project.
30    #[command(visible_alias = "r")]
31    Resolve(ResolveArgs),
32}
33
34/// Dependency info struct, exists only because tuple gets serialized as an array.
35#[derive(Serialize)]
36struct Dependency {
37    name: String,
38    version: Version,
39}
40
41/// Resolved compiler within the project.
42#[derive(Serialize)]
43struct ResolvedCompiler {
44    /// Compiler name
45    name: String,
46    /// Compiler version.
47    version: Version,
48    /// Max supported EVM version of compiler.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    evm_version: Option<EvmVersion>,
51    /// Source paths.
52    #[serde(skip_serializing_if = "Vec::is_empty")]
53    paths: Vec<String>,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    /// dependency of the compiler
56    dependency: Option<Dependency>,
57}
58
59/// CLI arguments for `forge compiler resolve`.
60#[derive(Debug, Parser)]
61pub struct ResolveArgs {
62    /// The root directory
63    #[arg(long, short, value_hint = ValueHint::DirPath, value_name = "PATH")]
64    root: Option<PathBuf>,
65
66    /// Skip files that match the given regex pattern.
67    #[arg(long, short, value_name = "REGEX")]
68    skip: Option<regex::Regex>,
69
70    /// Use resolc.
71    #[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                            // Skip files that match the given regex pattern.
118                            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                        // `Input.version` will always differ from `compiler_version`
148                        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            // Sort by SemVer version.
175            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            // Skip language if no paths are found after filtering.
181            if !versions_with_paths.is_empty() {
182                // Clear paths if verbosity is 0, performed only after filtering to avoid being
183                // skipped.
184                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}