1use super::{install, watch::WatchArgs};
2use clap::Parser;
3use eyre::{Result, eyre};
4use forge_lint::{linter::Linter, sol::SolidityLinter};
5use foundry_cli::{
6 opts::{BuildOpts, solar_pcx_from_build_opts},
7 utils::{LoadConfig, cache_local_signatures},
8};
9use foundry_common::{compile::ProjectCompiler, shell};
10use foundry_compilers::{
11 CompilationError, FileFilter, Project, ProjectCompileOutput,
12 compilers::{Language, multi::MultiCompilerLanguage},
13 multi::SolidityCompiler,
14 solc::SolcLanguage,
15 utils::source_files_iter,
16};
17use foundry_config::{
18 Config, SkipBuildFilters,
19 figment::{
20 self, Metadata, Profile, Provider,
21 error::Kind::InvalidType,
22 value::{Dict, Map, Value},
23 },
24 filter::expand_globs,
25 revive,
26};
27use serde::Serialize;
28use std::path::PathBuf;
29
30foundry_config::merge_impl_figment_convert!(BuildArgs, build);
31
32#[derive(Clone, Debug, Default, Serialize, Parser)]
44#[command(next_help_heading = "Build options", about = None, long_about = None)] pub struct BuildArgs {
46 #[serde(skip)]
48 pub paths: Option<Vec<PathBuf>>,
49
50 #[arg(long)]
52 #[serde(skip)]
53 pub names: bool,
54
55 #[arg(long)]
58 #[serde(skip)]
59 pub sizes: bool,
60
61 #[arg(long, alias = "ignore-initcode-size")]
63 #[serde(skip)]
64 pub ignore_eip_3860: bool,
65
66 #[command(flatten)]
67 #[serde(flatten)]
68 pub build: BuildOpts,
69
70 #[command(flatten)]
71 #[serde(skip)]
72 pub watch: WatchArgs,
73}
74
75impl BuildArgs {
76 pub fn run(self) -> Result<ProjectCompileOutput> {
77 let mut config = self.load_config()?;
78
79 if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings {
80 config = self.load_config()?;
82 }
83
84 let project = config.project()?;
85
86 let mut files = vec![];
88 if let Some(paths) = &self.paths {
89 for path in paths {
90 let joined = project.root().join(path);
91 let path = if joined.exists() { &joined } else { path };
92 files.extend(source_files_iter(path, MultiCompilerLanguage::FILE_EXTENSIONS));
93 }
94 if files.is_empty() {
95 eyre::bail!("No source files found in specified build paths.")
96 }
97 }
98
99 let format_json = shell::is_json();
100 let mut compiler = ProjectCompiler::new()
101 .files(files)
102 .dynamic_test_linking(config.dynamic_test_linking)
103 .print_names(self.names)
104 .print_sizes(self.sizes)
105 .ignore_eip_3860(self.ignore_eip_3860)
106 .bail(!format_json);
107
108 if config.polkadot.resolc_compile {
109 compiler =
110 compiler.size_limits(revive::CONTRACT_SIZE_LIMIT, revive::CONTRACT_SIZE_LIMIT);
111 }
112
113 let output = compiler.compile(&project)?;
114
115 cache_local_signatures(&output)?;
117
118 if format_json && !self.names && !self.sizes {
119 sh_println!("{}", serde_json::to_string_pretty(&output.output())?)?;
120 }
121
122 if config.lint.lint_on_build && !output.output().errors.iter().any(|e| e.is_error()) {
124 self.lint(&project, &config, self.paths.as_deref())
125 .map_err(|err| eyre!("Lint failed: {err}"))?;
126 }
127
128 Ok(output)
129 }
130
131 fn lint(&self, project: &Project, config: &Config, files: Option<&[PathBuf]>) -> Result<()> {
132 let format_json = shell::is_json();
133 if matches!(
134 &project.compiler.solidity,
135 SolidityCompiler::Solc(_) | SolidityCompiler::Resolc(_)
136 ) && !shell::is_quiet()
137 {
138 let linter = SolidityLinter::new(config.project_paths())
139 .with_json_emitter(format_json)
140 .with_description(!format_json)
141 .with_severity(if config.lint.severity.is_empty() {
142 None
143 } else {
144 Some(config.lint.severity.clone())
145 })
146 .without_lints(if config.lint.exclude_lints.is_empty() {
147 None
148 } else {
149 Some(
150 config
151 .lint
152 .exclude_lints
153 .iter()
154 .filter_map(|s| forge_lint::sol::SolLint::try_from(s.as_str()).ok())
155 .collect(),
156 )
157 });
158
159 let ignored = expand_globs(&config.root, config.lint.ignore.iter())?
161 .iter()
162 .flat_map(foundry_common::fs::canonicalize_path)
163 .collect::<Vec<_>>();
164
165 let skip = SkipBuildFilters::new(config.skip.clone(), config.root.clone());
166 let curr_dir = std::env::current_dir()?;
167 let input_files = config
168 .project_paths::<SolcLanguage>()
169 .input_files_iter()
170 .filter(|p| {
171 if let Some(files) = files {
173 return files.iter().any(|file| &curr_dir.join(file) == p);
174 }
175 skip.is_match(p)
176 && !(ignored.contains(p) || ignored.contains(&curr_dir.join(p)))
177 })
178 .collect::<Vec<_>>();
179
180 if !input_files.is_empty() {
181 let sess = linter.init();
182
183 let pcx = solar_pcx_from_build_opts(
184 &sess,
185 &self.build,
186 Some(project),
187 Some(&input_files),
188 )?;
189 linter.early_lint(&input_files, pcx);
190
191 let pcx = solar_pcx_from_build_opts(
192 &sess,
193 &self.build,
194 Some(project),
195 Some(&input_files),
196 )?;
197 linter.late_lint(&input_files, pcx);
198 }
199 }
200
201 Ok(())
202 }
203
204 pub fn project(&self) -> Result<Project> {
210 self.build.project()
211 }
212
213 pub fn is_watch(&self) -> bool {
215 self.watch.watch.is_some()
216 }
217
218 pub(crate) fn watchexec_config(&self) -> Result<watchexec::Config> {
220 self.watch.watchexec_config(|| {
223 let config = self.load_config()?;
224 let foundry_toml: PathBuf = config.root.join(Config::FILE_NAME);
225 Ok([config.src, config.test, config.script, foundry_toml])
226 })
227 }
228}
229
230impl Provider for BuildArgs {
232 fn metadata(&self) -> Metadata {
233 Metadata::named("Build Args Provider")
234 }
235
236 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
237 let value = Value::serialize(self)?;
238 let error = InvalidType(value.to_actual(), "map".into());
239 let mut dict = value.into_dict().ok_or(error)?;
240
241 if self.names {
242 dict.insert("names".to_string(), true.into());
243 }
244
245 if self.sizes {
246 dict.insert("sizes".to_string(), true.into());
247 }
248
249 if self.ignore_eip_3860 {
250 dict.insert("ignore_eip_3860".to_string(), true.into());
251 }
252
253 Ok(Map::from([(Config::selected_profile(), dict)]))
254 }
255}