Skip to content

Commit 0373d6b

Browse files
authored
fix(ci): disable audio feature for musl static builds (#100)
* fix(ci): disable audio feature for musl static builds The alsa-sys crate requires glibc-based ALSA libraries which are incompatible with musl static builds. This change makes the audio feature optional and disables it for musl targets. Changes: - Made rodio dependency optional in cortex-tui behind 'audio' feature - Added conditional compilation for audio-related code in sound.rs - Disabled audio feature for musl builds in release workflow - Non-audio builds fall back to terminal bell notifications This allows portable static Linux binaries to be built without audio support, while preserving full audio functionality for standard (glibc) builds. * style: fix formatting
1 parent 812f977 commit 0373d6b

File tree

4 files changed

+61
-6
lines changed

4 files changed

+61
-6
lines changed

.github/workflows/release.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ jobs:
246246
- name: Build release binary (static musl x86_64)
247247
if: matrix.target == 'x86_64-unknown-linux-musl'
248248
run: |
249-
cargo +nightly build --release --target ${{ matrix.target }} -p cortex-cli
249+
# Build without audio feature (rodio/alsa) for musl - uses terminal bell fallback
250+
cargo +nightly build --release --target ${{ matrix.target }} -p cortex-cli --no-default-features --features cortex-tui
250251
env:
251252
RUSTFLAGS: "-Zthreads=32 -C target-feature=+crt-static"
252253
CARGO_PROFILE_RELEASE_LTO: thin
@@ -257,7 +258,8 @@ jobs:
257258
- name: Build release binary (static musl aarch64)
258259
if: matrix.target == 'aarch64-unknown-linux-musl'
259260
run: |
260-
cargo +nightly build --release --target ${{ matrix.target }} -p cortex-cli
261+
# Build without audio feature (rodio/alsa) for musl - uses terminal bell fallback
262+
cargo +nightly build --release --target ${{ matrix.target }} -p cortex-cli --no-default-features --features cortex-tui
261263
env:
262264
RUSTFLAGS: "-Zthreads=32 -C target-feature=+crt-static"
263265
CARGO_PROFILE_RELEASE_LTO: thin

src/cortex-cli/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ path = "src/lib.rs"
1717
workspace = true
1818

1919
[features]
20-
default = ["cortex-tui"]
20+
default = ["cortex-tui", "audio"]
2121
# Use the new Cortex TUI (120 FPS, Cortex theme)
2222
cortex-tui = ["dep:cortex-tui"]
23+
# Audio notifications - disabled on musl targets due to alsa-sys incompatibility
24+
# Falls back to terminal bell when disabled
25+
audio = ["cortex-tui?/audio"]
2326

2427
[dependencies]
2528
cortex-engine = { workspace = true }

src/cortex-tui/Cargo.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ license.workspace = true
77
repository.workspace = true
88
authors.workspace = true
99

10+
[features]
11+
default = ["audio"]
12+
# Audio notifications - disabled on musl targets due to alsa-sys incompatibility
13+
audio = ["dep:rodio"]
14+
1015
[dependencies]
1116
# Cortex TUI core (the TUI engine)
1217
cortex-core = { path = "../cortex-core" }
@@ -66,8 +71,8 @@ walkdir = { workspace = true }
6671
# External editor
6772
which = { workspace = true }
6873

69-
# Audio notifications
70-
rodio = { workspace = true }
74+
# Audio notifications (optional - disabled on musl targets)
75+
rodio = { workspace = true, optional = true }
7176

7277
[target.'cfg(unix)'.dependencies]
7378
libc = "0.2"

src/cortex-tui/src/sound.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@
66
//! Audio playback is handled in a dedicated thread since rodio's OutputStream
77
//! is not Send/Sync. We use a channel-based approach to send sound requests
88
//! from any thread to the dedicated audio thread.
9+
//!
10+
//! On platforms without audio support (e.g., musl builds), falls back to
11+
//! terminal bell notifications.
912
10-
use std::io::{Cursor, Write};
13+
#[cfg(feature = "audio")]
14+
use std::io::Cursor;
15+
use std::io::Write;
1116
use std::sync::OnceLock;
17+
#[cfg(feature = "audio")]
1218
use std::sync::mpsc;
19+
#[cfg(feature = "audio")]
1320
use std::thread;
1421

1522
/// Type of sound notification
@@ -23,16 +30,24 @@ pub enum SoundType {
2330

2431
/// Channel sender for sound requests (Send + Sync)
2532
/// Using sync_channel with capacity of 16 to prevent unbounded growth
33+
#[cfg(feature = "audio")]
2634
static SOUND_SENDER: OnceLock<mpsc::SyncSender<SoundType>> = OnceLock::new();
2735

36+
/// Track whether sound system has been initialized (for non-audio builds)
37+
#[cfg(not(feature = "audio"))]
38+
static SOUND_INITIALIZED: OnceLock<bool> = OnceLock::new();
39+
2840
/// Embedded WAV data for response complete sound
41+
#[cfg(feature = "audio")]
2942
const COMPLETE_WAV: &[u8] = include_bytes!("sounds/complete.wav");
3043
/// Embedded WAV data for approval required sound
44+
#[cfg(feature = "audio")]
3145
const APPROVAL_WAV: &[u8] = include_bytes!("sounds/approval.wav");
3246

3347
/// Initialize the global sound system.
3448
/// Spawns a dedicated audio thread that owns the OutputStream.
3549
/// Should be called once at application startup.
50+
#[cfg(feature = "audio")]
3651
pub fn init() {
3752
// Only initialize once
3853
if SOUND_SENDER.get().is_some() {
@@ -78,7 +93,17 @@ pub fn init() {
7893
.expect("Failed to spawn audio thread");
7994
}
8095

96+
/// Initialize the global sound system (no-op for non-audio builds).
97+
/// Falls back to terminal bell for notifications.
98+
#[cfg(not(feature = "audio"))]
99+
pub fn init() {
100+
// Mark as initialized so is_initialized() returns true
101+
let _ = SOUND_INITIALIZED.set(true);
102+
tracing::debug!("Audio support not available, using terminal bell fallback");
103+
}
104+
81105
/// Internal function to play WAV data using a stream handle
106+
#[cfg(feature = "audio")]
82107
fn play_wav_internal(
83108
handle: &rodio::OutputStreamHandle,
84109
data: &'static [u8],
@@ -103,6 +128,7 @@ fn emit_terminal_bell() {
103128
/// If `enabled` is false or audio is unavailable, this function does nothing.
104129
/// Falls back to terminal bell if the sound system is not initialized.
105130
/// This function is non-blocking - sound plays in background thread.
131+
#[cfg(feature = "audio")]
106132
pub fn play(sound_type: SoundType, enabled: bool) {
107133
if !enabled {
108134
return;
@@ -121,6 +147,16 @@ pub fn play(sound_type: SoundType, enabled: bool) {
121147
}
122148
}
123149

150+
/// Play a notification sound (non-audio build - uses terminal bell).
151+
#[cfg(not(feature = "audio"))]
152+
pub fn play(_sound_type: SoundType, enabled: bool) {
153+
if !enabled {
154+
return;
155+
}
156+
// No audio support, use terminal bell
157+
emit_terminal_bell();
158+
}
159+
124160
/// Play notification for response completion
125161
pub fn play_response_complete(enabled: bool) {
126162
play(SoundType::ResponseComplete, enabled);
@@ -133,10 +169,17 @@ pub fn play_approval_required(enabled: bool) {
133169

134170
/// Check if the sound system has been initialized.
135171
/// Useful for testing and diagnostics.
172+
#[cfg(feature = "audio")]
136173
pub fn is_initialized() -> bool {
137174
SOUND_SENDER.get().is_some()
138175
}
139176

177+
/// Check if the sound system has been initialized (non-audio build).
178+
#[cfg(not(feature = "audio"))]
179+
pub fn is_initialized() -> bool {
180+
SOUND_INITIALIZED.get().is_some()
181+
}
182+
140183
#[cfg(test)]
141184
mod tests {
142185
use super::*;
@@ -183,13 +226,15 @@ mod tests {
183226
}
184227

185228
#[test]
229+
#[cfg(feature = "audio")]
186230
fn test_embedded_wav_data_not_empty() {
187231
// Verify that the embedded WAV files are not empty
188232
assert!(!COMPLETE_WAV.is_empty(), "complete.wav should not be empty");
189233
assert!(!APPROVAL_WAV.is_empty(), "approval.wav should not be empty");
190234
}
191235

192236
#[test]
237+
#[cfg(feature = "audio")]
193238
fn test_embedded_wav_data_has_riff_header() {
194239
// WAV files should start with "RIFF" magic bytes
195240
assert!(

0 commit comments

Comments
 (0)