diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000000..3cea81f34c5 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +unpublished = "run --package xtask-unpublished --" diff --git a/.gitignore b/.gitignore index 8e162b0ff42..1cd9ebdfa4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ target Cargo.lock -.cargo /config.stamp /Makefile /config.mk diff --git a/Cargo.lock b/Cargo.lock index 62eccdc50b8..238f12f854a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3694,6 +3694,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "xtask-unpublished" +version = "0.0.0" +dependencies = [ + "anyhow", + "cargo", + "clap 4.2.1", + "env_logger 0.10.0", + "log", +] + [[package]] name = "yansi" version = "0.5.1" diff --git a/crates/xtask-unpublished/Cargo.toml b/crates/xtask-unpublished/Cargo.toml new file mode 100644 index 00000000000..a7a85323f81 --- /dev/null +++ b/crates/xtask-unpublished/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "xtask-unpublished" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1.0.47" +cargo = { path = "../.." } +clap = "4.2.0" +env_logger = "0.10.0" +log = "0.4.17" diff --git a/crates/xtask-unpublished/src/main.rs b/crates/xtask-unpublished/src/main.rs new file mode 100644 index 00000000000..1942a3621cf --- /dev/null +++ b/crates/xtask-unpublished/src/main.rs @@ -0,0 +1,15 @@ +mod xtask; + +fn main() { + env_logger::init_from_env("CARGO_LOG"); + let cli = xtask::cli(); + let matches = cli.get_matches(); + + let mut config = cargo::util::config::Config::default().unwrap_or_else(|e| { + let mut eval = cargo::core::shell::Shell::new(); + cargo::exit_with_error(e.into(), &mut eval) + }); + if let Err(e) = xtask::exec(&matches, &mut config) { + cargo::exit_with_error(e, &mut config.shell()) + } +} diff --git a/crates/xtask-unpublished/src/xtask.rs b/crates/xtask-unpublished/src/xtask.rs new file mode 100644 index 00000000000..095b483b7f1 --- /dev/null +++ b/crates/xtask-unpublished/src/xtask.rs @@ -0,0 +1,157 @@ +use cargo::core::registry::PackageRegistry; +use cargo::core::QueryKind; +use cargo::core::Registry; +use cargo::core::SourceId; +use cargo::util::command_prelude::*; + +pub fn cli() -> clap::Command { + clap::Command::new("xtask-unpublished") + .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::("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::("unstable-features") { + unstable_flags.extend(values.cloned()); + } + let mut config_args = vec![]; + if let Some(values) = args.get_many::("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 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 ws.members() { + 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.clone(), + )?; + 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()?, + } + }; + if let Some(last) = possibilities.iter().map(|s| s.version()).max() { + if last != current { + results.push(( + name.to_string(), + Some(last.to_string()), + current.to_string(), + )); + } else { + log::trace!("{name} {current} is published"); + } + } else { + results.push((name.to_string(), None, current.to_string())); + } + } + } + + if !results.is_empty() { + results.insert( + 0, + ( + "name".to_owned(), + Some("published".to_owned()), + "current".to_owned(), + ), + ); + results.insert( + 1, + ( + "====".to_owned(), + Some("=========".to_owned()), + "=======".to_owned(), + ), + ); + } + for (name, last, current) in results { + if let Some(last) = last { + println!("{name} {last} {current}"); + } else { + println!("{name} - {current}"); + } + } + + Ok(()) +} + +#[test] +fn verify_cli() { + cli().debug_assert(); +}