diff --git a/.github/buildomat/jobs/tuf-repo.sh b/.github/buildomat/jobs/tuf-repo.sh index 2ed1ae08c3..2e3050b489 100755 --- a/.github/buildomat/jobs/tuf-repo.sh +++ b/.github/buildomat/jobs/tuf-repo.sh @@ -65,4 +65,4 @@ esac pfexec zfs create -p "rpool/images/$USER/host" pfexec zfs create -p "rpool/images/$USER/recovery" -cargo run --release --bin omicron-releng -- --output-dir /work +cargo xtask releng --output-dir /work diff --git a/dev-tools/releng/src/main.rs b/dev-tools/releng/src/main.rs index f382f5f222..445090115d 100644 --- a/dev-tools/releng/src/main.rs +++ b/dev-tools/releng/src/main.rs @@ -96,7 +96,6 @@ static WORKSPACE_DIR: Lazy = Lazy::new(|| { dir }); -#[derive(Parser)] /// Run the Oxide release engineering process and produce a TUF repo that can be /// used to update a rack. /// @@ -104,6 +103,8 @@ static WORKSPACE_DIR: Lazy = Lazy::new(|| { /// /// Note that `--host-dataset` and `--recovery-dataset` must be set to different /// values to build the two OS images in parallel. This is strongly recommended. +#[derive(Parser)] +#[command(name = "cargo xtask releng", bin_name = "cargo xtask releng")] struct Args { /// ZFS dataset to use for `helios-build` when building the host image #[clap(long, default_value_t = Self::default_dataset("host"))] diff --git a/dev-tools/xtask/src/external.rs b/dev-tools/xtask/src/external.rs new file mode 100644 index 0000000000..9c0bc69b55 --- /dev/null +++ b/dev-tools/xtask/src/external.rs @@ -0,0 +1,72 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! External xtasks. (extasks?) + +use std::ffi::{OsStr, OsString}; +use std::os::unix::process::CommandExt; +use std::process::Command; + +use anyhow::{Context, Result}; +use clap::Parser; + +/// Argument parser for external xtasks. +/// +/// In general we want all developer tasks to be discoverable simply by running +/// `cargo xtask`, but some development tools end up with a particularly +/// large dependency tree. It's not ideal to have to pay the cost of building +/// our release engineering tooling if all the user wants to do is check for +/// workspace dependency issues. +/// +/// `External` provides a pattern for creating xtasks that live in other crates. +/// An external xtask is defined on `crate::Cmds` as a tuple variant containing +/// `External`, which captures all arguments and options (even `--help`) as +/// a `Vec`. The main function then calls `External::exec` with the +/// appropriate bin target name and any additional Cargo arguments. +#[derive(Parser)] +#[clap( + disable_help_flag(true), + disable_help_subcommand(true), + disable_version_flag(true) +)] +pub struct External { + #[clap(trailing_var_arg(true), allow_hyphen_values(true))] + args: Vec, + + // This stores an in-progress Command builder. `cargo_args` appends args + // to it, and `exec` consumes it. Clap does not treat this as a command + // (`skip`), but fills in this field by calling `new_command`. + #[clap(skip = new_command())] + command: Command, +} + +impl External { + /// Add additional arguments to `cargo run` (for instance, to run the + /// external xtask in release mode). + pub fn cargo_args( + mut self, + args: impl IntoIterator>, + ) -> External { + self.command.args(args); + self + } + + pub fn exec(mut self, bin_target: impl AsRef) -> Result<()> { + let error = self + .command + .arg("--bin") + .arg(bin_target) + .arg("--") + .args(self.args) + .exec(); + Err(error).context("failed to exec `cargo run`") + } +} + +fn new_command() -> Command { + let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into()); + let mut command = Command::new(cargo); + command.arg("run"); + command +} diff --git a/dev-tools/xtask/src/main.rs b/dev-tools/xtask/src/main.rs index 56d01d0ff0..9f1131e758 100644 --- a/dev-tools/xtask/src/main.rs +++ b/dev-tools/xtask/src/main.rs @@ -12,6 +12,8 @@ use clap::{Parser, Subcommand}; mod check_workspace_deps; mod clippy; +#[cfg_attr(not(target_os = "illumos"), allow(dead_code))] +mod external; #[cfg(target_os = "illumos")] mod verify_libraries; @@ -19,7 +21,11 @@ mod verify_libraries; mod virtual_hardware; #[derive(Parser)] -#[command(name = "cargo xtask", about = "Workspace-related developer tools")] +#[command( + name = "cargo xtask", + bin_name = "cargo xtask", + about = "Workspace-related developer tools" +)] struct Args { #[command(subcommand)] cmd: Cmds, @@ -33,6 +39,9 @@ enum Cmds { /// Run configured clippy checks Clippy(clippy::ClippyArgs), + #[cfg(target_os = "illumos")] + /// Build a TUF repo + Releng(external::External), /// Verify we are not leaking library bindings outside of intended /// crates #[cfg(target_os = "illumos")] @@ -41,6 +50,9 @@ enum Cmds { #[cfg(target_os = "illumos")] VirtualHardware(virtual_hardware::Args), + /// (this command is only available on illumos) + #[cfg(not(target_os = "illumos"))] + Releng, /// (this command is only available on illumos) #[cfg(not(target_os = "illumos"))] VerifyLibraries, @@ -55,13 +67,17 @@ fn main() -> Result<()> { Cmds::Clippy(args) => clippy::run_cmd(args), Cmds::CheckWorkspaceDeps => check_workspace_deps::run_cmd(), + #[cfg(target_os = "illumos")] + Cmds::Releng(external) => { + external.cargo_args(["--release"]).exec("omicron-releng") + } #[cfg(target_os = "illumos")] Cmds::VerifyLibraries(args) => verify_libraries::run_cmd(args), #[cfg(target_os = "illumos")] Cmds::VirtualHardware(args) => virtual_hardware::run_cmd(args), #[cfg(not(target_os = "illumos"))] - Cmds::VerifyLibraries | Cmds::VirtualHardware => { + Cmds::Releng | Cmds::VerifyLibraries | Cmds::VirtualHardware => { anyhow::bail!("this command is only available on illumos"); } }