Skip to content

Commit 9aa36c4

Browse files
committed
fix(#504): Add BusyBox ar compatibility with ranlib fallback
Fixes compilation failures when using BusyBox ar on Windows (and other platforms with non-GNU ar implementations). BusyBox's ar doesn't support the -s flag for symbol table generation. This change adds graceful fallback behavior: 1. Try ar s first (works with GNU ar and most implementations) 2. If ar s fails, fall back to ranlib (equivalent functionality) 3. If ranlib fails/unavailable, continue without symbol tables (acceptable as symbol tables are an optimization, not a requirement) Includes comprehensive cross-platform test coverage that verifies the fallback mechanism works correctly. Fixes #504
1 parent 7c01d41 commit 9aa36c4

File tree

3 files changed

+144
-3
lines changed

3 files changed

+144
-3
lines changed

src/lib.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2688,7 +2688,26 @@ impl Build {
26882688
// NOTE: We add `s` even if flags were passed using $ARFLAGS/ar_flag, because `s`
26892689
// here represents a _mode_, not an arbitrary flag. Further discussion of this choice
26902690
// can be seen in https://github.com/rust-lang/cc-rs/pull/763.
2691-
run(ar.arg("s").arg(dst), &self.cargo_output)?;
2691+
2692+
// Try ar s first (works with GNU ar and most standard implementations)
2693+
let ar_result = run(ar.arg("s").arg(dst), &self.cargo_output);
2694+
2695+
// If ar s fails (e.g., BusyBox ar doesn't support -s flag), fall back to ranlib.
2696+
// See https://github.com/rust-lang/cc-rs/issues/504
2697+
if ar_result.is_err() {
2698+
// ranlib is equivalent to ar s
2699+
match self.try_get_ranlib() {
2700+
Ok(mut ranlib) => {
2701+
// Ignore ranlib errors - symbol tables are recommended but not always required.
2702+
// Many linkers work fine without explicit symbol tables.
2703+
let _ = run(ranlib.arg(dst), &self.cargo_output);
2704+
}
2705+
Err(_) => {
2706+
// No ranlib available - continue without symbol table generation.
2707+
// This is acceptable as symbol tables are an optimization, not a requirement.
2708+
}
2709+
}
2710+
}
26922711
}
26932712

26942713
Ok(())

tests/busybox_ar_fallback.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//! Test for issue #504: BusyBox ar compatibility
2+
//! BusyBox ar doesn't support the `-s` flag, so we need to fall back to ranlib
3+
4+
#![allow(clippy::disallowed_methods)]
5+
6+
use crate::support::Test;
7+
8+
mod support;
9+
10+
#[test]
11+
fn busybox_ar_fallback() {
12+
// Use standard test setup with proper cc-shim
13+
let test = Test::gnu();
14+
15+
// Override ar with a BusyBox-like version that fails on -s flag
16+
// But still creates the archive file for other operations
17+
let ar_script = if cfg!(windows) {
18+
r#"@echo off
19+
REM Check for -s flag - fail if present
20+
for %%a in (%*) do (
21+
if "%%a"=="s" (
22+
echo BusyBox ar: unknown option -- s >&2
23+
exit /b 1
24+
)
25+
)
26+
27+
REM Create the archive file (last argument is typically the output file)
28+
REM Get last argument
29+
set "LAST_ARG="
30+
for %%a in (%*) do set "LAST_ARG=%%a"
31+
REM Create an empty file to simulate archive creation
32+
if not "%LAST_ARG%"=="" type nul > "%LAST_ARG%"
33+
exit /b 0
34+
"#
35+
} else {
36+
r#"#!/bin/sh
37+
# Check for -s flag - fail if present
38+
for arg in "$@"; do
39+
if [ "$arg" = "s" ]; then
40+
echo "BusyBox ar: unknown option -- s" >&2
41+
exit 1
42+
fi
43+
done
44+
45+
# Create the archive file (last argument is typically the output file)
46+
# Get the last argument
47+
for arg in "$@"; do
48+
LAST_ARG="$arg"
49+
done
50+
# Create an empty file to simulate archive creation
51+
if [ -n "$LAST_ARG" ]; then
52+
touch "$LAST_ARG"
53+
fi
54+
exit 0
55+
"#
56+
};
57+
58+
// Overwrite the shimmed ar with our BusyBox-like version
59+
let ar_name = format!("ar{}", std::env::consts::EXE_SUFFIX);
60+
let ar_path = test.td.path().join(&ar_name);
61+
std::fs::write(&ar_path, ar_script).unwrap();
62+
63+
#[cfg(unix)]
64+
{
65+
use std::fs;
66+
use std::os::unix::fs::PermissionsExt;
67+
let mut perms = fs::metadata(&ar_path).unwrap().permissions();
68+
perms.set_mode(0o755);
69+
fs::set_permissions(&ar_path, perms).unwrap();
70+
}
71+
72+
// Create a mock ranlib that records it was called
73+
let ranlib_script = if cfg!(windows) {
74+
format!(
75+
r#"@echo off
76+
echo ranlib-called >> "{}\ranlib-calls.txt"
77+
exit /b 0
78+
"#,
79+
test.td.path().display()
80+
)
81+
} else {
82+
format!(
83+
r#"#!/bin/sh
84+
echo "ranlib-called" >> "{}/ranlib-calls.txt"
85+
exit 0
86+
"#,
87+
test.td.path().display()
88+
)
89+
};
90+
91+
let ranlib_name = format!("ranlib{}", std::env::consts::EXE_SUFFIX);
92+
let ranlib_path = test.td.path().join(&ranlib_name);
93+
std::fs::write(&ranlib_path, ranlib_script).unwrap();
94+
95+
#[cfg(unix)]
96+
{
97+
use std::fs;
98+
use std::os::unix::fs::PermissionsExt;
99+
let mut perms = fs::metadata(&ranlib_path).unwrap().permissions();
100+
perms.set_mode(0o755);
101+
fs::set_permissions(&ranlib_path, perms).unwrap();
102+
}
103+
104+
// Build with the BusyBox-like ar
105+
// This should succeed even though ar -s fails, because it falls back to ranlib
106+
test.gcc().file("foo.c").compile("foo");
107+
108+
// Verify ranlib was called (fallback worked)
109+
let ranlib_calls = test.td.path().join("ranlib-calls.txt");
110+
assert!(
111+
ranlib_calls.exists(),
112+
"ranlib should have been called as fallback when ar -s failed"
113+
);
114+
115+
// Verify the contents show ranlib was invoked
116+
let content = std::fs::read_to_string(&ranlib_calls).expect("Failed to read ranlib-calls.txt");
117+
assert!(
118+
content.contains("ranlib-called"),
119+
"ranlib-calls.txt should contain 'ranlib-called', but contains: {}",
120+
content
121+
);
122+
}

tests/support/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ impl Test {
6060

6161
pub fn gnu() -> Test {
6262
let t = Test::new();
63-
t.shim("cc").shim("c++").shim("ar");
63+
t.shim("cc").shim("c++").shim("ar").shim("ranlib");
6464
t
6565
}
6666

@@ -81,7 +81,7 @@ impl Test {
8181

8282
pub fn clang() -> Test {
8383
let t = Test::new();
84-
t.shim("clang").shim("clang++").shim("ar");
84+
t.shim("clang").shim("clang++").shim("ar").shim("ranlib");
8585
t
8686
}
8787

0 commit comments

Comments
 (0)