Skip to content

Commit 5c306bb

Browse files
committed
stdbuf: fix cross-compilation
Partial fix for #6591 The code contained a workaround to enforce that libstdbuf was compiled before stdbuf: it was declaring libstdbuf as a build-dependency of stdbuf. This broke cross-compilation, because build-dependencies were compiled for the host architecture, and not for the target architecture. Changes: 1. Removed cpp/cpp_build dependencies: The cpp, cpp_build, and related dependencies were removed. The build system no longer uses the C++-based approach for building libstdbuf, because cross-compiling c++ in a build.rs file would have added a lot of complexity. 2. Build libstdbuf as a separate Rust crate using Cargo, to make cross-compilation work. In the future, "bindeps" could be used to simplify the code, however this is available only in cargo nightly at the moment. The ctor crate is now used to provide constructor attributes for initialization. 3. Remove "feat_require_crate_cpp" which is not needed any more, since stdbuf was the only utility using the cpp crate. 4. Switched from C++/cpp macro-based initialization to using the Rust ctor crate for library initialization. Provided Rust implementations for getting stdin, stdout, and stderr pointers. Tests: This commit fixes this test: cross test --target aarch64-unknown-linux-gnu --features stdbuf test_stdbuf::test_setvbuf_resolution -- --nocapture Signed-off-by: Etienne Cordonnier <[email protected]>
1 parent 598489b commit 5c306bb

File tree

8 files changed

+405
-258
lines changed

8 files changed

+405
-258
lines changed

Cargo.lock

