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