]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
build: reduce size of multi-call binary by reusing test logic
authorCollin Funk <collin.funk1@gmail.com>
Sat, 18 Jul 2026 03:06:28 +0000 (20:06 -0700)
committerCollin Funk <collin.funk1@gmail.com>
Sat, 18 Jul 2026 18:14:46 +0000 (11:14 -0700)
Map the '[' command to which selects appropriate behavior at runtime,
rather than separate binaries for each closely related utility.

    $ size src/coreutils-prev
       text    data     bss     dec     hex filename
    1485188    7348 2191968 3684504  383898 src/coreutils-prev
    $ size src/coreutils
       text    data     bss     dec     hex filename
    1476092    7348 2191936 3675376  3814f0 src/coreutils

* src/local.mk (noinst_HEADERS): Add src/test.h.
(src___SOURCES): Remove src/lbracket.c. Add src/test.c and
src/test-lbracket.c.
(src_test_SOURCES): New variable.
(src/coreutils.h): Adjust the function name to be
single_binary_main_lbracket instead of single_binary_main__.
* build-aux/gen-single-binary.sh: Likewise. Map '[' to 'test'.
(override_single): Adjust the file name to be coreutils-lbracket.c
instead of coreutils-_.c.
* src/lbracket.c: Remove file.
* cfg.mk (_src): Don't match src/lbracket.c in the regular
expression. Match src/test-lbracket.c and src/test-test.c.
* src/coreutils-lbracket.c: New file.
* src/test-lbracket.c: Likewise.
* src/test-test.c: Likewise.
* src/test.h: Likewise.
* src/test.c: Include test.h.
(LBRACKET): Remove macro.
(PROGRAM_NAME): Use the variable TEST_MODE to determine the program
name.
(main): Use the variable TEST_MODE instead of the LBRACKET macro to
implement behavior specific to '['.
* NEWS: Mention the improvement.

