forge/cmd/
build.rs
1use super::{install, watch::WatchArgs};
2use clap::Parser;
3use eyre::Result;
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 solc::SolcLanguage,
14 utils::source_files_iter,
15};
16use foundry_config::{
17 Config, SkipBuildFilters,
18 figment::{
19 self, Metadata, Profile, Provider,
20 error::Kind::InvalidType,
21 value::{Dict, Map, Value},
22 },
23 filter::expand_globs,
24};
25use serde::Serialize;
26use std::path::PathBuf;
27
28foundry_config::merge_impl_figment_convert!(BuildArgs, build);
29
30#[derive(Clone, Debug, Default, Serialize, Parser)]
42#[command(next_help_heading = "Build options", about = None, long_about = None)] pub struct BuildArgs {
44 #[serde(skip)]
46 pub paths: Option<Vec<PathBuf>>,
47
48 #[arg(long)]
50 #[serde(skip)]
51 pub names: bool,
52
53 #[arg(long)]
56 #[serde(skip)]
57 pub sizes: bool,
58
59 #[arg(long, alias = "ignore-initcode-size")]
61 #[serde(skip)]
62 pub ignore_eip_3860: bool,
63
64 #[command(flatten)]
65 #[serde(flatten)]
66 pub build: BuildOpts,
67
68 #[command(flatten)]
69 #[serde(skip)]
70 pub watch: WatchArgs,
71}
72
73impl BuildArgs {
74 pub fn run(self) -> Result<ProjectCompileOutput> {
75 let mut config = self.load_config()?;
76
77 if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings {
78 config = self.load_config()?;
80 }
81
82 let project = config.project()?;
83
84 let mut files = vec![];
86 if let Some(paths) = &self.paths {
87 for path in paths {
88 let joined = project.root().join(path);
89 let path = if joined.exists() { &joined } else { path };
90 files.extend(source_files_iter(path, MultiCompilerLanguage::FILE_EXTENSIONS));
91 }
92 if files.is_empty() {
93 eyre::bail!("No source files found in specified build paths.")
94 }
95 }
96
97 let format_json = shell::is_json();
98 let compiler = ProjectCompiler::new()
99 .files(files)
100 .dynamic_test_linking(config.dynamic_test_linking)
101 .print_names(self.names)
102 .print_sizes(self.sizes)
103 .ignore_eip_3860(self.ignore_eip_3860)
104 .bail(!format_json);
105
106 let output = compiler.compile(&project)?;
107
108 cache_local_signatures(&output)?;
110
111 if format_json && !self.names && !self.sizes {
112 sh_println!("{}", serde_json::to_string_pretty(&output.output())?)?;
113 }
114
115 if output.output().errors.iter().all(|e| !e.is_error()) {
117 self.lint(&project, &config)?;
118 }
119
120 Ok(output)
121 }
122
123 fn lint(&self, project: &Project, config: &Config) -> Result<()> {
124 let format_json = shell::is_json();
125 if project.compiler.solc.is_some() && config.lint.lint_on_build && !shell::is_quiet() {
126 let linter = SolidityLinter::new(config.project_paths())
127 .with_json_emitter(format_json)
128 .with_description(!format_json)
129 .with_severity(if config.lint.severity.is_empty() {
130 None
131 } else {
132 Some(config.lint.severity.clone())
133 })
134 .without_lints(if config.lint.exclude_lints.is_empty() {
135 None
136 } else {
137 Some(
138 config
139 .lint
140 .exclude_lints
141 .iter()
142 .filter_map(|s| forge_lint::sol::SolLint::try_from(s.as_str()).ok())
143 .collect(),
144 )
145 });
146
147 let ignored = expand_globs(&config.root, config.lint.ignore.iter())?
149 .iter()
150 .flat_map(foundry_common::fs::canonicalize_path)
151 .collect::<Vec<_>>();
152
153 let skip = SkipBuildFilters::new(config.skip.clone(), config.root.clone());
154 let curr_dir = std::env::current_dir()?;
155 let input_files = config
156 .project_paths::<SolcLanguage>()
157 .input_files_iter()
158 .filter(|p| {
159 skip.is_match(p)
160 && !(ignored.contains(p) || ignored.contains(&curr_dir.join(p)))
161 })
162 .collect::<Vec<_>>();
163
164 if !input_files.is_empty() {
165 let sess = linter.init();
166
167 let pcx = solar_pcx_from_build_opts(
168 &sess,
169 &self.build,
170 Some(project),
171 Some(&input_files),
172 )?;
173 linter.early_lint(&input_files, pcx);
174
175 let pcx = solar_pcx_from_build_opts(
176 &sess,
177 &self.build,
178 Some(project),
179 Some(&input_files),
180 )?;
181 linter.late_lint(&input_files, pcx);
182 }
183 }
184
185 Ok(())
186 }
187
188 pub fn project(&self) -> Result<Project> {
194 self.build.project()
195 }
196
197 pub fn is_watch(&self) -> bool {
199 self.watch.watch.is_some()
200 }
201
202 pub(crate) fn watchexec_config(&self) -> Result<watchexec::Config> {
204 self.watch.watchexec_config(|| {
207 let config = self.load_config()?;
208 let foundry_toml: PathBuf = config.root.join(Config::FILE_NAME);
209 Ok([config.src, config.test, config.script, foundry_toml])
210 })
211 }
212}
213
214impl Provider for BuildArgs {
216 fn metadata(&self) -> Metadata {
217 Metadata::named("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.names {
226 dict.insert("names".to_string(), true.into());
227 }
228
229 if self.sizes {
230 dict.insert("sizes".to_string(), true.into());
231 }
232
233 if self.ignore_eip_3860 {
234 dict.insert("ignore_eip_3860".to_string(), true.into());
235 }
236
237 Ok(Map::from([(Config::selected_profile(), dict)]))
238 }
239}