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,
7 utils::{cache_local_signatures, LoadConfig},
8};
9use foundry_common::{compile::ProjectCompiler, shell};
10use foundry_compilers::{
11 compilers::{multi::MultiCompilerLanguage, Language},
12 solc::SolcLanguage,
13 utils::source_files_iter,
14 Project, ProjectCompileOutput,
15};
16use foundry_config::{
17 figment::{
18 self,
19 error::Kind::InvalidType,
20 value::{Dict, Map, Value},
21 Metadata, Profile, Provider,
22 },
23 filter::expand_globs,
24 Config,
25};
26use serde::Serialize;
27use std::path::PathBuf;
28
29foundry_config::merge_impl_figment_convert!(BuildArgs, build);
30
31#[derive(Clone, Debug, Default, Serialize, Parser)]
43#[command(next_help_heading = "Build options", about = None, long_about = None)] pub struct BuildArgs {
45 #[serde(skip)]
47 pub paths: Option<Vec<PathBuf>>,
48
49 #[arg(long)]
51 #[serde(skip)]
52 pub names: bool,
53
54 #[arg(long)]
57 #[serde(skip)]
58 pub sizes: bool,
59
60 #[arg(long, alias = "ignore-initcode-size")]
62 #[serde(skip)]
63 pub ignore_eip_3860: bool,
64
65 #[command(flatten)]
66 #[serde(flatten)]
67 pub build: BuildOpts,
68
69 #[command(flatten)]
70 #[serde(skip)]
71 pub watch: WatchArgs,
72}
73
74impl BuildArgs {
75 pub fn run(self) -> Result<ProjectCompileOutput> {
76 let mut config = self.load_config()?;
77
78 if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings {
79 config = self.load_config()?;
81 }
82
83 let project = config.project()?;
84
85 let mut files = vec![];
87 if let Some(paths) = &self.paths {
88 for path in paths {
89 let joined = project.root().join(path);
90 let path = if joined.exists() { &joined } else { path };
91 files.extend(source_files_iter(path, MultiCompilerLanguage::FILE_EXTENSIONS));
92 }
93 if files.is_empty() {
94 eyre::bail!("No source files found in specified build paths.")
95 }
96 }
97
98 let format_json = shell::is_json();
99 let compiler = ProjectCompiler::new()
100 .files(files)
101 .dynamic_test_linking(config.dynamic_test_linking)
102 .print_names(self.names)
103 .print_sizes(self.sizes)
104 .ignore_eip_3860(self.ignore_eip_3860)
105 .bail(!format_json);
106
107 self.lint(&project, &config)?;
109 let output = compiler.compile(&project)?;
110
111 cache_local_signatures(&output)?;
113
114 if format_json && !self.names && !self.sizes {
115 sh_println!("{}", serde_json::to_string_pretty(&output.output())?)?;
116 }
117
118 Ok(output)
119 }
120
121 fn lint(&self, project: &Project, config: &Config) -> Result<()> {
122 let format_json = shell::is_json();
123 if project.compiler.solc.is_some() && config.lint.lint_on_build && !shell::is_quiet() {
124 let linter = SolidityLinter::new(config.project_paths())
125 .with_json_emitter(format_json)
126 .with_description(!format_json)
127 .with_severity(if config.lint.severity.is_empty() {
128 None
129 } else {
130 Some(config.lint.severity.clone())
131 })
132 .without_lints(if config.lint.exclude_lints.is_empty() {
133 None
134 } else {
135 Some(
136 config
137 .lint
138 .exclude_lints
139 .iter()
140 .filter_map(|s| forge_lint::sol::SolLint::try_from(s.as_str()).ok())
141 .collect(),
142 )
143 });
144
145 let ignored = expand_globs(&config.root, config.lint.ignore.iter())?
147 .iter()
148 .flat_map(foundry_common::fs::canonicalize_path)
149 .collect::<Vec<_>>();
150
151 let curr_dir = std::env::current_dir()?;
152 let input_files = config
153 .project_paths::<SolcLanguage>()
154 .input_files_iter()
155 .filter(|p| !(ignored.contains(p) || ignored.contains(&curr_dir.join(p))))
156 .collect::<Vec<_>>();
157
158 if !input_files.is_empty() {
159 linter.lint(&input_files);
160 }
161 }
162
163 Ok(())
164 }
165
166 pub fn project(&self) -> Result<Project> {
172 self.build.project()
173 }
174
175 pub fn is_watch(&self) -> bool {
177 self.watch.watch.is_some()
178 }
179
180 pub(crate) fn watchexec_config(&self) -> Result<watchexec::Config> {
182 self.watch.watchexec_config(|| {
185 let config = self.load_config()?;
186 let foundry_toml: PathBuf = config.root.join(Config::FILE_NAME);
187 Ok([config.src, config.test, config.script, foundry_toml])
188 })
189 }
190}
191
192impl Provider for BuildArgs {
194 fn metadata(&self) -> Metadata {
195 Metadata::named("Build Args Provider")
196 }
197
198 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
199 let value = Value::serialize(self)?;
200 let error = InvalidType(value.to_actual(), "map".into());
201 let mut dict = value.into_dict().ok_or(error)?;
202
203 if self.names {
204 dict.insert("names".to_string(), true.into());
205 }
206
207 if self.sizes {
208 dict.insert("sizes".to_string(), true.into());
209 }
210
211 if self.ignore_eip_3860 {
212 dict.insert("ignore_eip_3860".to_string(), true.into());
213 }
214
215 Ok(Map::from([(Config::selected_profile(), dict)]))
216 }
217}