NEWS
build-aux/gen-single-binary.sh
cfg.mk
src/coreutils-lbracket.c [new file with mode: 0644]
src/lbracket.c [deleted file]
src/local.mk
src/test-lbracket.c [new file with mode: 0644]
src/test-test.c [new file with mode: 0644]
src/test.c
src/test.h [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 501f813b551ff5747c7447795e86c4a682f256ba..19fb04ce1e965e4199bb6b5347661b7b25f58206 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -79,6 +79,10 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** Build-related
 
+  The multi-call binary built with configure --enable-single-binary
+  in size by around 9KB through the more efficient reuse of the 'test' utility
+  by '['.
+
   configure no longer accepts the --with-linux-crypto option, which allowed
   cksum, md5sum, and sha*sum to use the Linux AF_ALG API.  This API will be
   deprecated in Linux 7.2 and is less performant than OpenSSL.
index a89a6bc839c260f4027b9747dc7e686752660ed9..8d9fb0d5299dda5bf9a1df6fef6537d1703dafb2 100755 (executable)
@@ -62,12 +62,20 @@ echo "## Automatically generated by $me.  DO NOT EDIT BY HAND!"
 override_single() {
   from="$1"; to="$2";
 
-  eval "src_${from}_SOURCES='src/coreutils-${from}.c'"
+  # Use coreutils-lbracket.c instead of coreutils-_.c.
+  if test "$from" = _; then
+    src_name=lbracket
+  else
+    src_name="$from";
+  fi
+  eval "src_${from}_SOURCES='src/coreutils-${src_name}.c'"
   eval "src_from_LDADD=\$src_${from}_LDADD"
   eval "src_${from}_LDADD='$src_from_LDADD src/libsinglebin_${to}.a'"
   eval "src_libsinglebin_${from}_a_DEPENDENCIES='src/libsinglebin_${to}.a'"
   echo "src_libsinglebin_${from}_a_DEPENDENCIES = src/libsinglebin_${to}.a"
 }
+# This is for the '[' program.  Automake transliterates '[' and '/' to '_'.
+override_single _ test
 override_single dir ls
 override_single vdir ls
 override_single arch uname
@@ -104,10 +112,16 @@ for cmd in $ALL_PROGRAMS; do
 
   # CFLAGS
   # Hack any other program defining a main() replacing its main by
-  # single_binary_main_$PROGRAM_NAME.
-  echo "${base}_CFLAGS = \"-Dmain=single_binary_main_${cmd} (int, char **);" \
-       " int single_binary_main_${cmd}\" " \
-       "-Dusage=_usage_${cmd} \$(src_coreutils_CFLAGS)"
+  # single_binary_main_$PROGRAM_NAME.  Use single_binary_main_lbracket
+  # instead of single_binary_main__.
+  if test "$cmd" = _; then
+    fn=lbracket
+  else
+    fn="$cmd"
+  fi
+  echo "${base}_CFLAGS = \"-Dmain=single_binary_main_${fn} (int, char **);" \
+       " int single_binary_main_${fn}\" " \
+       "-Dusage=_usage_${fn} \$(src_coreutils_CFLAGS)"
   var=src_${cmd}_CFLAGS
   eval "value=\$$var"
   if [ "x$value" != "x" ]; then
diff --git a/cfg.mk b/cfg.mk
index d5e8f9025d675724d8f252c66aaed3f56b1d581c..2848d6fdc08ebdcefd66cd08c3bec6c2767c5287 100644 (file)
--- a/cfg.mk
+++ b/cfg.mk
@@ -929,8 +929,9 @@ _x_system_c := (libstdbuf|make-prime-list)\.c
 exclude_file_name_regexp--sc_system_h_headers = \
   ^src/($(_x_system_h)|$(_x_system_c))$$
 
-_src := (false|lbracket|chown-(chgrp|chown)
-_src := $(_src)|ls-(dir|ls|vdir)|make-prime-list|tac-pipe|uname-(arch|uname))
+_src := (false|chown-(chgrp|chown)
+_src := $(_src)|ls-(dir|ls|vdir)|make-prime-list|tac-pipe|test-(lbracket|test)
+_src := $(_src)|uname-(arch|uname))
 _gl_src = (xdecto.max|cl-strtold)
 exclude_file_name_regexp--sc_require_config_h_first = \
   (^lib/buffer-lcm\.c|gl/lib/$(_gl_src)\.c|src/$(_src)\.c)$$
diff --git a/src/coreutils-lbracket.c b/src/coreutils-lbracket.c
new file mode 100644 (file)
index 0000000..8b705b4
--- /dev/null
@@ -0,0 +1,31 @@
+/* lbracket -- wrapper to test with the right test_mode.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include "system.h"
+
+#include "test.h"
+/* Ensure that the main for test is declared even if the tool is not being
+   built in this single-binary. */
+int single_binary_main_test (int argc, char **argv);
+int single_binary_main_lbracket (int argc, char **argv);
+
+int
+single_binary_main_lbracket (int argc, char **argv)
+{
+  test_mode = TEST_LBRACKET;
+  return single_binary_main_test (argc, argv);
+}
diff --git a/src/lbracket.c b/src/lbracket.c
deleted file mode 100644 (file)
index b57ca9b..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-#define LBRACKET 1
-#include "test.c"
index 9d9c9814bbf2d56ec4ab134248daae6ddc081d62..1a6079d69e7ec495a0135efec4eac854b100f905 100644 (file)
@@ -66,6 +66,7 @@ noinst_HEADERS =              \
   src/system.h                 \
   src/temp-stream.h            \
   src/term-sig.h               \
+  src/test.h                   \
   src/uname.h                  \
   src/wc.h
 
@@ -381,7 +382,8 @@ src_ginstall_SOURCES = src/install.c src/prog-fprintf.c $(copy_sources) \
                       $(selinux_sources)
 
 # This is for the '[' program.  Automake transliterates '[' and '/' to '_'.
-src___SOURCES = src/lbracket.c
+src___SOURCES = src/test.c src/test-lbracket.c
+src_test_SOURCES = src/test.c src/test-test.c
 
 nodist_src_coreutils_SOURCES = src/coreutils.h
 src_coreutils_SOURCES = src/coreutils.c
@@ -768,7 +770,7 @@ src/speedlist.h: src/termios.c lib/config.h src/speedgen
 #   SINGLE_BINARY_PROGRAM(program_name_str, main_name)
 # once for each program list on $(single_binary_progs). Note that
 # for [ the macro invocation is:
-#   SINGLE_BINARY_PROGRAM("[", _)
+#   SINGLE_BINARY_PROGRAM("[", lbracket)
 DISTCLEANFILES += src/coreutils.h
 src/coreutils.h: Makefile
        $(AM_V_GEN)rm -f $@
@@ -776,7 +778,11 @@ src/coreutils.h: Makefile
        $(AM_V_at)for prog in x $(single_binary_progs); do      \
          test $$prog = x && continue;                          \
          prog=`basename $$prog`;                               \
-         main=`echo $$prog | tr '[' '_'`;                      \
+         main=`if test $$prog = '['; then                      \
+                 echo lbracket;                                \
+               else                                            \
+                 echo $$prog;                                  \
+               fi`;                                            \
          echo "SINGLE_BINARY_PROGRAM(\"$$prog\", $$main)";     \
        done | sort > $@t
        $(AM_V_at)chmod a-w $@t
diff --git a/src/test-lbracket.c b/src/test-lbracket.c
new file mode 100644 (file)
index 0000000..74dadbc
--- /dev/null
@@ -0,0 +1,2 @@
+#include "test.h"
+enum test_modes test_mode = TEST_LBRACKET;
diff --git a/src/test-test.c b/src/test-test.c
new file mode 100644 (file)
index 0000000..2364628
--- /dev/null
@@ -0,0 +1,2 @@
+#include "test.h"
+enum test_modes test_mode = TEST_TEST;
index ab7f249929353e58e55e7b7fcb7f5a4b7aba0d8f..692c37263b7b5ed1d070bb6695d22fdb793d1377 100644 (file)
 
 #define TEST_STANDALONE 1
 
-#ifndef LBRACKET
-# define LBRACKET 0
-#endif
-
 /* The official name of this program (e.g., no 'g' prefix).  */
-#if LBRACKET
-# define PROGRAM_NAME "["
-#else
-# define PROGRAM_NAME "test"
-#endif
+#define PROGRAM_NAME (test_mode == TEST_TEST ? "test" : "[")
 
 #include "system.h"
 #include "assure.h"
@@ -44,6 +36,7 @@
 #include "quote.h"
 #include "stat-time.h"
 #include "strnumcmp.h"
+#include "test.h"
 
 #include <stdarg.h>
 
@@ -864,7 +857,7 @@ main (int margc, char **margv)
 
   argv = margv;
 
-  if (LBRACKET)
+  if (test_mode == TEST_LBRACKET)
     {
       /* Recognize --help or --version, but only when invoked in the
          "[" form, when the last argument is not "]".  Use direct
diff --git a/src/test.h b/src/test.h
new file mode 100644 (file)
index 0000000..f432487
--- /dev/null
@@ -0,0 +1,10 @@
+enum test_modes
+{
+  /* This is for the 'test' program.  */
+  TEST_TEST,
+
+  /* This is for the '[' program.  */
+  TEST_LBRACKET
+};
+
+extern enum test_modes test_mode;