Skip to main content

foundry_cli/opts/
global.rs

1use 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_config::{Config, figment::Profile};
7use serde::{Deserialize, Serialize};
8
9/// Global arguments for the CLI.
10#[derive(Clone, Debug, Default, Serialize, Deserialize, Parser)]
11pub struct GlobalArgs {
12    /// Verbosity level of the log messages.
13    ///
14    /// Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv).
15    ///
16    /// Depending on the context the verbosity levels have different meanings.
17    ///
18    /// For example, the verbosity levels of the EVM are:
19    /// - 2 (-vv): Print logs for all tests.
20    /// - 3 (-vvv): Print execution traces for failing tests.
21    /// - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests.
22    /// - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes and
23    ///   backtraces with line numbers.
24    #[arg(help_heading = "Display options", global = true, short, long, verbatim_doc_comment, conflicts_with = "quiet", action = ArgAction::Count)]
25    verbosity: Verbosity,
26
27    /// Do not print log messages.
28    #[arg(help_heading = "Display options", global = true, short, long, alias = "silent")]
29    quiet: bool,
30
31    /// Format log messages as JSON.
32    #[arg(help_heading = "Display options", global = true, long, alias = "format-json", conflicts_with_all = &["quiet", "color"])]
33    json: bool,
34
35    /// Format log messages as Markdown.
36    #[arg(
37        help_heading = "Display options",
38        global = true,
39        long,
40        alias = "markdown",
41        conflicts_with = "json"
42    )]
43    md: bool,
44
45    /// The color of the log messages.
46    #[arg(help_heading = "Display options", global = true, long, value_enum)]
47    color: Option<ColorChoice>,
48
49    /// Number of threads to use. Specifying 0 defaults to the number of logical cores.
50    #[arg(global = true, long, short = 'j', visible_alias = "jobs")]
51    threads: Option<usize>,
52
53    /// The configuration profile to use.
54    #[arg(global = true, long, value_name = "PROFILE")]
55    profile: Option<Profile>,
56}
57
58impl GlobalArgs {
59    /// Check if `--markdown-help` was passed and print CLI reference as Markdown, then exit.
60    ///
61    /// This must be called **before** parsing arguments, since commands with required
62    /// subcommands would fail parsing before the flag is checked.
63    pub fn check_markdown_help<C: clap::CommandFactory>() {
64        if std::env::args_os().take_while(|a| a != "--").any(|a| a == "--markdown-help") {
65            // Pre-parse: `Shell` is not initialized yet, so `sh_*` is unavailable.
66            foundry_cli_markdown::print_help_markdown::<C>();
67            std::process::exit(0);
68        }
69    }
70
71    /// Initialize the global options.
72    pub fn init(&self) -> eyre::Result<()> {
73        if let Some(profile) = &self.profile
74            && let Err(selected) = Config::try_set_selected_profile(profile.clone())
75        {
76            eyre::bail!(
77                "configuration profile was already initialized as `{selected}`, cannot select \
78                 `{profile}`"
79            );
80        }
81        let _ = Config::selected_profile();
82
83        // Set the global shell.
84        let shell = self.shell();
85        // Argument takes precedence over the env var global color choice.
86        match shell.color_choice() {
87            ColorChoice::Auto => {}
88            ColorChoice::Always => yansi::enable(),
89            ColorChoice::Never => yansi::disable(),
90        }
91        shell.set();
92
93        // Initialize the thread pool only if `threads` was requested to avoid unnecessary overhead.
94        if self.threads.is_some() {
95            self.force_init_thread_pool()?;
96        }
97
98        // Display a warning message if the current version is not stable.
99        if IS_NIGHTLY_VERSION
100            && !self.json
101            && std::env::var_os("FOUNDRY_DISABLE_NIGHTLY_WARNING").is_none()
102        {
103            let _ = sh_warn!("{}", NIGHTLY_VERSION_WARNING_MESSAGE);
104        }
105
106        Ok(())
107    }
108
109    /// Create a new shell instance.
110    pub fn shell(&self) -> Shell {
111        let mode = match self.quiet {
112            true => OutputMode::Quiet,
113            false => OutputMode::Normal,
114        };
115        let color = self.json.then_some(ColorChoice::Never).or(self.color).unwrap_or_default();
116        let format = if self.json {
117            OutputFormat::Json
118        } else if self.md {
119            OutputFormat::Markdown
120        } else {
121            OutputFormat::Text
122        };
123
124        Shell::new_with(format, mode, color, self.verbosity)
125    }
126
127    /// Initialize the global thread pool.
128    pub fn force_init_thread_pool(&self) -> eyre::Result<()> {
129        init_thread_pool(self.threads.unwrap_or(0))
130    }
131
132    /// Creates a new tokio runtime.
133    #[track_caller]
134    pub fn tokio_runtime(&self) -> tokio::runtime::Runtime {
135        let mut builder = tokio::runtime::Builder::new_multi_thread();
136        if let Some(threads) = self.threads
137            && threads > 0
138        {
139            builder.worker_threads(threads);
140        }
141        builder.enable_all().build().expect("failed to create tokio runtime")
142    }
143
144    /// Creates a new tokio runtime and blocks on the future.
145    #[track_caller]
146    pub fn block_on<F: std::future::Future>(&self, future: F) -> F::Output {
147        self.tokio_runtime().block_on(future)
148    }
149}
150
151/// Initialize the global thread pool.
152pub fn init_thread_pool(threads: usize) -> eyre::Result<()> {
153    rayon::ThreadPoolBuilder::new()
154        .thread_name(|i| format!("foundry-{i}"))
155        .num_threads(threads)
156        .build_global()?;
157    Ok(())
158}