From 46b241eb94f6bc9234c20d69159c5b4c5f784add Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:26:16 +0200 Subject: [PATCH] Add `xtask` crate (#4293) Replaces only the cargo_deny.sh script for now. Can be expanded over time to replace the other shell and python scripts, so only Rust is needed to work with the repository. Closes Closes --------- Co-authored-by: Emil Ernerfeldt --- .cargo/config.toml | 3 +++ Cargo.lock | 4 +++ Cargo.toml | 2 ++ scripts/cargo_deny.sh | 20 +-------------- xtask/Cargo.toml | 9 +++++++ xtask/README.md | 12 +++++++++ xtask/src/deny.rs | 60 +++++++++++++++++++++++++++++++++++++++++++ xtask/src/main.rs | 40 +++++++++++++++++++++++++++++ xtask/src/utils.rs | 45 ++++++++++++++++++++++++++++++++ 9 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 xtask/Cargo.toml create mode 100644 xtask/README.md create mode 100644 xtask/src/deny.rs create mode 100644 xtask/src/main.rs create mode 100644 xtask/src/utils.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index be614b00236..b18b6ce735d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,3 +4,6 @@ # we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93 [target.wasm32-unknown-unknown] rustflags = ["--cfg=web_sys_unstable_apis"] + +[alias] +xtask = "run --quiet --package xtask --" diff --git a/Cargo.lock b/Cargo.lock index 112c7d78ef1..8698db26e7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4762,6 +4762,10 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "xtask" +version = "0.27.2" + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 598eae08cb1..425eb8de208 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ members = [ "crates/epaint", "examples/*", + + "xtask", ] [workspace.package] diff --git a/scripts/cargo_deny.sh b/scripts/cargo_deny.sh index 560b8efd1bb..bf811e85204 100755 --- a/scripts/cargo_deny.sh +++ b/scripts/cargo_deny.sh @@ -1,21 +1,3 @@ #!/usr/bin/env bash -set -eu -script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -cd "$script_path/.." -set -x - -cargo install --quiet cargo-deny - -cargo deny --all-features --log-level error --target aarch64-apple-darwin check -cargo deny --all-features --log-level error --target aarch64-linux-android check -cargo deny --all-features --log-level error --target i686-pc-windows-gnu check -cargo deny --all-features --log-level error --target i686-pc-windows-msvc check -cargo deny --all-features --log-level error --target i686-unknown-linux-gnu check -cargo deny --all-features --log-level error --target wasm32-unknown-unknown check -cargo deny --all-features --log-level error --target x86_64-apple-darwin check -cargo deny --all-features --log-level error --target x86_64-pc-windows-gnu check -cargo deny --all-features --log-level error --target x86_64-pc-windows-msvc check -cargo deny --all-features --log-level error --target x86_64-unknown-linux-gnu check -cargo deny --all-features --log-level error --target x86_64-unknown-linux-musl check -cargo deny --all-features --log-level error --target x86_64-unknown-redox check +cargo xtask deny diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000000..ba5b9382089 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "xtask" +edition.workspace = true +license.workspace = true +rust-version.workspace = true +version.workspace = true +publish = false + +[dependencies] diff --git a/xtask/README.md b/xtask/README.md new file mode 100644 index 00000000000..bb5e9277762 --- /dev/null +++ b/xtask/README.md @@ -0,0 +1,12 @@ +## xtask - Task automation + +This crate is meant to automate common tasks on the repository. It serves as a +replacement for shell scripts that is more portable across host operating +systems (namely Windows) and hopefully also easier to work with for +contributors who are already familiar with Rust (and not necessarily with shell +scripting). + +The executable can be invoked via the subcommand `cargo xtask`, thanks to an +alias defined in `.cargo/config.toml`. + +For more information, see . diff --git a/xtask/src/deny.rs b/xtask/src/deny.rs new file mode 100644 index 00000000000..5c24949bd43 --- /dev/null +++ b/xtask/src/deny.rs @@ -0,0 +1,60 @@ +//! Run `cargo deny` +//! +//! Also installs the subcommand if it is not already installed. + +use std::process::Command; + +use super::DynError; + +pub fn deny(args: &[&str]) -> Result<(), DynError> { + if !args.is_empty() { + return Err(format!("Invalid arguments: {args:?}").into()); + } + install_cargo_deny()?; + let targets = [ + "aarch64-apple-darwin", + "aarch64-linux-android", + "i686-pc-windows-gnu", + "i686-pc-windows-msvc", + "i686-unknown-linux-gnu", + "wasm32-unknown-unknown", + "x86_64-apple-darwin", + "x86_64-pc-windows-gnu", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "x86_64-unknown-redox", + ]; + for target in targets { + let mut cmd = Command::new("cargo"); + cmd.args([ + "deny", + "--all-features", + "--log-level", + "error", + "--target", + target, + "check", + ]); + super::utils::print_cmd(&cmd); + let status = cmd.status()?; + if !status.success() { + return Err(status.to_string().into()); + } + } + Ok(()) +} + +fn install_cargo_deny() -> Result<(), DynError> { + let already_installed = Command::new("cargo") + .args(["deny", "--version"]) + .output() + .is_ok_and(|out| out.status.success()); + if already_installed { + return Ok(()); + } + let mut cmd = Command::new("cargo"); + cmd.args(["+stable", "install", "--quiet", "--locked", "cargo-deny"]); + let reason = "install cargo-deny"; + super::utils::ask_to_run(cmd, true, reason) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000000..b194629203a --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,40 @@ +#![allow(clippy::print_stdout)] +#![allow(clippy::print_stderr)] +#![allow(clippy::exit)] + +mod deny; +pub(crate) mod utils; + +type DynError = Box; + +fn main() { + if let Err(e) = try_main() { + eprintln!("{e}"); + std::process::exit(-1); + } +} + +fn try_main() -> Result<(), DynError> { + let arg_strings: Vec<_> = std::env::args().skip(1).collect(); + let args: Vec<_> = arg_strings.iter().map(String::as_str).collect(); + + match args.as_slice() { + &[] | &["-h"] | &["--help"] => print_help(), + &["deny", ..] => deny::deny(&args[1..])?, + c => Err(format!("Invalid arguments {c:?}"))?, + } + Ok(()) +} + +fn print_help() { + let help = " + xtask help + + Subcommands + deny: Run cargo-deny for all targets + + Options + -h, --help: print help and exit + "; + println!("{help}"); +} diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs new file mode 100644 index 00000000000..760c27f8f14 --- /dev/null +++ b/xtask/src/utils.rs @@ -0,0 +1,45 @@ +use std::{ + env, + io::{self, Write as _}, + process::Command, +}; + +use super::DynError; + +/// Print the command and its arguments as if the user had typed them +pub fn print_cmd(cmd: &Command) { + print!("{} ", cmd.get_program().to_string_lossy()); + for arg in cmd.get_args() { + print!("{} ", arg.to_string_lossy()); + } + println!(); +} + +/// Prompt user before running a command +/// +/// Adapted from [miri](https://github.com/rust-lang/miri/blob/dba35d2be72f4b78343d1a0f0b4737306f310672/cargo-miri/src/util.rs#L181-L204) +pub fn ask_to_run(mut cmd: Command, ask: bool, reason: &str) -> Result<(), DynError> { + // Disable interactive prompts in CI (GitHub Actions, Travis, AppVeyor, etc). + // Azure doesn't set `CI` though (nothing to see here, just Microsoft being Microsoft), + // so we also check their `TF_BUILD`. + let is_ci = env::var_os("CI").is_some() || env::var_os("TF_BUILD").is_some(); + if ask && !is_ci { + let mut buf = String::new(); + print!("The script is going to run: \n\n`{cmd:?}`\n\n To {reason}.\nProceed? [Y/n] ",); + io::stdout().flush().unwrap(); + io::stdin().read_line(&mut buf).unwrap(); + match buf.trim().to_lowercase().as_ref() { + "" | "y" | "yes" => {} + "n" | "no" => return Err("Aborting as per your request".into()), + a => return Err(format!("Invalid answer `{a}`").into()), + }; + } else { + eprintln!("Running `{cmd:?}` to {reason}."); + } + + let status = cmd.status()?; + if !status.success() { + return Err(format!("failed to {reason}: {status}").into()); + } + Ok(()) +}