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 #[arg(long, help_heading = "Cache options")]
28 #[serde(skip)]
29 pub force: bool,
30
31 #[arg(long)]
33 #[serde(skip)]
34 pub no_cache: bool,
35
36 #[arg(long, conflicts_with = "no_cache")]
38 #[serde(skip)]
39 pub no_dynamic_test_linking: bool,
40
41 #[arg(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")]
43 #[serde(skip_serializing_if = "Vec::is_empty")]
44 pub libraries: Vec<String>,
45
46 #[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 #[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 #[arg(long = "deny-warnings", hide = true)]
71 pub deny_warnings: bool,
72
73 #[arg(long, help_heading = "Compiler options")]
75 #[serde(skip)]
76 pub no_auto_detect: bool,
77
78 #[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 #[arg(help_heading = "Compiler options", long)]
94 #[serde(skip)]
95 pub offline: bool,
96
97 #[arg(long, help_heading = "Compiler options")]
99 #[serde(skip)]
100 pub use_literal_content: bool,
101
102 #[arg(long, help_heading = "Compiler options")]
106 #[serde(skip)]
107 pub no_metadata: bool,
108
109 #[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 #[arg(long, help_heading = "Project options", value_name = "REVERT")]
125 #[serde(skip)]
126 pub revert_strings: Option<RevertStrings>,
127
128 #[arg(long, help_heading = "Project options")]
130 #[serde(skip)]
131 pub build_info: bool,
132
133 #[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 #[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 pub fn project(&self) -> Result<Project<MultiCompiler>> {
167 let config = self.load_config()?;
168 Ok(config.project()?)
169 }
170
171 #[deprecated(note = "Use ProjectPathsArgs::get_remappings() instead")]
173 pub fn get_remappings(&self) -> Vec<Remapping> {
174 self.project_paths.get_remappings()
175 }
176}
177
178impl<'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 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 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}