Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rename ShellOtps to GlobalOpts #9313

Merged
merged 23 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
82a3ef9
rename ShellOpts to GlobalOpts
zerosnacks Nov 7, 2024
187eba2
prefer arg / command over clap
zerosnacks Nov 11, 2024
901923d
add global opts
zerosnacks Nov 11, 2024
0b04f7d
remove redundant GlobalOpts injection, only use where access to the g…
zerosnacks Nov 11, 2024
c73a44b
add global thread pool
zerosnacks Nov 11, 2024
be82c4b
add try_jobs method for global rayon pool
zerosnacks Nov 12, 2024
b0a4c20
limit unnecessary globalopts injection where shell::* is preferred
zerosnacks Nov 13, 2024
0aa262b
merge in master, fix conflicts
zerosnacks Nov 13, 2024
8282dd5
fix tests
zerosnacks Nov 13, 2024
f11f430
port custom threads iterator to use global rayon thread pool
zerosnacks Nov 13, 2024
21b1e49
remove redundant ignores
zerosnacks Nov 13, 2024
66650b7
remove leftover from merge conflict, fix clashing args with inlined g…
zerosnacks Nov 13, 2024
d4a0d6f
leftovers
zerosnacks Nov 13, 2024
99d43c9
add back global args in script args
zerosnacks Nov 13, 2024
2e6d9b6
Merge branch 'master' into zerosnacks/rename-shellotps-globalopts
zerosnacks Nov 13, 2024
d7fa024
fix unused global opts
zerosnacks Nov 13, 2024
dbb865b
ignore attempted multiple initializations of the global thread pool
zerosnacks Nov 14, 2024
6056a66
add init, default spawn with default rayon settings on forge test
zerosnacks Nov 14, 2024
3cc9af8
make test thread number configurable
zerosnacks Nov 14, 2024
247956b
add back max threads back test to reduce pressure
zerosnacks Nov 14, 2024
a222393
Merge branch 'master' into zerosnacks/rename-shellotps-globalopts
zerosnacks Nov 20, 2024
d7ddce5
remove global --jobs rayon pool, revert to current implementation
zerosnacks Nov 20, 2024
680ba16
fix import
zerosnacks Nov 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 7 additions & 5 deletions crates/anvil/src/anvil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use anvil::cmd::NodeArgs;
use clap::{CommandFactory, Parser, Subcommand};
use eyre::Result;
use foundry_cli::{opts::ShellOpts, utils};
use foundry_cli::{opts::GlobalOpts, utils};

#[cfg(all(feature = "jemalloc", unix))]
#[global_allocator]
Expand All @@ -13,14 +13,15 @@ static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
#[derive(Parser)]
#[command(name = "anvil", version = anvil::VERSION_MESSAGE, next_display_order = None)]
pub struct Anvil {
/// Include the global options.
#[command(flatten)]
pub global: GlobalOpts,

#[command(flatten)]
pub node: NodeArgs,

#[command(subcommand)]
pub cmd: Option<AnvilSubcommand>,

#[clap(flatten)]
pub shell: ShellOpts,
}

