stdbuf: build on Windows (depending on cygwin dll)

This commit is contained in:
oech3
2026-05-17 00:08:50 +09:00
committed by Etienne Cordonnier
parent a8d51a2cfd
commit db7b8a6e58
8 changed files with 49 additions and 26 deletions
+1
View File
@@ -275,6 +275,7 @@ feat_os_unix_musl = [
# "feat_os_windows" == set of utilities which can be built/run on modern/usual windows platforms
feat_os_windows = [
"feat_Tier1", ## == "feat_os_windows_legacy" + "hostname"
"stdbuf",
]
## (secondary platforms) feature sets
# "feat_os_unix_gnueabihf" == set of utilities which can be built/run on the "arm-unknown-linux-gnueabihf" target (ARMv6 Linux [hardfloat])
+1 -1
View File
@@ -81,7 +81,7 @@ $(info Detected OS = $(OS))
ifeq (,$(findstring windows,$(OS)))
FEATURE_EXTRACT_UTILS := feat_os_unix
else
FEATURE_EXTRACT_UTILS := feat_Tier1
FEATURE_EXTRACT_UTILS := windows
endif
PROGS := $(shell cargo tree --depth 1 --features $(FEATURE_EXTRACT_UTILS) --format "{p}" --prefix none | sed -E -n 's/^uu_([^ ]+).*/\1/p')
+3 -1
View File
@@ -22,12 +22,14 @@ doctest = false
[dependencies]
clap = { workspace = true }
libstdbuf = { package = "uu_stdbuf_libstdbuf", version = "0.8.0", path = "src/libstdbuf" }
tempfile = { workspace = true }
uucore = { workspace = true, features = ["parser-size"] }
thiserror = { workspace = true }
fluent = { workspace = true }
[target.'cfg(unix)'.dependencies]
libstdbuf = { package = "uu_stdbuf_libstdbuf", version = "0.8.0", path = "src/libstdbuf" }
# "feat_external_libstdbuf": use an external libstdbuf.so for stdbuf instead of embedding it into
# the stdbuf binary.
# There are 2 use-cases:
+5 -4
View File
@@ -10,6 +10,11 @@ use std::path::Path;
use std::process::Command;
fn main() {
// do not compile libstdbuf for windows target. The windows stdbuf.exe loads libstdbuf.dll compiled for the cygwin target.
if env::var("CARGO_CFG_UNIX").is_err() {
println!("cargo:rustc-cfg=feature=\"feat_external_libstdbuf\"");
return;
}
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=src/libstdbuf/src/libstdbuf.rs");
@@ -38,11 +43,7 @@ fn main() {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_vendor = env::var("CARGO_CFG_TARGET_VENDOR").unwrap();
let target_family = env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
if target_family != "unix" {
return;
}
let dylib_ext = if target_vendor == "apple" {
".dylib"
} else if target_os == "cygwin" {
+1
View File
@@ -1,4 +1,5 @@
stdbuf-about = Run COMMAND, with modified buffering operations for its standard streams.
stdbuf-about-windows = Run COMMAND linked against cygwin runtime, with modified buffering operations for its standard streams.
Mandatory arguments to long options are mandatory for short options too.
stdbuf-usage = stdbuf [OPTION]... COMMAND
+4
View File
@@ -7,6 +7,10 @@
use std::env;
fn main() {
// do not compile libstdbuf for windows target. The windows stdbuf.exe loads libstdbuf.dll compiled for the cygwin target.
if env::var("CARGO_CFG_UNIX").is_err() {
return;
}
// Make sure we're building position-independent code for use with LD_PRELOAD
println!("cargo:rustc-link-arg=-fPIC");
+19 -5
View File
@@ -4,8 +4,6 @@
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) tempdir dyld dylib optgrps libstdbuf
#[cfg(not(unix))]
compile_error!("stdbuf is not supported on the target");
use clap::{Arg, ArgAction, ArgMatches, Command};
use std::ffi::OsString;
@@ -42,7 +40,10 @@ const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf
#[cfg(all(not(feature = "feat_external_libstdbuf"), target_vendor = "apple"))]
const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.dylib"));
#[cfg(all(not(feature = "feat_external_libstdbuf"), target_os = "cygwin"))]
#[cfg(all(
not(feature = "feat_external_libstdbuf"),
any(target_os = "cygwin", target_os = "windows")
))]
const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.dll"));
enum BufferType {
@@ -89,7 +90,7 @@ fn preload_strings() -> (&'static str, &'static str) {
("DYLD_LIBRARY_PATH", "dylib")
}
#[cfg(target_os = "cygwin")]
#[cfg(any(target_os = "cygwin", windows))]
fn preload_strings() -> (&'static str, &'static str) {
("LD_PRELOAD", "dll")
}
@@ -211,7 +212,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
command.args(command_params);
// Replace the current process with the target program (no fork) using exec.
#[cfg(unix)]
let e = command.exec();
#[cfg(windows)]
let e = match command.spawn() {
Ok(mut child) => {
let status = child.wait().unwrap();
process::exit(status.code().unwrap_or(0));
}
Err(err) => err,
};
// exec() only returns if there was an error
match e.kind() {
std::io::ErrorKind::PermissionDenied => Err(USimpleError::new(
@@ -230,10 +240,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}
pub fn uu_app() -> Command {
#[cfg(unix)]
let about = translate!("stdbuf-about");
#[cfg(windows)]
let about = translate!("stdbuf-about-windows");
Command::new("stdbuf")
.version(uucore::crate_version!())
.help_template(uucore::localized_help_template("stdbuf"))
.about(translate!("stdbuf-about"))
.about(about)
.after_help(translate!("stdbuf-after-help"))
.override_usage(format_usage(&translate!("stdbuf-usage")))
.trailing_var_arg(true)
+15 -15
View File
@@ -3,19 +3,19 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore cmdline dyld dylib PDEATHSIG setvbuf
#[cfg(target_os = "linux")]
use uutests::at_and_ucmd;
use uutests::new_ucmd;
#[cfg(not(target_os = "windows"))]
use uutests::util::TestScenario;
use uutests::util_name;
#[cfg(unix)]
use uutests::{new_ucmd, util::TestScenario, util_name};
#[test]
#[cfg(unix)]
fn invalid_input() {
new_ucmd!().arg("-/").fails_with_code(125);
}
#[cfg(not(feature = "feat_external_libstdbuf"))]
#[cfg(all(unix, not(feature = "feat_external_libstdbuf")))]
#[test]
fn test_permission() {
new_ucmd!()
@@ -28,7 +28,7 @@ fn test_permission() {
// LD_DEBUG is not available on macOS, OpenBSD, Android, or musl
#[cfg(all(
feature = "feat_external_libstdbuf",
not(target_os = "windows"),
unix,
not(target_os = "openbsd"),
not(target_os = "macos"),
not(target_os = "android"),
@@ -121,7 +121,7 @@ fn test_stdbuf_search_order_exe_dir_first() {
);
}
#[cfg(not(feature = "feat_external_libstdbuf"))]
#[cfg(all(unix, not(feature = "feat_external_libstdbuf")))]
#[test]
fn test_no_such() {
new_ucmd!()
@@ -135,7 +135,7 @@ fn test_no_such() {
// does not provide musl-compiled system utilities (like head), leading to dynamic linker errors
// when preloading musl-compiled libstdbuf.so into glibc-compiled binaries. Same thing for FreeBSD.
#[cfg(all(
not(target_os = "windows"),
unix,
not(target_os = "freebsd"),
not(target_os = "openbsd"),
not(all(target_arch = "x86_64", target_env = "musl"))
@@ -157,7 +157,7 @@ fn test_stdbuf_unbuffered_stdout() {
// does not provide musl-compiled system utilities (like head), leading to dynamic linker errors
// when preloading musl-compiled libstdbuf.so into glibc-compiled binaries. Same thing for FreeBSD.
#[cfg(all(
not(target_os = "windows"),
unix,
not(target_os = "freebsd"),
not(target_os = "openbsd"),
not(all(target_arch = "x86_64", target_env = "musl"))
@@ -174,8 +174,8 @@ fn test_stdbuf_line_buffered_stdout() {
.stdout_is("The quick brown fox jumps over the lazy dog.");
}
#[cfg(not(target_os = "windows"))]
#[test]
#[cfg(unix)]
fn test_stdbuf_no_buffer_option_fails() {
let ts = TestScenario::new(util_name!());
@@ -185,8 +185,8 @@ fn test_stdbuf_no_buffer_option_fails() {
.stderr_contains("the following required arguments were not provided:");
}
#[cfg(not(target_os = "windows"))]
#[test]
#[cfg(unix)]
fn test_stdbuf_no_command_fails_with_125() {
// Test that missing command fails with exit code 125 (stdbuf error)
// This verifies proper error handling without unwrap panic
@@ -200,7 +200,7 @@ fn test_stdbuf_no_command_fails_with_125() {
// does not provide musl-compiled system utilities (like tail), leading to dynamic linker errors
// when preloading musl-compiled libstdbuf.so into glibc-compiled binaries. Same thing for FreeBSD.
#[cfg(all(
not(target_os = "windows"),
unix,
not(target_os = "freebsd"),
not(target_os = "openbsd"),
not(all(target_arch = "x86_64", target_env = "musl"))
@@ -214,8 +214,8 @@ fn test_stdbuf_trailing_var_arg() {
.stdout_is("jumps over the lazy dog.");
}
#[cfg(not(target_os = "windows"))]
#[test]
#[cfg(unix)]
fn test_stdbuf_line_buffering_stdin_fails() {
new_ucmd!()
.args(&["-i", "L", "head"])
@@ -223,8 +223,8 @@ fn test_stdbuf_line_buffering_stdin_fails() {
.usage_error("line buffering stdin is meaningless");
}
#[cfg(not(target_os = "windows"))]
#[test]
#[cfg(unix)]
fn test_stdbuf_invalid_mode_fails() {
let options = ["--input", "--output", "--error"];
for option in &options {
@@ -254,7 +254,7 @@ fn test_stdbuf_invalid_mode_fails() {
// and is sometimes disabled. Disable test on Android for now.
// musl libc dynamic loader does not support LD_DEBUG, so disable on musl targets as well.
#[cfg(all(
not(target_os = "windows"),
unix,
not(target_os = "openbsd"),
not(target_os = "macos"),
not(target_os = "android"),