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 #[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 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(long, help_heading = "Compiler options")]
54 #[serde(skip)]
55 pub deny_warnings: bool,
56
57 #[arg(long, help_heading = "Compiler options")]
59 #[serde(skip)]
60 pub no_auto_detect: bool,
61
62 #[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 #[arg(help_heading = "Compiler options", long)]
78 #[serde(skip)]
79 pub offline: bool,
80
81 #[arg(long, help_heading = "Compiler options")]
83 #[serde(skip)]
84 pub via_ir: bool,
85
86 #[arg(long, help_heading = "Compiler options")]
88 #[serde(skip)]
89 pub use_literal_content: bool,
90
91 #[arg(long, help_heading = "Compiler options")]
95 #[serde(skip)]
96 pub no_metadata: bool,
97
98 #[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 #[arg(long, help_heading = "Project options", value_name = "REVERT")]
114 #[serde(skip)]
115 pub revert_strings: Option<RevertStrings>,
116
117 #[arg(long, help_heading = "Project options")]
119 #[serde(skip)]
120 pub build_info: bool,
121
122 #[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 #[arg(long)]
135 #[serde(skip)]
136 pub eof: bool,
137
138 #[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 pub fn project(&self) -> Result<Project<MultiCompiler>> {
161 let config = self.load_config()?;
162 Ok(config.project()?)
163 }
164
165 #[deprecated(note = "Use ProjectPathsArgs::get_remappings() instead")]
167 pub fn get_remappings(&self) -> Vec<Remapping> {
168 self.project_paths.get_remappings()
169 }
170}
171
172impl<'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 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 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}