Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(xtask): Add cargo xtask unpublished #12039

Merged
merged 4 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[alias]
xtask = "run --package xtask --"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you leaned toward multiple binaries (matklad/cargo-xtask#2)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its been a while since I've thought of xtasks all that much (that issue being almost 4 years old!). There are trade offs, performance in some cases vs performance in others, discoverability (run cargo xtask to discover all xtasks), etc.

No strong preference for what works with cargo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could build each binary on demand. xtask binary acts as a thin layer of parent command delegates other subcommands.

That is, when running cargo xtask unpublished it spawns a process running cargo run -p xtask --bin unpublished. With that we can probably avoid polling too many dependencies.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking of making src/doc/build.sh an xtask, which shouldn't compile the whole Cargo when running.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wrong. That mean we need unpublished to be a separate bin. However, I feel like we should go multi-binaries way to make build time less suck.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone ahead and created a dedicated binary

1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
target
Cargo.lock
.cargo
/config.stamp
/Makefile
/config.mk
Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions crates/xtask/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "xtask"
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"
16 changes: 16 additions & 0 deletions crates/xtask/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
mod unpublished;
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())
}
}
86 changes: 86 additions & 0 deletions crates/xtask/src/unpublished.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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("unpublished")
}

pub fn exec(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(())
}
83 changes: 83 additions & 0 deletions crates/xtask/src/xtask.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use cargo::util::command_prelude::*;

pub fn cli() -> clap::Command {
clap::Command::new("xtask")
.subcommand_required(true)
.arg_required_else_help(true)
.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),
)
.subcommands([crate::unpublished::cli()])
}

pub fn exec(args: &clap::ArgMatches, config: &mut cargo::util::Config) -> cargo::CliResult {
config_configure(config, args)?;

match args.subcommand() {
Some(("unpublished", args)) => crate::unpublished::exec(args, config)?,
Some((name, _)) => unreachable!("clap enforces {name} to not exist"),
None => unreachable!("clap enforces a subcommand is present"),
}

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(())
}

#[test]
fn verify_cli() {
cli().debug_assert();
}