+2009-12-04 Eric Blake <ebb9@byu.net>
+
+ Warn if using unnamed diversion.
+ * lib/m4sugar/m4sugar.m4 (_m4_divert, m4_divert_push): Add
+ optional parameter, which controls warning.
+ (m4_divert_pop, m4_cleardivert, m4_divert_require)
+ (_m4_require_call): Adjust callers.
+ * lib/m4sugar/m4sh.m4 (AS_REQUIRE): Likewise.
+ * tests/m4sh.at (AT_DATA_LINENO): Avoid triggering the warning.
+ * tests/m4sugar.at (AT_CHECK_M4SUGAR_TEXT, m4@&t@_append)
+ (m4@&t@_text_wrap, recursion): Likewise.
+ (m4@&t@_warn, m4@&t@_divert_stack): Adjust expected output.
+ * tests/tools.at (autom4te and whitespace in file names)
+ (autoconf: the empty token): Avoid triggering the warning.
+ (autoconf: AC_PRESERVE_HELP_ORDER): New test.
+ * tests/mktests.sh (ac_exclude_list): Retire prior test.
+ * NEWS: Document the warning.
+ * doc/autoconf.texi (Redefined M4 Macros) <m4_divert>,
+ <m4_undivert>: Make even more explicit that using these directly
+ is discouraged.
+ (Diversion support): Further warn against improper diversion
+ changes.
+ <m4_divert_text>: Give an example of proper use.
+ Reported by Mike Frysinger.
+
2009-11-30 Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
manual: AC_SEARCH_LIBS also prepends to LIBS.
** AC_FUNC_MMAP works in C++ mode again. Regression introduced in 2.64.
+** Use of m4_divert without a named diversion now issues a syntax warning,
+ since it is seldom right to change diversions behind autoconf's back.
+
* Major changes in Autoconf 2.65 (2009-11-21) [stable]
Released by Eric Blake, based on git versions 2.64.*.
@end example
@noindent
@xref{Diversion support}, for more details about the use of the
-diversion stack.
+diversion stack. In particular, this implies that @var{diversion}
+should be a named diversion rather than a raw number. But be aware that
+it is seldom necessary to explicitly change the diversion stack, and
+that when done incorrectly, it can lead to syntactically invalid
+scripts.
@end defmac
@defmac m4_dumpdef (@var{name}@dots{})
Also, since the M4sugar diversion stack prefers named
diversions, the use of @code{m4_undivert} to include files is risky.
@xref{Diversion support}, for more details about the use of the
-diversion stack.
+diversion stack. But be aware that it is seldom necessary to explicitly
+change the diversion stack, and that when done incorrectly, it can lead
+to syntactically invalid scripts.
@end defmac
@defmac m4_wrap (@var{text})
@node Diversion support
@subsection Diversion support
-M4sugar makes heavy use of diversions, because it is often the case that
+M4sugar makes heavy use of diversions under the hood, because it is
+often the case that
text that must appear early in the output is not discovered until late
in the input. Additionally, some of the topological sorting algorithms
used in resolving macro dependencies use diversions. However, most
macros should not need to change diversions directly, but rather rely on
-higher-level M4sugar macros to manage diversions transparently.
+higher-level M4sugar macros to manage diversions transparently. If you
+change diversions improperly, you risk generating a syntactically
+invalid script, because an incorrect diversion will violate assumptions
+made by many macros about whether prerequisite text has been previously
+output. In short, if you manually change the diversion, you should not
+expect any macros provided by the Autoconf package to work until you
+have restored the diversion stack back to its original state.
In the rare case that it is necessary to write a macro that explicitly
outputs text to a different diversion, it is important to be aware of an
To make diversion management easier, M4sugar uses the concept of named
diversions. Rather than using diversion numbers directly, it is nicer
-to associate a name with each diversion; the diversion number associated
-with a particular diversion name is an implementation detail, so you
-should only use diversion names. In general, you should not output text
+to associate a name with each diversion. The diversion number associated
+with a particular diversion name is an implementation detail, and a
+syntax warning is issued if a diversion number is used instead of a
+name. In general, you should not output text
to a named diversion until after calling the appropriate initialization
routine for your language (@code{m4_init}, @code{AS_INIT},
@code{AT_INIT}, @dots{}), although there are some exceptions documented
will work even before @code{AT_INIT}.
@end table
-For now, the named diversions of Autoconf and Autoheader, and the
-remaining diversions of Autotest, are not documented. In other words,
+Autoconf inherits diversions from M4sh, and adds the following named
+diversions which developers can utilize.
+@table @code
+@item DEFAULTS
+This diversion contains shell variable assignments to set defaults that
+must be in place before arguments are parsed. This diversion is placed
+early enough in @file{configure} that it is unsafe to expand any
+autoconf macros into this diversion.
+@item HELP_ENABLE
+If @code{AC_PRESERVE_HELP_ORDER} was used, then text placed in this
+diversion will be included as part of a quoted here-doc providing all of
+the @option{--help} output of @file{configure} related to options
+created by @code{AC_ARG_WITH} and @code{AC_ARG_ENABLE}.
+@item INIT_PREPARE
+This diversion occurs after all command line options have been parsed,
+but prior to the main body of the @file{configure} script. This
+diversion is the last chance to insert shell code such as variable
+assignments or shell function declarations that will used by the
+expansion of other macros.
+@end table
+
+For now, the remaining named diversions of Autoconf, Autoheader, and
+Autotest are not documented. In other words,
intentionally outputting text into an undocumented diversion is subject
to breakage in a future release of Autoconf.
m4_divert_push([@var{diversion}])@var{content}
m4_divert_pop([@var{diversion}])dnl
@end example
+
+One use of @code{m4_divert_text} is to develop two related macros, where
+macro @samp{MY_A} does the work, but adjusts what work is performed
+based on whether the optional macro @samp{MY_B} has also been expanded.
+Of course, it is possible to use @code{AC_BEFORE} within @code{MY_A} to
+require that @samp{MY_B} occurs first, if it occurs at all. But this
+imposes an ordering restriction on the user; it would be nicer if macros
+@samp{MY_A} and @samp{MY_B} can be invoked in either order. The trick
+is to let @samp{MY_B} leave a breadcrumb in an early diversion, which
+@samp{MY_A} can then use to determine whether @samp{MY_B} has been
+expanded.
+
+@example
+AC_DEFUN([MY_A],
+[# various actions
+if test -n "$b_was_used"; then
+ # extra action
+fi])
+AC_DEFUN([MY_B],
+[AC_REQUIRE([MY_A])dnl
+m4_divert_text([INIT_PREPARE], [b_was_used=true])])
+@end example
+
@end defmac
@defmac m4_init
# either m4_require([$1], [$2]) or m4_divert_require(desired, [$1], [$2]).
m4_defun([AS_REQUIRE],
[m4_define([_m4_divert_desired], [m4_default_quoted([$3], [M4SH-INIT])])]dnl
-[m4_if(m4_eval(_m4_divert_dump - 0 <= _m4_divert(_m4_divert_desired)),
+[m4_if(m4_eval(_m4_divert_dump - 0 <= _m4_divert(_m4_divert_desired, [-])),
1, [m4_require(],
[m4_divert_require(_m4_divert_desired,]) [$1], [$2])])
m4_define([m4_cleardivert],
[m4_if([$#], [0], [m4_fatal([$0: missing argument])],
[_m4_divert_raw([-1])m4_undivert($@)_m4_divert_raw(
- _m4_divert(_m4_defn([_m4_divert_diversion])))])])
+ _m4_divert(_m4_defn([_m4_divert_diversion]), [-]))])])
-# _m4_divert(DIVERSION-NAME or NUMBER)
-# ------------------------------------
+# _m4_divert(DIVERSION-NAME or NUMBER, [NOWARN])
+# ----------------------------------------------
# If DIVERSION-NAME is the name of a diversion, return its number,
-# otherwise if it is a NUMBER return it.
+# otherwise if it is a NUMBER return it. Issue a warning about
+# the use of a number instead of a name, unless NOWARN is provided.
m4_define([_m4_divert],
[m4_ifdef([_m4_divert($1)],
[m4_indir([_m4_divert($1)])],
- [$1])])
+ [m4_if([$2], [], [m4_warn([syntax],
+ [prefer named diversions])])$1])])
# KILL is only used to suppress output.
m4_define([_m4_divert(KILL)], -1)
[_m4_divert_raw(_m4_divert([$1]))])
-# m4_divert_push(DIVERSION-NAME)
-# ------------------------------
+# m4_divert_push(DIVERSION-NAME, [NOWARN])
+# ----------------------------------------
# Change the diversion stream to DIVERSION-NAME, while stacking old values.
+# For internal use only: if NOWARN is not empty, DIVERSION-NAME can be a
+# number instead of a name.
m4_define([m4_divert_push],
[m4_divert_stack_push([$0], [$1])]dnl
[m4_pushdef([_m4_divert_diversion], [$1])]dnl
-[_m4_divert_raw(_m4_divert([$1]))])
+[_m4_divert_raw(_m4_divert([$1], [$2]))])
# m4_divert_pop([DIVERSION-NAME])
[_m4_popdef([_m4_divert_stack], [_m4_divert_diversion])]dnl
[m4_ifdef([_m4_divert_diversion], [],
[m4_fatal([too many m4_divert_pop])])]dnl
-[_m4_divert_raw(_m4_divert(_m4_defn([_m4_divert_diversion])))])
+[_m4_divert_raw(_m4_divert(_m4_defn([_m4_divert_diversion]), [-]))])
# m4_divert_text(DIVERSION-NAME, CONTENT)
[m4_if(_m4_divert_dump, [],
[m4_fatal([$0($2): cannot be used outside of an m4_defun'd macro])])]dnl
[m4_provide_if([$2], [],
- [_m4_require_call([$2], [$3], _m4_divert([$1]))])])
+ [_m4_require_call([$2], [$3], _m4_divert([$1], [-]))])])
# m4_defun(NAME, EXPANSION, [MACRO = m4_define])
m4_define([_m4_require_call],
[m4_pushdef([_m4_divert_grow], m4_decr(_m4_divert_grow))]dnl
[m4_pushdef([_m4_diverting([$1])])m4_pushdef([_m4_diverting], [$1])]dnl
-[m4_divert_push(_m4_divert_grow)]dnl
+[m4_divert_push(_m4_divert_grow, [-])]dnl
[m4_if([$2], [], [$1], [$2])
m4_provide_if([$1], [m4_set_remove([_m4_provide], [$1])],
[m4_warn([syntax], [$1 is m4_require'd but not m4_defun'd])])]dnl
m4_define([AT_DATA_LINENO],
[AT_DATA([$1.tas],
[[AS@&t@_INIT
-m4@&t@_divert_text([0], [
+m4@&t@_divert_text([], [
if $2; then
AS@&t@_UNSET([LINENO])
fi
[
AT_DATA_M4SUGAR([script.4s],
[[m4_init
-m4_divert_push(0)[]dnl
+m4_divert_push([])[]dnl
]$1[[]dnl
-m4_divert_pop(0)
+m4_divert_pop([])
]])
AT_CHECK_M4SUGAR([-o-],, [$2], [$3])
]])
AT_CHECK_M4SUGAR([-o-], 0, [],
-[script.4s:7: warning: syntax
+[script.4s:4: warning: prefer named diversions
+script.4s:7: warning: syntax
])
AT_CHECK_M4SUGAR([-o- -Wall -f], 0, [],
-[script.4s:5: warning: obsolete
+[script.4s:4: warning: prefer named diversions
+script.4s:5: warning: obsolete
script.4s:6: warning: cross
script.4s:2: cross_warning is expanded from...
script.4s:6: the top level
AT_KEYWORDS([m4@&t@_divert m4@&t@_divert_push m4@&t@_divert_pop
m4@&t@_undivert m4@&t@_cleardivert m4@&t@_divert_text])
-AT_CHECK_M4SUGAR_TEXT([[1.m4_divert_stack
-m4_divert_push([10])2.m4_divert_stack
-m4_divert_text([20], [3.m4_divert_stack])dnl
-m4_divert([30])4.m4_divert_stack
-m4_divert_pop([30])dnl
-5.m4_undivert([20], [30])
+dnl This test names some diversions to avoid a warning.
+AT_CHECK_M4SUGAR_TEXT([[m4_define([_m4_divert(ten)], [10])dnl
+m4_define([_m4_divert(twenty)], [20])dnl
+m4_define([_m4_divert(thirty)], [30])dnl
+1.m4_divert_stack
+m4_divert_push([ten])2.m4_divert_stack
+m4_divert_text([twenty], [3.m4_divert_stack])dnl
+m4_divert([thirty])4.m4_divert_stack
+m4_divert_pop([thirty])dnl
+5.m4_undivert([twenty], [thirty])
m4_pattern_allow([^m4_divert])dnl
-]], [[1.script.4s:2: m4@&t@_divert_push: 0
+]], [[1.script.4s:2: m4@&t@_divert_push:
script.4s:1: m4@&t@_divert: KILL
-5.3.script.4s:5: m4@&t@_divert_push: 20
-script.4s:4: m4@&t@_divert_push: 10
-script.4s:2: m4@&t@_divert_push: 0
+5.3.script.4s:8: m4@&t@_divert_push: twenty
+script.4s:7: m4@&t@_divert_push: ten
+script.4s:2: m4@&t@_divert_push:
script.4s:1: m4@&t@_divert: KILL
-4.script.4s:6: m4@&t@_divert: 30
-script.4s:2: m4@&t@_divert_push: 0
+4.script.4s:9: m4@&t@_divert: thirty
+script.4s:2: m4@&t@_divert_push:
script.4s:1: m4@&t@_divert: KILL
-2.script.4s:4: m4@&t@_divert_push: 10
-script.4s:2: m4@&t@_divert_push: 0
+2.script.4s:7: m4@&t@_divert_push: ten
+script.4s:2: m4@&t@_divert_push:
script.4s:1: m4@&t@_divert: KILL
]])
]],
[[one
four
+]],
+[[script.4s:4: warning: prefer named diversions
+script.4s:5: warning: prefer named diversions
+script.4s:6: warning: prefer named diversions
+script.4s:7: warning: prefer named diversions
+script.4s:8: warning: prefer named diversions
]])
AT_DATA_M4SUGAR([script.4s],
[[m4_init[]dnl
m4_append_uniq([str], [a], [ ])
m4_append_uniq([str], [a b], [ ])
-m4_divert([0])dnl
+m4_divert([])dnl
str
]])
# m4-listification.
AT_DATA_M4SUGAR([script.4s],
-[[m4_init[]m4_divert([0])dnl
+[[m4_init[]m4_divert([])dnl
m4_define([a], [OOPS])dnl
m4_escape([a[b $c#]d])
m4_if(m4_escape([a[b $c#]d]), [a[b $c#]d], [oops],
dnl undefined to exercise the alternate code path.
AT_DATA_M4SUGAR([script.4s],
[[m4_init
-m4_divert_push(0)[]dnl
+m4_divert_push([])[]dnl
m4_len(m4_foreach_w([j], m4_do(m4_for([i], [1], [10000], [], [,i ])), [j ]))
m4_shiftn(9998m4_for([i], [1], [10000], [], [,i]))
m4_len(m4_join([--],, m4_dquote_elt(m4_for([i], [1], [10000], [], [,i])),))
m4_cond(m4_for([i], [1], [10000], [], [[up([j])], [9990], i,]) [oops]) j
m4_count(m4_map_args_pair([,m4_quote], []m4_map_args([,m4_echo]m4_for([i],
[1], [10000], [], [,i]))))
-m4_divert_pop(0)
+m4_divert_pop([])
]])
AT_CHECK_M4SUGAR([-o-], [0], [[48894
5001
m4_exit([0])])
m4_init
-m4_divert_push(0)[]dnl
+m4_divert_push([])[]dnl
m4_len(m4_foreach_w([j], m4_do(m4_for([i], [1], [10000], [], [,i ])), [j ]))
m4_shiftn(9998m4_for([i], [1], [10000], [], [,i]))
m4_len(m4_join([--],, m4_dquote_elt(m4_for([i], [1], [10000], [], [,i])),))
m4_cond(m4_for([i], [1], [10000], [], [[up([j])], [9990], i,]) [oops]) j
m4_count(m4_map_args_pair([,m4_quote], []m4_map_args([,m4_echo]m4_for([i],
[1], [10000], [], [,i]))))
-m4_divert_pop(0)
+m4_divert_pop([])
]])
AT_CHECK_M4SUGAR([-o-], [0], [[48894
# Already tested by AT_CHECK_MACRO.
/^AC_OUTPUT$/ {next}
+
+ # Tested alongside m4_divert_text.
+ /^AC_PRESERVE_HELP_ORDER$/ {next}
'
cat >"$file" <<'END'
[m4@&t@_init[]m4@&t@_include(foo.m4)
-m4@&t@_divert(0)d@&t@nl
+m4@&t@_divert([])d@&t@nl
FOO]
END
cat >"$dir"/foo.m4 <<'END'
cat >"$file" <<'END'
[m4@&t@_init[]m4@&t@_include(foo.m4)
m4@&t@_pattern_forbid([^bar$])
-m4@&t@_divert(0)d@&t@nl
+m4@&t@_divert([])d@&t@nl
FOO]
END
rm -rf "$outfile" "$cachedir"
cat >"$file" <<'END'
[m4@&t@_init[]m4@&t@_include(foo.m4)
-m4@&t@_divert(0)d@&t@nl]
+m4@&t@_divert([])d@&t@nl]
END
rm -rf "$file.m4f"
AT_CHECK_AUTOM4TE([-C "$cachedir" -I "$dir" --language=m4sugar --freeze -o "$file.m4f" "$file"])
AT_DATA_M4SH([configure.ac],
[[m4_init[]m4_pattern_allow([^foo$])
-m4_divert([0])dnl
+m4_divert([])dnl
line that begins with a space
]])
+# autoconf: AC_PRESERVE_HELP_ORDER
+# --------------------------------
+AT_SETUP([autoconf: AC_PRESERVE_HELP_ORDER])
+AT_KEYWORDS([m4@&t@_divert_text])
+
+AT_DATA_AUTOCONF([configure.ac],
+[[AC_INIT
+AC_PRESERVE_HELP_ORDER
+AC_ARG_WITH([one], [ --with-one])
+AC_ARG_ENABLE([two], [ --enable-two])
+m4_divert_text([HELP_ENABLE], [arbitrary $text])
+AC_ARG_WITH([three], [ --with-three])
+AC_OUTPUT
+]])
+
+AT_CHECK_AUTOCONF
+AT_CHECK_CONFIGURE([--help], [], [stdout])
+AT_CHECK([sed -n '/^Optional/,/^$/p' stdout], [],
+[[Optional Features and Packages:
+ --disable-option-checking ignore unrecognized --enable/--with options
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
+ --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
+ --with-one
+ --enable-two
+arbitrary $text
+ --with-three
+
+]])
+
+AT_CLEANUP
+
+
+
## --------- ##
## ifnames. ##
## --------- ##