From cf9b7d466fb9a4b74b7f0936adac7310de523e55 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 25 Feb 2026 17:44:43 +0000 Subject: [PATCH] build: Add macOS stub compilation support This is the start of work on https://github.com/bootc-dev/bcvk/issues/21 Gate Linux-only modules and dependencies with #[cfg(target_os = "linux")] so the crate compiles on macOS. For now only the `ephemeral` verb exists as a stub that errors out, but the idea is we can fill that in. Also replace std::process::exit(0) with Ok(()) for proper cleanup. Assisted-by: OpenCode (Claude claude-opus-4-5@20251101) Assisted-by: OpenCode (Claude claude-opus-4-6) Signed-off-by: Colin Walters --- .github/workflows/main.yml | 17 +++- Cargo.lock | 16 ++-- crates/kit/Cargo.toml | 21 +++-- crates/kit/src/cpio.rs | 3 + crates/kit/src/install_options.rs | 3 + crates/kit/src/instancetypes.rs | 3 + crates/kit/src/lib.rs | 5 +- crates/kit/src/main.rs | 137 ++++++++++++++++++++++++++---- crates/kit/src/qemu_img.rs | 3 + crates/kit/src/xml_utils.rs | 3 + 10 files changed, 177 insertions(+), 34 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d5620943..3973d21b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,6 +14,20 @@ env: jobs: + build-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Check build + run: cargo check --all-targets + + - name: Run unit tests + run: cargo test --lib + build: runs-on: ubuntu-24.04 @@ -181,10 +195,11 @@ jobs: # Sentinel job for required checks - configure this job name in repository settings required-checks: if: always() - needs: [build, integration-tests] + needs: [build-macos, build, integration-tests] runs-on: ubuntu-latest steps: - run: exit 1 if: >- + needs.build-macos.result != 'success' || needs.build.result != 'success' || needs.integration-tests.result != 'success' diff --git a/Cargo.lock b/Cargo.lock index c795ad18..0a765cb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "cap-primitives" -version = "3.4.4" +version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e394ed14f39f8bc26f59d4c0c010dbe7f0a1b9bafff451b1f98b67c8af62a" +checksum = "b6cf3aea8a5081171859ef57bc1606b1df6999df4f1110f8eef68b30098d1d3a" dependencies = [ "ambient-authority", "fs-set-times", @@ -315,15 +315,15 @@ dependencies = [ "maybe-owned", "rustix", "rustix-linux-procfs", - "windows-sys 0.59.0", + "windows-sys 0.52.0", "winx", ] [[package]] name = "cap-std" -version = "3.4.4" +version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0355ca583dd58f176c3c12489d684163861ede3c9efa6fd8bba314c984189" +checksum = "b6dc3090992a735d23219de5c204927163d922f42f575a0189b005c62d37549a" dependencies = [ "cap-primitives", "io-extras", @@ -344,9 +344,9 @@ dependencies = [ [[package]] name = "cap-tempfile" -version = "3.4.4" +version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bdc50d18ee6c3551b30eb7ad5c4628d7c73ed9e1696b63c432a55602d634d7d" +checksum = "68d8ad5cfac469e58e632590f033d45c66415ef7a8aa801409884818036706f5" dependencies = [ "cap-std", "rand 0.8.5", @@ -1364,7 +1364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" dependencies = [ "io-lifetimes", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/crates/kit/Cargo.toml b/crates/kit/Cargo.toml index e8232b9b..167c4e57 100644 --- a/crates/kit/Cargo.toml +++ b/crates/kit/Cargo.toml @@ -5,10 +5,7 @@ edition = "2021" publish = false [dependencies] -bcvk-qemu = { path = "../bcvk-qemu" } base64 = "0.22" -# For some recent APIs, TODO switch back to published version -cap-std-ext = { git = "https://github.com/coreos/cap-std-ext", rev = "cfdb25d51ffc697e70aa0d8d3cefe9ec2133bd0a" } chrono = { version = "0.4", features = ["serde"] } const_format = { workspace = true } color-eyre = { workspace = true } @@ -17,12 +14,9 @@ clap_mangen = { version = "0.2.20", optional = true } data-encoding = { version = "2.9" } dirs = "5.0" fn-error-context = { version = "0.2" } -bootc-mount = { git = "https://github.com/bootc-dev/bootc", rev = "93b22f4dbc2d54f7cca7c1df3ee59fcdec0b2cf1" } -bootc-utils = { git = "https://github.com/bootc-dev/bootc", rev = "93b22f4dbc2d54f7cca7c1df3ee59fcdec0b2cf1" } indicatif = "0.17" notify = "6.1" thiserror = "1.0" -rustix = { "version" = "1", features = ["thread", "net", "fs", "pipe", "system", "process", "mount"] } serde = { version = "1.0.199", features = ["derive"] } serde_json = "1.0.116" serde_yaml = "0.9" @@ -41,9 +35,6 @@ cfg-if = { workspace = true } indoc = "2.0.6" regex = "1.10" itertools = "0.14.0" -vsock = "0.5" -nix = { version = "0.29", features = ["socket"] } -libc = "0.2" camino = "1.1.12" comfy-table = "7.1" strum = { version = "0.26", features = ["derive"] } @@ -53,6 +44,18 @@ sha2 = "0.10" which = "7.0" cpio = "0.4" +# Linux-only dependencies +[target.'cfg(target_os = "linux")'.dependencies] +bcvk-qemu = { path = "../bcvk-qemu" } +# For some recent APIs, TODO switch back to published version +cap-std-ext = { git = "https://github.com/coreos/cap-std-ext", rev = "cfdb25d51ffc697e70aa0d8d3cefe9ec2133bd0a" } +bootc-mount = { git = "https://github.com/bootc-dev/bootc", rev = "93b22f4dbc2d54f7cca7c1df3ee59fcdec0b2cf1" } +bootc-utils = { git = "https://github.com/bootc-dev/bootc", rev = "93b22f4dbc2d54f7cca7c1df3ee59fcdec0b2cf1" } +rustix = { version = "1", features = ["thread", "net", "fs", "pipe", "system", "process", "mount"] } +vsock = "0.5" +nix = { version = "0.29", features = ["socket"] } +libc = "0.2" + [dev-dependencies] similar-asserts = "1.5" diff --git a/crates/kit/src/cpio.rs b/crates/kit/src/cpio.rs index cb42afdc..c937373c 100644 --- a/crates/kit/src/cpio.rs +++ b/crates/kit/src/cpio.rs @@ -3,6 +3,9 @@ //! The Linux kernel supports concatenating multiple CPIO archives, //! so we can simply append our files to an existing initramfs. +// On non-Linux, this module is unused as it's for initramfs manipulation +#![cfg_attr(not(target_os = "linux"), allow(dead_code))] + use std::io::{self, Write}; use cpio::newc::Builder as NewcBuilder; diff --git a/crates/kit/src/install_options.rs b/crates/kit/src/install_options.rs index 2e33ae18..a284558c 100644 --- a/crates/kit/src/install_options.rs +++ b/crates/kit/src/install_options.rs @@ -4,6 +4,9 @@ //! operations, ensuring consistency across to-disk, libvirt-upload-disk, //! and other installation-related commands. +// On non-Linux, this module is unused as it's for installation operations +#![cfg_attr(not(target_os = "linux"), allow(dead_code))] + use camino::Utf8PathBuf; use clap::Parser; diff --git a/crates/kit/src/instancetypes.rs b/crates/kit/src/instancetypes.rs index edaaf3d7..70a8c4cc 100644 --- a/crates/kit/src/instancetypes.rs +++ b/crates/kit/src/instancetypes.rs @@ -10,6 +10,9 @@ //! //! Source: https://github.com/kubevirt/common-instancetypes +// On non-Linux, this module is unused as it's for VM instance types +#![cfg_attr(not(target_os = "linux"), allow(dead_code))] + /// Instance type variants with associated vCPU and memory specifications /// /// Source: https://github.com/kubevirt/common-instancetypes/blob/main/instancetypes/u/1/sizes.yaml diff --git a/crates/kit/src/lib.rs b/crates/kit/src/lib.rs index aa9486ba..279e5caa 100644 --- a/crates/kit/src/lib.rs +++ b/crates/kit/src/lib.rs @@ -1,6 +1,9 @@ //! bcvk library - exposes internal modules for testing pub mod cpio; -pub mod kernel; pub mod qemu_img; pub mod xml_utils; + +// Linux-only modules +#[cfg(target_os = "linux")] +pub mod kernel; diff --git a/crates/kit/src/main.rs b/crates/kit/src/main.rs index 468acd71..ef566798 100644 --- a/crates/kit/src/main.rs +++ b/crates/kit/src/main.rs @@ -1,40 +1,65 @@ //! Bootc Virtualization Kit (bcvk) - A toolkit for bootc containers and local virtualization -use cap_std_ext::cap_std::fs::Dir; use clap::{Parser, Subcommand}; -use color_eyre::{eyre::Context as _, Report, Result}; +#[cfg(target_os = "linux")] +use color_eyre::eyre::Context as _; +use color_eyre::{Report, Result}; +mod cli_json; +mod common_opts; +mod cpio; +mod install_options; +mod instancetypes; +mod qemu_img; +mod xml_utils; + +// Linux-only modules +#[cfg(target_os = "linux")] mod arch; +#[cfg(target_os = "linux")] mod boot_progress; +#[cfg(target_os = "linux")] mod cache_metadata; -mod cli_json; -mod common_opts; +#[cfg(target_os = "linux")] mod container_entrypoint; -mod cpio; +#[cfg(target_os = "linux")] mod credentials; +#[cfg(target_os = "linux")] mod domain_list; +#[cfg(target_os = "linux")] mod ephemeral; +#[cfg(target_os = "linux")] mod images; -mod install_options; -mod instancetypes; +#[cfg(target_os = "linux")] mod kernel; +#[cfg(target_os = "linux")] mod libvirt; +#[cfg(target_os = "linux")] mod libvirt_upload_disk; +#[cfg(target_os = "linux")] #[allow(dead_code)] mod podman; +#[cfg(target_os = "linux")] mod qemu; -mod qemu_img; +#[cfg(target_os = "linux")] mod run_ephemeral; +#[cfg(target_os = "linux")] mod run_ephemeral_ssh; +#[cfg(target_os = "linux")] mod ssh; +#[cfg(target_os = "linux")] mod status_monitor; +#[cfg(target_os = "linux")] mod supervisor_status; +#[cfg(target_os = "linux")] pub(crate) mod systemd; +#[cfg(target_os = "linux")] mod to_disk; +#[cfg(target_os = "linux")] mod utils; -mod xml_utils; /// Default state directory for bcvk container data +#[cfg(target_os = "linux")] pub const CONTAINER_STATEDIR: &str = "/var/lib/bcvk"; /// A comprehensive toolkit for bootc containers and local virtualization. @@ -50,12 +75,14 @@ struct Cli { command: Commands, } +#[cfg(target_os = "linux")] #[derive(Parser)] struct DebugInternalsOpts { #[command(subcommand)] command: DebugInternalsCmds, } +#[cfg(target_os = "linux")] #[derive(Subcommand)] enum DebugInternalsCmds { OpenTree { path: std::path::PathBuf }, @@ -75,21 +102,54 @@ enum InternalsCmds { DumpCliJson, } +/// Stub subcommands for macOS (shows error message when run) +#[cfg(not(target_os = "linux"))] +#[derive(Debug, Subcommand)] +pub enum StubEphemeralCommands { + /// Run bootc containers as ephemeral VMs + #[clap(name = "run")] + Run, + /// Run ephemeral VM and SSH into it + #[clap(name = "run-ssh")] + RunSsh, + /// Connect to running VMs via SSH + #[clap(name = "ssh")] + Ssh, + /// List ephemeral VM containers + #[clap(name = "ps")] + Ps, + /// Remove all ephemeral VM containers + #[clap(name = "rm-all")] + RmAll, +} + /// Available bcvk commands for container and VM management. #[derive(Subcommand)] enum Commands { + // Linux-only commands with full functionality + #[cfg(target_os = "linux")] /// Manage and inspect bootc container images #[clap(subcommand)] Images(images::ImagesOpts), + #[cfg(target_os = "linux")] /// Manage ephemeral VMs for bootc containers #[clap(subcommand)] Ephemeral(ephemeral::EphemeralCommands), + // macOS stub: ephemeral command exists but errors out + #[cfg(not(target_os = "linux"))] + /// Manage ephemeral VMs for bootc containers (not available on this platform) + #[clap(subcommand)] + Ephemeral(StubEphemeralCommands), + + #[cfg(target_os = "linux")] /// Install bootc images to persistent disk images #[clap(name = "to-disk")] ToDisk(to_disk::ToDiskOpts), + // Note: libvirt is intentionally NOT available on macOS + #[cfg(target_os = "linux")] /// Manage libvirt integration for bootc containers Libvirt { /// Hypervisor connection URI (e.g., qemu:///system, qemu+ssh://host/system) @@ -100,14 +160,17 @@ enum Commands { command: libvirt::LibvirtSubcommands, }, + #[cfg(target_os = "linux")] /// Upload bootc disk images to libvirt (deprecated) #[clap(name = "libvirt-upload-disk", hide = true)] LibvirtUploadDisk(libvirt_upload_disk::LibvirtUploadDiskOpts), + #[cfg(target_os = "linux")] /// Internal container entrypoint command (hidden from help) #[clap(hide = true)] ContainerEntrypoint(container_entrypoint::ContainerEntrypointOpts), + #[cfg(target_os = "linux")] /// Internal debugging and diagnostic tools (hidden from help) #[clap(hide = true)] DebugInternals(DebugInternalsOpts), @@ -149,22 +212,43 @@ fn install_tracing() { /// Initializes logging, error handling, and command dispatch for all /// bcvk operations including VM management, SSH access, and /// container image handling. +// On non-Linux, all commands return errors so post-match code is unreachable +#[cfg_attr(not(target_os = "linux"), allow(unreachable_code))] fn main() -> Result<(), Report> { install_tracing(); color_eyre::install()?; let cli = Cli::parse(); + + #[cfg(target_os = "linux")] let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .context("Init tokio runtime")?; match cli.command { + #[cfg(target_os = "linux")] Commands::Images(opts) => opts.run()?, + + #[cfg(target_os = "linux")] Commands::Ephemeral(cmd) => cmd.run()?, + + // macOS stub: ephemeral command exists but errors out + #[cfg(not(target_os = "linux"))] + Commands::Ephemeral(_) => { + return Err(color_eyre::eyre::eyre!( + "The 'ephemeral' command is not available on macOS.\n\ + bcvk requires Linux with KVM/QEMU for VM operations.\n\ + See https://github.com/bootc-dev/bcvk/issues/21 for more information." + )); + } + + #[cfg(target_os = "linux")] Commands::ToDisk(opts) => { to_disk::run(opts)?; } + + #[cfg(target_os = "linux")] Commands::Libvirt { connect, command } => { let options = libvirt::LibvirtOptions { connect }; match command { @@ -193,12 +277,16 @@ fn main() -> Result<(), Report> { } } } + + #[cfg(target_os = "linux")] Commands::LibvirtUploadDisk(opts) => { eprintln!( "Warning: 'libvirt-upload-disk' is deprecated. Use 'libvirt upload' instead." ); libvirt_upload_disk::run(opts)?; } + + #[cfg(target_os = "linux")] Commands::ContainerEntrypoint(opts) => { // Create a tokio runtime for async container entrypoint operations rt.block_on(async move { @@ -208,8 +296,11 @@ fn main() -> Result<(), Report> { })?; tracing::trace!("Exiting runtime"); } + + #[cfg(target_os = "linux")] Commands::DebugInternals(opts) => match opts.command { DebugInternalsCmds::OpenTree { path } => { + use cap_std_ext::cap_std::fs::Dir; let fd = rustix::mount::open_tree( rustix::fs::CWD, path, @@ -220,16 +311,32 @@ fn main() -> Result<(), Report> { tracing::debug!("{:?}", fd.entries()?.into_iter().collect::>()); } }, - Commands::Internals(opts) => match opts.command { + + Commands::Internals(opts) => { #[cfg(feature = "docgen")] - InternalsCmds::DumpCliJson => { - let json = cli_json::dump_cli_json()?; - println!("{}", json); + match opts.command { + InternalsCmds::DumpCliJson => { + let json = cli_json::dump_cli_json()?; + println!("{}", json); + } } - }, + + // Without docgen feature, Internals has no subcommands + #[cfg(not(feature = "docgen"))] + { + let _ = opts; + return Err(color_eyre::eyre::eyre!( + "No internal commands available without docgen feature" + )); + } + } } + tracing::debug!("exiting"); + // Ensure we don't block on any spawned tasks + #[cfg(target_os = "linux")] rt.shutdown_background(); - std::process::exit(0) + + Ok(()) } diff --git a/crates/kit/src/qemu_img.rs b/crates/kit/src/qemu_img.rs index 1047ac4f..86a328c4 100644 --- a/crates/kit/src/qemu_img.rs +++ b/crates/kit/src/qemu_img.rs @@ -1,5 +1,8 @@ //! Helper functions for interacting with qemu-img +// On non-Linux, this module is unused as it's for qemu-img operations +#![cfg_attr(not(target_os = "linux"), allow(dead_code))] + use camino::Utf8Path; use color_eyre::{eyre::Context, Result}; use serde::Deserialize; diff --git a/crates/kit/src/xml_utils.rs b/crates/kit/src/xml_utils.rs index f0214438..e7ea4ecf 100644 --- a/crates/kit/src/xml_utils.rs +++ b/crates/kit/src/xml_utils.rs @@ -3,6 +3,9 @@ //! This module provides helper functions for generating and parsing XML using the quick-xml crate, //! replacing string-based XML manipulation with proper XML handling. +// On non-Linux, much of this module is unused as it's primarily for libvirt XML +#![cfg_attr(not(target_os = "linux"), allow(dead_code))] + use color_eyre::{eyre::eyre, Result}; use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; use quick_xml::reader::Reader;