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