Lines changed: 238 additions & 198 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ expensive_tests = []
3636
test_risky_names = []
3737
# * only build `uudoc` when `--feature uudoc` is activated
3838
uudoc = ["zip", "dep:uuhelp_parser"]
39+
## Optional feature for stdbuf
40+
# "use-external-stdbuf" == use an external libstdbuf.so for stdbuf instead of embedding it
3941
## features
4042
# "feat_acl" == enable support for ACLs (access control lists; by using`--features feat_acl`)
4143
# NOTE:
@@ -158,7 +160,6 @@ feat_os_macos = [
158160
feat_os_unix = [
159161
"feat_Tier1",
160162
#
161-
"feat_require_crate_cpp",
162163
"feat_require_unix",
163164
"feat_require_unix_utmpx",
164165
"feat_require_unix_hostid",
@@ -185,8 +186,6 @@ feat_os_unix_android = [
185186
#
186187
# ** NOTE: these `feat_require_...` sets should be minimized as much as possible to encourage cross-platform availability of utilities
187188
#
188-
# "feat_require_crate_cpp" == set of utilities requiring the `cpp` crate (which fail to compile on several platforms; as of 2020-04-23)
189-
feat_require_crate_cpp = ["stdbuf"]
190189
# "feat_require_unix" == set of utilities requiring support which is only available on unix platforms (as of 2020-04-23)
191190
feat_require_unix = [
192191
"chgrp",
@@ -220,8 +219,6 @@ feat_require_selinux = ["chcon", "runcon"]
220219
feat_os_unix_fuchsia = [
221220
"feat_common_core",
222221
#
223-
"feat_require_crate_cpp",
224-
#
225222
"chgrp",
226223
"chmod",
227224
"chown",
@@ -289,6 +286,7 @@ clap_mangen = "0.2"
289286
compare = "0.1.0"
290287
coz = { version = "0.1.3" }
291288
crossterm = "0.29.0"
289+
ctor = "0.4.1"
292290
ctrlc = { version = "3.4.4", features = ["termination"] }
293291
dns-lookup = { version = "2.0.4" }
294292
exacl = "0.12.0"
@@ -517,7 +515,6 @@ uucore = { workspace = true, features = [
517515
walkdir = { workspace = true }
518516
hex-literal = "1.0.0"
519517
rstest = { workspace = true }
520-
ctor = "0.4.1"
521518

522519
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
523520
procfs = { version = "0.17", default-features = false }

src/uu/stdbuf/Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@ path = "src/stdbuf.rs"
1919

2020
[dependencies]
2121
clap = { workspace = true }
22+
libstdbuf = { package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" }
2223
tempfile = { workspace = true }
2324
uucore = { workspace = true, features = ["parser"] }
2425

25-
[build-dependencies]
26-
libstdbuf = { version = "0.0.30", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" }
27-
2826
[[bin]]
2927
name = "stdbuf"
3028
path = "src/main.rs"

src/uu/stdbuf/build.rs

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
// spell-checker:ignore (ToDO) dylib libstdbuf deps liblibstdbuf
66

77
use std::env;
8-
use std::env::current_exe;
98
use std::fs;
109
use std::path::Path;
10+
use std::process::Command;
1111

1212
#[cfg(not(any(target_vendor = "apple", target_os = "windows")))]
1313
mod platform {
@@ -25,25 +25,103 @@ mod platform {
2525
}
2626

2727
fn main() {
28-
let current_exe = current_exe().unwrap();
28+
println!("cargo:rerun-if-changed=build.rs");
29+
println!("cargo:rerun-if-changed=src/libstdbuf/src/libstdbuf.rs");
2930

30-
let out_dir_string = env::var("OUT_DIR").unwrap();
31-
let out_dir = Path::new(&out_dir_string);
31+
let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
32+
let target = env::var("TARGET").unwrap_or_else(|_| "unknown".to_string());
3233

33-
let deps_dir = current_exe.ancestors().nth(3).unwrap().join("deps");
34-
dbg!(&deps_dir);
34+
// Create a separate build directory for libstdbuf to avoid conflicts
35+
let build_dir = Path::new(&out_dir).join("libstdbuf-build");
36+
fs::create_dir_all(&build_dir).expect("Failed to create build directory");
3537

36-
let libstdbuf = deps_dir
37-
.read_dir()
38-
.unwrap()
39-
.flatten()
40-
.find(|entry| {
41-
let n = entry.file_name();
42-
let name = n.to_string_lossy();
38+
// Get the cargo executable
39+
let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
4340

44-
name.starts_with("liblibstdbuf") && name.ends_with(platform::DYLIB_EXT)
45-
})
46-
.expect("unable to find libstdbuf");
41+
// Build libstdbuf as a separate process with its own target directory
42+
// to avoid interference with the main build
43+
let mut cmd = Command::new(&cargo);
44+
cmd.current_dir(Path::new("src/libstdbuf")).args([
45+
"build",
46+
"--target-dir",
47+
build_dir.to_str().unwrap(),
48+
]);
4749

48-
fs::copy(libstdbuf.path(), out_dir.join("libstdbuf.so")).unwrap();
50+
// Pass the target architecture if we're cross-compiling
51+
if !target.is_empty() && target != "unknown" {
52+
cmd.arg("--target").arg(&target);
53+
}
54+
55+
let status = cmd.status().expect("Failed to build libstdbuf");
56+
57+
if !status.success() {
58+
panic!("Failed to build libstdbuf");
59+
}
60+
61+
// Copy the built library to OUT_DIR for include_bytes! to find
62+
let lib_name = format!("liblibstdbuf{}", platform::DYLIB_EXT);
63+
let dest_path = Path::new(&out_dir).join(format!("libstdbuf{}", platform::DYLIB_EXT));
64+
65+
// Check multiple possible locations for the built library
66+
let possible_paths = if !target.is_empty() && target != "unknown" {
67+
vec![
68+
build_dir.join("debug").join(&target).join(&lib_name),
69+
build_dir.join(&target).join("debug").join(&lib_name),
70+
build_dir
71+
.join(&target)
72+
.join("debug")
73+
.join("deps")
74+
.join(&lib_name),
75+
build_dir
76+
.join(&target)
77+
.join("debug")
78+
.join(format!("lib{}", lib_name)),
79+
]
80+
} else {
81+
vec![
82+
build_dir.join("debug").join(&lib_name),
83+
build_dir.join("debug").join("deps").join(&lib_name),
84+
]
85+
};
86+
87+
// Try to find the library in any of the possible locations
88+
let mut found = false;
89+
for source_path in &possible_paths {
90+
if source_path.exists() {
91+
fs::copy(source_path, &dest_path).expect("Failed to copy libstdbuf library");
92+
found = true;
93+
break;
94+
}
95+
}
96+
97+
if !found {
98+
// Try to find any .so files to help with debugging
99+
find_libs(&build_dir, &platform::DYLIB_EXT);
100+
101+
// Fail the build with helpful error message
102+
panic!(
103+
"Could not find built libstdbuf library. Searched in: {:?}.",
104+
possible_paths
105+
);
106+
}
107+
}
108+
109+
// Helper function to recursively find library files
110+
fn find_libs(dir: &Path, ext: &str) {
111+
if !dir.exists() || !dir.is_dir() {
112+
return;
113+
}
114+
115+
if let Ok(entries) = fs::read_dir(dir) {
116+
for entry in entries.filter_map(Result::ok) {
117+
let path = entry.path();
118+
if path.is_dir() {
119+
find_libs(&path, ext);
120+
} else if let Some(extension) = path.extension() {
121+
if extension == ext.trim_start_matches('.') {
122+
println!("Found library: {}", path.display());
123+
}
124+
}
125+
}
126+
}
49127
}

src/uu/stdbuf/src/libstdbuf/Cargo.toml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,8 @@ edition.workspace = true
1313
[lib]
1414
name = "libstdbuf"
1515
path = "src/libstdbuf.rs"
16-
crate-type = [
17-
"cdylib",
18-
"rlib",
19-
] # XXX: note: the rlib is just to prevent Cargo from spitting out a warning
16+
crate-type = ["cdylib"]
2017

2118
[dependencies]
22-
cpp = "0.5.10"
2319
libc = { workspace = true }
24-
25-
[build-dependencies]
26-
cpp_build = "0.5.10"
20+
ctor = { workspace = true }

src/uu/stdbuf/src/libstdbuf/build.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,24 @@
44
// file that was distributed with this source code.
55
// spell-checker:ignore (ToDO) libstdbuf
66

7-
use cpp_build::Config;
7+
use std::env;
88

99
fn main() {
10-
Config::new().pic(true).build("src/libstdbuf.rs");
10+
// Get and print target information for debugging
11+
let target = env::var("TARGET").unwrap_or_else(|_| "unknown".to_string());
12+
println!("Building libstdbuf for target triple: {}", target);
13+
14+
if let Ok(target_arch) = env::var("CARGO_CFG_TARGET_ARCH") {
15+
println!("Building for target architecture: {}", target_arch);
16+
}
17+
18+
// Make sure we're building position-independent code for use with LD_PRELOAD
19+
println!("cargo:rustc-link-arg=-fPIC");
20+
21+
// Ensure the library doesn't have any undefined symbols
22+
println!("cargo:rustc-link-arg=-z");
23+
println!("cargo:rustc-link-arg=defs");
24+
25+
// Rebuild if the libstdbuf.rs file changes
26+
println!("cargo:rerun-if-changed=src/libstdbuf.rs");
1127
}

src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,41 @@
22
//
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
5-
// spell-checker:ignore (ToDO) IOFBF IOLBF IONBF cstdio setvbuf
5+
// spell-checker:ignore (ToDO) IOFBF IOLBF IONBF setvbuf
66

7-
use cpp::cpp;
7+
use ctor::ctor;
88
use libc::{_IOFBF, _IOLBF, _IONBF, FILE, c_char, c_int, fileno, size_t};
99
use std::env;
1010
use std::ptr;
1111

12-
cpp! {{
13-
#include <cstdio>
14-
15-
extern "C" {
16-
void __stdbuf(void);
12+
// This runs automatically when the library is loaded via LD_PRELOAD
13+
#[ctor]
14+
fn init() {
15+
unsafe { __stdbuf() };
16+
}
1717

18-
void __attribute((constructor))
19-
__stdbuf_init(void) {
20-
__stdbuf();
21-
}
18+
#[unsafe(no_mangle)]
19+
pub unsafe extern "C" fn __stdbuf_get_stdin() -> *mut FILE {
20+
unsafe extern "C" {
21+
static mut stdin: *mut FILE;
22+
}
23+
unsafe { stdin }
24+
}
2225

23-
FILE *__stdbuf_get_stdin() { return stdin; }
24-
FILE *__stdbuf_get_stdout() { return stdout; }
25-
FILE *__stdbuf_get_stderr() { return stderr; }
26+
#[unsafe(no_mangle)]
27+
pub unsafe extern "C" fn __stdbuf_get_stdout() -> *mut FILE {
28+
unsafe extern "C" {
29+
static mut stdout: *mut FILE;
2630
}
27-
}}
31+
unsafe { stdout }
32+
}
2833

29-
unsafe extern "C" {
30-
fn __stdbuf_get_stdin() -> *mut FILE;
31-
fn __stdbuf_get_stdout() -> *mut FILE;
32-
fn __stdbuf_get_stderr() -> *mut FILE;
34+
#[unsafe(no_mangle)]
35+
pub unsafe extern "C" fn __stdbuf_get_stderr() -> *mut FILE {
36+
unsafe extern "C" {
37+
static mut stderr: *mut FILE;
38+
}
39+
unsafe { stderr }
3340
}
3441

3542
fn set_buffer(stream: *mut FILE, value: &str) {
@@ -60,17 +67,25 @@ fn set_buffer(stream: *mut FILE, value: &str) {
6067
}
6168
}
6269

63-
/// # Safety
64-
/// ToDO ... (safety note)
70+
/// Main function that gets called when the library is loaded to adjust buffering
71+
///
72+
/// This function is meant to be called automatically when the library is loaded
73+
/// via LD_PRELOAD. It accesses stdio handles and modifies their buffering.
6574
#[unsafe(no_mangle)]
6675
pub unsafe extern "C" fn __stdbuf() {
6776
if let Ok(val) = env::var("_STDBUF_E") {
68-
set_buffer(unsafe { __stdbuf_get_stderr() }, &val);
77+
unsafe {
78+
set_buffer(__stdbuf_get_stderr(), &val);
79+
}
6980
}
7081
if let Ok(val) = env::var("_STDBUF_I") {
71-
set_buffer(unsafe { __stdbuf_get_stdin() }, &val);
82+
unsafe {
83+
set_buffer(__stdbuf_get_stdin(), &val);
84+
}
7285
}
7386
if let Ok(val) = env::var("_STDBUF_O") {
74-
set_buffer(unsafe { __stdbuf_get_stdout() }, &val);
87+
unsafe {
88+
set_buffer(__stdbuf_get_stdout(), &val);
89+
}
7590
}
7691
}

src/uu/stdbuf/src/stdbuf.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,15 @@ fn get_preload_env(tmp_dir: &TempDir) -> UResult<(String, PathBuf)> {
133133
let (preload, extension) = preload_strings()?;
134134
let inject_path = tmp_dir.path().join("libstdbuf").with_extension(extension);
135135

136+
// Check if the library was properly embedded (not empty)
137+
if STDBUF_INJECT.is_empty() {
138+
return Err(USimpleError::new(
139+
1,
140+
"libstdbuf.so is empty or missing. This may happen during cross-compilation.\n\
141+
Please ensure the build completed successfully and libstdbuf.so is present.",
142+
));
143+
}
144+
136145
let mut file = File::create(&inject_path)?;
137146
file.write_all(STDBUF_INJECT)?;
138147

0 commit comments

Comments
 (0)