Some projects want to offer a recently-added archive format (such as
dist-bzip3, which entered Automake in 1.18 - bug#73795, or dist-lzip)
while remaining portable to systems whose installed Automake predates
the option, or whose maintainer host lacks the matching compressor or
archiver. Listing dist-bzip3 or dist-lzip in AM_INIT_AUTOMAKE on such a
system either fails at automake-time because the option is not
recognized, or at make-dist-time because the tool is not on PATH.
The new AM_OPTIONAL_AUTOMAKE macro covers exactly this case. It
sidesteps the default machinery offering more fault tolerance: at
configure time it AC_CHECK_PROGs each requested tool; at config.status
time it appends a guarded 'am--optional-dist-TOOL' rule to the generated
top-level Makefile and pushes that rule onto DIST_TARGETS. The recipe
re-probes the tool with 'command -v' before running it, deletes any
partial output on failure, and always exits 0 so the rest of 'make dist'
is never disturbed. Unknown OPTIONS entries trigger an m4_warn(syntax)
at autoreconf time and are otherwise ignored.
The macro relies only on Automake's 'distdir' target and on
$(am__tar), both present since Automake 1.10, so it works with any
reasonably recent automake; there is no need to teach automake
itself about the option.
It is concieveable that this will be used in future for more than just
codecs to make the adoption process of new features seamless in light of
Automake's long release cycle and mild unhappiness of distro maintainers
to ship new versions.
* m4/optional.m4: New file. Define AM_OPTIONAL_AUTOMAKE and its
internal helpers _AM_OPTIONAL_AUTOMAKE_ONE, _AM_OPTIONAL_TAR, and
_AM_OPTIONAL_ZIP.
* m4/local.mk (dist_automake_ac_DATA): Distribute optional.m4.
* t/am-optional-automake.sh: New test exercising the four
documented scenarios (tool present; unknown option; tool absent at
configure; tool present at configure but absent at 'make dist').
* t/list-of-tests.mk (handwritten_TESTS): Add it.
* doc/automake.texi (Public Macros): Document AM_OPTIONAL_AUTOMAKE.
(The 'dist' rules): Cross-reference it from the dist-bzip3,
dist-lzip, and dist-zstd descriptions.
* NEWS: Mention the new macro under 1.18.2, not released yet.
---
NEWS | 14 ++++
doc/automake.texi | 75 ++++++++++++++++-
m4/local.mk | 1 +
m4/optional.m4 | 161 ++++++++++++++++++++++++++++++++++++
t/am-optional-automake.sh | 169 ++++++++++++++++++++++++++++++++++++++
t/list-of-tests.mk | 1 +
6 files changed, 418 insertions(+), 3 deletions(-)
create mode 100644 m4/optional.m4
create mode 100644 t/am-optional-automake.sh
--
With Valediction,
Kamila Szewczyk (https://iczelia.net)
diff --git a/NEWS b/NEWS
index 7862559..b00e290 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,20 @@
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, a never-fail companion to
+ AM_INIT_AUTOMAKE for opportunistic `dist-XXX' archives. The
+ listed archive formats are produced by `make dist' only when the
+ matching tool is found at configure time and is still available
+ at `make dist' time; otherwise the archive is silently omitted
+ and the rest of `make dist' is unaffected. Useful for offering
+ newer formats like `dist-bzip3' without breaking on hosts where
+ bzip3 is not installed.
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
New in 1.18.1 (2025-06-25):
diff --git a/doc/automake.texi b/doc/automake.texi
index eeacee7..ee82c27 100644
--- a/doc/automake.texi
+++ b/doc/automake.texi
@@ -4137,6 +4137,66 @@ 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
+@cindex Best-effort distribution archives
+A never-fail companion to @code{AM_INIT_AUTOMAKE} for opportunistic
+@code{dist-} archives. The argument @var{options} is a
+whitespace-separated list of @code{dist-} option names accepted by
+@code{AM_INIT_AUTOMAKE}: @option{dist-bzip2}, @option{dist-bzip3},
+@option{dist-xz}, @option{dist-lzip}, @option{dist-zstd}, and
+@option{dist-zip}. For each one, @samp{make dist} produces the
+matching archive @emph{only when the compressor or archiver tool is
+available}; otherwise the archive is silently omitted.
+
+Unlike passing the same options to @code{AM_INIT_AUTOMAKE},
+@code{AM_OPTIONAL_AUTOMAKE} guarantees that nothing it does will fail
+the build:
+
+@itemize @bullet
+@item
+An unknown or misspelled @var{option} produces a @code{syntax}
+diagnostic at autoreconf time and is then ignored. Configuration
+and the build keep working.
+
+@item
+The tool was not found by @command{./configure}: no extra rule is
+appended to the generated @file{Makefile}, so @samp{make dist}
+simply does not produce that archive.
+
+@item
+The tool was present at configure time but disappeared by the time
+@samp{make dist} runs: the recipe re-checks the tool with
+@command{command -v}, prints a warning to standard error, removes any
+partial output, and still exits successfully. The rest of
+@samp{make dist} is unaffected.
+
+@item
+The user's @command{automake} is too old to know about the option
+(@option{dist-bzip3} was only added in Automake 1.18, for example):
+irrelevant, since @code{AM_OPTIONAL_AUTOMAKE} never passes the option
+to @command{automake}. It ships its own opportunistic rule and only
+relies on the @code{distdir} target and @code{$(am__tar)} shell
+fragment, both present since Automake 1.10.
+@end itemize
+
+A typical use mixes ``required'' formats in @code{AM_INIT_AUTOMAKE}
+with ``opportunistic'' ones in @code{AM_OPTIONAL_AUTOMAKE}:
+
+@example
+AM_INIT_AUTOMAKE([-Wall foreign dist-bzip2 dist-xz dist-zip])
+AM_OPTIONAL_AUTOMAKE([dist-bzip3])
+@end example
+
+Here the @file{.tar.gz}, @file{.tar.bz2}, @file{.tar.xz}, and
+@file{.zip} archives are always built (and @samp{make dist} fails if
+their tools are missing), while @file{.tar.bz3} is built only on
+hosts where @command{bzip3} is installed.
+
+The macro may be called any number of times with any number of
+options. Requesting the same option twice is a harmless no-op.
+
@item AM_PATH_LISPDIR
@acindex AM_PATH_LISPDIR
@vindex EMACS
@@ -9468,7 +9528,10 @@ 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. If you would like @samp{make dist} to produce
+@file{.tar.bz3} when @command{bzip3} is available and to silently
+skip it when it is not, see @code{AM_OPTIONAL_AUTOMAKE}
+(@pxref{Public Macros}).
@item @code{dist-lzip}
@trindex dist-lzip
@@ -9478,7 +9541,10 @@ 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. If you
+would like @samp{make dist} to produce @file{.tar.lz} when
+@command{lzip} is available and to silently skip it when it is not,
+see @code{AM_OPTIONAL_AUTOMAKE} (@pxref{Public Macros}).
@item @code{dist-xz}
@trindex dist-xz
@@ -9510,7 +9576,10 @@ 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. If you would like @samp{make dist} to produce
+@file{.tar.zst} when @command{zstd} is available and to silently skip
+it when it is not, 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 5e9830a..6b76fd2 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 0000000..a519918
--- /dev/null
+++ b/m4/optional.m4
@@ -0,0 +1,161 @@
+## -*- Autoconf -*-
+## Best-effort `dist-XXX' archives -- AM_OPTIONAL_AUTOMAKE.
+
+# 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 is a whitespace-separated list of `dist-XXX' option names
+# (the same spellings accepted by AM_INIT_AUTOMAKE: dist-bzip2,
+# dist-bzip3, dist-xz, dist-lzip, dist-zstd, dist-zip). For each
+# entry, arrange for `make dist' to also produce the matching archive,
+# but *only when the compressor/archiver tool is available*, with
+# strictly never-fail semantics:
+#
+# - Unknown / misspelled entry: a `syntax' warning is emitted at
+# autoreconf time and the entry is ignored. configure and make
+# keep working.
+#
+# - The tool was not found by `./configure': no extra rule is
+# appended to the generated Makefile, so `make dist' simply
+# doesn't produce that archive.
+#
+# - The tool was present at configure time but disappeared by `make
+# dist' time: the recipe re-checks the tool with `command -v',
+# prints a warning to stderr, deletes any partial archive, and
+# still exits 0. Nothing else in `make dist' is affected.
+#
+# - The user's automake is too old to know about the option (or
+# about `dist-bzip3' at all): irrelevant; this macro never asks
+# automake to handle the option, it ships its own rule. We rely
+# only on automake's `distdir' target and the `$(am__tar)' shell
+# fragment, both of which have been present since automake 1.10.
+#
+# Implementation note: the rule is appended to the generated
+# Makefile by an AC_CONFIG_COMMANDS hook, so it survives every
+# `config.status' rerun (and therefore every automatic regeneration
+# of Makefile from Makefile.in). Each distinct OPTION produces one
+# such hook, tagged uniquely by the option name; AM_OPTIONAL_AUTOMAKE
+# may be called any number of times with any number of options, and
+# requesting the same option twice is a harmless no-op.
+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 the spelling of OPT. An unrecognised OPT produces an
+# `m4_warn(syntax)' diagnostic at autoreconf time and is otherwise
+# ignored, per the never-fail contract.
+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-zip], [_AM_OPTIONAL_ZIP],
+ [m4_warn([syntax],
+ [am-optional: unknown option `$1', ignoring it])])dnl
+])
+
+
+# _AM_OPTIONAL_TAR(TOOL, VAR-TAG, EXT, FLAGS)
+# -------------------------------------------
+# Wire up an opportunistic `am--optional-dist-TOOL' target whose
+# recipe pipes $(am__tar) through TOOL with FLAGS and writes the
+# result to $(distdir).tar.EXT. VAR-TAG is the lowercase tool name
+# used to derive the internal shell / make variable name
+# `am__optional_VAR_TAG' (in line with HACKING's convention that
+# automake-internal substitutions use a leading `am__').
+AC_DEFUN([_AM_OPTIONAL_TAR],
+[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],
+[# Append the dist-$1 rule to the top-level Makefile, but only when
+# the tool was found by `./configure' AND we have something that
+# looks like a top-level Makefile to edit. This step never fails.
+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_TARGETS += am--optional-dist-$1
+
+.PHONY: am--optional-dist-$1
+am--optional-dist-$1: distdir
+ @if test -n "$(am__optional_$2)" \
+ && (command -v "$(am__optional_$2)" >/dev/null 2>&1); 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; \
+ :
+_am_opt_rule_eof_
+fi
+],
+[am__optional_$2="$am__optional_$2"
+])])dnl
+])
+
+
+# _AM_OPTIONAL_ZIP
+# ----------------
+# Variant for `dist-zip', which doesn't follow the tar-pipe pattern.
+AC_DEFUN([_AM_OPTIONAL_ZIP],
+[m4_ifdef([_AM_OPTIONAL_DONE_zip], [],
+ [m4_define([_AM_OPTIONAL_DONE_zip])dnl
+AC_CHECK_PROG([am__optional_zip], [zip], [zip])
+AC_SUBST([am__optional_zip])dnl
+AC_CONFIG_COMMANDS([am--optional-rule-zip],
+[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_zip"; then
+ cat >>"$am__optional_mf" <<'_am_opt_rule_eof_'
+
+# Appended by am-optional for dist-zip.
+DIST_TARGETS += am--optional-dist-zip
+
+.PHONY: am--optional-dist-zip
+am--optional-dist-zip: distdir
+ @if test -n "$(am__optional_zip)" \
+ && (command -v "$(am__optional_zip)" >/dev/null 2>&1); 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; \
+ :
+_am_opt_rule_eof_
+fi
+],
+[am__optional_zip="$am__optional_zip"
+])])dnl
+])
diff --git a/t/am-optional-automake.sh b/t/am-optional-automake.sh
new file mode 100644
index 0000000..afe8b25
--- /dev/null
+++ b/t/am-optional-automake.sh
@@ -0,0 +1,169 @@
+#! /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 <https://www.gnu.org/licenses/>.
+
+# Check AM_OPTIONAL_AUTOMAKE. Four 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 absent when
+# `make dist' runs -> the recipe prints a warning to stderr,
+# `make dist' still succeeds, and no $distdir.tar.bz3 appears.
+
+required='bzip3'
+. test-init.sh
+
+# ---- (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
+
+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.*
+
+# ---- (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.*
+
+# ---- (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
+
+# Hide bzip3 from PATH by walking $PATH and creating a fresh sandbox
+# directory of symlinks to every executable except bzip3. Other tools
+# the test harness needs ($am_bindir / t/ax) come from automake's
+# build tree and are not affected.
+mkdir sandbox-bin
+( IFS=:
+ for d in $PATH; do
+ test -d "$d" || continue
+ for f in "$d"/*; do
+ test -f "$f" || continue
+ n=`basename "$f"`
+ test "$n" = bzip3 && continue
+ test -x "$f" && ln -sf "$f" "sandbox-bin/$n" 2>/dev/null
+ done
+ done )
+test ! -e sandbox-bin/bzip3
+
+saved_PATH=$PATH
+PATH=`pwd`/sandbox-bin:$am_bindir:$am_top_srcdir/t/ax
+
+$ACLOCAL
+$AUTOCONF
+$AUTOMAKE --add-missing
+./configure
+$MAKE dist
+test -s am-optional-automake-1.0.tar.gz
+test ! -e am-optional-automake-1.0.tar.bz3
+
+PATH=$saved_PATH
+rm -rf sandbox-bin 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.*
+
+# ---- (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 on PATH here, so the rule is appended
+
+# Now re-run `make dist' with bzip3 hidden. The appended recipe
+# must re-probe with `command -v', print a warning, leave the
+# rest of `make dist' running, and exit 0.
+mkdir sandbox-bin
+( IFS=:
+ for d in $PATH; do
+ test -d "$d" || continue
+ for f in "$d"/*; do
+ test -f "$f" || continue
+ n=`basename "$f"`
+ test "$n" = bzip3 && continue
+ test -x "$f" && ln -sf "$f" "sandbox-bin/$n" 2>/dev/null
+ done
+ done )
+test ! -e sandbox-bin/bzip3
+
+saved_PATH=$PATH
+PATH=`pwd`/sandbox-bin:$am_bindir:$am_top_srcdir/t/ax
+$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:.*bzip3 unavailable' stderr
+
+:
diff --git a/t/list-of-tests.mk b/t/list-of-tests.mk
index 67bf7b5..e044698 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.53.0