foundry_cli/opts/
global.rs1use clap::{ArgAction, Parser};
2use foundry_common::{
3 shell::{ColorChoice, OutputFormat, OutputMode, Shell, Verbosity},
4 version::{IS_NIGHTLY_VERSION, NIGHTLY_VERSION_WARNING_MESSAGE},
5};
6use foundry_compilers::error::{Result as CompilerResult, SolcError};
7use foundry_config::{Config, figment::Profile};
8use serde::{Deserialize, Serialize};
9use std::{
10 io::{self, IsTerminal, Write},
11 path::{Path, PathBuf},
12 sync::Mutex,
13};
14
15static LOCAL_COMPILER_APPROVALS: Mutex<Vec<(PathBuf, bool)>> = Mutex::new(Vec::new());
16
17fn ensure_local_compiler_approved(path: &Path) -> CompilerResult<()> {
18 let mut approvals = LOCAL_COMPILER_APPROVALS.lock().unwrap_or_else(|err| err.into_inner());
19 if let Some((_, approved)) = approvals.iter().find(|(approved_path, _)| approved_path == path) {
20 return if *approved { Ok(()) } else { Err(local_compiler_not_approved(path)) };
21 }
22
23 if !io::stdin().is_terminal() || !io::stderr().is_terminal() {
24 return Err(local_compiler_not_approved(path));
25 }
26
27 let mut stderr = io::stderr().lock();
28 writeln!(
29 stderr,
30 "Warning: this project is configured to use a local compiler executable:\n {path:?}\n\
31 Running this executable may execute arbitrary code.",
32 )
33 .map_err(|err| SolcError::msg(format!("failed to write compiler approval prompt: {err}")))?;
34 write!(stderr, "Do you trust this compiler and want to continue? [y/N] ")
35 .and_then(|_| stderr.flush())
36 .map_err(|err| {
37 SolcError::msg(format!("failed to write compiler approval prompt: {err}"))
38 })?;
39
40 let mut response = String::new();
41 io::stdin()
42 .read_line(&mut response)
43 .map_err(|err| SolcError::msg(format!("failed to read compiler approval: {err}")))?;
44 let approved = matches!(response.trim().to_ascii_lowercase().as_str(), "y" | "yes");
45 approvals.push((path.to_path_buf(), approved));
46
47 if approved { Ok(()) } else { Err(local_compiler_not_approved(path)) }
48}
49
50fn local_compiler_not_approved(path: &Path) -> SolcError {
51 SolcError::msg(format!(
52 "refusing to run unapproved local compiler {path:?}; pass `--allow-local-compiler` if you trust this executable"
53 ))
54}
55
56#[derive(Clone, Debug, Default, Serialize, Deserialize, Parser)]
58pub struct GlobalArgs {
59 #[arg(help_heading = "Display options", global = true, short, long, verbatim_doc_comment, conflicts_with = "quiet", action = ArgAction::Count)]
72 verbosity: Verbosity,
73
74 #[arg(help_heading = "Display options", global = true, short, long, alias = "silent")]
76 quiet: bool,
77
78 #[arg(help_heading = "Display options", global = true, long, alias = "format-json", conflicts_with_all = &["quiet", "color"])]
80 json: bool,
81
82 #[arg(
84 help_heading = "Display options",
85 global = true,
86 long,
87 alias = "markdown",
88 conflicts_with = "json"
89 )]
90 md: bool,
91
92 #[arg(help_heading = "Display options", global = true, long, value_enum)]
94 color: Option<ColorChoice>,
95
96 #[arg(global = true, long, short = 'j', visible_alias = "jobs")]
98 threads: Option<usize>,
99
100 #[arg(global = true, long, value_name = "PROFILE")]
102 profile: Option<Profile>,
103
104 #[arg(global = true, long, help_heading = "Compiler options")]
106 allow_local_compiler: bool,
107
108 #[arg(global = true, long, help_heading = "Project options")]
110 allow_project_env: bool,
111}
112
113impl GlobalArgs {
114 pub fn check_markdown_help<C: clap::CommandFactory>() {
119 if std::env::args().take_while(|a| a != "--").any(|a| a == "--markdown-help") {
120 foundry_cli_markdown::print_help_markdown::<C>();
122 std::process::exit(0);
123 }
124 }
125
126 pub fn init(&self) -> eyre::Result<()> {
128 if let Some(profile) = &self.profile
129 && let Err(selected) = Config::try_set_selected_profile(profile.clone())
130 {
131 eyre::bail!(
132 "configuration profile was already initialized as `{selected}`, cannot select \
133 `{profile}`"
134 );
135 }
136 let _ = Config::selected_profile();
137
138 let allow_local_compiler = self.allow_local_compiler;
139 foundry_compilers::set_compiler_approval_handler(move |path| {
140 if allow_local_compiler { Ok(()) } else { ensure_local_compiler_approved(path) }
141 });
142
143 let shell = self.shell();
145 match shell.color_choice() {
147 ColorChoice::Auto => {}
148 ColorChoice::Always => yansi::enable(),
149 ColorChoice::Never => yansi::disable(),
150 }
151 shell.set();
152
153 if self.threads.is_some() {
155 self.force_init_thread_pool()?;
156 }
157
158 if IS_NIGHTLY_VERSION
160 && !self.json
161 && std::env::var_os("FOUNDRY_DISABLE_NIGHTLY_WARNING").is_none()
162 {
163 let _ = sh_warn!("{}", NIGHTLY_VERSION_WARNING_MESSAGE);
164 }
165
166 Ok(())
167 }
168
169 pub fn shell(&self) -> Shell {
171 let mode = match self.quiet {
172 true => OutputMode::Quiet,
173 false => OutputMode::Normal,
174 };
175 let color = self.json.then_some(ColorChoice::Never).or(self.color).unwrap_or_default();
176 let format = if self.json {
177 OutputFormat::Json
178 } else if self.md {
179 OutputFormat::Markdown
180 } else {
181 OutputFormat::Text
182 };
183
184 Shell::new_with(format, mode, color, self.verbosity)
185 }
186
187 pub fn force_init_thread_pool(&self) -> eyre::Result<()> {
189 init_thread_pool(self.threads.unwrap_or(0))
190 }
191
192 #[track_caller]
194 pub fn tokio_runtime(&self) -> tokio::runtime::Runtime {
195 let mut builder = tokio::runtime::Builder::new_multi_thread();
196 if let Some(threads) = self.threads
197 && threads > 0
198 {
199 builder.worker_threads(threads);
200 }
201 builder.enable_all().build().expect("failed to create tokio runtime")
202 }
203
204 #[track_caller]
206 pub fn block_on<F: std::future::Future>(&self, future: F) -> F::Output {
207 self.tokio_runtime().block_on(future)
208 }
209}
210
211pub fn init_thread_pool(threads: usize) -> eyre::Result<()> {
213 rayon::ThreadPoolBuilder::new()
214 .thread_name(|i| format!("foundry-{i}"))
215 .num_threads(threads)
216 .build_global()?;
217 Ok(())
218}