Skip to main content

foundry_cli/opts/build/
core.rs

1use super::ProjectPathOpts;
2use crate::{opts::CompilerOpts, utils::LoadConfig};
3use clap::{Parser, ValueHint};
4use eyre::Result;
5use foundry_compilers::{
6    Project,
7    artifacts::{RevertStrings, remappings::Remapping},
8    compilers::multi::MultiCompiler,
9    utils::canonicalized,
10};
11use foundry_config::{
12    Config, DenyLevel, Remappings,
13    figment::{
14        self, Figment, Metadata, Profile, Provider,
15        error::Kind::InvalidType,
16        value::{Dict, Map, Value},
17    },
18    filter::SkipBuildFilter,
19};
20use serde::Serialize;
21use std::path::PathBuf;
22
23#[derive(Clone, Debug, Default, Serialize, Parser)]
24#[command(next_help_heading = "Build options")]
25pub struct BuildOpts {
26    /// Clear the cache and artifacts folder and recompile.
27    #[arg(long, help_heading = "Cache options")]
28    #[serde(skip)]
29    pub force: bool,
30
31    /// Disable the cache.
32    #[arg(long)]
33    #[serde(skip)]
34    pub no_cache: bool,
35
36    /// Disable dynamic test linking.
37    #[arg(long, conflicts_with = "no_cache")]
38    #[serde(skip)]
39    pub no_dynamic_test_linking: bool,
40
41    /// Set pre-linked libraries.
42    #[arg(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")]
43    #[serde(skip_serializing_if = "Vec::is_empty")]
44    pub libraries: Vec<String>,
45
46    /// Ignore solc warnings by error code.
47    #[arg(long, help_heading = "Compiler options", value_name = "ERROR_CODES")]
48    #[serde(skip_serializing_if = "Vec::is_empty")]
49    pub ignored_error_codes: Vec<u64>,
50
51    /// A compiler error will be triggered at the specified diagnostic level.
52    ///
53    /// Replaces the deprecated `--deny-warnings` flag.
54    ///
55    /// Possible values:
56    ///  - `never`: Do not treat any diagnostics as errors.
57    ///  - `warnings`: Treat warnings as errors.
58    ///  - `notes`: Treat both, warnings and notes, as errors.
59    #[arg(
60        long,
61        short = 'D',
62        help_heading = "Compiler options",
63        value_name = "LEVEL",
64        conflicts_with = "deny_warnings"
65    )]
66    #[serde(skip)]
67    pub deny: Option<DenyLevel>,
68
69    /// Deprecated: use `--deny=warnings` instead.
70    #[arg(long = "deny-warnings", hide = true)]
71    pub deny_warnings: bool,
72
73    /// Do not auto-detect the `solc` version.
74    #[arg(long, help_heading = "Compiler options")]
75    #[serde(skip)]
76    pub no_auto_detect: bool,
77
78    /// Specify the solc version, or a path to a local solc, to build with.
79    ///
80    /// Valid values are in the format `x.y.z`, `solc:x.y.z` or `path/to/solc`.
81    #[arg(
82        long = "use",
83        alias = "compiler-version",
84        help_heading = "Compiler options",
85        value_name = "SOLC_VERSION"
86    )]
87    #[serde(skip)]
88    pub use_solc: Option<String>,
89
90    /// Do not access the network.
91    ///
92    /// Missing solc versions will not be installed.
93    #[arg(help_heading = "Compiler options", long)]
94    #[serde(skip)]
95    pub offline: bool,
96
97    /// Changes compilation to only use literal content and not URLs.
98    #[arg(long, help_heading = "Compiler options")]
99    #[serde(skip)]
100    pub use_literal_content: bool,
101
102    /// Do not append any metadata to the bytecode.
103    ///
104    /// This is equivalent to setting `bytecode_hash` to `none` and `cbor_metadata` to `false`.
105    #[arg(long, help_heading = "Compiler options")]
106    #[serde(skip)]
107    pub no_metadata: bool,
108
109    /// The path to the contract artifacts folder.
110    #[arg(
111        long = "out",
112        short,
113        help_heading = "Project options",
114        value_hint = ValueHint::DirPath,
115        value_name = "PATH",
116    )]
117    #[serde(rename = "out", skip_serializing_if = "Option::is_none")]
118    pub out_path: Option<PathBuf>,
119
120    /// Revert string configuration.
121    ///
122    /// Possible values are "default", "strip" (remove),
123    /// "debug" (Solidity-generated revert strings) and "verboseDebug"
124    #[arg(long, help_heading = "Project options", value_name = "REVERT")]
125    #[serde(skip)]
126    pub revert_strings: Option<RevertStrings>,
127
128    /// Generate build info files.
129    #[arg(long, help_heading = "Project options")]
130    #[serde(skip)]
131    pub build_info: bool,
132
133    /// Output path to directory that build info files will be written to.
134    #[arg(
135        long,
136        help_heading = "Project options",
137        value_hint = ValueHint::DirPath,
138        value_name = "PATH",
139        requires = "build_info",
140    )]
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub build_info_path: Option<PathBuf>,
143
144    /// Skip building files whose names contain the given filter.
145    ///
146    /// `test` and `script` are aliases for `.t.sol` and `.s.sol`.
147    #[arg(long, num_args(1..))]
148    #[serde(skip)]
149    pub skip: Option<Vec<SkipBuildFilter>>,
150
151    #[command(flatten)]
152    #[serde(flatten)]
153    pub compiler: CompilerOpts,
154
155    #[command(flatten)]
156    #[serde(flatten)]
157    pub project_paths: ProjectPathOpts,
158}
159
160impl BuildOpts {
161    /// Returns the `Project` for the current workspace
162    ///
163    /// This loads the `foundry_config::Config` for the current workspace (see
164    /// `find_project_root` and merges the cli `BuildArgs` into it before returning
165    /// [`foundry_config::Config::project()`]).
166    pub fn project(&self) -> Result<Project<MultiCompiler>> {
167        let config = self.load_config()?;
168        Ok(config.project()?)
169    }
170
171    /// Returns the remappings to add to the config
172    #[deprecated(note = "Use ProjectPathsArgs::get_remappings() instead")]
173    pub fn get_remappings(&self) -> Vec<Remapping> {
174        self.project_paths.get_remappings()
175    }
176}
177
178// Loads project's figment and merges the build cli arguments into it
179impl<'a> From<&'a BuildOpts> for Figment {
180    fn from(args: &'a BuildOpts) -> Self {
181        let root = if let Some(config_path) = &args.project_paths.config_path {
182            assert!(
183                config_path.exists(),
184                "error: config-path `{}` does not exist",
185                config_path.display()
186            );
187            assert!(
188                config_path.ends_with(Config::FILE_NAME),
189                "error: the config-path must be a path to a foundry.toml file"
190            );
191            let config_path = canonicalized(config_path);
192            config_path.parent().unwrap().to_path_buf()
193        } else {
194            args.project_paths.project_root()
195        };
196        let mut figment = Config::figment_with_root(root);
197
198        // remappings should stack
199        let mut remappings = Remappings::new_with_remappings(args.project_paths.get_remappings())
200            .with_figment(&figment);
201        remappings
202            .extend(figment.extract_inner::<Vec<Remapping>>("remappings").unwrap_or_default());
203        figment = figment.merge(("remappings", remappings.into_inner())).merge(args);
204
205        if let Some(skip) = &args.skip {
206            let mut skip = skip.iter().map(|s| s.file_pattern().to_string()).collect::<Vec<_>>();
207            skip.extend(figment.extract_inner::<Vec<String>>("skip").unwrap_or_default());
208            figment = figment.merge(("skip", skip));
209        };
210
211        figment
212    }
213}
214
215impl Provider for BuildOpts {
216    fn metadata(&self) -> Metadata {
217        Metadata::named("Core Build Args Provider")
218    }
219
220    fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
221        let value = Value::serialize(self)?;
222        let error = InvalidType(value.to_actual(), "map".into());
223        let mut dict = value.into_dict().ok_or(error)?;
224
225        if self.no_auto_detect {
226            dict.insert("auto_detect_solc".to_string(), false.into());
227        }
228
229        if let Some(ref solc) = self.use_solc {
230            dict.insert("solc".to_string(), solc.trim_start_matches("solc:").into());
231        }
232
233        if self.offline {
234            dict.insert("offline".to_string(), true.into());
235        }
236
237        if self.deny_warnings {
238            dict.insert("deny".to_string(), figment::value::Value::serialize(DenyLevel::Warnings)?);
239            _ = sh_warn!("`--deny-warnings` is being deprecated in favor of `--deny warnings`.");
240        } else if let Some(deny) = self.deny {
241            dict.insert("deny".to_string(), figment::value::Value::serialize(deny)?);
242        }
243
244        if self.use_literal_content {
245            dict.insert("use_literal_content".to_string(), true.into());
246        }
247
248        if self.no_metadata {
249            dict.insert("bytecode_hash".to_string(), "none".into());
250            dict.insert("cbor_metadata".to_string(), false.into());
251        }
252
253        if self.force {
254            dict.insert("force".to_string(), self.force.into());
255        }
256
257        // we need to ensure no_cache set accordingly
258        if self.no_cache {
259            dict.insert("cache".to_string(), false.into());
260        }
261
262        if self.no_dynamic_test_linking {
263            dict.insert("dynamic_test_linking".to_string(), false.into());
264        }
265
266        if self.build_info {
267            dict.insert("build_info".to_string(), self.build_info.into());
268        }
269
270        if self.compiler.ast {
271            dict.insert("ast".to_string(), true.into());
272        }
273
274        if let Some(optimize) = self.compiler.optimize {
275            dict.insert("optimizer".to_string(), optimize.into());
276        }
277
278        if self.compiler.via_ir {
279            dict.insert("via_ir".to_string(), true.into());
280        }
281
282        if self.compiler.via_ssa_cfg {
283            dict.insert("via_ssa_cfg".to_string(), true.into());
284        }
285
286        if self.compiler.experimental {
287            dict.insert("experimental".to_string(), true.into());
288        }
289
290        if !self.compiler.extra_output.is_empty() {
291            let selection: Vec<_> =
292                self.compiler.extra_output.iter().map(|s| s.to_string()).collect();
293            dict.insert("extra_output".to_string(), selection.into());
294        }
295
296        if !self.compiler.extra_output_files.is_empty() {
297            let selection: Vec<_> =
298                self.compiler.extra_output_files.iter().map(|s| s.to_string()).collect();
299            dict.insert("extra_output_files".to_string(), selection.into());
300        }
301
302        if let Some(ref revert) = self.revert_strings {
303            dict.insert("revert_strings".to_string(), revert.to_string().into());
304        }
305
306        Ok(Map::from([(Config::selected_profile(), dict)]))
307    }
308}