From ef6eb27b9bd2caade89c570beca4f694bc5e75ce Mon Sep 17 00:00:00 2001 From: Benjamin Lee Date: Thu, 20 Jun 2024 19:06:57 -0700 Subject: [PATCH] add complement wrapper xtask script --- .cargo/config.toml | 2 + Cargo.lock | 126 ++++++++++++++++++++++++++++++ Cargo.toml | 4 + nix/pkgs/default/default.nix | 1 + nix/shell.nix | 6 ++ xtask/Cargo.toml | 14 ++++ xtask/src/complement.rs | 32 ++++++++ xtask/src/complement/docker.rs | 58 ++++++++++++++ xtask/src/complement/test2json.rs | 19 +++++ xtask/src/main.rs | 30 +++++++ 10 files changed, 292 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/complement.rs create mode 100644 xtask/src/complement/docker.rs create mode 100644 xtask/src/complement/test2json.rs create mode 100644 xtask/src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..35049cbc --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run --package xtask --" diff --git a/Cargo.lock b/Cargo.lock index caff42d9..b161e921 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -247,6 +247,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + [[package]] name = "base64" version = "0.21.7" @@ -1168,6 +1177,12 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "itertools" version = "0.12.1" @@ -1393,6 +1408,37 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "miette" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "mime" version = "0.3.17" @@ -1626,6 +1672,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "owo-colors" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" + [[package]] name = "parking_lot" version = "0.12.3" @@ -2693,6 +2745,12 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "socket2" version = "0.5.7" @@ -2756,6 +2814,27 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "supports-color" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + [[package]] name = "syn" version = "2.0.77" @@ -2792,6 +2871,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.64" @@ -3279,6 +3369,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.24" @@ -3288,6 +3384,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -3690,6 +3792,30 @@ version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" +[[package]] +name = "xshell" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db0ab86eae739efd1b054a8d3d16041914030ac4e01cd1dca0cf252fd8b6437" +dependencies = [ + "xshell-macros", +] + +[[package]] +name = "xshell-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" + +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "clap", + "miette", + "xshell", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index f5c132a8..9493e894 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,5 @@ +[workspace] +members = ["xtask"] [workspace.package] license = "Apache-2.0" @@ -97,6 +99,7 @@ hyper-util = { version = "0.1.8", features = ["client", "client-legacy", "servic image = { version = "0.25.2", default-features = false, features = ["jpeg", "png", "gif"] } jsonwebtoken = "9.3.0" lru-cache = "0.1.2" +miette = { version = "7.2.0", features = ["fancy"] } nix = { version = "0.29", features = ["resource"] } num_cpus = "1.16.0" once_cell = "1.19.0" @@ -136,6 +139,7 @@ tracing-opentelemetry = "0.25.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } trust-dns-resolver = "0.23.2" xdg = "2.5.2" +xshell = "0.2.6" [package] name = "grapevine" diff --git a/nix/pkgs/default/default.nix b/nix/pkgs/default/default.nix index fe31cc74..7359c126 100644 --- a/nix/pkgs/default/default.nix +++ b/nix/pkgs/default/default.nix @@ -84,6 +84,7 @@ let "Cargo.lock" "Cargo.toml" "src" + "xtask" ]; }; diff --git a/nix/shell.nix b/nix/shell.nix index 130d63e6..01d6f430 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -2,6 +2,8 @@ { buildPlatform , default , engage +, complement +, go , inputs , jq , lychee @@ -34,6 +36,10 @@ mkShell { markdownlint-cli mdbook toolchain + + # TODO: don't pollute the devshell with these + go + complement ] ++ default.nativeBuildInputs diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000..5d282c75 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" +license.workspace = true +rust-version.workspace = true + +[dependencies] +clap.workspace = true +miette.workspace = true +xshell.workspace = true + +[lints] +workspace = true diff --git a/xtask/src/complement.rs b/xtask/src/complement.rs new file mode 100644 index 00000000..456e9a41 --- /dev/null +++ b/xtask/src/complement.rs @@ -0,0 +1,32 @@ +use std::path::PathBuf; + +use miette::{IntoDiagnostic, Result, WrapErr}; +use xshell::{cmd, Shell}; + +mod docker; +mod test2json; + +use self::{docker::load_docker_image, test2json::run_complement}; + +#[derive(clap::Args)] +pub(crate) struct Args; + +#[allow(clippy::needless_pass_by_value)] +pub(crate) fn main(_args: Args) -> Result<()> { + let sh = Shell::new().unwrap(); + let toplevel = get_toplevel_path(&sh) + .wrap_err("failed to determine repository root directory")?; + let docker_image = load_docker_image(&sh, &toplevel).wrap_err( + "failed to build and load complement-grapevine docker image", + )?; + run_complement(&sh, &docker_image) + .wrap_err("failed to run complement tests")?; + Ok(()) +} + +/// Returns the path to the repository root +fn get_toplevel_path(sh: &Shell) -> Result { + let path = + cmd!(sh, "git rev-parse --show-toplevel").read().into_diagnostic()?; + Ok(path.into()) +} diff --git a/xtask/src/complement/docker.rs b/xtask/src/complement/docker.rs new file mode 100644 index 00000000..ada0c8a9 --- /dev/null +++ b/xtask/src/complement/docker.rs @@ -0,0 +1,58 @@ +//! Functions for working with docker images and containers. + +use std::path::Path; + +use miette::{miette, IntoDiagnostic, LabeledSpan, Result, WrapErr}; +use xshell::{cmd, Shell}; + +/// Build the 'grapevine-complement' OCI image and load it into the docker +/// daemon. +pub(crate) fn load_docker_image(sh: &Shell, toplevel: &Path) -> Result { + // > i would Not trust that parser as far as i can throw it + // - @jade_:matrix.org, 2024-06-19 + // + // So we're not even gonna try to escape the arbitrary top level path + // correctly for a flake installable reference. Instead we're just gonna cd + // into toplevel before running nix commands. + let _pushd_guard = sh.push_dir(toplevel); + + let installable = ".#complement-grapevine-oci-image"; + cmd!(sh, "nix-build-and-cache just {installable} -- --no-link") + .run() + .into_diagnostic() + .wrap_err("error building complement-grapevine-oci-image")?; + let oci_image_path = cmd!(sh, "nix path-info {installable}") + .read() + .into_diagnostic() + .wrap_err( + "error getting nix store path for complement-grapevine-oci-image", + )?; + + // Instead of building the image with a fixed tag, we let nix choose the tag + // based on the input hash, and then determine the image/tag it used by + // parsing the 'docker load' output. This is to avoid a race condition + // between multiple concurrent 'xtask complement' invocations, which might + // otherwise assign the same tag to different images. + let load_output = cmd!(sh, "docker image load --input {oci_image_path}") + .read() + .into_diagnostic() + .wrap_err("error loading complement-grapevine docker image")?; + let expected_prefix = "Loaded image: "; + let docker_image = load_output + .strip_prefix(expected_prefix) + .ok_or_else(|| { + // Miette doesn't support inclusive ranges. + // + #[allow(clippy::range_plus_one)] + let span = 0..(expected_prefix.len().min(load_output.len()) + 1); + let label = + LabeledSpan::at(span, format!("Expected {expected_prefix:?}")); + miette!( + labels = vec![label], + "failed to parse 'docker image load' output" + ) + .with_source_code(load_output.clone()) + })? + .to_owned(); + Ok(docker_image) +} diff --git a/xtask/src/complement/test2json.rs b/xtask/src/complement/test2json.rs new file mode 100644 index 00000000..595813e3 --- /dev/null +++ b/xtask/src/complement/test2json.rs @@ -0,0 +1,19 @@ +//! Functions for working with the go [`test2json`][test2json] tool. +//! +//! [test2json]: https://pkg.go.dev/cmd/test2json@go1.22.4 + +use miette::{IntoDiagnostic, Result}; +use xshell::{cmd, Shell}; + +/// Runs complement test suite +pub(crate) fn run_complement(sh: &Shell, docker_image: &str) -> Result<()> { + // TODO: handle SIG{INT,TERM} + // TODO: XTASK_PATH variable, so that we don't need to pollute devshell with + // go + cmd!(sh, "go tool test2json complement.test -test.v=test2json") + .env("COMPLEMENT_BASE_IMAGE", docker_image) + .env("COMPLEMENT_SPAWN_HS_TIMEOUT", "5") + .env("COMPLEMENT_ALWAYS_PRINT_SERVER_LOGS", "1") + .run() + .into_diagnostic() +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000..70fa34c2 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,30 @@ +mod complement; + +use std::process::ExitCode; + +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +struct Args { + #[clap(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + Complement(complement::Args), +} + +fn main() -> ExitCode { + let args = Args::parse(); + let result = match args.command { + Command::Complement(args) => complement::main(args), + }; + let Err(e) = result else { + return ExitCode::SUCCESS; + }; + // Include a leading newline because sometimes an error will occur in + // the middle of displaying a progress indicator. + eprintln!("\n{e:?}"); + ExitCode::FAILURE +}