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}"); + } + } +}