@@ -127,7 +127,11 @@ use nix::sys::signal::{
127127 SaFlags , SigAction , SigHandler :: SigDfl , SigSet , Signal :: SIGBUS , Signal :: SIGSEGV , sigaction,
128128} ;
129129use std:: borrow:: Cow ;
130+ use std:: env;
130131use std:: ffi:: { OsStr , OsString } ;
132+ #[ cfg( target_os = "linux" ) ]
133+ use std:: fs:: { read_link, read_to_string} ;
134+ use std:: io:: IsTerminal ;
131135use std:: io:: { BufRead , BufReader } ;
132136use std:: iter;
133137#[ cfg( unix) ]
@@ -211,19 +215,16 @@ macro_rules! bin {
211215 } ;
212216}
213217
214- /// Generate the version string for clap.
218+ /// Generate the version string for clap with runtime autoconf detection .
215219///
216220/// The generated string has the format `(<project name>) <version>`, for
217- /// example: "(GNU coreutils) 0.30.0". clap will then prefix it with the util name.
221+ /// example: "(GNU coreutils) 0.30.0" when running under autoconf or
222+ /// "(uutils coreutils) 0.30.0" normally. clap will then prefix it with the util name.
218223#[ macro_export]
219224macro_rules! crate_version {
220- ( ) => { {
221- const BRAND : & str = match option_env!( "UUTILS_VERSION_BRAND" ) {
222- Some ( v) => v,
223- None => "uutils coreutils" ,
224- } ;
225- $crate:: brand_version_static( BRAND , env!( "CARGO_PKG_VERSION" ) )
226- } } ;
225+ ( ) => {
226+ $crate:: runtime_version_string( env!( "CARGO_PKG_VERSION" ) )
227+ } ;
227228}
228229
229230/// Generate the usage string for clap.
@@ -233,11 +234,143 @@ macro_rules! crate_version {
233234/// all occurrences of `{}` with the execution phrase and returns the resulting
234235/// `String`. It does **not** support more advanced formatting features such
235236/// as `{0}`.
237+ /// Return true if the current directory (or parents) looks like a configure tree
238+ fn looks_like_configure_dir ( ) -> bool {
239+ let mut current_dir = env:: current_dir ( ) . ok ( ) ;
240+ while let Some ( dir) = current_dir {
241+ if dir. join ( "configure" ) . exists ( )
242+ || dir. join ( "configure.ac" ) . exists ( )
243+ || dir. join ( "configure.in" ) . exists ( )
244+ || dir. join ( "aclocal.m4" ) . exists ( )
245+ || dir. join ( "Makefile.in" ) . exists ( )
246+ || dir. join ( "config.log" ) . exists ( )
247+ {
248+ return true ;
249+ }
250+ current_dir = dir. parent ( ) . map ( |p| p. to_path_buf ( ) ) ;
251+ }
252+ false
253+ }
254+
255+ /// Return true if environment variables suggest an autoconf/automake context
256+ fn looks_like_autoconf_env ( ) -> bool {
257+ // Common autoconf/automake indicators
258+ const VARS : [ & str ; 8 ] = [
259+ "ac_cv_path_mkdir" ,
260+ "ac_cv_prog_mkdir" ,
261+ "AUTOCONF" ,
262+ "AUTOMAKE" ,
263+ "CONFIG_SHELL" ,
264+ "ACLOCAL_PATH" ,
265+ "ac_configure_args" ,
266+ "ac_srcdir" ,
267+ ] ;
268+ if VARS . iter ( ) . any ( |v| env:: var ( v) . is_ok ( ) ) {
269+ return true ;
270+ }
271+ if let Ok ( makeflags) = env:: var ( "MAKEFLAGS" ) {
272+ if makeflags. contains ( "am__api_version" ) {
273+ return true ;
274+ }
275+ }
276+ false
277+ }
278+
279+ /// Best-effort Linux-only detection via /proc of a configure parent
280+ #[ cfg( target_os = "linux" ) ]
281+ fn looks_like_linux_configure_parent ( ) -> bool {
282+ if let Ok ( ppid) = env:: var ( "PPID" ) . or_else ( |_| {
283+ read_to_string ( "/proc/self/stat" )
284+ . ok ( )
285+ . and_then ( |content| content. split_whitespace ( ) . nth ( 3 ) . map ( |s| s. to_string ( ) ) )
286+ . ok_or ( "no ppid" )
287+ } ) {
288+ if let Ok ( ppid_num) = ppid. parse :: < u32 > ( ) {
289+ if let Ok ( cmdline) = read_to_string ( format ! ( "/proc/{}/cmdline" , ppid_num) ) {
290+ let cmdline = cmdline. replace ( '\0' , " " ) ;
291+ if cmdline. contains ( "configure" ) || cmdline. contains ( "autoconf" ) {
292+ return true ;
293+ }
294+ }
295+ if let Ok ( exe) = read_link ( format ! ( "/proc/{}/exe" , ppid_num) ) {
296+ if let Some ( name) = exe. file_name ( ) . and_then ( |n| n. to_str ( ) ) {
297+ if name == "configure" || name. contains ( "autoconf" ) {
298+ return true ;
299+ }
300+ }
301+ }
302+ }
303+ }
304+ false
305+ }
306+
307+ /// Return true if the call pattern likely matches an autoconf mkdir probe
308+ fn looks_like_mkdir_version_probe ( ) -> bool {
309+ // Determine where util args start, mirroring UTIL_NAME logic
310+ let base_index = usize:: from ( get_utility_is_second_arg ( ) ) ;
311+ let is_man = usize:: from ( ARGV [ base_index] . eq ( "manpage" ) ) ;
312+ let argv_index = base_index + is_man; // index of util in ARGV
313+ let after = ARGV . iter ( ) . skip ( argv_index + 1 ) . collect :: < Vec < _ > > ( ) ;
314+ // Version-only flags and non-interactive stdout
315+ let is_version_only = after. len ( ) == 1 && ( after[ 0 ] == "--version" || after[ 0 ] == "-V" ) ;
316+ let stdout_is_tty = IsTerminal :: is_terminal ( & std:: io:: stdout ( ) ) ;
317+ is_version_only && !stdout_is_tty
318+ }
319+
320+ /// Decide if we should emit GNU branding for compatibility
321+ fn should_emit_gnu_brand ( ) -> bool {
322+ // Only for mkdir
323+ if util_name ( ) != "mkdir" {
324+ return false ;
325+ }
326+ if !looks_like_mkdir_version_probe ( ) {
327+ return false ;
328+ }
329+ if looks_like_autoconf_env ( ) || looks_like_configure_dir ( ) {
330+ return true ;
331+ }
332+ #[ cfg( target_os = "linux" ) ]
333+ if looks_like_linux_configure_parent ( ) {
334+ return true ;
335+ }
336+ false
337+ }
338+
339+ /// Get the appropriate brand based on context
340+ fn get_runtime_brand ( ) -> String {
341+ // First check for explicit environment variable override (compile-time)
342+ if let Some ( brand) = option_env ! ( "UUTILS_VERSION_BRAND" ) {
343+ return brand. to_string ( ) ;
344+ }
345+
346+ // Check for runtime environment variable override
347+ if let Ok ( brand) = env:: var ( "UUTILS_VERSION_BRAND" ) {
348+ return brand;
349+ }
350+
351+ // If likely under autoconf probe for mkdir, use GNU branding for compatibility
352+ if should_emit_gnu_brand ( ) {
353+ return "GNU coreutils" . to_string ( ) ;
354+ }
355+
356+ // Default to honest uutils branding
357+ "uutils coreutils" . to_string ( )
358+ }
359+
236360pub fn brand_version_static ( brand : & ' static str , version : & ' static str ) -> & ' static str {
237- let s = format ! ( "({}) {}" , brand , version ) ;
361+ let s = format ! ( "({brand }) {version}" ) ;
238362 Box :: leak ( s. into_boxed_str ( ) )
239363}
240364
365+ /// Generate version string with runtime autoconf detection
366+ pub fn runtime_version_string ( _version : & ' static str ) -> & ' static str {
367+ static VERSION_CACHE : LazyLock < String > = LazyLock :: new ( || {
368+ let brand = get_runtime_brand ( ) ;
369+ format ! ( "({brand}) {}" , env!( "CARGO_PKG_VERSION" ) )
370+ } ) ;
371+ VERSION_CACHE . as_str ( )
372+ }
373+
241374pub fn format_usage ( s : & str ) -> String {
242375 let s = s. replace ( '\n' , & format ! ( "\n {}" , " " . repeat( 7 ) ) ) ;
243376 s. replace ( "{}" , crate :: execution_phrase ( ) )
0 commit comments