From ac0188f315b5771ad718fe8f374a08b6980683c6 Mon Sep 17 00:00:00 2001 From: Kaedo17 Date: Sun, 25 Oct 2026 13:45:19 +0800 Subject: [PATCH] ci: name the missing ALSA package instead of dying at link time When the ALSA development package is missing, `cargo xtask ci`'s audio stage failed at link time with a bare linker error. The check now says which package installs ALSA's contents on each supported distro, the missing one is named in the error, and the stage's own tests pin both the present-ALSA pass and the per-distro message. --- CONTRIBUTING.md | 14 ++++++++++++ xtask/src/main.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dfa28c9f0..16c969a72 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,20 @@ work apply to everyone, people and AI agents alike, and they are not negotiable. You need [Rust](https://rustup.rs/) 1.95 or newer. +On Linux the desktop app plays preview sound through `cpal`, so the build also needs the ALSA +development headers. Without them `alsa-sys` fails at link time with an unrelated-looking error; +`cargo xtask ci` checks for them up front and names the package: + +```sh +sudo dnf install alsa-lib-devel # Fedora +sudo apt install libasound2-dev # Debian / Ubuntu +sudo pacman -S alsa-lib # Arch +``` + +If you cannot install the package, point `PKG_CONFIG_PATH` at a directory holding an `alsa.pc` +whose `Libs` line names your system's `libasound` (usually `libasound.so.2`); every CI run needs +that variable set. + ```sh cargo run -p effectcraft -- --demo # the desktop app, with the demo project cargo test --workspace diff --git a/xtask/src/main.rs b/xtask/src/main.rs index da8026d78..e5c26cd2c 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -407,6 +407,40 @@ fn serve_dir(dir: &std::path::Path, port: u16) -> Result<(), String> { Ok(()) } +/// The guidance printed when the ALSA development package is missing. Split out so the test can +/// check the package names without needing a machine that is actually missing them. +fn audio_backend_hint(found: bool) -> Result<(), String> { + if found { + return Ok(()); + } + Err("Linux audio needs the ALSA development package, and pkg-config cannot find it.\n\ + The desktop app plays preview sound through cpal, whose alsa-sys dependency asks\n\ + pkg-config for `alsa`; without the package the build dies minutes later with an\n\ + unrelated-looking linker error.\n\ + \n\ + Fedora: sudo dnf install alsa-lib-devel\n\ + Debian/Ubuntu: sudo apt install libasound2-dev\n\ + Arch: sudo pacman -S alsa-lib\n\ + \n\ + Install one of those and run `cargo xtask ci` again. If you cannot install it, point\n\ + PKG_CONFIG_PATH at a directory holding an `alsa.pc` whose Libs line names your system's\n\ + libasound (usually `libasound.so.2`); every CI run will need that variable set." + .into()) +} + +/// `cargo xtask ci` builds the desktop app on the host, so on Linux it needs ALSA headers to be +/// present. Checking here turns a late, confusing link error into one line naming the package. +#[cfg(target_os = "linux")] +fn audio_backend() -> Result<(), String> { + let found = Command::new("pkg-config").args(["--exists", "alsa"]).status().map(|s| s.success()).unwrap_or(false); + audio_backend_hint(found) +} + +#[cfg(not(target_os = "linux"))] +fn audio_backend() -> Result<(), String> { + Ok(()) +} + /// Lib, bin and integration tests (examples aren't linked; clippy checked them), then doctests, /// which nextest doesn't run. /// @@ -438,7 +472,8 @@ type Stage = fn() -> Result<(), String>; /// Every stage runs even when an earlier one fails, so one run reports all the problems. fn ci() -> Result<(), String> { - let stages: [(&str, Stage); 7] = [ + let stages: [(&str, Stage); 8] = [ + ("audio backend", audio_backend), ("fmt", || run(Command::new(env!("CARGO")).args(["fmt", "--check"]))), ("clippy", || run(Command::new(env!("CARGO")).args(["clippy", "--workspace", "--all-targets", "--", "-D", "warnings"]))), ("tests", tests), @@ -484,3 +519,21 @@ fn main() -> ExitCode { pub fn root() -> std::path::PathBuf { std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent().expect("xtask has a parent dir").to_path_buf() } + +#[cfg(test)] +mod tests { + use super::audio_backend_hint; + + #[test] + fn alsa_present_is_not_an_error() { + assert!(audio_backend_hint(true).is_ok()); + } + + #[test] + fn alsa_missing_names_the_package_for_each_distro() { + let e = audio_backend_hint(false).expect_err("missing ALSA must fail CI"); + for want in ["alsa-lib-devel", "libasound2-dev", "alsa-lib", "pkg-config", "PKG_CONFIG_PATH"] { + assert!(e.contains(want), "hint lost `{want}`:\n{e}"); + } + } +}