From db7b8a6e589b105963ce8bc00cea79a020feed52 Mon Sep 17 00:00:00 2001 From: oech3 <79379754+oech3@users.noreply.github.com> Date: Sun, 17 May 2026 00:08:50 +0900 Subject: [PATCH] stdbuf: build on Windows (depending on cygwin dll) --- Cargo.toml | 1 + GNUmakefile | 2 +- src/uu/stdbuf/Cargo.toml | 4 +++- src/uu/stdbuf/build.rs | 9 +++++---- src/uu/stdbuf/locales/en-US.ftl | 1 + src/uu/stdbuf/src/libstdbuf/build.rs | 4 ++++ src/uu/stdbuf/src/stdbuf.rs | 24 +++++++++++++++++----- tests/by-util/test_stdbuf.rs | 30 ++++++++++++++-------------- 8 files changed, 49 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3c63028fc..62550e923 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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]) diff --git a/GNUmakefile b/GNUmakefile index 7a00f4121..09bba0399 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -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') diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 274cc7a32..bafe6ebef 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -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: diff --git a/src/uu/stdbuf/build.rs b/src/uu/stdbuf/build.rs index f94b9e90b..81a1e052b 100644 --- a/src/uu/stdbuf/build.rs +++ b/src/uu/stdbuf/build.rs @@ -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" { diff --git a/src/uu/stdbuf/locales/en-US.ftl b/src/uu/stdbuf/locales/en-US.ftl index 50269efc3..7a8fcafdc 100644 --- a/src/uu/stdbuf/locales/en-US.ftl +++ b/src/uu/stdbuf/locales/en-US.ftl @@ -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 diff --git a/src/uu/stdbuf/src/libstdbuf/build.rs b/src/uu/stdbuf/src/libstdbuf/build.rs index 7584bf31f..e9c2265b7 100644 --- a/src/uu/stdbuf/src/libstdbuf/build.rs +++ b/src/uu/stdbuf/src/libstdbuf/build.rs @@ -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"); diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index a5128bf68..e4ffa9d61 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -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) diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index 0b60ef464..56cfae9ab 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -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"),