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 providers::relative_remapping_preserving_context_boundary,
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 #[arg(long, help_heading = "Cache options")]
29 #[serde(skip)]
30 pub force: bool,
31
32 #[arg(long)]
34 #[serde(skip)]
35 pub no_cache: bool,
36
37 #[arg(long, conflicts_with = "no_cache")]
39 #[serde(skip)]
40 pub no_dynamic_test_linking: bool,
41
42 #[arg(long, help_heading = "Linker options")]
47 #[serde(skip)]
48 pub disallow_linked_libraries: bool,
49
50 #[arg(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")]
52 #[serde(skip_serializing_if = "Vec::is_empty")]
53 pub libraries: Vec<String>,
54
55 #[arg(long, help_heading = "Compiler options", value_name = "ERROR_CODES")]
57 #[serde(skip_serializing_if = "Vec::is_empty")]
58 pub ignored_error_codes: Vec<u64>,
59
60 #[arg(
69 long,
70 short = 'D',
71 help_heading = "Compiler options",
72 value_name = "LEVEL",
73 conflicts_with = "deny_warnings"
74 )]
75 #[serde(skip)]
76 pub deny: Option<DenyLevel>,
77
78 #[arg(long = "deny-warnings", hide = true)]
80 pub deny_warnings: bool,
81
82 #[arg(long, help_heading = "Compiler options")]
84 #[serde(skip)]
85 pub no_auto_detect: bool,
86
87 #[arg(
91 long = "use",
92 alias = "compiler-version",
93 help_heading = "Compiler options",
94 value_name = "SOLC_VERSION"
95 )]
96 #[serde(skip)]
97 pub use_solc: Option<String>,
98
99 #[arg(help_heading = "Compiler options", long)]
103 #[serde(skip)]
104 pub offline: bool,
105
106 #[arg(long, help_heading = "Compiler options")]
108 #[serde(skip)]
109 pub use_literal_content: bool,
110
111 #[arg(long, help_heading = "Compiler options")]
115 #[serde(skip)]
116 pub no_metadata: bool,
117
118 #[arg(
120 long = "out",
121 short,
122 help_heading = "Project options",
123 value_hint = ValueHint::DirPath,
124 value_name = "PATH",
125 )]
126 #[serde(rename = "out", skip_serializing_if = "Option::is_none")]
127 pub out_path: Option<PathBuf>,
128
129 #[arg(long, help_heading = "Project options", value_name = "REVERT")]
134 #[serde(skip)]
135 pub revert_strings: Option<RevertStrings>,
136
137 #[arg(long, help_heading = "Project options")]
139 #[serde(skip)]
140 pub build_info: bool,
141
142 #[arg(
144 long,
145 help_heading = "Project options",
146 value_hint = ValueHint::DirPath,
147 value_name = "PATH",
148 requires = "build_info",
149 )]
150 #[serde(skip_serializing_if = "Option::is_none")]
151 pub build_info_path: Option<PathBuf>,
152
153 #[arg(long, num_args(1..))]
157 #[serde(skip)]
158 pub skip: Option<Vec<SkipBuildFilter>>,
159
160 #[command(flatten)]
161 #[serde(flatten)]
162 pub compiler: CompilerOpts,
163
164 #[command(flatten)]
165 #[serde(flatten)]
166 pub project_paths: ProjectPathOpts,
167}
168
169impl BuildOpts {
170 pub fn project(&self) -> Result<Project<MultiCompiler>> {
176 let config = self.load_config()?;
177 Ok(config.project()?)
178 }
179
180 #[deprecated(note = "Use ProjectPathsArgs::get_remappings() instead")]
182 pub fn get_remappings(&self) -> Vec<Remapping> {
183 self.project_paths.get_remappings()
184 }
185}
186
187impl<'a> From<&'a BuildOpts> for Figment {
189 fn from(args: &'a BuildOpts) -> Self {
190 let root = args.project_paths.project_root();
191 let mut figment = Config::figment_with_root(&root);
192
193 let remappings_root = canonicalized(&root);
195 let cli_remappings = args
196 .project_paths
197 .get_remappings()
198 .into_iter()
199 .map(|remapping| {
200 let remapping = relative_remapping_preserving_context_boundary(remapping, &root)
201 .to_relative_remapping();
202 relative_remapping_preserving_context_boundary(remapping, &remappings_root)
203 .to_relative_remapping()
204 })
205 .collect();
206 let mut remappings = Remappings::new_with_remappings(cli_remappings).with_figment(&figment);
207 let generated_remappings = Remappings::generated_from_figment(&figment);
208 remappings.extend_with_config_remappings(
209 figment.extract_inner::<Vec<Remapping>>("remappings").unwrap_or_default(),
210 &generated_remappings,
211 );
212 let remappings = remappings
213 .into_inner()
214 .into_iter()
215 .map(|remapping| relative_remapping_preserving_context_boundary(remapping, &root))
216 .collect::<Vec<_>>();
217 figment = figment.merge(("remappings", remappings)).merge(args);
218
219 if let Some(skip) = &args.skip {
220 let mut skip = skip.iter().map(|s| s.file_pattern().to_string()).collect::<Vec<_>>();
221 skip.extend(figment.extract_inner::<Vec<String>>("skip").unwrap_or_default());
222 figment = figment.merge(("skip", skip));
223 };
224
225 figment
226 }
227}
228
229impl Provider for BuildOpts {
230 fn metadata(&self) -> Metadata {
231 Metadata::named("Core Build Args Provider")
232 }
233
234 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
235 let value = Value::serialize(self)?;
236 let error = InvalidType(value.to_actual(), "map".into());
237 let mut dict = value.into_dict().ok_or(error)?;
238
239 if self.no_auto_detect {
240 dict.insert("auto_detect_solc".to_string(), false.into());
241 }
242
243 if let Some(ref solc) = self.use_solc {
244 dict.insert("solc".to_string(), solc.trim_start_matches("solc:").into());
245 }
246
247 if self.offline {
248 dict.insert("offline".to_string(), true.into());
249 }
250
251 if self.deny_warnings {
252 dict.insert("deny".to_string(), figment::value::Value::serialize(DenyLevel::Warnings)?);
253 _ = sh_warn!("`--deny-warnings` is being deprecated in favor of `--deny warnings`.");
254 } else if let Some(deny) = self.deny {
255 dict.insert("deny".to_string(), figment::value::Value::serialize(deny)?);
256 }
257
258 if self.use_literal_content {
259 dict.insert("use_literal_content".to_string(), true.into());
260 }
261
262 if self.no_metadata {
263 dict.insert("bytecode_hash".to_string(), "none".into());
264 dict.insert("cbor_metadata".to_string(), false.into());
265 }
266
267 if self.force {
268 dict.insert("force".to_string(), self.force.into());
269 }
270
271 if self.no_cache {
273 dict.insert("cache".to_string(), false.into());
274 }
275
276 if self.no_dynamic_test_linking {
277 dict.insert("dynamic_test_linking".to_string(), false.into());
278 }
279
280 if self.build_info {
281 dict.insert("build_info".to_string(), self.build_info.into());
282 }
283
284 if self.compiler.ast {
285 dict.insert("ast".to_string(), true.into());
286 }
287
288 if let Some(optimize) = self.compiler.optimize {
289 dict.insert("optimizer".to_string(), optimize.into());
290 }
291
292 if self.compiler.via_ir {
293 dict.insert("via_ir".to_string(), true.into());
294 }
295
296 if self.compiler.via_ssa_cfg {
297 dict.insert("via_ssa_cfg".to_string(), true.into());
298 }
299
300 if self.compiler.experimental {
301 dict.insert("experimental".to_string(), true.into());
302 }
303
304 if !self.compiler.extra_output.is_empty() {
305 let selection: Vec<_> =
306 self.compiler.extra_output.iter().map(|s| s.to_string()).collect();
307 dict.insert("extra_output".to_string(), selection.into());
308 }
309
310 if !self.compiler.extra_output_files.is_empty() {
311 let selection: Vec<_> =
312 self.compiler.extra_output_files.iter().map(|s| s.to_string()).collect();
313 dict.insert("extra_output_files".to_string(), selection.into());
314 }
315
316 if let Some(ref revert) = self.revert_strings {
317 dict.insert("revert_strings".to_string(), revert.to_string().into());
318 }
319
320 let mut data = Map::from([(Config::selected_profile(), dict)]);
321 if self.disallow_linked_libraries {
322 data.entry(Profile::Global)
324 .or_default()
325 .insert("allow_linked_libraries".to_string(), false.into());
326 }
327 Ok(data)
328 }
329}