#[derive(Subcommand)]
Expand Down Expand Up @@ -48,7 +49,8 @@ fn run() -> Result<()> {
utils::load_dotenv();

let mut args = Anvil::parse();
args.shell.shell().set();
args.global.try_spawn()?;
args.global.shell().set();
args.node.evm_opts.resolve_rpc_alias();

if let Some(cmd) = &args.cmd {
Expand Down
9 changes: 5 additions & 4 deletions crates/cast/bin/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use alloy_primitives::{Address, B256, U256};
use alloy_rpc_types::BlockId;
use clap::{Parser, Subcommand, ValueHint};
use eyre::Result;
use foundry_cli::opts::{EtherscanOpts, RpcOpts, ShellOpts};
use foundry_cli::opts::{EtherscanOpts, GlobalOpts, RpcOpts};
use foundry_common::ens::NameOrAddress;
use std::{path::PathBuf, str::FromStr};

Expand All @@ -31,11 +31,12 @@ const VERSION_MESSAGE: &str = concat!(
next_display_order = None,
)]
pub struct Cast {
/// Include the global options.
#[command(flatten)]
pub global: GlobalOpts,

#[command(subcommand)]
pub cmd: CastSubcommand,

#[clap(flatten)]
pub shell: ShellOpts,
}

#[derive(Subcommand)]
Expand Down
53 changes: 26 additions & 27 deletions crates/cast/bin/cmd/create2.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use alloy_primitives::{hex, keccak256, Address, B256, U256};
use clap::Parser;
use eyre::{Result, WrapErr};
use foundry_cli::opts::GlobalOpts;
use rand::{rngs::StdRng, RngCore, SeedableRng};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use regex::RegexSetBuilder;
use std::{
num::NonZeroUsize,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
Expand All @@ -18,6 +19,10 @@ const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c";
/// CLI arguments for `cast create2`.
#[derive(Clone, Debug, Parser)]
pub struct Create2Args {
/// Include the global options.
#[command(flatten)]
pub global: GlobalOpts,

/// Prefix for the contract address.
#[arg(
long,
Expand Down Expand Up @@ -73,10 +78,6 @@ pub struct Create2Args {
#[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")]
init_code_hash: Option<String>,

/// Number of threads to use. Defaults to and caps at the number of logical cores.
#[arg(short, long)]
jobs: Option<NonZeroUsize>,

/// Address of the caller. Used for the first 20 bytes of the salt.
#[arg(long, value_name = "ADDRESS")]
caller: Option<Address>,
Expand All @@ -99,6 +100,7 @@ pub struct Create2Output {
impl Create2Args {
pub fn run(self) -> Result<Create2Output> {
let Self {
global,
starts_with,
ends_with,
matching,
Expand All @@ -107,10 +109,10 @@ impl Create2Args {
salt,
init_code,
init_code_hash,
jobs,
caller,
seed,
no_random,
..
} = self;

let init_code_hash = if let Some(init_code_hash) = init_code_hash {
Expand Down Expand Up @@ -167,13 +169,7 @@ impl Create2Args {

let regex = RegexSetBuilder::new(regexs).case_insensitive(!case_sensitive).build()?;

let mut n_threads = std::thread::available_parallelism().map_or(1, |n| n.get());
if let Some(jobs) = jobs {
n_threads = n_threads.min(jobs.get());
}
if cfg!(test) {
n_threads = n_threads.min(2);
}
let n_threads = global.jobs().unwrap_or(1);

let mut salt = B256::ZERO;
let remaining = if let Some(caller_address) = caller {
Expand All @@ -197,19 +193,21 @@ impl Create2Args {
sh_println!(
"Starting to generate deterministic contract address with {n_threads} threads..."
)?;
let mut handles = Vec::with_capacity(n_threads);
let found = Arc::new(AtomicBool::new(false));
let timer = Instant::now();

// Loops through all possible salts in parallel until a result is found.
// Each thread iterates over `(i..).step_by(n_threads)`.
for i in 0..n_threads {
zerosnacks marked this conversation as resolved.
Show resolved Hide resolved
// Create local copies for the thread.
let increment = n_threads;
let regex = regex.clone();
let regex_len = regex.patterns().len();
let found = Arc::clone(&found);
handles.push(std::thread::spawn(move || {
// Collect the first successful result from the parallel iterator.
let result = (0..n_threads)
.into_par_iter()
.find_map_any(|i| {
// Create local copies for the thread.
let increment = n_threads;
let regex = regex.clone();
let regex_len = regex.patterns().len();
let found = Arc::clone(&found);

// Read the first bytes of the salt as a usize to be able to increment it.
struct B256Aligned(B256, [usize; 0]);
let mut salt = B256Aligned(salt, []);
Expand All @@ -224,7 +222,7 @@ impl Create2Args {
loop {
// Stop if a result was found in another thread.
if found.load(Ordering::Relaxed) {
break None;
return None;
}

// Calculate the `CREATE2` address.
Expand All @@ -239,17 +237,18 @@ impl Create2Args {
if regex.matches(s).into_iter().count() == regex_len {
// Notify other threads that we found a result.
found.store(true, Ordering::Relaxed);
break Some((addr, salt.0));
// Return the successful address and salt.
return Some((addr, salt.0));
}

// Increment the salt for the next iteration.
*salt_word = salt_word.wrapping_add(increment);
}
}));
}
})
.expect("Failed to find a matching address");

let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::<Vec<_>>();
let (address, salt) = results.into_iter().next().unwrap();
// Extract the address and salt from the result.
let (address, salt) = result;
sh_println!("Successfully found contract address in {:?}", timer.elapsed())?;
sh_println!("Address: {address}")?;
sh_println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?;
Expand Down
4 changes: 3 additions & 1 deletion crates/cast/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ fn run() -> Result<()> {
utils::load_dotenv();
utils::subscriber();
utils::enable_paint();

let args = CastArgs::parse();
args.shell.shell().set();
args.global.try_spawn()?;
args.global.shell().set();
main_args(args)
}

Expand Down
12 changes: 12 additions & 0 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ Display options:
- 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests.
- 5 (-vvvvv): Print execution and setup traces for all tests.

Concurrency options:
-j, --jobs <JOBS>
Number of threads to use.

If 0, the number of threads will be equal to the number of logical CPUs.
If set to a value greater than 0, it will use that number of threads capped at the number
of logical CPUs.

If not provided it will not spawn the global thread pool.

[aliases: threads]

Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html

"#]]);
Expand Down
13 changes: 8 additions & 5 deletions crates/chisel/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use clap::{Parser, Subcommand};
use eyre::Context;
use foundry_cli::{
handler,
opts::{CoreBuildArgs, ShellOpts},
opts::{CoreBuildArgs, GlobalOpts},
utils::{self, LoadConfig},
};
use foundry_common::{evm::EvmArgs, fs};
Expand Down Expand Up @@ -50,12 +50,13 @@ const VERSION_MESSAGE: &str = concat!(
#[derive(Debug, Parser)]
#[command(name = "chisel", version = VERSION_MESSAGE)]
pub struct Chisel {
/// Include the global options.
#[command(flatten)]
pub global: GlobalOpts,

#[command(subcommand)]
pub cmd: Option<ChiselSubcommand>,

#[clap(flatten)]
pub shell: ShellOpts,

/// Path to a directory containing Solidity files to import, or path to a single Solidity file.
///
/// These files will be evaluated before the top-level of the
Expand Down Expand Up @@ -117,8 +118,10 @@ fn run() -> eyre::Result<()> {
handler::install();
utils::subscriber();
utils::load_dotenv();

let args = Chisel::parse();
args.shell.shell().set();
args.global.try_spawn()?;
DaniPopes marked this conversation as resolved.
Show resolved Hide resolved
args.global.shell().set();
main_args(args)
}

Expand Down
1 change: 1 addition & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dotenvy = "0.15"
eyre.workspace = true
futures.workspace = true
indicatif = "0.17"
rayon.workspace = true
regex = { workspace = true, default-features = false }
serde.workspace = true
strsim = "0.11"
Expand Down
115 changes: 115 additions & 0 deletions crates/cli/src/opts/global.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use clap::{ArgAction, Parser};
use foundry_common::shell::{ColorChoice, OutputFormat, OutputMode, Shell, Verbosity};
use rayon::{current_num_threads, ThreadPoolBuilder};
use serde::{Deserialize, Serialize};

/// Global options.
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, Parser)]
pub struct GlobalOpts {
/// Verbosity level of the log messages.
///
/// Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv).
///
/// Depending on the context the verbosity levels have different meanings.
///
/// For example, the verbosity levels of the EVM are:
/// - 2 (-vv): Print logs for all tests.
/// - 3 (-vvv): Print execution traces for failing tests.
/// - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests.
/// - 5 (-vvvvv): Print execution and setup traces for all tests.
#[clap(short, long, global = true, verbatim_doc_comment, conflicts_with = "quiet", action = ArgAction::Count, help_heading = "Display options")]
pub verbosity: Verbosity,

/// Do not print log messages.
#[clap(short, long, global = true, alias = "silent", help_heading = "Display options")]
quiet: bool,

/// Format log messages as JSON.
#[clap(
long,
global = true,
alias = "format-json",
conflicts_with_all = &["quiet", "color"],
help_heading = "Display options"
)]
json: bool,

/// The color of the log messages.
#[clap(long, global = true, value_enum, help_heading = "Display options")]
color: Option<ColorChoice>,

/// Number of threads to use.
///
/// If 0, the number of threads will be equal to the number of logical CPUs.
/// If set to a value greater than 0, it will use that number of threads capped at the number
/// of logical CPUs.
///
/// If not provided it will not spawn the global thread pool.
#[clap(
short,
long,
global = true,
verbatim_doc_comment,
visible_alias = "threads",
help_heading = "Concurrency options"
)]
jobs: Option<usize>,
}

impl GlobalOpts {
/// Spawn a new global thread pool.
pub fn try_spawn(self) -> Result<(), rayon::ThreadPoolBuildError> {
if let Some(jobs) = self.jobs() {
// Attempt to spawn the global thread pool with the specified number of threads.
// If it is already initialized simply return.
if ThreadPoolBuilder::new().num_threads(jobs).build_global().is_err() {
return Ok(());
}

trace!(target: "forge::cli", "starting global thread pool with up to {} threads", jobs);

Ok(())
} else {
// If `--jobs` is not provided, do not spawn the global thread pool.
Ok(())
}
}

/// Get the number of threads to use.
///
/// Try to use the number of threads specified by `--jobs` if provided, otherwise use the number
/// of logical CPUs. If running tests, use at least 2 threads.
pub fn jobs(&self) -> Option<usize> {
let num_threads = self.jobs.map(|jobs| {
if jobs == 0 {
current_num_threads()
} else {
jobs.min(current_num_threads())
}
});

// If we are running tests, we want to use at least 2 threads.
if cfg!(test) {
if let Some(num_threads) = num_threads {
return Some(num_threads.min(2));
}
}
DaniPopes marked this conversation as resolved.
Show resolved Hide resolved

num_threads
}

/// Create a new shell instance.
pub fn shell(self) -> Shell {
let mode = match self.quiet {
true => OutputMode::Quiet,
false => OutputMode::Normal,
};
let color = self.json.then_some(ColorChoice::Never).or(self.color).unwrap_or_default();
let format = match self.json {
true => OutputFormat::Json,
false => OutputFormat::Text,
};

Shell::new_with(format, mode, color, self.verbosity)
}
}
Loading
Loading