blob: f1086951f8aea72a69a2ad3e63aecdd90ee9b1c6 [file] [log] [blame]
//! `xtask-unpublished` outputs a table with publish status --- a local version
//! and a version on crates.io for comparisons.
//!
//! This aims to help developers check if there is any crate required a new
//! publish, as well as detect if a version bump is needed in CI pipeline.
use std::collections::HashSet;
use cargo::core::registry::PackageRegistry;
use cargo::core::QueryKind;
use cargo::core::Registry;
use cargo::core::SourceId;
use cargo::ops::Packages;
use cargo::util::command_prelude::*;
pub fn cli() -> clap::Command {
clap::Command::new("xtask-unpublished")
.arg_package_spec_simple("Package to inspect the published status")
.arg(
opt(
"verbose",
"Use verbose output (-vv very verbose/build.rs output)",
)
.short('v')
.action(ArgAction::Count)
.global(true),
)
.arg_quiet()
.arg(
opt("color", "Coloring: auto, always, never")
.value_name("WHEN")
.global(true),
)
.arg(flag("frozen", "Require Cargo.lock and cache are up to date").global(true))
.arg(flag("locked", "Require Cargo.lock is up to date").global(true))
.arg(flag("offline", "Run without accessing the network").global(true))
.arg(multi_opt("config", "KEY=VALUE", "Override a configuration value").global(true))
.arg(
Arg::new("unstable-features")
.help("Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details")
.short('Z')
.value_name("FLAG")
.action(ArgAction::Append)
.global(true),
)
}
pub fn exec(args: &clap::ArgMatches, config: &mut cargo::util::Config) -> cargo::CliResult {
config_configure(config, args)?;
unpublished(args, config)?;
Ok(())
}
fn config_configure(config: &mut Config, args: &ArgMatches) -> CliResult {
let verbose = args.verbose();
// quiet is unusual because it is redefined in some subcommands in order
// to provide custom help text.
let quiet = args.flag("quiet");
let color = args.get_one::<String>("color").map(String::as_str);
let frozen = args.flag("frozen");
let locked = args.flag("locked");
let offline = args.flag("offline");
let mut unstable_flags = vec![];
if let Some(values) = args.get_many::<String>("unstable-features") {
unstable_flags.extend(values.cloned());
}
let mut config_args = vec![];
if let Some(values) = args.get_many::<String>("config") {
config_args.extend(values.cloned());
}
config.configure(
verbose,
quiet,
color,
frozen,
locked,
offline,
&None,
&unstable_flags,
&config_args,
)?;
Ok(())
}
fn unpublished(args: &clap::ArgMatches, config: &mut cargo::util::Config) -> cargo::CliResult {
let ws = args.workspace(config)?;
let members_to_inspect: HashSet<_> = {
let pkgs = args.packages_from_flags()?;
if let Packages::Packages(_) = pkgs {
HashSet::from_iter(pkgs.get_packages(&ws)?)
} else {
HashSet::from_iter(ws.members())
}
};
let mut results = Vec::new();
{
let mut registry = PackageRegistry::new(config)?;
let _lock = config.acquire_package_cache_lock()?;
registry.lock_patches();
let source_id = SourceId::crates_io(config)?;
for member in members_to_inspect {
let name = member.name();
let current = member.version();
if member.publish() == &Some(vec![]) {
log::trace!("skipping {name}, `publish = false`");
continue;
}
let version_req = format!("<={current}");
let query =
cargo::core::dependency::Dependency::parse(name, Some(&version_req), source_id)?;
let possibilities = loop {
// Exact to avoid returning all for path/git
match registry.query_vec(&query, QueryKind::Exact) {
std::task::Poll::Ready(res) => {
break res?;
}
std::task::Poll::Pending => registry.block_until_ready()?,
}
};
let (last, published) = possibilities
.iter()
.map(|s| s.version())
.max()
.map(|last| (last.to_string(), last == current))
.unwrap_or(("-".to_string(), false));
results.push(vec![
name.to_string(),
last,
current.to_string(),
if published { "yes" } else { "no" }.to_string(),
]);
}
}
results.sort();
if results.is_empty() {
return Ok(());
}
results.insert(
0,
vec![
"name".to_owned(),
"crates.io".to_owned(),
"local".to_owned(),
"published?".to_owned(),
],
);
output_table(results);
Ok(())
}
/// Outputs a markdown table like this.
///
/// ```text
/// | name | crates.io | local | published? |
/// |------------------|-----------|--------|------------|
/// | cargo | 0.70.1 | 0.72.0 | no |
/// | cargo-platform | 0.1.2 | 0.1.2 | yes |
/// | cargo-util | - | 0.2.4 | no |
/// | crates-io | 0.36.0 | 0.36.0 | yes |
/// | home | - | 0.5.6 | no |
/// ```
fn output_table(table: Vec<Vec<String>>) {
let header = table.first().unwrap();
let paddings = table.iter().fold(vec![0; header.len()], |mut widths, row| {
for (width, field) in widths.iter_mut().zip(row) {
*width = usize::max(*width, field.len());
}
widths
});
let print = |row: &[_]| {
for (field, pad) in row.iter().zip(&paddings) {
print!("| {field:pad$} ");
}
println!("|");
};
print(header);
paddings.iter().for_each(|fill| print!("|-{:-<fill$}-", ""));
println!("|");
table.iter().skip(1).for_each(|r| print(r));
}
#[test]
fn verify_cli() {
cli().debug_assert();
}