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 #[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 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(long, help_heading = "Compiler options")]
53 #[serde(skip)]
54 pub deny_warnings: bool,
55
56 #[arg(long, help_heading = "Compiler options")]
58 #[serde(skip)]
59 pub no_auto_detect: bool,
60
61 #[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 #[arg(help_heading = "Compiler options", long)]
77 #[serde(skip)]
78 pub offline: bool,
79
80 #[arg(long, help_heading = "Compiler options")]
82 #[serde(skip)]
83 pub via_ir: bool,
84
85 #[arg(long, help_heading = "Compiler options")]
87 #[serde(skip)]
88 pub use_literal_content: bool,
89
90 #[arg(long, help_heading = "Compiler options")]
94 #[serde(skip)]
95 pub no_metadata: bool,
96
97 #[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 #[arg(long, help_heading = "Project options", value_name = "REVERT")]
113 #[serde(skip)]
114 pub revert_strings: Option<RevertStrings>,
115
116 #[arg(long, help_heading = "Project options")]
118 #[serde(skip)]
119 pub build_info: bool,
120
121 #[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 #[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 pub fn project(&self) -> Result<Project<MultiCompiler>> {
155 let config = self.load_config()?;
156 Ok(config.project()?)
157 }
158
159 #[deprecated(note = "Use ProjectPathsArgs::get_remappings() instead")]
161 pub fn get_remappings(&self) -> Vec<Remapping> {
162 self.project_paths.get_remappings()
163 }
164}
165
166impl<'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 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 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}