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