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