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", env = "DAPP_LIBRARIES")]
44 #[serde(skip_serializing_if = "Vec::is_empty")]
45 pub libraries: Vec<String>,
46
47 #[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 #[arg(
61 long,
62 short = 'D',
63 help_heading = "Compiler options",
64 value_name = "LEVEL",
65 conflicts_with = "deny_warnings"
66 )]
67 #[serde(skip)]
68 pub deny: Option<DenyLevel>,
69
70 #[arg(long = "deny-warnings", hide = true)]
72 pub deny_warnings: bool,
73
74 #[arg(long, help_heading = "Compiler options")]
76 #[serde(skip)]
77 pub no_auto_detect: bool,
78
79 #[arg(
83 long = "use",
84 alias = "compiler-version",
85 help_heading = "Compiler options",
86 value_name = "SOLC_VERSION"
87 )]
88 #[serde(skip)]
89 pub use_solc: Option<String>,
90
91 #[arg(help_heading = "Compiler options", long)]
95 #[serde(skip)]
96 pub offline: bool,
97
98 #[arg(long, help_heading = "Compiler options")]
100 #[serde(skip)]
101 pub use_literal_content: bool,
102
103 #[arg(long, help_heading = "Compiler options")]
107 #[serde(skip)]
108 pub no_metadata: bool,
109
110 #[arg(
112 long = "out",
113 short,
114 help_heading = "Project options",
115 value_hint = ValueHint::DirPath,
116 value_name = "PATH",
117 )]
118 #[serde(rename = "out", skip_serializing_if = "Option::is_none")]
119 pub out_path: Option<PathBuf>,
120
121 #[arg(long, help_heading = "Project options", value_name = "REVERT")]
126 #[serde(skip)]
127 pub revert_strings: Option<RevertStrings>,
128
129 #[arg(long, help_heading = "Project options")]
131 #[serde(skip)]
132 pub build_info: bool,
133
134 #[arg(
136 long,
137 help_heading = "Project options",
138 value_hint = ValueHint::DirPath,
139 value_name = "PATH",
140 requires = "build_info",
141 )]
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub build_info_path: Option<PathBuf>,
144
145 #[arg(long, num_args(1..))]
149 #[serde(skip)]
150 pub skip: Option<Vec<SkipBuildFilter>>,
151
152 #[command(flatten)]
153 #[serde(flatten)]
154 pub compiler: CompilerOpts,
155
156 #[command(flatten)]
157 #[serde(flatten)]
158 pub project_paths: ProjectPathOpts,
159}
160
161impl BuildOpts {
162 pub fn project(&self) -> Result<Project<MultiCompiler>> {
168 let config = self.load_config()?;
169 Ok(config.project()?)
170 }
171
172 #[deprecated(note = "Use ProjectPathsArgs::get_remappings() instead")]
174 pub fn get_remappings(&self) -> Vec<Remapping> {
175 self.project_paths.get_remappings()
176 }
177}
178
179impl<'a> From<&'a BuildOpts> for Figment {
181 fn from(args: &'a BuildOpts) -> Self {
182 let root = if let Some(config_path) = &args.project_paths.config_path {
183 assert!(
184 config_path.exists(),
185 "error: config-path `{}` does not exist",
186 config_path.display()
187 );
188 assert!(
189 config_path.ends_with(Config::FILE_NAME),
190 "error: the config-path must be a path to a foundry.toml file"
191 );
192 let config_path = canonicalized(config_path);
193 config_path.parent().unwrap().to_path_buf()
194 } else {
195 args.project_paths.project_root()
196 };
197 let mut figment = Config::figment_with_root(&root);
198
199 let remappings_root = canonicalized(&root);
201 let cli_remappings = args
202 .project_paths
203 .get_remappings()
204 .into_iter()
205 .map(|remapping| {
206 let remapping = relative_remapping_preserving_context_boundary(remapping, &root)
207 .to_relative_remapping();
208 relative_remapping_preserving_context_boundary(remapping, &remappings_root)
209 .to_relative_remapping()
210 })
211 .collect();
212 let mut remappings = Remappings::new_with_remappings(cli_remappings).with_figment(&figment);
213 let generated_remappings = Remappings::generated_from_figment(&figment);
214 remappings.extend_with_config_remappings(
215 figment.extract_inner::<Vec<Remapping>>("remappings").unwrap_or_default(),
216 &generated_remappings,
217 );
218 let remappings = remappings
219 .into_inner()
220 .into_iter()
221 .map(|remapping| relative_remapping_preserving_context_boundary(remapping, &root))
222 .collect::<Vec<_>>();
223 figment = figment.merge(("remappings", remappings)).merge(args);
224
225 if let Some(skip) = &args.skip {
226 let mut skip = skip.iter().map(|s| s.file_pattern().to_string()).collect::<Vec<_>>();
227 skip.extend(figment.extract_inner::<Vec<String>>("skip").unwrap_or_default());
228 figment = figment.merge(("skip", skip));
229 };
230
231 figment
232 }
233}
234
235impl Provider for BuildOpts {
236 fn metadata(&self) -> Metadata {
237 Metadata::named("Core Build Args Provider")
238 }
239
240 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
241 let value = Value::serialize(self)?;
242 let error = InvalidType(value.to_actual(), "map".into());
243 let mut dict = value.into_dict().ok_or(error)?;
244
245 if self.no_auto_detect {
246 dict.insert("auto_detect_solc".to_string(), false.into());
247 }
248
249 if let Some(ref solc) = self.use_solc {
250 dict.insert("solc".to_string(), solc.trim_start_matches("solc:").into());
251 }
252
253 if self.offline {
254 dict.insert("offline".to_string(), true.into());
255 }
256
257 if self.deny_warnings {
258 dict.insert("deny".to_string(), figment::value::Value::serialize(DenyLevel::Warnings)?);
259 _ = sh_warn!("`--deny-warnings` is being deprecated in favor of `--deny warnings`.");
260 } else if let Some(deny) = self.deny {
261 dict.insert("deny".to_string(), figment::value::Value::serialize(deny)?);
262 }
263
264 if self.use_literal_content {
265 dict.insert("use_literal_content".to_string(), true.into());
266 }
267
268 if self.no_metadata {
269 dict.insert("bytecode_hash".to_string(), "none".into());
270 dict.insert("cbor_metadata".to_string(), false.into());
271 }
272
273 if self.force {
274 dict.insert("force".to_string(), self.force.into());
275 }
276
277 if self.no_cache {
279 dict.insert("cache".to_string(), false.into());
280 }
281
282 if self.no_dynamic_test_linking {
283 dict.insert("dynamic_test_linking".to_string(), false.into());
284 }
285
286 if self.build_info {
287 dict.insert("build_info".to_string(), self.build_info.into());
288 }
289
290 if self.compiler.ast {
291 dict.insert("ast".to_string(), true.into());
292 }
293
294 if let Some(optimize) = self.compiler.optimize {
295 dict.insert("optimizer".to_string(), optimize.into());
296 }
297
298 if self.compiler.via_ir {
299 dict.insert("via_ir".to_string(), true.into());
300 }
301
302 if self.compiler.via_ssa_cfg {
303 dict.insert("via_ssa_cfg".to_string(), true.into());
304 }
305
306 if self.compiler.experimental {
307 dict.insert("experimental".to_string(), true.into());
308 }
309
310 if !self.compiler.extra_output.is_empty() {
311 let selection: Vec<_> =
312 self.compiler.extra_output.iter().map(|s| s.to_string()).collect();
313 dict.insert("extra_output".to_string(), selection.into());
314 }
315
316 if !self.compiler.extra_output_files.is_empty() {
317 let selection: Vec<_> =
318 self.compiler.extra_output_files.iter().map(|s| s.to_string()).collect();
319 dict.insert("extra_output_files".to_string(), selection.into());
320 }
321
322 if let Some(ref revert) = self.revert_strings {
323 dict.insert("revert_strings".to_string(), revert.to_string().into());
324 }
325
326 Ok(Map::from([(Config::selected_profile(), dict)]))
327 }
328}