Generate a script to rerun the failed sub-command in cargo-test-bpf (#18582)
* Generate a script to rerun the failed sub-command in cargo-test-bpf * Generate a script to rerun the failed sub-command in cargo-build-bpf * Add cargo-build-bpf test for generate-child-script-on-failure option
This commit is contained in:
parent
ad3f18f031
commit
6f72f8b1fc
|
@ -27,3 +27,5 @@ log-*/
|
||||||
/spl_*.so
|
/spl_*.so
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
# scripts that may be generated by cargo *-bpf commands
|
||||||
|
**/cargo-*-bpf-child-script-*.sh
|
||||||
|
|
|
@ -26,6 +26,7 @@ struct Config<'a> {
|
||||||
bpf_sdk: PathBuf,
|
bpf_sdk: PathBuf,
|
||||||
dump: bool,
|
dump: bool,
|
||||||
features: Vec<String>,
|
features: Vec<String>,
|
||||||
|
generate_child_script_on_failure: bool,
|
||||||
no_default_features: bool,
|
no_default_features: bool,
|
||||||
offline: bool,
|
offline: bool,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
|
@ -46,6 +47,7 @@ impl Default for Config<'_> {
|
||||||
bpf_out_dir: None,
|
bpf_out_dir: None,
|
||||||
dump: false,
|
dump: false,
|
||||||
features: vec![],
|
features: vec![],
|
||||||
|
generate_child_script_on_failure: false,
|
||||||
no_default_features: false,
|
no_default_features: false,
|
||||||
offline: false,
|
offline: false,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
@ -54,13 +56,13 @@ impl Default for Config<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn<I, S>(program: &Path, args: I) -> String
|
fn spawn<I, S>(program: &Path, args: I, generate_child_script_on_failure: bool) -> String
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = S>,
|
I: IntoIterator<Item = S>,
|
||||||
S: AsRef<OsStr>,
|
S: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
let args = args.into_iter().collect::<Vec<_>>();
|
let args = args.into_iter().collect::<Vec<_>>();
|
||||||
print!("Running: {}", program.display());
|
print!("cargo-build-bpf child: {}", program.display());
|
||||||
for arg in args.iter() {
|
for arg in args.iter() {
|
||||||
print!(" {}", arg.as_ref().to_str().unwrap_or("?"));
|
print!(" {}", arg.as_ref().to_str().unwrap_or("?"));
|
||||||
}
|
}
|
||||||
|
@ -77,6 +79,29 @@ where
|
||||||
|
|
||||||
let output = child.wait_with_output().expect("failed to wait on child");
|
let output = child.wait_with_output().expect("failed to wait on child");
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
|
if !generate_child_script_on_failure {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
eprintln!("cargo-build-bpf exited on command execution failure");
|
||||||
|
let script_name = format!(
|
||||||
|
"cargo-build-bpf-child-script-{}.sh",
|
||||||
|
program.file_name().unwrap().to_str().unwrap(),
|
||||||
|
);
|
||||||
|
let file = File::create(&script_name).unwrap();
|
||||||
|
let mut out = BufWriter::new(file);
|
||||||
|
for (key, value) in env::vars() {
|
||||||
|
writeln!(out, "{}=\"{}\" \\", key, value).unwrap();
|
||||||
|
}
|
||||||
|
write!(out, "{}", program.display()).unwrap();
|
||||||
|
for arg in args.iter() {
|
||||||
|
write!(out, " {}", arg.as_ref().to_str().unwrap_or("?")).unwrap();
|
||||||
|
}
|
||||||
|
writeln!(out).unwrap();
|
||||||
|
out.flush().unwrap();
|
||||||
|
eprintln!(
|
||||||
|
"To rerun the failed command for debugging use {}",
|
||||||
|
script_name,
|
||||||
|
);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
output
|
output
|
||||||
|
@ -276,7 +301,11 @@ fn check_undefined_symbols(config: &Config, program: &Path) {
|
||||||
.join("llvm-readelf");
|
.join("llvm-readelf");
|
||||||
let mut readelf_args = vec!["--dyn-symbols"];
|
let mut readelf_args = vec!["--dyn-symbols"];
|
||||||
readelf_args.push(program.to_str().unwrap());
|
readelf_args.push(program.to_str().unwrap());
|
||||||
let output = spawn(&readelf, &readelf_args);
|
let output = spawn(
|
||||||
|
&readelf,
|
||||||
|
&readelf_args,
|
||||||
|
config.generate_child_script_on_failure,
|
||||||
|
);
|
||||||
if config.verbose {
|
if config.verbose {
|
||||||
println!("{}", output);
|
println!("{}", output);
|
||||||
}
|
}
|
||||||
|
@ -309,7 +338,11 @@ fn link_bpf_toolchain(config: &Config) {
|
||||||
.join("rust");
|
.join("rust");
|
||||||
let rustup = PathBuf::from("rustup");
|
let rustup = PathBuf::from("rustup");
|
||||||
let rustup_args = vec!["toolchain", "list", "-v"];
|
let rustup_args = vec!["toolchain", "list", "-v"];
|
||||||
let rustup_output = spawn(&rustup, &rustup_args);
|
let rustup_output = spawn(
|
||||||
|
&rustup,
|
||||||
|
&rustup_args,
|
||||||
|
config.generate_child_script_on_failure,
|
||||||
|
);
|
||||||
if config.verbose {
|
if config.verbose {
|
||||||
println!("{}", rustup_output);
|
println!("{}", rustup_output);
|
||||||
}
|
}
|
||||||
|
@ -321,7 +354,11 @@ fn link_bpf_toolchain(config: &Config) {
|
||||||
let path = it.next();
|
let path = it.next();
|
||||||
if path.unwrap() != toolchain_path.to_str().unwrap() {
|
if path.unwrap() != toolchain_path.to_str().unwrap() {
|
||||||
let rustup_args = vec!["toolchain", "uninstall", "bpf"];
|
let rustup_args = vec!["toolchain", "uninstall", "bpf"];
|
||||||
let output = spawn(&rustup, &rustup_args);
|
let output = spawn(
|
||||||
|
&rustup,
|
||||||
|
&rustup_args,
|
||||||
|
config.generate_child_script_on_failure,
|
||||||
|
);
|
||||||
if config.verbose {
|
if config.verbose {
|
||||||
println!("{}", output);
|
println!("{}", output);
|
||||||
}
|
}
|
||||||
|
@ -333,7 +370,11 @@ fn link_bpf_toolchain(config: &Config) {
|
||||||
}
|
}
|
||||||
if do_link {
|
if do_link {
|
||||||
let rustup_args = vec!["toolchain", "link", "bpf", toolchain_path.to_str().unwrap()];
|
let rustup_args = vec!["toolchain", "link", "bpf", toolchain_path.to_str().unwrap()];
|
||||||
let output = spawn(&rustup, &rustup_args);
|
let output = spawn(
|
||||||
|
&rustup,
|
||||||
|
&rustup_args,
|
||||||
|
config.generate_child_script_on_failure,
|
||||||
|
);
|
||||||
if config.verbose {
|
if config.verbose {
|
||||||
println!("{}", output);
|
println!("{}", output);
|
||||||
}
|
}
|
||||||
|
@ -475,7 +516,11 @@ fn build_bpf_package(config: &Config, target_directory: &Path, package: &cargo_m
|
||||||
cargo_build_args.push(arg);
|
cargo_build_args.push(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let output = spawn(&cargo_build, &cargo_build_args);
|
let output = spawn(
|
||||||
|
&cargo_build,
|
||||||
|
&cargo_build_args,
|
||||||
|
config.generate_child_script_on_failure,
|
||||||
|
);
|
||||||
if config.verbose {
|
if config.verbose {
|
||||||
println!("{}", output);
|
println!("{}", output);
|
||||||
}
|
}
|
||||||
|
@ -520,6 +565,7 @@ fn build_bpf_package(config: &Config, target_directory: &Path, package: &cargo_m
|
||||||
let output = spawn(
|
let output = spawn(
|
||||||
&config.bpf_sdk.join("scripts").join("strip.sh"),
|
&config.bpf_sdk.join("scripts").join("strip.sh"),
|
||||||
&[&program_unstripped_so, &program_so],
|
&[&program_unstripped_so, &program_so],
|
||||||
|
config.generate_child_script_on_failure,
|
||||||
);
|
);
|
||||||
if config.verbose {
|
if config.verbose {
|
||||||
println!("{}", output);
|
println!("{}", output);
|
||||||
|
@ -530,6 +576,7 @@ fn build_bpf_package(config: &Config, target_directory: &Path, package: &cargo_m
|
||||||
let output = spawn(
|
let output = spawn(
|
||||||
&config.bpf_sdk.join("scripts").join("dump.sh"),
|
&config.bpf_sdk.join("scripts").join("dump.sh"),
|
||||||
&[&program_unstripped_so, &program_dump],
|
&[&program_unstripped_so, &program_dump],
|
||||||
|
config.generate_child_script_on_failure,
|
||||||
);
|
);
|
||||||
if config.verbose {
|
if config.verbose {
|
||||||
println!("{}", output);
|
println!("{}", output);
|
||||||
|
@ -643,6 +690,12 @@ fn main() {
|
||||||
.multiple(true)
|
.multiple(true)
|
||||||
.help("Space-separated list of features to activate"),
|
.help("Space-separated list of features to activate"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("generate_child_script_on_failure")
|
||||||
|
.long("generate-child-script-on-failure")
|
||||||
|
.takes_value(false)
|
||||||
|
.help("Generate rerun-script.sh to rerun test command on failure"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("manifest_path")
|
Arg::with_name("manifest_path")
|
||||||
.long("manifest-path")
|
.long("manifest-path")
|
||||||
|
@ -706,6 +759,7 @@ fn main() {
|
||||||
features: values_t!(matches, "features", String)
|
features: values_t!(matches, "features", String)
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap_or_else(Vec::new),
|
.unwrap_or_else(Vec::new),
|
||||||
|
generate_child_script_on_failure: matches.is_present("generate_child_script_on_failure"),
|
||||||
no_default_features: matches.is_present("no_default_features"),
|
no_default_features: matches.is_present("no_default_features"),
|
||||||
offline: matches.is_present("offline"),
|
offline: matches.is_present("offline"),
|
||||||
verbose: matches.is_present("verbose"),
|
verbose: matches.is_present("verbose"),
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serial_test;
|
extern crate serial_test;
|
||||||
|
|
||||||
fn run_cargo_build(extra_args: &[&str]) -> Output {
|
fn run_cargo_build(crate_name: &str, extra_args: &[&str]) -> Output {
|
||||||
let cwd = env::current_dir().expect("Unable to get current working directory");
|
let cwd = env::current_dir().expect("Unable to get current working directory");
|
||||||
let root = cwd
|
let root = cwd
|
||||||
.parent()
|
.parent()
|
||||||
|
@ -17,7 +17,7 @@ fn run_cargo_build(extra_args: &[&str]) -> Output {
|
||||||
let toml = cwd
|
let toml = cwd
|
||||||
.join("tests")
|
.join("tests")
|
||||||
.join("crates")
|
.join("crates")
|
||||||
.join("noop")
|
.join(crate_name)
|
||||||
.join("Cargo.toml");
|
.join("Cargo.toml");
|
||||||
let toml = format!("{}", toml.display());
|
let toml = format!("{}", toml.display());
|
||||||
let mut args = vec!["--bpf-sdk", "../bpf", "--manifest-path", &toml];
|
let mut args = vec!["--bpf-sdk", "../bpf", "--manifest-path", &toml];
|
||||||
|
@ -42,7 +42,7 @@ fn run_cargo_build(extra_args: &[&str]) -> Output {
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_build() {
|
fn test_build() {
|
||||||
let output = run_cargo_build(&[]);
|
let output = run_cargo_build("noop", &[]);
|
||||||
assert!(output.status.success());
|
assert!(output.status.success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ fn test_dump() {
|
||||||
.status()
|
.status()
|
||||||
.expect("Unable to install rustfilt required for --dump option")
|
.expect("Unable to install rustfilt required for --dump option")
|
||||||
.success());
|
.success());
|
||||||
let output = run_cargo_build(&["--dump"]);
|
let output = run_cargo_build("noop", &["--dump"]);
|
||||||
assert!(output.status.success());
|
assert!(output.status.success());
|
||||||
let cwd = env::current_dir().expect("Unable to get current working directory");
|
let cwd = env::current_dir().expect("Unable to get current working directory");
|
||||||
let dump = cwd
|
let dump = cwd
|
||||||
|
@ -71,10 +71,25 @@ fn test_dump() {
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_out_dir() {
|
fn test_out_dir() {
|
||||||
let output = run_cargo_build(&["--bpf-out-dir", "tmp_out"]);
|
let output = run_cargo_build("noop", &["--bpf-out-dir", "tmp_out"]);
|
||||||
assert!(output.status.success());
|
assert!(output.status.success());
|
||||||
let cwd = env::current_dir().expect("Unable to get current working directory");
|
let cwd = env::current_dir().expect("Unable to get current working directory");
|
||||||
let dir = cwd.join("tmp_out");
|
let dir = cwd.join("tmp_out");
|
||||||
assert!(dir.exists());
|
assert!(dir.exists());
|
||||||
fs::remove_dir_all("tmp_out").expect("Failed to remove tmp_out dir");
|
fs::remove_dir_all("tmp_out").expect("Failed to remove tmp_out dir");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_generate_child_script_on_failre() {
|
||||||
|
let output = run_cargo_build("fail", &["--generate-child-script-on-failure"]);
|
||||||
|
assert!(!output.status.success());
|
||||||
|
let cwd = env::current_dir().expect("Unable to get current working directory");
|
||||||
|
let scr = cwd
|
||||||
|
.join("tests")
|
||||||
|
.join("crates")
|
||||||
|
.join("fail")
|
||||||
|
.join("cargo-build-bpf-child-script-cargo.sh");
|
||||||
|
assert!(scr.exists());
|
||||||
|
fs::remove_file(scr).expect("Failed to remove script");
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "fail"
|
||||||
|
version = "1.8.0"
|
||||||
|
description = "Solana BPF test program written in Rust"
|
||||||
|
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||||
|
repository = "https://github.com/solana-labs/solana"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
homepage = "https://solana.com/"
|
||||||
|
edition = "2018"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
solana-program = { path = "../../../../program", version = "=1.8.0" }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[workspace]
|
|
@ -0,0 +1,15 @@
|
||||||
|
//! @brief Example Rust-based BPF noop program
|
||||||
|
|
||||||
|
extern crate solana_program;
|
||||||
|
use solana_program::{
|
||||||
|
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
|
entrypoint!(process_instruction);
|
||||||
|
fn process_instruction(
|
||||||
|
_program_id: &Pubkey,
|
||||||
|
_accounts: &[AccountInfo],
|
||||||
|
_instruction_data: &[u8],
|
||||||
|
) -> ProgramResult {
|
||||||
|
// error to make build fail: no return value
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ use clap::{
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
|
fs::File,
|
||||||
|
io::{prelude::*, BufWriter},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::exit,
|
process::exit,
|
||||||
process::Command,
|
process::Command,
|
||||||
|
@ -16,6 +18,7 @@ struct Config {
|
||||||
cargo_build_bpf: PathBuf,
|
cargo_build_bpf: PathBuf,
|
||||||
extra_cargo_test_args: Vec<String>,
|
extra_cargo_test_args: Vec<String>,
|
||||||
features: Vec<String>,
|
features: Vec<String>,
|
||||||
|
generate_child_script_on_failure: bool,
|
||||||
test_name: Option<String>,
|
test_name: Option<String>,
|
||||||
no_default_features: bool,
|
no_default_features: bool,
|
||||||
no_run: bool,
|
no_run: bool,
|
||||||
|
@ -33,6 +36,7 @@ impl Default for Config {
|
||||||
cargo_build_bpf: PathBuf::from("cargo-build-bpf"),
|
cargo_build_bpf: PathBuf::from("cargo-build-bpf"),
|
||||||
extra_cargo_test_args: vec![],
|
extra_cargo_test_args: vec![],
|
||||||
features: vec![],
|
features: vec![],
|
||||||
|
generate_child_script_on_failure: false,
|
||||||
test_name: None,
|
test_name: None,
|
||||||
no_default_features: false,
|
no_default_features: false,
|
||||||
no_run: false,
|
no_run: false,
|
||||||
|
@ -43,13 +47,13 @@ impl Default for Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn<I, S>(program: &Path, args: I)
|
fn spawn<I, S>(program: &Path, args: I, generate_child_script_on_failure: bool)
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = S>,
|
I: IntoIterator<Item = S>,
|
||||||
S: AsRef<OsStr>,
|
S: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
let args = args.into_iter().collect::<Vec<_>>();
|
let args = args.into_iter().collect::<Vec<_>>();
|
||||||
print!("Running: {}", program.display());
|
print!("cargo-test-bpf child: {}", program.display());
|
||||||
for arg in args.iter() {
|
for arg in args.iter() {
|
||||||
print!(" {}", arg.as_ref().to_str().unwrap_or("?"));
|
print!(" {}", arg.as_ref().to_str().unwrap_or("?"));
|
||||||
}
|
}
|
||||||
|
@ -65,6 +69,29 @@ where
|
||||||
|
|
||||||
let exit_status = child.wait().expect("failed to wait on child");
|
let exit_status = child.wait().expect("failed to wait on child");
|
||||||
if !exit_status.success() {
|
if !exit_status.success() {
|
||||||
|
if !generate_child_script_on_failure {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
eprintln!("cargo-test-bpf exited on command execution failure");
|
||||||
|
let script_name = format!(
|
||||||
|
"cargo-test-bpf-child-script-{}.sh",
|
||||||
|
program.file_name().unwrap().to_str().unwrap(),
|
||||||
|
);
|
||||||
|
let file = File::create(&script_name).unwrap();
|
||||||
|
let mut out = BufWriter::new(file);
|
||||||
|
for (key, value) in env::vars() {
|
||||||
|
writeln!(out, "{}=\"{}\" \\", key, value).unwrap();
|
||||||
|
}
|
||||||
|
write!(out, "{}", program.display()).unwrap();
|
||||||
|
for arg in args.iter() {
|
||||||
|
write!(out, " {}", arg.as_ref().to_str().unwrap_or("?")).unwrap();
|
||||||
|
}
|
||||||
|
writeln!(out).unwrap();
|
||||||
|
out.flush().unwrap();
|
||||||
|
eprintln!(
|
||||||
|
"To rerun the failed command for debugging use {}",
|
||||||
|
script_name,
|
||||||
|
);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +126,11 @@ fn test_bpf_package(config: &Config, target_directory: &Path, package: &cargo_me
|
||||||
build_bpf_args.push("--bpf-out-dir");
|
build_bpf_args.push("--bpf-out-dir");
|
||||||
build_bpf_args.push(&bpf_out_dir);
|
build_bpf_args.push(&bpf_out_dir);
|
||||||
|
|
||||||
spawn(&config.cargo_build_bpf, &build_bpf_args);
|
spawn(
|
||||||
|
&config.cargo_build_bpf,
|
||||||
|
&build_bpf_args,
|
||||||
|
config.generate_child_script_on_failure,
|
||||||
|
);
|
||||||
|
|
||||||
// Pass --bpf-out-dir along to the solana-program-test crate
|
// Pass --bpf-out-dir along to the solana-program-test crate
|
||||||
env::set_var("BPF_OUT_DIR", bpf_out_dir);
|
env::set_var("BPF_OUT_DIR", bpf_out_dir);
|
||||||
|
@ -124,7 +155,11 @@ fn test_bpf_package(config: &Config, target_directory: &Path, package: &cargo_me
|
||||||
for extra_cargo_test_arg in &config.extra_cargo_test_args {
|
for extra_cargo_test_arg in &config.extra_cargo_test_args {
|
||||||
cargo_args.push(extra_cargo_test_arg);
|
cargo_args.push(extra_cargo_test_arg);
|
||||||
}
|
}
|
||||||
spawn(&config.cargo, &cargo_args);
|
spawn(
|
||||||
|
&config.cargo,
|
||||||
|
&cargo_args,
|
||||||
|
config.generate_child_script_on_failure,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_bpf(config: Config, manifest_path: Option<PathBuf>) {
|
fn test_bpf(config: Config, manifest_path: Option<PathBuf>) {
|
||||||
|
@ -239,6 +274,12 @@ fn main() {
|
||||||
.takes_value(false)
|
.takes_value(false)
|
||||||
.help("Run without accessing the network"),
|
.help("Run without accessing the network"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("generate_child_script_on_failure")
|
||||||
|
.long("generate-child-script-on-failure")
|
||||||
|
.takes_value(false)
|
||||||
|
.help("Generate rerun-script.sh to rerun test command on failure"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("verbose")
|
Arg::with_name("verbose")
|
||||||
.short("v")
|
.short("v")
|
||||||
|
@ -271,6 +312,7 @@ fn main() {
|
||||||
features: values_t!(matches, "features", String)
|
features: values_t!(matches, "features", String)
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap_or_else(Vec::new),
|
.unwrap_or_else(Vec::new),
|
||||||
|
generate_child_script_on_failure: matches.is_present("generate_child_script_on_failure"),
|
||||||
test_name: value_t!(matches, "test", String).ok(),
|
test_name: value_t!(matches, "test", String).ok(),
|
||||||
no_default_features: matches.is_present("no_default_features"),
|
no_default_features: matches.is_present("no_default_features"),
|
||||||
no_run: matches.is_present("no_run"),
|
no_run: matches.is_present("no_run"),
|
||||||
|
|
Loading…
Reference in New Issue