From 3bdc8f46749b94047cef3e981c0c065c589f57f6 Mon Sep 17 00:00:00 2001 From: Kamila Szewczyk Date: Thu, 21 May 2026 17:26:35 -0700 Subject: [PATCH] Patch from https://bugs.gnu.org/81040. * m4/optional.m4: New file. AM_OPTIONAL_AUTOMAKE(OPTIONS) makes `make dist' build the listed dist-XXX archives when their tool is available and skip them otherwise, without failing. Supports all the available compression options: dist-bzip2, dist-bzip3, dist-xz, dist-lzip, dist-zstd, dist-zip, dist-shar, dist-tarZ. dist-shar is handled separately: shar is run to an intermediate file before gzip so a missing shar cannot leave a bogus .shar.gz behind. * m4/local.mk (dist_automake_ac_DATA): Add m4/optional.m4. * doc/automake.texi (Public Macros): Document it. Cross-reference it from dist-bzip3, dist-lzip and dist-zstd. * t/am-optional-automake.sh: New test, including a distcheck case. * t/list-of-tests.mk: Add it. * NEWS: Mention the new macro. --- NEWS | 12 +++ doc/automake.texi | 55 +++++++++++- m4/local.mk | 1 + m4/optional.m4 | 135 ++++++++++++++++++++++++++++ t/am-optional-automake.sh | 185 ++++++++++++++++++++++++++++++++++++++ t/list-of-tests.mk | 1 + 6 files changed, 386 insertions(+), 3 deletions(-) create mode 100644 m4/optional.m4 create mode 100644 t/am-optional-automake.sh diff --git a/NEWS b/NEWS index 786255932..535f65eff 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,18 @@ For planned incompatibilities in a possible future Automake 2.0 release, please see NEWS-future and start following the advice there now. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +New in 1.18.2 (????-??-??): + +* New features added + + - New autoconf macro AM_OPTIONAL_AUTOMAKE. It takes a list of + `dist-XXX' option names (dist-bzip2, dist-bzip3, dist-xz, + dist-lzip, dist-zstd, dist-zip, dist-shar, dist-tarZ); + `make dist' builds each archive when its tool is available and skips + it otherwise, without making `make dist' fail. This is for + systems that intentionally lack some compression tools. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ New in 1.18.1 (2025-06-25): diff --git a/doc/automake.texi b/doc/automake.texi index eeacee762..a098e7e03 100644 --- a/doc/automake.texi +++ b/doc/automake.texi @@ -4137,6 +4137,51 @@ option (@pxref{List of Automake options}): AM_INIT_AUTOMAKE([no-define ...]) @end example +@item AM_OPTIONAL_AUTOMAKE([@var{options}]) +@acindex AM_OPTIONAL_AUTOMAKE +@cindex Optional @code{dist-} archives +@comment https://debbugs.gnu.org/cgi/bugreport.cgi?bug=81040 +@var{options} is a whitespace-separated list of @code{dist-} option +names, using the same spellings @code{AM_INIT_AUTOMAKE} accepts: +@option{dist-bzip2}, @option{dist-bzip3}, @option{dist-xz}, +@option{dist-lzip}, @option{dist-zstd}, @option{dist-zip}, +@option{dist-shar}, and @option{dist-tarZ}. For each, @samp{make +dist} builds the matching archive when its compressor or archiver is +available and skips it otherwise, including when a tool present at +@command{configure} time is gone by @samp{make dist} time; a missing +listed tool does not make @samp{make dist} fail. An unknown +@var{option} is reported as a @code{syntax} warning and ignored. + +@cindex minimal system images +@cindex CI images, minimal +@cindex container images, minimal +@cindex compression tools, making optional +This is meant for systems that intentionally lack some compression +tools, such as minimal container or CI images, where installing +@command{bzip3}, @command{lzip}, @command{zstd}, etc.@: only so that +@samp{make distcheck} succeeds is undesirable. It avoids overriding +@code{DIST_TARGETS} or @code{DIST_ARCHIVES} by hand. + +The macro does not pass the options to @command{automake}; it uses +only the @code{distdir} target and @code{$(am__tar)}, present since +Automake 1.10, so an older @command{automake} that does not recognize +an option (such as @option{dist-bzip3}) is not a problem. + +@example +AM_INIT_AUTOMAKE([-Wall foreign dist-bzip2 dist-xz dist-zip]) +AM_OPTIONAL_AUTOMAKE([dist-bzip3]) +@end example + +Here @file{.tar.gz}, @file{.tar.bz2}, @file{.tar.xz}, and @file{.zip} +are always built (and @samp{make dist} fails if their tools are +missing), while @file{.tar.bz3} is built only if @command{bzip3} is +installed. + +A given @code{dist-} option should appear in @code{AM_INIT_AUTOMAKE} +or @code{AM_OPTIONAL_AUTOMAKE}, not both; listing it in +@code{AM_INIT_AUTOMAKE} already makes that format mandatory. The +macro may be called more than once; repeating an option is a no-op. + @item AM_PATH_LISPDIR @acindex AM_PATH_LISPDIR @vindex EMACS @@ -9468,7 +9513,8 @@ make it use a different one, set the @env{BZIP2} environment variable. @vindex BZIP3 Generate a @samp{bzip3} tar archive of the distribution. Unlike the other compression programs here, @command{bzip3} does not read any -environment variables. +environment variables. To build this only when @command{bzip3} is +present, see @code{AM_OPTIONAL_AUTOMAKE} (@pxref{Public Macros}). @item @code{dist-lzip} @trindex dist-lzip @@ -9478,7 +9524,9 @@ Generate an @samp{lzip} tar archive of the distribution. @command{lzip} archives are usually smaller than @command{bzip2}-compressed archives. By default, this rule makes @samp{lzip} use a compression option of @option{-9}. To make it use a -different one, set the @env{LZIP_OPT} environment variable. +different one, set the @env{LZIP_OPT} environment variable. To build +this only when @command{lzip} is present, see +@code{AM_OPTIONAL_AUTOMAKE} (@pxref{Public Macros}). @item @code{dist-xz} @trindex dist-xz @@ -9510,7 +9558,8 @@ default compression ratio, but with a progress indicator: @samp{make dist-zstd ZSTD_OPT=-19v}. However, note that for compatibility with @command{zstd} itself, you may instead set the @env{ZSTD_CLEVEL} environment variable, in which case, any @env{ZSTD_OPT} setting is -ignored. +ignored. To build this only when @command{zstd} is present, see +@code{AM_OPTIONAL_AUTOMAKE} (@pxref{Public Macros}). @item @code{dist-shar} @trindex dist-shar diff --git a/m4/local.mk b/m4/local.mk index 5e9830ae6..6b76fd2b9 100644 --- a/m4/local.mk +++ b/m4/local.mk @@ -43,6 +43,7 @@ dist_automake_ac_DATA = \ %D%/missing.m4 \ %D%/mkdirp.m4 \ %D%/obsolete.m4 \ + %D%/optional.m4 \ %D%/options.m4 \ %D%/python.m4 \ %D%/prog-cc-c-o.m4 \ diff --git a/m4/optional.m4 b/m4/optional.m4 new file mode 100644 index 000000000..9d0e96bb4 --- /dev/null +++ b/m4/optional.m4 @@ -0,0 +1,135 @@ +## -*- Autoconf -*- +## AM_OPTIONAL_AUTOMAKE -- skip `dist-XXX' formats whose tool is absent. + +# Copyright (C) 2026 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + + +# AM_OPTIONAL_AUTOMAKE(OPTIONS) +# ----------------------------- +# OPTIONS: whitespace-separated `dist-XXX' names (AM_INIT_AUTOMAKE +# spellings) for dist-bzip2/bzip3/xz/lzip/zstd/zip/shar/tarZ. `make +# dist' builds each archive when its tool is present and skips it +# otherwise, without failing. Unknown OPTION warns at autoreconf; a +# tool absent at configure appends no rule; a tool lost afterwards +# fails the pipeline, which the recipe catches. See bug#81040. +AC_DEFUN([AM_OPTIONAL_AUTOMAKE], +[m4_foreach_w([_am_opt_o], [$1], + [_AM_OPTIONAL_AUTOMAKE_ONE(_m4_defn([_am_opt_o]))])dnl +]) + + +# _AM_OPTIONAL_AUTOMAKE_ONE(OPT) +# ------------------------------ +# Dispatch on OPT; unrecognised OPT warns and is ignored. +AC_DEFUN([_AM_OPTIONAL_AUTOMAKE_ONE], +[m4_case([$1], + [dist-bzip2], [_AM_OPTIONAL_TAR([bzip2], [bzip2], [bz2], [-c])], + [dist-bzip3], [_AM_OPTIONAL_TAR([bzip3], [bzip3], [bz3], [-c])], + [dist-xz], [_AM_OPTIONAL_TAR([xz], [xz], [xz], [-c])], + [dist-lzip], [_AM_OPTIONAL_TAR([lzip], [lzip], [lz], [-c])], + [dist-zstd], [_AM_OPTIONAL_TAR([zstd], [zstd], [zst], [-c -q])], + [dist-tarZ], [_AM_OPTIONAL_TAR([compress], [compress], [Z], [-c])], + [dist-zip], [_AM_OPTIONAL_ZIP()], + [dist-shar], [_AM_OPTIONAL_SHAR()], + [m4_warn([syntax], + [am-optional: unknown option `$1', ignoring it])])dnl +]) + + +# _AM_OPTIONAL_RULE(TAG, VAR, RECIPE, ARTIFACTS) +# ---------------------------------------------- +# AC_CHECK_PROG for TAG, then once append RECIPE to the top Makefile +# via AC_CONFIG_COMMANDS (survives config.status reruns) iff configure +# found the tool. Hooked into dist/dist-all as an extra prerequisite. +# ARTIFACTS stay out of $(DIST_ARCHIVES), so distcleancheck must remove +# the copies that the in-tree `make dist' run by `make distcheck' leaves. +AC_DEFUN([_AM_OPTIONAL_RULE], +[m4_ifdef([_AM_OPTIONAL_DONE_$1], [], + [m4_define([_AM_OPTIONAL_DONE_$1])dnl +AC_CHECK_PROG([am__optional_$2], [$1], [$1]) +AC_SUBST([am__optional_$2])dnl +AC_CONFIG_COMMANDS([am--optional-rule-$1], +[am__optional_mf= +for am__optional_cand in Makefile GNUmakefile makefile; do + if test -f "$am__optional_cand"; then + am__optional_mf=$am__optional_cand + break + fi +done +if test -n "$am__optional_mf" && test -n "$am__optional_$2"; then + cat >>"$am__optional_mf" <<'_am_opt_rule_eof_' + +# Appended by am-optional for dist-$1. +dist dist-all: am--optional-dist-$1 +distcleancheck: am--optional-rm-$1 +.PHONY: am--optional-dist-$1 am--optional-rm-$1 +$3 +am--optional-rm-$1: + -rm -f $4 +_am_opt_rule_eof_ +fi +], +[am__optional_$2="$am__optional_$2" +])])dnl +]) + + +# _AM_OPTIONAL_TAR(TOOL, VAR-TAG, EXT, FLAGS) +# ------------------------------------------- +# Single-tool tar pipe; TOOL is last, so its failure is what's caught. +AC_DEFUN([_AM_OPTIONAL_TAR], +[_AM_OPTIONAL_RULE([$1], [$2], +[am--optional-dist-$1: distdir + @if test -n "$(am__optional_$2)"; then \ + { tardir=$(distdir) \ + && $(am__tar) | "$(am__optional_$2)" $4 > $(distdir).tar.$3; } \ + || { rm -f $(distdir).tar.$3; \ + echo "am-optional: dist-$1 failed; archive not built" >&2; }; \ + else \ + echo "am-optional: $1 unavailable, skipping $(distdir).tar.$3" >&2; \ + fi; \ + :], [$(distdir).tar.$3])dnl +]) + + +# _AM_OPTIONAL_ZIP +# ---------------- +AC_DEFUN([_AM_OPTIONAL_ZIP], +[_AM_OPTIONAL_RULE([zip], [zip], +[am--optional-dist-zip: distdir + @if test -n "$(am__optional_zip)"; then \ + rm -f $(distdir).zip; \ + { "$(am__optional_zip)" -rq $(distdir).zip $(distdir); } \ + || { rm -f $(distdir).zip; \ + echo "am-optional: dist-zip failed; archive not built" >&2; }; \ + else \ + echo "am-optional: zip unavailable, skipping $(distdir).zip" >&2; \ + fi; \ + :], [$(distdir).zip])dnl +]) + + +# _AM_OPTIONAL_SHAR +# ----------------- +# Only `shar' is optional (gzip is a hard automake prereq). shar +# writes an intermediate file instead of piping into gzip: piped, gzip +# would succeed on empty input and leave a bogus .shar.gz when shar +# failed; the `&&' chain catches shar's exit instead. +AC_DEFUN([_AM_OPTIONAL_SHAR], +[_AM_OPTIONAL_RULE([shar], [shar], +[am--optional-dist-shar: distdir + @if test -n "$(am__optional_shar)"; then \ + rm -f $(distdir).shar $(distdir).shar.gz; \ + { "$(am__optional_shar)" $(distdir) > $(distdir).shar \ + && eval GZIP= gzip $(GZIP_ENV) -f $(distdir).shar; } \ + || { rm -f $(distdir).shar $(distdir).shar.gz; \ + echo "am-optional: dist-shar failed; archive not built" >&2; }; \ + else \ + echo "am-optional: shar unavailable, skipping $(distdir).shar.gz" >&2; \ + fi; \ + :], [$(distdir).shar $(distdir).shar.gz])dnl +]) diff --git a/t/am-optional-automake.sh b/t/am-optional-automake.sh new file mode 100644 index 000000000..e41dc53fc --- /dev/null +++ b/t/am-optional-automake.sh @@ -0,0 +1,185 @@ +#! /bin/sh +# 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 2, 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 . + +# Check AM_OPTIONAL_AUTOMAKE. Five scenarios: +# (1) The tool is on PATH at configure time -> dist-bzip3 produces +# $distdir.tar.bz3 alongside the always-built $distdir.tar.gz. +# (2) An unknown option produces an autoreconf-time `syntax' warning +# but does not break configure or make dist. +# (3) The tool is missing at configure time -> no extra rule is +# appended; `make dist' runs cleanly and does not produce +# $distdir.tar.bz3. +# (4) The tool is present at configure time but fails when invoked +# by `make dist' -> the recipe cleans up, prints a warning to +# stderr, `make dist' still succeeds, and no $distdir.tar.bz3 +# appears. +# (5) dist-shar and dist-tarZ, the two non-tar-pipe formats. + +required='bzip3' +. test-init.sh + +: "Test 1: bzip3 on PATH at configure time." + +cat > configure.ac <<'END' +AC_INIT([am-optional-automake], [1.0]) +AM_INIT_AUTOMAKE([foreign]) +AM_OPTIONAL_AUTOMAKE([dist-bzip3]) +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT +END +: > Makefile.am + +$ACLOCAL +$AUTOCONF +$AUTOMAKE --add-missing +./configure +$MAKE dist +test -s am-optional-automake-1.0.tar.gz +test -s am-optional-automake-1.0.tar.bz3 +# These archives are not in $(DIST_ARCHIVES); distcheck must still +# pass (its in-tree copies get cleaned before distcleancheck). +$MAKE distcheck +test -s am-optional-automake-1.0.tar.bz3 + +rm -rf autom4te*.cache aclocal.m4 configure Makefile.in install-sh \ + missing config.status config.log Makefile \ + am-optional-automake-1.0 am-optional-automake-1.0.tar.* + +: "Test 2: Unknown option is a warning, not an error." + +cat > configure.ac <<'END' +AC_INIT([am-optional-automake], [1.0]) +AM_INIT_AUTOMAKE([foreign]) +AM_OPTIONAL_AUTOMAKE([dist-banana dist-bzip3]) +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT +END +: > Makefile.am + +# aclocal/autoconf must succeed. The `syntax' warning is emitted by +# m4_warn() during macro expansion, which happens when autoconf runs +# (aclocal only scans). We don't promote it to error here: AM_INIT's +# default warning set is plenty. Capture autoconf's stderr and +# assert the warning mentions the offending token. +$ACLOCAL +$AUTOCONF -Wno-error 2>stderr +grep "dist-banana" stderr + +$AUTOMAKE --add-missing +./configure +$MAKE dist +test -s am-optional-automake-1.0.tar.bz3 + +rm -rf autom4te*.cache aclocal.m4 configure Makefile.in install-sh \ + missing config.status config.log Makefile stderr \ + am-optional-automake-1.0 am-optional-automake-1.0.tar.* + +: "Test 3: bzip3 missing at configure time." + +cat > configure.ac <<'END' +AC_INIT([am-optional-automake], [1.0]) +AM_INIT_AUTOMAKE([foreign]) +AM_OPTIONAL_AUTOMAKE([dist-bzip3]) +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT +END +: > Makefile.am + +# Make AC_CHECK_PROG see bzip3 as absent without touching PATH, by +# pre-seeding its cache variable to the empty string. +$ACLOCAL +$AUTOCONF +$AUTOMAKE --add-missing +ac_cv_prog_am__optional_bzip3= ./configure +$MAKE dist +test -s am-optional-automake-1.0.tar.gz +test ! -e am-optional-automake-1.0.tar.bz3 + +rm -rf autom4te*.cache aclocal.m4 configure Makefile.in \ + install-sh missing config.status config.log Makefile \ + am-optional-automake-1.0 am-optional-automake-1.0.tar.* + +: "Test 4: bzip3 present at configure, vanished at make dist." + +cat > configure.ac <<'END' +AC_INIT([am-optional-automake], [1.0]) +AM_INIT_AUTOMAKE([foreign]) +AM_OPTIONAL_AUTOMAKE([dist-bzip3]) +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT +END +: > Makefile.am + +$ACLOCAL +$AUTOCONF +$AUTOMAKE --add-missing +./configure # bzip3 IS visible here, so the rule is appended + +# Shadow bzip3 with a stub that exits non-zero. The recipe must +# catch the failure, clean up the partial archive, print a warning, +# leave the rest of `make dist' running, and exit 0. +mkdir stub-bin +cat > stub-bin/bzip3 <<'EOS' +#! /bin/sh +exit 1 +EOS +chmod +x stub-bin/bzip3 + +saved_PATH=$PATH +PATH=`pwd`/stub-bin:$PATH +$MAKE dist 2>stderr +PATH=$saved_PATH + +test -s am-optional-automake-1.0.tar.gz +test ! -e am-optional-automake-1.0.tar.bz3 +grep 'am-optional:.*dist-bzip3 failed' stderr + +rm -rf stub-bin autom4te*.cache aclocal.m4 configure Makefile.in \ + install-sh missing config.status config.log Makefile stderr \ + am-optional-automake-1.0 am-optional-automake-1.0.tar.* + +: "Test 5: dist-shar and dist-tarZ alongside the tar-pipe forms." +# These two formats do not follow the single-tool tar-pipe pattern, +# so they get their own exercise. shar and compress are rarely +# installed, so only assert the archive when the tool is actually +# present; otherwise we just check that `make dist' did not fail. + +cat > configure.ac <<'END' +AC_INIT([am-optional-automake], [1.0]) +AM_INIT_AUTOMAKE([foreign]) +AM_OPTIONAL_AUTOMAKE([dist-shar dist-tarZ]) +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT +END +: > Makefile.am + +$ACLOCAL +$AUTOCONF +$AUTOMAKE --add-missing +./configure +$MAKE dist +test -s am-optional-automake-1.0.tar.gz + +if (command -v shar) >/dev/null 2>&1; then + test -s am-optional-automake-1.0.shar.gz + # The intermediate uncompressed .shar must not be left behind. + test ! -e am-optional-automake-1.0.shar +fi +if (command -v compress) >/dev/null 2>&1; then + test -s am-optional-automake-1.0.tar.Z +fi + +: diff --git a/t/list-of-tests.mk b/t/list-of-tests.mk index 67bf7b5b9..e0446984e 100644 --- a/t/list-of-tests.mk +++ b/t/list-of-tests.mk @@ -137,6 +137,7 @@ t/alpha2.sh \ t/amhello-cflags.sh \ t/amhello-cross-compile.sh \ t/amhello-binpkg.sh \ +t/am-optional-automake.sh \ t/aminit-moreargs-deprecation.sh \ t/aminit-trailing-dnl-comment-pr16841.sh \ t/amassign.sh \ -- 2.47.3