Migrate to Clap 4.0

It was kind of a bumpy experience. Gradual migration to the Derive API
is coming soon.
This commit is contained in:
Zhaofeng Li 2022-10-09 15:26:37 -06:00
parent bf95e2dce8
commit 872949504b
16 changed files with 209 additions and 192 deletions

36
Cargo.lock generated
View file

@ -86,33 +86,31 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.2.22"
version = "4.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750"
checksum = "4ed45cc2c62a3eff523e718d8576ba762c83a3146151093283ac62ae11933a73"
dependencies = [
"atty",
"bitflags",
"clap_lex",
"indexmap",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_complete"
version = "3.2.5"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8"
checksum = "11cba7abac9b56dfe2f035098cdb3a43946f276e6db83b72c4e692343f9aab9a"
dependencies = [
"clap",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
dependencies = [
"os_str_bytes",
]
@ -355,12 +353,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.4.0"
@ -420,16 +412,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
[[package]]
name = "indexmap"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "indicatif"
version = "0.17.1"
@ -849,12 +831,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "textwrap"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
[[package]]
name = "thiserror"
version = "1.0.37"

View file

@ -10,8 +10,8 @@ edition = "2021"
async-stream = "0.3.2"
async-trait = "0.1.42"
atty = "0.2"
clap = "3.0.0"
clap_complete = "3.0.0"
clap = "4.0.11"
clap_complete = "4.0.2"
clicolors-control = "1"
console = "0.15.0"
const_format = "0.2.22"

View file

@ -2,7 +2,10 @@
use std::env;
use clap::{value_parser, Arg, ArgMatches, ColorChoice, Command as ClapCommand};
use clap::{
builder::PossibleValue, value_parser, Arg, ArgMatches, ColorChoice, Command as ClapCommand,
ValueEnum,
};
use clap_complete::Shell;
use const_format::concatcp;
use env_logger::fmt::WriteStyle;
@ -93,7 +96,34 @@ macro_rules! handle_command {
};
}
pub fn build_cli(include_internal: bool) -> ClapCommand<'static> {
/// When to display color.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ColorWhen {
/// Detect automatically.
Auto,
/// Always display colors.
Always,
/// Never display colors.
Never,
}
impl ValueEnum for ColorWhen {
fn value_variants<'a>() -> &'a [Self] {
&[Self::Auto, Self::Always, Self::Never]
}
fn to_possible_value<'a>(&self) -> Option<PossibleValue> {
match self {
Self::Auto => Some(PossibleValue::new("auto")),
Self::Always => Some(PossibleValue::new("always")),
Self::Never => Some(PossibleValue::new("never")),
}
}
}
pub fn build_cli(include_internal: bool) -> ClapCommand {
let version = env!("CARGO_PKG_VERSION");
let mut app = ClapCommand::new("Colmena")
.bin_name("colmena")
@ -123,13 +153,13 @@ pub fn build_cli(include_internal: bool) -> ClapCommand<'static> {
.help("Show debug information for Nix commands")
.long_help("Passes --show-trace to Nix commands")
.global(true)
.takes_value(false))
.num_args(0))
.arg(Arg::new("impure")
.long("impure")
.help("Allow impure expressions")
.long_help("Passes --impure to Nix commands")
.global(true)
.takes_value(false))
.num_args(0))
.arg(Arg::new("color")
.long("color")
.help("When to colorize the output")
@ -139,7 +169,7 @@ It's also possible to specify the preference using environment variables. See <h
"#)
.display_order(HELP_ORDER_LOW)
.value_name("WHEN")
.possible_values(&["auto", "always", "never"])
.value_parser(value_parser!(ColorWhen))
.default_value("auto")
.global(true));
@ -153,7 +183,7 @@ It's also possible to specify the preference using environment variables. See <h
.index(1)
.value_parser(value_parser!(Shell))
.required(true)
.takes_value(true),
.num_args(1),
),
);
@ -187,7 +217,7 @@ pub async fn run() {
let mut app = build_cli(true);
let matches = app.clone().get_matches();
set_color_pref(matches.value_of("color").unwrap());
set_color_pref(matches.get_one("color").unwrap());
init_logging();
handle_command!(apply, matches);
@ -221,9 +251,9 @@ fn gen_completions(args: &ArgMatches) {
clap_complete::generate(shell, &mut app, "colmena", &mut std::io::stdout());
}
fn set_color_pref(cli: &str) {
if cli != "auto" {
clicolors_control::set_colors_enabled(cli == "always");
fn set_color_pref(when: &ColorWhen) {
if when != &ColorWhen::Auto {
clicolors_control::set_colors_enabled(when == &ColorWhen::Always);
}
}
@ -247,3 +277,13 @@ fn init_logging() {
.write_style(style)
.init();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cli_debug_assert() {
build_cli(true).debug_assert()
}
}

View file

@ -1,11 +1,14 @@
use std::env;
use std::path::PathBuf;
use clap::{Arg, ArgMatches, Command as ClapCommand};
use clap::{
builder::{ArgPredicate, PossibleValuesParser, ValueParser},
value_parser, Arg, ArgMatches, Command as ClapCommand,
};
use crate::error::ColmenaError;
use crate::nix::deployment::{
Deployment, EvaluationNodeLimit, Evaluator, Goal, Options, ParallelismLimit,
Deployment, EvaluationNodeLimit, EvaluatorType, Goal, Options, ParallelismLimit,
};
use crate::nix::NodeFilter;
use crate::progress::SimpleProgressOutput;
@ -24,17 +27,18 @@ The evaluation process is RAM-intensive. The default behavior is to limit the ma
Set to 0 to disable the limit.
"#)
.default_value("auto")
.takes_value(true)
.validator(|s| {
.num_args(1)
.value_parser(ValueParser::new(|s: &str| -> Result<EvaluationNodeLimit, String> {
if s == "auto" {
return Ok(());
return Ok(EvaluationNodeLimit::Heuristic);
}
match s.parse::<usize>() {
Ok(_) => Ok(()),
Ok(0) => Ok(EvaluationNodeLimit::None),
Ok(n) => Ok(EvaluationNodeLimit::Manual(n)),
Err(_) => Err(String::from("The value must be a valid number")),
}
}))
})))
.arg(Arg::new("parallel")
.short('p')
.long("parallel")
@ -45,13 +49,8 @@ Set to 0 to disable the limit.
Set to 0 to disable parallemism limit.
"#)
.default_value("10")
.takes_value(true)
.validator(|s| {
match s.parse::<usize>() {
Ok(_) => Ok(()),
Err(_) => Err(String::from("The value must be a valid number")),
}
}))
.num_args(1)
.value_parser(value_parser!(usize)))
.arg(Arg::new("keep-result")
.long("keep-result")
.help("Create GC roots for built profiles")
@ -60,13 +59,13 @@ Set to 0 to disable parallemism limit.
The built system profiles will be added as GC roots so that they will not be removed by the garbage collector.
The links will be created under .gcroots in the directory the Hive configuration is located.
"#)
.takes_value(false))
.num_args(0))
.arg(Arg::new("verbose")
.short('v')
.long("verbose")
.help("Be verbose")
.long_help("Deactivates the progress spinner and prints every line of output.")
.takes_value(false))
.num_args(0))
.arg(Arg::new("no-keys")
.long("no-keys")
.help("Do not upload keys")
@ -75,23 +74,23 @@ The links will be created under .gcroots in the directory the Hive configuration
By default, Colmena will upload keys set in `deployment.keys` before deploying the new profile on a node.
To upload keys without building or deploying the rest of the configuration, use `colmena upload-keys`.
"#)
.takes_value(false))
.num_args(0))
.arg(Arg::new("reboot")
.long("reboot")
.help("Reboot nodes after activation")
.long_help("Reboots nodes after activation and waits for them to come back up.")
.takes_value(false))
.num_args(0))
.arg(Arg::new("no-substitute")
.long("no-substitute")
.alias("no-substitutes")
.help("Do not use substitutes")
.long_help("Disables the use of substituters when copying closures to the remote host.")
.takes_value(false))
.num_args(0))
.arg(Arg::new("no-gzip")
.long("no-gzip")
.help("Do not use gzip")
.long_help("Disables the use of gzip when copying closures to the remote host.")
.takes_value(false))
.num_args(0))
.arg(Arg::new("build-on-target")
.long("build-on-target")
.help("Build the system profiles on the target nodes")
@ -101,17 +100,17 @@ If enabled, the system profiles will be built on the target nodes themselves, no
This overrides per-node perferences set in `deployment.buildOnTarget`.
To temporarily disable remote build on all nodes, use `--no-build-on-target`.
"#)
.takes_value(false))
.num_args(0))
.arg(Arg::new("no-build-on-target")
.long("no-build-on-target")
.hide(true)
.takes_value(false))
.num_args(0))
.arg(Arg::new("force-replace-unknown-profiles")
.long("force-replace-unknown-profiles")
.help("Ignore all targeted nodes deployment.replaceUnknownProfiles setting")
.long_help(r#"If `deployment.replaceUnknownProfiles` is set for a target, using this switch
will treat deployment.replaceUnknownProfiles as though it was set true and perform unknown profile replacement."#)
.takes_value(false))
.num_args(0))
.arg(Arg::new("evaluator")
.long("evaluator")
.help("The evaluator to use (experimental)")
@ -119,10 +118,10 @@ will treat deployment.replaceUnknownProfiles as though it was set true and perfo
This is an experimental feature."#)
.default_value("chunked")
.possible_values(Evaluator::possible_values()))
.value_parser(value_parser!(EvaluatorType)))
}
pub fn subcommand() -> ClapCommand<'static> {
pub fn subcommand() -> ClapCommand {
let command = ClapCommand::new("apply")
.about("Apply configurations on remote machines")
.arg(
@ -141,10 +140,10 @@ Same as the targets for switch-to-configuration, with the following extra pseudo
"#,
)
.default_value("switch")
.default_value_if("reboot", None, Some("boot"))
.default_value_if("reboot", ArgPredicate::IsPresent, Some("boot"))
.default_value("switch")
.index(1)
.possible_values(&[
.value_parser(PossibleValuesParser::new([
"build",
"push",
"switch",
@ -152,7 +151,7 @@ Same as the targets for switch-to-configuration, with the following extra pseudo
"test",
"dry-activate",
"keys",
]),
])),
);
let command = register_deploy_args(command);
@ -164,10 +163,15 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
let ssh_config = env::var("SSH_CONFIG_FILE").ok().map(PathBuf::from);
let goal_arg = local_args.value_of("goal").unwrap();
// FIXME: Just get_one::<Goal>
let goal_arg = local_args.get_one::<String>("goal").unwrap();
let goal = Goal::from_str(goal_arg).unwrap();
let filter = local_args.value_of("on").map(NodeFilter::new).transpose()?;
// FIXME: Just get_one::<NodeFilter>
let filter = local_args
.get_one::<String>("on")
.map(NodeFilter::new)
.transpose()?;
if filter.is_none() && goal != Goal::Build {
// User did not specify node, we should check meta and see rules
@ -184,7 +188,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
.await?;
let n_targets = targets.len();
let verbose = local_args.is_present("verbose") || goal == Goal::DryActivate;
let verbose = local_args.get_flag("verbose") || goal == Goal::DryActivate;
let mut output = SimpleProgressOutput::new(verbose);
let progress = output.get_sender();
@ -193,22 +197,27 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
// FIXME: Configure limits
let options = {
let mut options = Options::default();
options.set_substituters_push(!local_args.is_present("no-substitute"));
options.set_gzip(!local_args.is_present("no-gzip"));
options.set_upload_keys(!local_args.is_present("no-keys"));
options.set_reboot(local_args.is_present("reboot"));
options.set_substituters_push(!local_args.get_flag("no-substitute"));
options.set_gzip(!local_args.get_flag("no-gzip"));
options.set_upload_keys(!local_args.get_flag("no-keys"));
options.set_reboot(local_args.get_flag("reboot"));
options.set_force_replace_unknown_profiles(
local_args.is_present("force-replace-unknown-profiles"),
local_args.get_flag("force-replace-unknown-profiles"),
);
options.set_evaluator(
local_args
.get_one::<EvaluatorType>("evaluator")
.unwrap()
.to_owned(),
);
options.set_evaluator(local_args.value_of_t("evaluator").unwrap());
if local_args.is_present("keep-result") {
if local_args.get_flag("keep-result") {
options.set_create_gc_roots(true);
}
if local_args.is_present("no-build-on-target") {
if local_args.get_flag("no-build-on-target") {
options.set_force_build_on_target(false);
} else if local_args.is_present("build-on-target") {
} else if local_args.get_flag("build-on-target") {
options.set_force_build_on_target(true);
}
@ -217,7 +226,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
deployment.set_options(options);
if local_args.is_present("no-keys") && goal == Goal::UploadKeys {
if local_args.get_flag("no-keys") && goal == Goal::UploadKeys {
log::error!("--no-keys cannot be used when the goal is to upload keys");
quit::with_code(1);
}
@ -225,11 +234,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
let parallelism_limit = {
let mut limit = ParallelismLimit::default();
limit.set_apply_limit({
let limit = local_args
.value_of("parallel")
.unwrap()
.parse::<usize>()
.unwrap();
let limit = local_args.get_one::<usize>("parallel").unwrap().to_owned();
if limit == 0 {
n_targets
} else {
@ -239,17 +244,10 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
limit
};
let evaluation_node_limit = match local_args.value_of("eval-node-limit").unwrap() {
"auto" => EvaluationNodeLimit::Heuristic,
number => {
let number = number.parse::<usize>().unwrap();
if number == 0 {
EvaluationNodeLimit::None
} else {
EvaluationNodeLimit::Manual(number)
}
}
};
let evaluation_node_limit = local_args
.get_one::<EvaluationNodeLimit>("eval-node-limit")
.unwrap()
.to_owned();
deployment.set_parallelism_limit(parallelism_limit);
deployment.set_evaluation_node_limit(evaluation_node_limit);

View file

@ -1,7 +1,7 @@
use regex::Regex;
use std::collections::HashMap;
use clap::{Arg, ArgMatches, Command as ClapCommand};
use clap::{builder::PossibleValuesParser, Arg, ArgMatches, Command as ClapCommand};
use tokio::fs;
use crate::error::ColmenaError;
@ -10,7 +10,7 @@ use crate::nix::{host::Local as LocalHost, NodeName};
use crate::progress::SimpleProgressOutput;
use crate::util;
pub fn subcommand() -> ClapCommand<'static> {
pub fn subcommand() -> ClapCommand {
ClapCommand::new("apply-local")
.about("Apply configurations on the local machine")
.arg(Arg::new("goal")
@ -18,16 +18,24 @@ pub fn subcommand() -> ClapCommand<'static> {
.long_help("Same as the targets for switch-to-configuration.\n\"push\" is noop in apply-local.")
.default_value("switch")
.index(1)
.possible_values(&["push", "switch", "boot", "test", "dry-activate", "keys"]))
.value_parser(PossibleValuesParser::new([
"push",
"switch",
"boot",
"test",
"dry-activate",
"keys",
])))
.arg(Arg::new("sudo")
.long("sudo")
.help("Attempt to escalate privileges if not run as root"))
.help("Attempt to escalate privileges if not run as root")
.num_args(0))
.arg(Arg::new("verbose")
.short('v')
.long("verbose")
.help("Be verbose")
.long_help("Deactivates the progress spinner and prints every line of output.")
.takes_value(false))
.num_args(0))
.arg(Arg::new("no-keys")
.long("no-keys")
.help("Do not deploy keys")
@ -35,11 +43,11 @@ pub fn subcommand() -> ClapCommand<'static> {
By default, Colmena will deploy keys set in `deployment.keys` before activating the profile on this host.
"#)
.takes_value(false))
.num_args(0))
.arg(Arg::new("node")
.long("node")
.help("Override the node name to use")
.takes_value(true))
.num_args(1))
// Removed
.arg(Arg::new("sudo-command")
@ -47,11 +55,11 @@ By default, Colmena will deploy keys set in `deployment.keys` before activating
.value_name("COMMAND")
.help("Removed: Configure deployment.privilegeEscalationCommand in node configuration")
.hide(true)
.takes_value(true))
.num_args(1))
}
pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
if local_args.occurrences_of("sudo-command") > 0 {
if local_args.contains_id("sudo-command") {
log::error!("--sudo-command has been removed. Please configure it in deployment.privilegeEscalationCommand in the node configuration.");
quit::with_code(1);
}
@ -68,8 +76,8 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
quit::with_code(5);
}
let escalate_privileges = local_args.is_present("sudo");
let verbose = local_args.is_present("verbose") || escalate_privileges; // cannot use spinners with interactive sudo
let escalate_privileges = local_args.get_flag("sudo");
let verbose = local_args.get_flag("verbose") || escalate_privileges; // cannot use spinners with interactive sudo
{
let euid: u32 = unsafe { libc::geteuid() };
@ -81,8 +89,8 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
let hive = util::hive_from_args(local_args).await.unwrap();
let hostname = {
let s = if local_args.is_present("node") {
local_args.value_of("node").unwrap().to_owned()
let s = if local_args.contains_id("node") {
local_args.get_one::<String>("node").unwrap().to_owned()
} else {
hostname::get()
.expect("Could not get hostname")
@ -92,7 +100,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
NodeName::new(s)?
};
let goal = Goal::from_str(local_args.value_of("goal").unwrap()).unwrap();
let goal = Goal::from_str(local_args.get_one::<String>("goal").unwrap()).unwrap();
let target = {
if let Some(info) = hive.deployment_info_single(&hostname).await.unwrap() {
@ -131,7 +139,7 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
let options = {
let mut options = Options::default();
options.set_upload_keys(!local_args.is_present("no-keys"));
options.set_upload_keys(!local_args.get_flag("no-keys"));
options
};

View file

@ -1,11 +1,11 @@
use clap::{Arg, Command as ClapCommand};
use clap::{builder::PossibleValuesParser, Arg, Command as ClapCommand};
use crate::util;
use super::apply;
pub use super::apply::run;
pub fn subcommand() -> ClapCommand<'static> {
pub fn subcommand() -> ClapCommand {
let command = ClapCommand::new("build")
.about("Build configurations but not push to remote machines")
.long_about(
@ -17,8 +17,8 @@ This subcommand behaves as if you invoked `apply` with the `build` goal."#,
Arg::new("goal")
.hide(true)
.default_value("build")
.possible_values(&["build"])
.takes_value(true),
.value_parser(PossibleValuesParser::new(["build"]))
.num_args(1),
);
let command = apply::register_deploy_args(command);

View file

@ -5,15 +5,15 @@ use clap::{Arg, ArgMatches, Command as ClapCommand};
use crate::error::ColmenaError;
use crate::util;
pub fn subcommand() -> ClapCommand<'static> {
pub fn subcommand() -> ClapCommand {
subcommand_gen("eval")
}
pub fn deprecated_alias() -> ClapCommand<'static> {
pub fn deprecated_alias() -> ClapCommand {
subcommand_gen("introspect").hide(true)
}
fn subcommand_gen(name: &str) -> ClapCommand<'static> {
fn subcommand_gen(name: &'static str) -> ClapCommand {
ClapCommand::new(name)
.about("Evaluate an expression using the complete configuration")
.long_about(r#"Evaluate an expression using the complete configuration
@ -28,16 +28,16 @@ For example, to retrieve the configuration of one node, you may write something
.index(1)
.value_name("FILE")
.help("The .nix file containing the expression")
.takes_value(true))
.num_args(1))
.arg(Arg::new("expression")
.short('E')
.value_name("EXPRESSION")
.help("The Nix expression")
.takes_value(true))
.num_args(1))
.arg(Arg::new("instantiate")
.long("instantiate")
.help("Actually instantiate the expression")
.takes_value(false))
.num_args(0))
}
pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
@ -49,15 +49,21 @@ pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<()
let hive = util::hive_from_args(local_args).await?;
if !(local_args.is_present("expression") ^ local_args.is_present("expression_file")) {
if !(local_args.contains_id("expression") ^ local_args.contains_id("expression_file")) {
log::error!("Either an expression (-E) or a .nix file containing an expression should be specified, not both.");
quit::with_code(1);
}
let expression = if local_args.is_present("expression") {
local_args.value_of("expression").unwrap().to_string()
let expression = if local_args.contains_id("expression") {
local_args
.get_one::<String>("expression")
.unwrap()
.to_owned()
} else {
let path: PathBuf = local_args.value_of("expression_file").unwrap().into();
let path = local_args
.get_one::<PathBuf>("expression_file")
.unwrap()
.to_owned();
format!(
"import {}",
path.canonicalize()
@ -67,7 +73,7 @@ pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<()
)
};
let instantiate = local_args.is_present("instantiate");
let instantiate = local_args.get_flag("instantiate");
let result = hive.introspect(expression, instantiate).await?;
if instantiate {

View file

@ -2,7 +2,7 @@ use std::env;
use std::path::PathBuf;
use std::sync::Arc;
use clap::{Arg, ArgMatches, Command as ClapCommand};
use clap::{value_parser, Arg, ArgMatches, Command as ClapCommand};
use futures::future::join_all;
use tokio::sync::Semaphore;
@ -12,10 +12,9 @@ use crate::nix::NodeFilter;
use crate::progress::SimpleProgressOutput;
use crate::util;
pub fn subcommand() -> ClapCommand<'static> {
pub fn subcommand() -> ClapCommand {
let command = ClapCommand::new("exec")
.about("Run a command on remote machines")
.trailing_var_arg(true)
.arg(
Arg::new("parallel")
.short('p')
@ -29,11 +28,8 @@ In `colmena exec`, the parallelism limit is disabled (0) by default.
"#,
)
.default_value("0")
.takes_value(true)
.validator(|s| match s.parse::<usize>() {
Ok(_) => Ok(()),
Err(_) => Err(String::from("The value must be a valid number")),
}),
.num_args(1)
.value_parser(value_parser!(usize)),
)
.arg(
Arg::new("verbose")
@ -41,15 +37,15 @@ In `colmena exec`, the parallelism limit is disabled (0) by default.
.long("verbose")
.help("Be verbose")
.long_help("Deactivates the progress spinner and prints every line of output.")
.takes_value(false),
.num_args(0),
)
.arg(
Arg::new("command")
.value_name("COMMAND")
.last(true)
.trailing_var_arg(true)
.help("Command")
.required(true)
.multiple_occurrences(true)
.num_args(1..)
.long_help(
r#"Command to run
@ -67,16 +63,16 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
let hive = util::hive_from_args(local_args).await?;
let ssh_config = env::var("SSH_CONFIG_FILE").ok().map(PathBuf::from);
let filter = local_args.value_of("on").map(NodeFilter::new).transpose()?;
// FIXME: Just get_one::<NodeFilter>
let filter = local_args
.get_one::<String>("on")
.map(NodeFilter::new)
.transpose()?;
let mut targets = hive.select_nodes(filter, ssh_config, true).await?;
let parallel_sp = Arc::new({
let limit = local_args
.value_of("parallel")
.unwrap()
.parse::<usize>()
.unwrap();
let limit = local_args.get_one::<usize>("parallel").unwrap().to_owned();
if limit > 0 {
Some(Semaphore::new(limit))
@ -87,13 +83,13 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
let command: Arc<Vec<String>> = Arc::new(
local_args
.values_of("command")
.get_many::<String>("command")
.unwrap()
.map(|s| s.to_string())
.cloned()
.collect(),
);
let mut output = SimpleProgressOutput::new(local_args.is_present("verbose"));
let mut output = SimpleProgressOutput::new(local_args.get_flag("verbose"));
let (mut monitor, meta) = JobMonitor::new(output.get_sender());

View file

@ -4,7 +4,7 @@ use crate::error::ColmenaError;
use crate::nix::evaluator::nix_eval_jobs::get_pinned_nix_eval_jobs;
use crate::nix::NixCheck;
pub fn subcommand() -> ClapCommand<'static> {
pub fn subcommand() -> ClapCommand {
ClapCommand::new("nix-info").about("Show information about the current Nix installation")
}

View file

@ -8,7 +8,7 @@ use crate::error::ColmenaError;
use crate::nix::info::NixCheck;
use crate::util;
pub fn subcommand() -> ClapCommand<'static> {
pub fn subcommand() -> ClapCommand {
ClapCommand::new("repl")
.about("Start an interactive REPL with the complete configuration")
.long_about(

View file

@ -14,7 +14,7 @@ macro_rules! node {
};
}
pub fn subcommand() -> ClapCommand<'static> {
pub fn subcommand() -> ClapCommand {
ClapCommand::new("test-progress")
.about("Run progress spinner tests")
.hide(true)

View file

@ -1,11 +1,11 @@
use clap::{Arg, Command as ClapCommand};
use clap::{builder::PossibleValuesParser, Arg, Command as ClapCommand};
use crate::util;
use super::apply;
pub use super::apply::run;
pub fn subcommand() -> ClapCommand<'static> {
pub fn subcommand() -> ClapCommand {
let command = ClapCommand::new("upload-keys")
.about("Upload keys to remote hosts")
.long_about(
@ -17,8 +17,8 @@ This subcommand behaves as if you invoked `apply` with the pseudo `keys` goal."#
Arg::new("goal")
.hide(true)
.default_value("keys")
.possible_values(&["keys"])
.takes_value(true),
.value_parser(PossibleValuesParser::new(["keys"]))
.num_args(1),
);
let command = apply::register_deploy_args(command);

View file

@ -8,7 +8,7 @@ pub mod limits;
pub use limits::{EvaluationNodeLimit, ParallelismLimit};
pub mod options;
pub use options::{Evaluator, Options};
pub use options::{EvaluatorType, Options};
use std::collections::HashMap;
use std::mem;
@ -162,10 +162,10 @@ impl Deployment {
let deployment = DeploymentHandle::new(self);
let meta_future = meta.run(|meta| async move {
match deployment.options.evaluator {
Evaluator::Chunked => {
EvaluatorType::Chunked => {
deployment.execute_chunked(meta.clone(), targets).await?;
}
Evaluator::Streaming => {
EvaluatorType::Streaming => {
log::warn!("Streaming evaluation is an experimental feature");
deployment.execute_streaming(meta.clone(), targets).await?;
}

View file

@ -1,8 +1,7 @@
//! Deployment options.
use std::str::FromStr;
use clap::{builder::PossibleValue, ValueEnum};
use crate::error::{ColmenaError, ColmenaResult};
use crate::nix::CopyOptions;
/// Options for a deployment.
@ -33,12 +32,12 @@ pub struct Options {
pub(super) force_replace_unknown_profiles: bool,
/// Which evaluator to use (experimental).
pub(super) evaluator: Evaluator,
pub(super) evaluator: EvaluatorType,
}
/// Which evaluator to use.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Evaluator {
pub enum EvaluatorType {
Chunked,
Streaming,
}
@ -72,7 +71,7 @@ impl Options {
self.force_replace_unknown_profiles = enable;
}
pub fn set_evaluator(&mut self, evaluator: Evaluator) {
pub fn set_evaluator(&mut self, evaluator: EvaluatorType) {
self.evaluator = evaluator;
}
@ -95,27 +94,20 @@ impl Default for Options {
create_gc_roots: false,
force_build_on_target: None,
force_replace_unknown_profiles: false,
evaluator: Evaluator::Chunked,
evaluator: EvaluatorType::Chunked,
}
}
}
impl FromStr for Evaluator {
type Err = ColmenaError;
impl ValueEnum for EvaluatorType {
fn value_variants<'a>() -> &'a [Self] {
&[Self::Chunked, Self::Streaming]
}
fn from_str(s: &str) -> ColmenaResult<Self> {
match s {
"chunked" => Ok(Self::Chunked),
"streaming" => Ok(Self::Streaming),
_ => Err(ColmenaError::Unknown {
message: "Valid values are 'chunked' and 'streaming'".to_string(),
}),
fn to_possible_value<'a>(&self) -> Option<PossibleValue> {
match self {
Self::Chunked => Some(PossibleValue::new("chunked")),
Self::Streaming => Some(PossibleValue::new("streaming")),
}
}
}
impl Evaluator {
pub fn possible_values() -> &'static [&'static str] {
&["chunked", "streaming"]
}
}

View file

@ -5,7 +5,7 @@
use std::env;
use std::future::Future;
use clap::ArgMatches;
use clap::{parser::ValueSource as ClapValueSource, ArgMatches};
use crate::error::ColmenaError;
@ -48,7 +48,7 @@ fn troubleshoot(
// in their Colmena checkout, and encounter NoFlakesSupport
// because Colmena always prefers flake.nix when it exists.
if global_args.occurrences_of("config") == 0 {
if let Some(ClapValueSource::DefaultValue) = global_args.value_source("config") {
let cwd = env::current_dir()?;
if cwd.join("flake.nix").is_file() && cwd.join("hive.nix").is_file() {
eprintln!(

View file

@ -3,7 +3,7 @@ use std::path::PathBuf;
use std::process::Stdio;
use async_trait::async_trait;
use clap::{Arg, ArgMatches, Command as ClapCommand};
use clap::{parser::ValueSource as ClapValueSource, Arg, ArgMatches, Command as ClapCommand};
use futures::future::join3;
use serde::de::DeserializeOwned;
use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader};
@ -193,8 +193,8 @@ impl CommandExt for CommandExecution {
}
pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> {
let path = match args.occurrences_of("config") {
0 => {
let path = match args.value_source("config").unwrap() {
ClapValueSource::DefaultValue => {
// traverse upwards until we find hive.nix
let mut cur = std::env::current_dir()?;
let mut file_path = None;
@ -231,9 +231,9 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> {
file_path.unwrap()
}
_ => {
ClapValueSource::CommandLine => {
let path = args
.value_of("config")
.get_one::<String>("config")
.expect("The config arg should exist")
.to_owned();
let fpath = PathBuf::from(&path);
@ -246,11 +246,11 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> {
let hive_path = HivePath::Flake(flake);
let mut hive = Hive::new(hive_path).await?;
if args.is_present("show-trace") {
if args.get_flag("show-trace") {
hive.set_show_trace(true);
}
if args.is_present("impure") {
if args.get_flag("impure") {
hive.set_impure(true);
}
@ -259,6 +259,7 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> {
fpath
}
x => panic!("Unexpected value source for config: {:?}", x),
};
let hive_path = HivePath::from_path(path).await?;
@ -273,11 +274,11 @@ pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> {
let mut hive = Hive::new(hive_path).await?;
if args.is_present("show-trace") {
if args.get_flag("show-trace") {
hive.set_show_trace(true);
}
if args.is_present("impure") {
if args.get_flag("impure") {
hive.set_impure(true);
}
@ -298,7 +299,7 @@ The list is comma-separated and globs are supported. To match tags, prepend the
- edge-*
- edge-*,core-*
- @a-tag,@tags-can-have-*"#)
.takes_value(true))
.num_args(1))
}
pub async fn capture_stream<R>(