forge/cmd/
build.rs
1use super::{install, watch::WatchArgs};
2use clap::Parser;
3use eyre::Result;
4use foundry_cli::{
5 opts::BuildOpts,
6 utils::{cache_local_signatures, LoadConfig},
7};
8use foundry_common::{compile::ProjectCompiler, shell};
9use foundry_compilers::{
10 compilers::{multi::MultiCompilerLanguage, Language},
11 utils::source_files_iter,
12 Project, ProjectCompileOutput,
13};
14use foundry_config::{
15 figment::{
16 self,
17 error::Kind::InvalidType,
18 value::{Dict, Map, Value},
19 Metadata, Profile, Provider,
20 },
21 Config,
22};
23use serde::Serialize;
24use std::path::PathBuf;
25
26foundry_config::merge_impl_figment_convert!(BuildArgs, build);
27
28#[derive(Clone, Debug, Default, Serialize, Parser)]
40#[command(next_help_heading = "Build options", about = None, long_about = None)] pub struct BuildArgs {
42 #[serde(skip)]
44 pub paths: Option<Vec<PathBuf>>,
45
46 #[arg(long)]
48 #[serde(skip)]
49 pub names: bool,
50
51 #[arg(long)]
54 #[serde(skip)]
55 pub sizes: bool,
56
57 #[arg(long, alias = "ignore-initcode-size")]
59 #[serde(skip)]
60 pub ignore_eip_3860: bool,
61
62 #[command(flatten)]
63 #[serde(flatten)]
64 pub build: BuildOpts,
65
66 #[command(flatten)]
67 #[serde(skip)]
68 pub watch: WatchArgs,
69}
70
71impl BuildArgs {
72 pub fn run(self) -> Result<ProjectCompileOutput> {
73 let mut config = self.load_config()?;
74
75 if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings {
76 config = self.load_config()?;
78 }
79
80 let project = config.project()?;
81
82 let mut files = vec![];
84 if let Some(paths) = &self.paths {
85 for path in paths {
86 let joined = project.root().join(path);
87 let path = if joined.exists() { &joined } else { path };
88 files.extend(source_files_iter(path, MultiCompilerLanguage::FILE_EXTENSIONS));
89 }
90 if files.is_empty() {
91 eyre::bail!("No source files found in specified build paths.")
92 }
93 }
94
95 let format_json = shell::is_json();
96 let compiler = ProjectCompiler::new()
97 .files(files)
98 .dynamic_test_linking(config.dynamic_test_linking)
99 .print_names(self.names)
100 .print_sizes(self.sizes)
101 .ignore_eip_3860(self.ignore_eip_3860)
102 .bail(!format_json);
103
104 let output = compiler.compile(&project)?;
105
106 cache_local_signatures(&output)?;
108
109 if format_json && !self.names && !self.sizes {
110 sh_println!("{}", serde_json::to_string_pretty(&output.output())?)?;
111 }
112
113 Ok(output)
114 }
115
116 pub fn project(&self) -> Result<Project> {
122 self.build.project()
123 }
124
125 pub fn is_watch(&self) -> bool {
127 self.watch.watch.is_some()
128 }
129
130 pub(crate) fn watchexec_config(&self) -> Result<watchexec::Config> {
132 self.watch.watchexec_config(|| {
135 let config = self.load_config()?;
136 let foundry_toml: PathBuf = config.root.join(Config::FILE_NAME);
137 Ok([config.src, config.test, config.script, foundry_toml])
138 })
139 }
140}
141
142impl Provider for BuildArgs {
144 fn metadata(&self) -> Metadata {
145 Metadata::named("Build Args Provider")
146 }
147
148 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
149 let value = Value::serialize(self)?;
150 let error = InvalidType(value.to_actual(), "map".into());
151 let mut dict = value.into_dict().ok_or(error)?;
152
153 if self.names {
154 dict.insert("names".to_string(), true.into());
155 }
156
157 if self.sizes {
158 dict.insert("sizes".to_string(), true.into());
159 }
160
161 if self.ignore_eip_3860 {
162 dict.insert("ignore_eip_3860".to_string(), true.into());
163 }
164
165 Ok(Map::from([(Config::selected_profile(), dict)]))
166 }
167}