Use clap for parsing Hive arguments

This commit is contained in:
i1i1 2023-08-10 00:24:49 +03:00 committed by Zhaofeng Li
parent 9ddc53136a
commit 21df0ac5a5
10 changed files with 179 additions and 119 deletions

14
Cargo.lock generated
View file

@ -171,6 +171,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive",
"once_cell",
] ]
[[package]] [[package]]
@ -195,6 +197,18 @@ dependencies = [
"clap", "clap",
] ]
[[package]]
name = "clap_derive"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.16",
]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.4.1" version = "0.4.1"

View file

@ -10,7 +10,7 @@ edition = "2021"
async-stream = "0.3.5" async-stream = "0.3.5"
async-trait = "0.1.68" async-trait = "0.1.68"
atty = "0.2" atty = "0.2"
clap = "4.2.7" clap = { version = "4.2.7", features = ["derive"] }
clap_complete = "4.2.3" clap_complete = "4.2.3"
clicolors-control = "1" clicolors-control = "1"
console = "0.15.5" console = "0.15.5"

View file

@ -4,16 +4,16 @@ use std::str::FromStr;
use clap::{ use clap::{
builder::{ArgPredicate, PossibleValuesParser, ValueParser}, builder::{ArgPredicate, PossibleValuesParser, ValueParser},
value_parser, Arg, ArgMatches, Command as ClapCommand, value_parser, Arg, ArgMatches, Command as ClapCommand, FromArgMatches,
}; };
use crate::error::ColmenaError;
use crate::nix::deployment::{ use crate::nix::deployment::{
Deployment, EvaluationNodeLimit, EvaluatorType, Goal, Options, ParallelismLimit, Deployment, EvaluationNodeLimit, EvaluatorType, Goal, Options, ParallelismLimit,
}; };
use crate::nix::NodeFilter; use crate::nix::NodeFilter;
use crate::progress::SimpleProgressOutput; use crate::progress::SimpleProgressOutput;
use crate::util; use crate::util;
use crate::{error::ColmenaError, nix::hive::HiveArgs};
pub fn register_deploy_args(command: ClapCommand) -> ClapCommand { pub fn register_deploy_args(command: ClapCommand) -> ClapCommand {
command command
@ -160,7 +160,11 @@ Same as the targets for switch-to-configuration, with the following extra pseudo
} }
pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> { pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
let hive = util::hive_from_args(local_args).await?; let hive = HiveArgs::from_arg_matches(local_args)
.unwrap()
.into_hive()
.await
.unwrap();
let ssh_config = env::var("SSH_CONFIG_FILE").ok().map(PathBuf::from); let ssh_config = env::var("SSH_CONFIG_FILE").ok().map(PathBuf::from);

View file

@ -2,14 +2,17 @@ use regex::Regex;
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
use clap::{builder::PossibleValuesParser, Arg, ArgMatches, Command as ClapCommand}; use clap::{
builder::PossibleValuesParser, Arg, ArgMatches, Command as ClapCommand, FromArgMatches,
};
use tokio::fs; use tokio::fs;
use crate::error::ColmenaError; use crate::error::ColmenaError;
use crate::nix::deployment::{Deployment, Goal, Options, TargetNode}; use crate::nix::deployment::{Deployment, Goal, Options, TargetNode};
use crate::nix::hive::HiveArgs;
use crate::nix::{host::Local as LocalHost, NodeName}; use crate::nix::{host::Local as LocalHost, NodeName};
use crate::progress::SimpleProgressOutput; use crate::progress::SimpleProgressOutput;
use crate::util;
pub fn subcommand() -> ClapCommand { pub fn subcommand() -> ClapCommand {
ClapCommand::new("apply-local") ClapCommand::new("apply-local")
@ -89,7 +92,11 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
} }
} }
let hive = util::hive_from_args(local_args).await.unwrap(); let hive = HiveArgs::from_arg_matches(local_args)
.unwrap()
.into_hive()
.await
.unwrap();
let hostname = { let hostname = {
let s = if local_args.contains_id("node") { let s = if local_args.contains_id("node") {
local_args.get_one::<String>("node").unwrap().to_owned() local_args.get_one::<String>("node").unwrap().to_owned()

View file

@ -1,9 +1,9 @@
use std::path::PathBuf; use std::path::PathBuf;
use clap::{value_parser, Arg, ArgMatches, Command as ClapCommand}; use clap::{value_parser, Arg, ArgMatches, Command as ClapCommand, FromArgMatches};
use crate::error::ColmenaError; use crate::error::ColmenaError;
use crate::util; use crate::nix::hive::HiveArgs;
pub fn subcommand() -> ClapCommand { pub fn subcommand() -> ClapCommand {
subcommand_gen("eval") subcommand_gen("eval")
@ -48,7 +48,11 @@ pub async fn run(global_args: &ArgMatches, local_args: &ArgMatches) -> Result<()
); );
} }
let hive = util::hive_from_args(local_args).await?; let hive = HiveArgs::from_arg_matches(local_args)
.unwrap()
.into_hive()
.await
.unwrap();
if !(local_args.contains_id("expression") ^ local_args.contains_id("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."); log::error!("Either an expression (-E) or a .nix file containing an expression should be specified, not both.");

View file

@ -2,12 +2,13 @@ use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use clap::{value_parser, Arg, ArgMatches, Command as ClapCommand}; use clap::{value_parser, Arg, ArgMatches, Command as ClapCommand, FromArgMatches};
use futures::future::join_all; use futures::future::join_all;
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
use crate::error::ColmenaError; use crate::error::ColmenaError;
use crate::job::{JobMonitor, JobState, JobType}; use crate::job::{JobMonitor, JobState, JobType};
use crate::nix::hive::HiveArgs;
use crate::nix::NodeFilter; use crate::nix::NodeFilter;
use crate::progress::SimpleProgressOutput; use crate::progress::SimpleProgressOutput;
use crate::util; use crate::util;
@ -60,7 +61,11 @@ It's recommended to use -- to separate Colmena options from the command to run.
} }
pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> { pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(), ColmenaError> {
let hive = util::hive_from_args(local_args).await?; let hive = HiveArgs::from_arg_matches(local_args)
.unwrap()
.into_hive()
.await
.unwrap();
let ssh_config = env::var("SSH_CONFIG_FILE").ok().map(PathBuf::from); let ssh_config = env::var("SSH_CONFIG_FILE").ok().map(PathBuf::from);
// FIXME: Just get_one::<NodeFilter> // FIXME: Just get_one::<NodeFilter>

View file

@ -1,12 +1,12 @@
use std::io::Write; use std::io::Write;
use clap::{ArgMatches, Command as ClapCommand}; use clap::{ArgMatches, Command as ClapCommand, FromArgMatches};
use tempfile::Builder as TempFileBuilder; use tempfile::Builder as TempFileBuilder;
use tokio::process::Command; use tokio::process::Command;
use crate::error::ColmenaError; use crate::error::ColmenaError;
use crate::nix::hive::HiveArgs;
use crate::nix::info::NixCheck; use crate::nix::info::NixCheck;
use crate::util;
pub fn subcommand() -> ClapCommand { pub fn subcommand() -> ClapCommand {
ClapCommand::new("repl") ClapCommand::new("repl")
@ -24,7 +24,11 @@ pub async fn run(_global_args: &ArgMatches, local_args: &ArgMatches) -> Result<(
let nix_check = NixCheck::detect().await; let nix_check = NixCheck::detect().await;
let nix_version = nix_check.version().expect("Could not detect Nix version"); let nix_version = nix_check.version().expect("Could not detect Nix version");
let hive = util::hive_from_args(local_args).await?; let hive = HiveArgs::from_arg_matches(local_args)
.unwrap()
.into_hive()
.await
.unwrap();
let expr = hive.get_repl_expression(); let expr = hive.get_repl_expression();

View file

@ -53,10 +53,10 @@ impl Flake {
} }
/// Creates a flake from a Flake URI. /// Creates a flake from a Flake URI.
pub async fn from_uri(uri: String) -> ColmenaResult<Self> { pub async fn from_uri(uri: impl AsRef<str>) -> ColmenaResult<Self> {
NixCheck::require_flake_support().await?; NixCheck::require_flake_support().await?;
let metadata = FlakeMetadata::resolve(&uri).await?; let metadata = FlakeMetadata::resolve(uri.as_ref()).await?;
Ok(Self { Ok(Self {
metadata, metadata,

View file

@ -6,7 +6,9 @@ mod tests;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::AsRef; use std::convert::AsRef;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr;
use clap::Args;
use tokio::process::Command; use tokio::process::Command;
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
use validator::Validate; use validator::Validate;
@ -16,11 +18,93 @@ use super::{
Flake, MetaConfig, NixExpression, NixFlags, NodeConfig, NodeFilter, NodeName, Flake, MetaConfig, NixExpression, NixFlags, NodeConfig, NodeFilter, NodeName,
ProfileDerivation, SerializedNixExpression, StorePath, ProfileDerivation, SerializedNixExpression, StorePath,
}; };
use crate::error::ColmenaResult; use crate::error::{ColmenaError, ColmenaResult};
use crate::job::JobHandle; use crate::job::JobHandle;
use crate::util::{CommandExecution, CommandExt}; use crate::util::{CommandExecution, CommandExt};
use assets::Assets; use assets::Assets;
#[derive(Debug, Args)]
pub struct HiveArgs {
#[arg(short = 'f', long, value_name = "CONFIG")]
config: Option<HivePath>,
#[arg(long)]
show_trace: bool,
#[arg(long)]
impure: bool,
#[arg(long, value_parser = crate::util::parse_key_val::<String, String>)]
nix_option: Vec<(String, String)>,
}
impl HiveArgs {
pub async fn into_hive(self) -> ColmenaResult<Hive> {
let path = match self.config {
Some(path) => path,
None => {
// traverse upwards until we find hive.nix
let mut cur = std::env::current_dir()?;
let mut file_path = None;
loop {
let flake = cur.join("flake.nix");
if flake.is_file() {
file_path = Some(flake);
break;
}
let legacy = cur.join("hive.nix");
if legacy.is_file() {
file_path = Some(legacy);
break;
}
match cur.parent() {
Some(parent) => {
cur = parent.to_owned();
}
None => {
break;
}
}
}
if file_path.is_none() {
log::error!(
"Could not find `hive.nix` or `flake.nix` in {:?} or any parent directory",
std::env::current_dir()?
);
}
HivePath::from_path(file_path.unwrap()).await?
}
};
match &path {
HivePath::Legacy(p) => {
log::info!("Using configuration: {}", p.to_string_lossy());
}
HivePath::Flake(flake) => {
log::info!("Using flake: {}", flake.uri());
}
}
let mut hive = Hive::new(path).await?;
if self.show_trace {
hive.set_show_trace(true);
}
if self.impure {
hive.set_impure(true);
}
for (name, value) in self.nix_option {
hive.add_nix_option(name, value);
}
Ok(hive)
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum HivePath { pub enum HivePath {
/// A Nix Flake. /// A Nix Flake.
@ -32,6 +116,29 @@ pub enum HivePath {
Legacy(PathBuf), Legacy(PathBuf),
} }
impl FromStr for HivePath {
type Err = ColmenaError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// TODO: check for escaped colon maybe?
let path = std::path::Path::new(s);
let handle = tokio::runtime::Handle::try_current()
.expect("We should always be executed after we have a runtime");
if !path.exists() && s.contains(':') {
// Treat as flake URI
let flake = handle.block_on(Flake::from_uri(s))?;
log::info!("Using flake: {}", flake.uri());
Ok(Self::Flake(flake))
} else {
handle.block_on(HivePath::from_path(path))
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Hive { pub struct Hive {
/// Path to the hive. /// Path to the hive.

View file

@ -1,9 +1,10 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use std::path::PathBuf; use std::error::Error;
use std::process::Stdio; use std::process::Stdio;
use async_trait::async_trait; use async_trait::async_trait;
use clap::{parser::ValueSource as ClapValueSource, Arg, ArgMatches, Command as ClapCommand}; use clap::{Arg, Command as ClapCommand};
use futures::future::join3; use futures::future::join3;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader}; use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader};
@ -12,7 +13,7 @@ use tokio::process::Command;
use super::error::{ColmenaError, ColmenaResult}; use super::error::{ColmenaError, ColmenaResult};
use super::job::JobHandle; use super::job::JobHandle;
use super::nix::deployment::TargetNodeMap; use super::nix::deployment::TargetNodeMap;
use super::nix::{Flake, Hive, HivePath, StorePath}; use super::nix::StorePath;
const NEWLINE: u8 = 0xa; const NEWLINE: u8 = 0xa;
@ -192,104 +193,18 @@ impl CommandExt for CommandExecution {
} }
} }
pub async fn hive_from_args(args: &ArgMatches) -> ColmenaResult<Hive> { /// Parse a single key-value pair
let path = match args.value_source("config").unwrap() { pub fn parse_key_val<T, U>(s: &str) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>>
ClapValueSource::DefaultValue => { where
// traverse upwards until we find hive.nix T: std::str::FromStr,
let mut cur = std::env::current_dir()?; T::Err: Error + Send + Sync + 'static,
let mut file_path = None; U: std::str::FromStr,
U::Err: Error + Send + Sync + 'static,
loop { {
let flake = cur.join("flake.nix"); let pos = s
if flake.is_file() { .find('=')
file_path = Some(flake); .ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`"))?;
break; Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
}
let legacy = cur.join("hive.nix");
if legacy.is_file() {
file_path = Some(legacy);
break;
}
match cur.parent() {
Some(parent) => {
cur = parent.to_owned();
}
None => {
break;
}
}
}
if file_path.is_none() {
log::error!(
"Could not find `hive.nix` or `flake.nix` in {:?} or any parent directory",
std::env::current_dir()?
);
}
file_path.unwrap()
}
ClapValueSource::CommandLine => {
let path = args
.get_one::<String>("config")
.expect("The config arg should exist")
.to_owned();
let fpath = PathBuf::from(&path);
if !fpath.exists() && path.contains(':') {
// Treat as flake URI
let flake = Flake::from_uri(path).await?;
log::info!("Using flake: {}", flake.uri());
let hive_path = HivePath::Flake(flake);
return hive_from_path(hive_path, args).await;
}
fpath
}
x => panic!("Unexpected value source for config: {:?}", x),
};
let hive_path = HivePath::from_path(path).await?;
hive_from_path(hive_path, args).await
}
pub async fn hive_from_path(hive_path: HivePath, args: &ArgMatches) -> ColmenaResult<Hive> {
match &hive_path {
HivePath::Legacy(p) => {
log::info!("Using configuration: {}", p.to_string_lossy());
}
HivePath::Flake(flake) => {
log::info!("Using flake: {}", flake.uri());
}
}
let mut hive = Hive::new(hive_path).await?;
if args.get_flag("show-trace") {
hive.set_show_trace(true);
}
if args.get_flag("impure") {
hive.set_impure(true);
}
if let Some(opts) = args.get_many::<String>("nix-option") {
let iter = opts;
let names = iter.clone().step_by(2);
let values = iter.clone().skip(1).step_by(2);
for (name, value) in names.zip(values) {
hive.add_nix_option(name.to_owned(), value.to_owned());
}
}
Ok(hive)
} }
pub fn register_selector_args(command: ClapCommand) -> ClapCommand { pub fn register_selector_args(command: ClapCommand) -> ClapCommand {