Skip to content

Commit 6b66492

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 6b66492

File tree

2 files changed

+138
-1
lines changed

2 files changed

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

0 commit comments

Comments
 (0)