Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobserver = []

[dev-dependencies]
tempfile = "3"
serial_test = "3"

[workspace]
members = [
Expand Down
21 changes: 20 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2688,7 +2688,26 @@ impl Build {
// NOTE: We add `s` even if flags were passed using $ARFLAGS/ar_flag, because `s`
// here represents a _mode_, not an arbitrary flag. Further discussion of this choice
// can be seen in https://github.com/rust-lang/cc-rs/pull/763.
run(ar.arg("s").arg(dst), &self.cargo_output)?;

// Try ar s first (works with GNU ar and most standard implementations)
let ar_result = run(ar.arg("s").arg(dst), &self.cargo_output);

// If ar s fails (e.g., BusyBox ar doesn't support -s flag), fall back to ranlib.
// See https://github.com/rust-lang/cc-rs/issues/504
if ar_result.is_err() {
// ranlib is equivalent to ar s
match self.try_get_ranlib() {
Ok(mut ranlib) => {
// Ignore ranlib errors - symbol tables are recommended but not always required.
// Many linkers work fine without explicit symbol tables.
let _ = run(ranlib.arg(dst), &self.cargo_output);
}
Err(_) => {
// No ranlib available - continue without symbol table generation.
// This is acceptable as symbol tables are an optimization, not a requirement.
}
}
}
}

Ok(())
Expand Down
120 changes: 120 additions & 0 deletions tests/busybox_ar_fallback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//! Test for issue #504: BusyBox ar compatibility
//! BusyBox ar doesn't support the `-s` flag, so we need to fall back to ranlib

#![allow(clippy::disallowed_methods)]

use crate::support::Test;
use serial_test::serial;

mod support;

#[test]
#[serial]
fn busybox_ar_fallback() {
// Use standard test setup with proper cc-shim
let test = Test::gnu();
test.shim("ranlib"); // Add ranlib shim for fallback testing

// Override ar with a BusyBox-like version that fails on -s flag
// But still creates the archive file for other operations
let ar_script = if cfg!(windows) {
r#"@echo off
REM Check for -s flag - fail if present
for %%a in (%*) do (
if "%%a"=="s" (
echo BusyBox ar: unknown option -- s >&2
exit /b 1
)
)

REM Create the archive file (last argument is typically the output file)
REM Get last argument
set "LAST_ARG="
for %%a in (%*) do set "LAST_ARG=%%a"
REM Create an empty file to simulate archive creation
if not "%LAST_ARG%"=="" type nul > "%LAST_ARG%"
exit /b 0
"#
} else {
r#"#!/bin/sh
# Check for -s flag - fail if present
for arg in "$@"; do
if [ "$arg" = "s" ]; then
echo "BusyBox ar: unknown option -- s" >&2
exit 1
fi
done

# Create the archive file (last argument is typically the output file)
# Get the last argument
for arg in "$@"; do
LAST_ARG="$arg"
done
# Create an empty file to simulate archive creation
if [ -n "$LAST_ARG" ]; then
touch "$LAST_ARG"
fi
exit 0
"#
};

// Overwrite the shimmed ar with our BusyBox-like version
let ar_name = format!("ar{}", std::env::consts::EXE_SUFFIX);
let ar_path = test.td.path().join(&ar_name);
std::fs::write(&ar_path, ar_script).unwrap();

#[cfg(unix)]
{
use std::fs;
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&ar_path).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&ar_path, perms).unwrap();
}

// Create a mock ranlib that records it was called
// Use CC_SHIM_OUT_DIR environment variable so it works across different test temp directories
let ranlib_script = if cfg!(windows) {
r#"@echo off
echo ranlib-called >> "%CC_SHIM_OUT_DIR%\ranlib-calls.txt"
exit /b 0
"#
} else {
r#"#!/bin/sh
echo "ranlib-called" >> "$CC_SHIM_OUT_DIR/ranlib-calls.txt"
exit 0
"#
};

let ranlib_name = format!("ranlib{}", std::env::consts::EXE_SUFFIX);
let ranlib_path = test.td.path().join(&ranlib_name);
std::fs::write(&ranlib_path, ranlib_script).unwrap();

#[cfg(unix)]
{
use std::fs;
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&ranlib_path).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&ranlib_path, perms).unwrap();
}

// Build with the BusyBox-like ar
// This should succeed even though ar -s fails, because it falls back to ranlib
test.gcc().file("foo.c").compile("foo");

// Verify ranlib was called (fallback worked)
let ranlib_calls = test.td.path().join("ranlib-calls.txt");
assert!(
ranlib_calls.exists(),
"ranlib should have been called as fallback when ar -s failed"
);

// Verify the contents show ranlib was invoked
let content = std::fs::read_to_string(&ranlib_calls).expect("Failed to read ranlib-calls.txt");
assert!(
content.contains("ranlib-called"),
"ranlib-calls.txt should contain 'ranlib-called', but contains: {}",
content
);
}
2 changes: 2 additions & 0 deletions tests/cflags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
mod support;

use crate::support::Test;
use serial_test::serial;
use std::env;

#[test]
#[serial]
fn cflags() {
gnu_no_warnings_if_cflags();
cflags_order();
Expand Down
Loading