use crate::{ExpectedError, Result, output::OutputContext};
use camino::Utf8PathBuf;
use clap::{Args, Subcommand, ValueEnum};
use nextest_runner::{config::core::NextestConfig, user_config::UserConfig};
use std::io::Write;
use tracing::info;
#[derive(Debug, Args)]
#[group(id = "version_spec", multiple = false)]
pub(crate) struct UpdateVersionOpt {
#[arg(long, help_heading = "Version selection")]
version: Option<String>,
#[arg(long, help_heading = "Version selection")]
beta: bool,
#[arg(long, help_heading = "Version selection")]
rc: bool,
}
impl UpdateVersionOpt {
#[cfg(feature = "self-update")]
pub(crate) fn to_update_version_req(
&self,
) -> Result<nextest_runner::update::UpdateVersionReq, ExpectedError> {
use nextest_runner::update::{PrereleaseKind, UpdateVersionReq};
if self.beta {
Ok(UpdateVersionReq::LatestPrerelease(PrereleaseKind::Beta))
} else if self.rc {
Ok(UpdateVersionReq::LatestPrerelease(PrereleaseKind::Rc))
} else if let Some(version) = &self.version {
version
.parse()
.map(UpdateVersionReq::Version)
.map_err(|err| ExpectedError::UpdateVersionParseError { err })
} else {
Ok(UpdateVersionReq::Latest)
}
}
}
#[derive(Debug, Subcommand)]
pub(crate) enum SelfCommand {
#[clap(hide = true)]
Setup {
#[arg(long, value_enum, default_value_t = SetupSource::User)]
source: SetupSource,
},
Schema {
#[command(subcommand)]
kind: SchemaCommand,
},
#[cfg_attr(
not(feature = "self-update"),
doc = "This version of nextest does not have self-update enabled.\n\
\n\
Always exits with code 93 (SELF_UPDATE_UNAVAILABLE).
"
)]
#[cfg_attr(
feature = "self-update",
doc = "Download and install updates to nextest.\n\
\n\
This command checks the internet for updates to nextest, then downloads and
installs them if an update is available."
)]
Update {
#[command(flatten)]
version: UpdateVersionOpt,
#[arg(short = 'n', long)]
check: bool,
#[arg(short = 'y', long, conflicts_with = "check")]
yes: bool,
#[arg(short, long)]
force: bool,
#[arg(long)]
releases_url: Option<String>,
},
}
#[derive(Clone, Copy, Debug, ValueEnum)]
pub(crate) enum SetupSource {
User,
SelfUpdate,
PackageManager,
}
#[derive(Debug, Subcommand)]
pub(crate) enum SchemaCommand {
RepoConfig(SchemaOutputOpts),
UserConfig(SchemaOutputOpts),
}
#[derive(Debug, Args)]
pub(crate) struct SchemaOutputOpts {
#[arg(short = 'o', long = "output", value_name = "PATH")]
output: Option<Utf8PathBuf>,
}
impl SchemaCommand {
fn exec(self) -> Result<i32> {
match self {
Self::RepoConfig(opts) => opts.exec(NextestConfig::SCHEMA, "repo config"),
Self::UserConfig(opts) => opts.exec(UserConfig::SCHEMA, "user config"),
}
}
}
impl SchemaOutputOpts {
fn exec(&self, schema: &str, label: &'static str) -> Result<i32> {
match &self.output {
Some(path) => {
std::fs::write(path, schema).map_err(|err| ExpectedError::SchemaWriteError {
label,
path: path.clone(),
err,
})?;
info!("wrote JSON Schema for {label} to {path}");
}
None => {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
stdout
.write_all(schema.as_bytes())
.map_err(|err| ExpectedError::WriteError { err })?;
stdout
.flush()
.map_err(|err| ExpectedError::WriteError { err })?;
}
}
Ok(0)
}
}
impl SelfCommand {
#[cfg_attr(not(feature = "self-update"), expect(unused_variables))]
pub(crate) fn exec(self, output: OutputContext) -> Result<i32> {
match self {
Self::Setup { source: _source } => Ok(0),
Self::Schema { kind } => kind.exec(),
Self::Update {
version,
check,
yes,
force,
releases_url,
} => {
cfg_if::cfg_if! {
if #[cfg(feature = "self-update")] {
let version_req = version.to_update_version_req()?;
crate::update::perform_update(
version_req,
check,
yes,
force,
releases_url,
output,
)
} else {
let _ = version;
tracing::info!(
"this version of cargo-nextest cannot perform self-updates\n\
(hint: this usually means nextest was installed by a package manager)"
);
Ok(nextest_metadata::NextestExitCode::SELF_UPDATE_UNAVAILABLE)
}
}
}
}
}
}