maint.mk (sc_Wundef_boolean): Skip test if no config.h.
[gnulib.git] / top / maint.mk
index 4e4c050..09da20a 100644 (file)
@@ -1,8 +1,8 @@
 # -*-Makefile-*-
 # This Makefile fragment tries to be general-purpose enough to be
-# used by at least coreutils, idutils, CPPI, Bison, and Autoconf.
+# used by many projects via the gnulib maintainer-makefile module.
 
-## Copyright (C) 2001-2009 Free Software Foundation, Inc.
+## Copyright (C) 2001-2010 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
@@ -21,6 +21,9 @@
 # ME := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
 ME := maint.mk
 
+# Override this in cfg.mk if you use a non-standard build-aux directory.
+build_aux ?= $(srcdir)/build-aux
+
 # Do not save the original name or timestamp in the .tar.gz file.
 # Use --rsyncable if available.
 gzip_rsyncable := \
@@ -29,18 +32,42 @@ GZIP_ENV = '--no-name --best $(gzip_rsyncable)'
 
 GIT = git
 VC = $(GIT)
-VC-tag = git tag -s -m '$(VERSION)'
+VC-tag = git tag -s -m '$(VERSION)' -u '$(gpg_key_ID)'
+
+VC_LIST = $(build_aux)/vc-list-files -C $(srcdir)
+
+# You can override this variable in cfg.mk to set your own regexp
+# matching files to ignore.
+VC_LIST_ALWAYS_EXCLUDE_REGEX ?= ^$$
+
+# This is to preprocess robustly the output of $(VC_LIST), so that even
+# when $(srcdir) is a pathological name like "....", the leading sed command
+# removes only the intended prefix.
+_dot_escaped_srcdir = $(subst .,\.,$(srcdir))
 
-VC_LIST = $(srcdir)/build-aux/vc-list-files
+# Post-process $(VC_LIST) output, prepending $(srcdir)/, but only
+# when $(srcdir) is not ".".
+ifeq ($(srcdir),.)
+_prepend_srcdir_prefix =
+else
+_prepend_srcdir_prefix = | sed 's|^|$(srcdir)/|'
+endif
 
+# In order to be able to consistently filter "."-relative names,
+# (i.e., with no $(srcdir) prefix), this definition is careful to
+# remove any $(srcdir) prefix, and to restore what it removes.
 VC_LIST_EXCEPT = \
-  $(VC_LIST) | if test -f .x-$@; then grep -vEf .x-$@; else grep -v ChangeLog; fi
+  $(VC_LIST) | sed 's|^$(_dot_escaped_srcdir)/||' \
+       | if test -f $(srcdir)/.x-$@; then grep -vEf $(srcdir)/.x-$@; \
+         else grep -Ev -e "$${VC_LIST_EXCEPT_DEFAULT-ChangeLog}"; fi \
+       | grep -Ev -e '$(VC_LIST_ALWAYS_EXCLUDE_REGEX)' \
+       $(_prepend_srcdir_prefix)
 
 ifeq ($(origin prev_version_file), undefined)
   prev_version_file = $(srcdir)/.prev-version
 endif
 
-PREV_VERSION := $(shell cat $(prev_version_file))
+PREV_VERSION := $(shell cat $(prev_version_file) 2>/dev/null)
 VERSION_REGEXP = $(subst .,\.,$(VERSION))
 PREV_VERSION_REGEXP = $(subst .,\.,$(PREV_VERSION))
 
@@ -58,6 +85,25 @@ my_distdir = $(PACKAGE)-$(VERSION)
 # Old releases are stored here.
 release_archive_dir ?= ../release
 
+# Override gnu_rel_host and url_dir_list in cfg.mk if these are not right.
+# Use alpha.gnu.org for alpha and beta releases.
+# Use ftp.gnu.org for stable releases.
+gnu_ftp_host-alpha = alpha.gnu.org
+gnu_ftp_host-beta = alpha.gnu.org
+gnu_ftp_host-stable = ftp.gnu.org
+gnu_rel_host ?= $(gnu_ftp_host-$(RELEASE_TYPE))
+
+ifeq ($(gnu_rel_host),ftp.gnu.org)
+url_dir_list ?= http://ftpmirror.gnu.org/$(PACKAGE)
+else
+url_dir_list ?= ftp://$(gnu_rel_host)/gnu/$(PACKAGE)
+endif
+
+# Override this in cfg.mk if you are using a different format in your
+# NEWS file.
+today = $(shell date +%Y-%m-%d)
+news-check-regexp ?= '^\*.* $(VERSION_REGEXP) \($(today)\)'
+
 # Prevent programs like 'sort' from considering distinct strings to be equal.
 # Doing it here saves us from having to set LC_ALL elsewhere in this file.
 export LC_ALL = C
@@ -66,9 +112,11 @@ export LC_ALL = C
 ## Sanity checks.  ##
 ## --------------- ##
 
+_cfg_mk := $(shell test -f $(srcdir)/cfg.mk && echo '$(srcdir)/cfg.mk')
+
 # Collect the names of rules starting with `sc_'.
-syntax-check-rules := $(shell sed -n 's/^\(sc_[a-zA-Z0-9_-]*\):.*/\1/p' \
-                        $(srcdir)/$(ME) $(srcdir)/cfg.mk)
+syntax-check-rules := $(sort $(shell sed -n 's/^\(sc_[a-zA-Z0-9_-]*\):.*/\1/p' \
+                       $(srcdir)/$(ME) $(_cfg_mk)))
 .PHONY: $(syntax-check-rules)
 
 local-checks-available = \
@@ -77,7 +125,9 @@ local-checks-available = \
 
 # Arrange to print the name of each syntax-checking rule just before running it.
 $(syntax-check-rules): %: %.m
-$(patsubst %, %.m, $(syntax-check-rules)):
+sc_m_rules_ = $(patsubst %, %.m, $(syntax-check-rules))
+.PHONY: $(sc_m_rules_)
+$(sc_m_rules_):
        @echo $(patsubst sc_%.m, %, $@)
 
 local-check := $(filter-out $(local-checks-to-skip), $(local-checks-available))
@@ -110,9 +160,9 @@ define _prohibit_regexp
 endef
 
 sc_avoid_if_before_free:
-       @$(srcdir)/build-aux/useless-if-before-free                     \
+       @$(build_aux)/useless-if-before-free                            \
                $(useless_free_options)                                 \
-           $$($(VC_LIST_EXCEPT)) &&                                    \
+           $$($(VC_LIST_EXCEPT) | grep -v useless-if-before-free) &&   \
          { echo '$(ME): found useless "if" before "free" above' 1>&2;  \
            exit 1; } || :
 
@@ -149,6 +199,21 @@ sc_prohibit_strcmp:
          { echo '$(ME): use STREQ in place of the above uses of str''cmp' \
                1>&2; exit 1; } || :
 
+# Pass EXIT_*, not number, to usage, exit, and error (when exiting)
+# Convert all uses automatically, via these two commands:
+# git grep -l '\<exit *(1)' \
+#  | grep -vEf .x-sc_prohibit_magic_number_exit \
+#  | xargs --no-run-if-empty \
+#      perl -pi -e 's/(^|[^.])\b(exit ?)\(1\)/$1$2(EXIT_FAILURE)/'
+# git grep -l '\<exit *(0)' \
+#  | grep -vEf .x-sc_prohibit_magic_number_exit \
+#  | xargs --no-run-if-empty \
+#      perl -pi -e 's/(^|[^.])\b(exit ?)\(0\)/$1$2(EXIT_SUCCESS)/'
+sc_prohibit_magic_number_exit:
+       @re='(^|[^.])\<(usage|exit) ?\([0-9]|\<error ?\([1-9][0-9]*,'   \
+       msg='use EXIT_* values rather than magic number'                \
+         $(_prohibit_regexp)
+
 # Using EXIT_SUCCESS as the first argument to error is misleading,
 # since when that parameter is 0, error does not exit.  Use `0' instead.
 sc_error_exit_success:
@@ -190,10 +255,13 @@ sc_prohibit_have_config_h:
          { echo '$(ME): found use of HAVE''_CONFIG_H; remove'          \
                1>&2; exit 1; } || :
 
-# Nearly all .c files must include <config.h>.
+# Nearly all .c files must include <config.h>.  However, we also permit this
+# via inclusion of a package-specific header, if cfg.mk specified one.
+# config_h_header must be suitable for grep -E.
+config_h_header ?= <config\.h>
 sc_require_config_h:
-       @if $(VC_LIST_EXCEPT) | grep -l '\.c$$' > /dev/null; then               \
-         grep -L '^# *include <config\.h>'                             \
+       @if $(VC_LIST_EXCEPT) | grep -l '\.c$$' > /dev/null; then       \
+         grep -EL '^# *include $(config_h_header)'                     \
                $$($(VC_LIST_EXCEPT) | grep '\.c$$')                    \
              | grep . &&                                               \
          { echo '$(ME): the above files do not include <config.h>'     \
@@ -202,12 +270,13 @@ sc_require_config_h:
        fi
 
 # You must include <config.h> before including any other header file.
+# This can possibly be via a package-specific header, if given by cfg.mk.
 sc_require_config_h_first:
        @if $(VC_LIST_EXCEPT) | grep -l '\.c$$' > /dev/null; then       \
          fail=0;                                                       \
          for i in $$($(VC_LIST_EXCEPT) | grep '\.c$$'); do             \
            grep '^# *include\>' $$i | sed 1q                           \
-               | grep '^# *include <config\.h>' > /dev/null            \
+               | grep -E '^# *include $(config_h_header)' > /dev/null  \
              || { echo $$i; fail=1; };                                 \
          done;                                                         \
          test $$fail = 1 &&                                            \
@@ -224,7 +293,8 @@ sc_prohibit_HAVE_MBRTOWC:
 # h: the header, enclosed in <> or ""
 # re: a regular expression that matches IFF something provided by $h is used.
 define _header_without_use
-  h_esc=`echo "$$h"|sed 's/\./\\./g'`;                                 \
+  dummy=; : so we do not need a semicolon before each use;             \
+  h_esc=`echo "$$h"|sed 's/\./\\\\./g'`;                               \
   if $(VC_LIST_EXCEPT) | grep -l '\.c$$' > /dev/null; then             \
     files=$$(grep -l '^# *include '"$$h_esc"                           \
             $$($(VC_LIST_EXCEPT) | grep '\.c$$')) &&                   \
@@ -239,6 +309,10 @@ endef
 sc_prohibit_assert_without_use:
        @h='<assert.h>' re='\<assert *\(' $(_header_without_use)
 
+# Prohibit the inclusion of close-stream.h without an actual use.
+sc_prohibit_close_stream_without_use:
+       @h='"close-stream.h"' re='\<close_stream *\(' $(_header_without_use)
+
 # Prohibit the inclusion of getopt.h without an actual use.
 sc_prohibit_getopt_without_use:
        @h='<getopt.h>' re='\<getopt(_long)? *\(' $(_header_without_use)
@@ -262,11 +336,55 @@ sc_prohibit_inttostr_without_use:
          $(_header_without_use)
 
 # Don't include this header unless you use one of its functions.
+sc_prohibit_ignore_value_without_use:
+       @h='"ignore-value.h"' re='\<ignore_(value|ptr) *\(' \
+         $(_header_without_use)
+
+# Don't include this header unless you use one of its functions.
 sc_prohibit_error_without_use:
        @h='"error.h"' \
        re='\<error(_at_line|_print_progname|_one_per_line|_message_count)? *\('\
          $(_header_without_use)
 
+# Don't include xalloc.h unless you use one of its functions.
+# Consider these symbols:
+# perl -lne '/^# *define (\w+)\(/ and print $1' lib/xalloc.h|grep -v '^__';
+# perl -lne '/^(?:extern )?(?:void|char) \*?(\w+) \(/ and print $1' lib/xalloc.h
+# Divide into two sets on case, and filter each through this:
+# | sort | perl -MRegexp::Assemble -le \
+#  'print Regexp::Assemble->new(file => "/dev/stdin")->as_string'|sed 's/\?://g'
+# Note this was produced by the above:
+# _xa1 = \
+#x(((2n?)?re|c(har)?|n(re|m)|z)alloc|alloc_(oversized|die)|m(alloc|emdup)|strdup)
+# But we can do better, in at least two ways:
+# 1) take advantage of two "dup"-suffixed strings:
+# x(((2n?)?re|c(har)?|n(re|m)|[mz])alloc|alloc_(oversized|die)|(mem|str)dup)
+# 2) notice that "c(har)?|[mz]" is equivalent to the shorter and more readable
+# "char|[cmz]"
+# x(((2n?)?re|char|n(re|m)|[cmz])alloc|alloc_(oversized|die)|(mem|str)dup)
+_xa1 = x(((2n?)?re|char|n(re|m)|[cmz])alloc|alloc_(oversized|die)|(mem|str)dup)
+_xa2 = X([CZ]|N?M)ALLOC
+sc_prohibit_xalloc_without_use:
+       @h='"xalloc.h"' \
+       re='\<($(_xa1)|$(_xa2)) *\('\
+         $(_header_without_use)
+
+# Extract function names:
+# perl -lne '/^(?:extern )?(?:void|char) \*?(\w+) \(/ and print $1' lib/hash.h
+_hash_re = \
+clear|delete|free|get_(first|next)|insert|lookup|print_statistics|reset_tuning
+_hash_fn = \<($(_hash_re)) *\(
+_hash_struct = (struct )?\<[Hh]ash_(table|tuning)\>
+sc_prohibit_hash_without_use:
+       @h='"hash.h"' \
+       re='$(_hash_fn)|$(_hash_struct)'\
+         $(_header_without_use)
+
+sc_prohibit_hash_pjw_without_use:
+       @h='"hash-pjw.h"' \
+       re='\<hash_pjw *\(' \
+         $(_header_without_use)
+
 sc_prohibit_safe_read_without_use:
        @h='"safe-read.h"' re='(\<SAFE_READ_ERROR\>|\<safe_read *\()' \
          $(_header_without_use)
@@ -276,11 +394,21 @@ sc_prohibit_argmatch_without_use:
        re='(\<(ARRAY_CARDINALITY|X?ARGMATCH(|_TO_ARGUMENT|_VERIFY))\>|\<argmatch(_exit_fn|_(in)?valid) *\()' \
          $(_header_without_use)
 
+sc_prohibit_canonicalize_without_use:
+       @h='"canonicalize.h"' \
+       re='CAN_(EXISTING|ALL_BUT_LAST|MISSING)|canonicalize_(mode_t|filename_mode)' \
+         $(_header_without_use)
+
 sc_prohibit_root_dev_ino_without_use:
        @h='"root-dev-ino.h"' \
        re='(\<ROOT_DEV_INO_(CHECK|WARN)\>|\<get_root_dev_ino *\()' \
          $(_header_without_use)
 
+sc_prohibit_openat_without_use:
+       @h='"openat.h"' \
+       re='\<(openat_(permissive|needs_fchdir|(save|restore)_fail)|l?(stat|ch(own|mod))at|(euid)?accessat)\>' \
+         $(_header_without_use)
+
 # Prohibit the inclusion of c-ctype.h without an actual use.
 ctype_re = isalnum|isalpha|isascii|isblank|iscntrl|isdigit|isgraph|islower\
 |isprint|ispunct|isspace|isupper|isxdigit|tolower|toupper
@@ -324,6 +452,19 @@ sc_prohibit_signal_without_use:
        re='\<($(_sig_function_re)) *\(|\<($(_sig_syms_re))\>'          \
          $(_header_without_use)
 
+# Get the list of symbol names with this:
+# perl -lne '/^# *define (\w+)\(/ and print $1' lib/intprops.h|grep -v '^s'|fmt
+_intprops_names =                                                      \
+  TYPE_IS_INTEGER TYPE_TWOS_COMPLEMENT TYPE_ONES_COMPLEMENT            \
+  TYPE_SIGNED_MAGNITUDE TYPE_SIGNED TYPE_MINIMUM TYPE_MAXIMUM          \
+  INT_STRLEN_BOUND INT_BUFSIZE_BOUND
+_intprops_syms_re = $(subst $(_sp),|,$(strip $(_intprops_names)))
+# Prohibit the inclusion of intprops.h without an actual use.
+sc_prohibit_intprops_without_use:
+       @h='"intprops.h"'                                               \
+       re='\<($(_intprops_syms_re)) *\('                               \
+         $(_header_without_use)
+
 sc_obsolete_symbols:
        @re='\<(HAVE''_FCNTL_H|O''_NDELAY)\>'                           \
        msg='do not use HAVE''_FCNTL_H or O'_NDELAY                     \
@@ -355,16 +496,19 @@ sc_program_name:
 # Require that the final line of each test-lib.sh-using test be this one:
 # Exit $fail
 # Note: this test requires GNU grep's --label= option.
+Exit_witness_file ?= tests/test-lib.sh
+Exit_base := $(notdir $(Exit_witness_file))
 sc_require_test_exit_idiom:
-       @if test -f $(srcdir)/tests/test-lib.sh; then                   \
+       @if test -f $(srcdir)/$(Exit_witness_file); then                \
          die=0;                                                        \
-         for i in $$(grep -l -F /../test-lib.sh $$($(VC_LIST) tests)); do \
-           tail -n1 $$i | grep '^Exit \$$fail$$' > /dev/null           \
+         for i in $$(grep -l -F 'srcdir/$(Exit_base)'                  \
+               $$($(VC_LIST) tests)); do                               \
+           tail -n1 $$i | grep '^Exit .' > /dev/null                   \
              && : || { die=1; echo $$i; }                              \
          done;                                                         \
          test $$die = 1 &&                                             \
            { echo 1>&2 '$(ME): the final line in each of the above is not:'; \
-             echo 1>&2 'Exit $$fail';                                  \
+             echo 1>&2 'Exit something';                               \
              exit 1; } || :;                                           \
        fi
 
@@ -410,6 +554,20 @@ sc_GPL_version:
        @re='either ''version [^3]' msg='GPL vN, N!=3'                  \
          $(_prohibit_regexp)
 
+# Require the latest GFDL.  Two regexp, since some .texi files end up
+# line wrapping between 'Free Documentation License,' and 'Version'.
+_GFDL_regexp = (Free ''Documentation.*Version 1\.[^3]|Version 1\.[^3] or any)
+sc_GFDL_version:
+       @re='$(_GFDL_regexp)' msg='GFDL vN, N!=3'                       \
+         $(_prohibit_regexp)
+
+# Don't use Texinfo @acronym{} as it is not a good idea.
+sc_texinfo_acronym:
+       @grep -nE '@acronym{'                                           \
+           $$($(VC_LIST_EXCEPT) | grep -E '\.texi$$') &&               \
+         { echo '$(ME): found use of Texinfo @acronym{}' 1>&2;         \
+           exit 1; } || :
+
 cvs_keywords = \
   Author|Date|Header|Id|Name|Locker|Log|RCSfile|Revision|Source|State
 
@@ -432,14 +590,20 @@ sc_prohibit_S_IS_definition:
        msg='do not define S_IS* macros; include <sys/stat.h>'          \
          $(_prohibit_regexp)
 
-# Each program that uses proper_name_utf8 must link with
-# one of the ICONV libraries.
+# Each program that uses proper_name_utf8 must link with one of the
+# ICONV libraries.  Otherwise, some ICONV library must appear in LDADD.
+# The perl -0777 invocation below extracts the possibly-multi-line
+# definition of LDADD from the appropriate Makefile.am and exits 0
+# when it contains "ICONV".
 sc_proper_name_utf8_requires_ICONV:
        @progs=$$(grep -l 'proper_name_utf8 ''("' $$($(VC_LIST_EXCEPT)));\
        if test "x$$progs" != x; then                                   \
          fail=0;                                                       \
          for p in $$progs; do                                          \
            dir=$$(dirname "$$p");                                      \
+           perl -0777                                                  \
+             -ne 'exit !(/^LDADD =(.+?[^\\]\n)/ms && $$1 =~ /ICONV/)'  \
+             $$dir/Makefile.am && continue;                            \
            base=$$(basename "$$p" .c);                                 \
            grep "$${base}_LDADD.*ICONV)" $$dir/Makefile.am > /dev/null \
              || { fail=1; echo 1>&2 "$(ME): $$p uses proper_name_utf8"; }; \
@@ -465,7 +629,8 @@ sc_const_long_option:
 NEWS_hash =                                                            \
   $$(sed -n '/^\*.* $(PREV_VERSION_REGEXP) ([0-9-]*)/,$$p'             \
        $(srcdir)/NEWS                                                  \
-     | grep -v '^Copyright .*Free Software'                            \
+     | perl -0777 -pe                                                  \
+       's/^Copyright.+?Free\sSoftware\sFoundation,\sInc\.\n//ms'       \
      | md5sum -                                                                \
      | sed 's/ .*//')
 
@@ -479,7 +644,7 @@ sc_immutable_NEWS:
 # Update the hash stored above.  Do this after each release and
 # for any corrections to old entries.
 update-NEWS-hash: NEWS
-       perl -pi -e 's/^(old_NEWS_hash = ).*/$${1}'"$(NEWS_hash)/" \
+       perl -pi -e 's/^(old_NEWS_hash[ \t]+:?=[ \t]+).*/$${1}'"$(NEWS_hash)/" \
          $(srcdir)/cfg.mk
 
 # Ensure that we use only the standard $(VAR) notation,
@@ -487,19 +652,22 @@ update-NEWS-hash: NEWS
 # to emit a definition for each substituted variable.
 # We use perl rather than "grep -nE ..." to exempt a single
 # use of an @...@-delimited variable name in src/Makefile.am.
-sc_makefile_check:
-       @perl -ne '/\@[A-Z_0-9]+\@/ && !/^cu_install_program =/'        \
+# Allow the package to add exceptions via a hook in cfg.mk;
+# for example, @PRAGMA_SYSTEM_HEADER@ can be permitted by
+# setting this to ' && !/PRAGMA_SYSTEM_HEADER/'.
+_makefile_at_at_check_exceptions ?=
+sc_makefile_at_at_check:
+       @perl -ne '/\@[A-Z_0-9]+\@/'$(_makefile_at_at_check_exceptions) \
          -e 'and (print "$$ARGV:$$.: $$_"), $$m=1; END {exit !$$m}'    \
            $$($(VC_LIST_EXCEPT) | grep -E '(^|/)Makefile\.am$$')       \
          && { echo '$(ME): use $$(...), not @...@' 1>&2; exit 1; } || :
 
-news-date-check: NEWS
-       today=`date +%Y-%m-%d`;                                         \
-       if head NEWS | grep '^\*.* $(VERSION_REGEXP) ('$$today')'       \
+news-check: NEWS
+       if head $(srcdir)/NEWS | grep -E $(news-check-regexp)           \
            >/dev/null; then                                            \
          :;                                                            \
        else                                                            \
-         echo "version or today's date is not in NEWS" 1>&2;           \
+         echo 'NEWS: $$(news-check-regexp) failed to match' 1>&2;      \
          exit 1;                                                       \
        fi
 
@@ -529,6 +697,7 @@ sc_po_check:
          for file in $$($(VC_LIST_EXCEPT)) lib/*.[ch]; do              \
            test -r $$file || continue;                                 \
            case $$file in                                              \
+             *.m4|*.mk) continue ;;                                    \
              *.?|*.??) ;;                                              \
              *) continue;;                                             \
            esac;                                                       \
@@ -592,8 +761,30 @@ sc_copyright_check:
               exit 1; };                                               \
        fi
 
+# #if HAVE_... will evaluate to false for any non numeric string.
+# That would be flagged by using -Wundef, however gnulib currently
+# tests many undefined macros, and so we can't enable that option.
+# So at least preclude common boolean strings as macro values.
+sc_Wundef_boolean:
+       @test -e '$(CONFIG_INCLUDE)' &&                                 \
+          grep -Ei '^#define.*(yes|no|true|false)$$' '$(CONFIG_INCLUDE)' && \
+            { echo 'Use 0 or 1 for macro values' 1>&2; exit 1; } || :
+
+sc_vulnerable_makefile_CVE-2009-4029:
+       @files=$$(find $(srcdir) -name Makefile.in);                    \
+       if test -n "$$files"; then                                      \
+         grep -E                                                       \
+           'perm -777 -exec chmod a\+rwx|chmod 777 \$$\(distdir\)'     \
+           $$files &&                                                  \
+         { echo '$(ME): the above files are vulnerable; beware of'     \
+           'running "make dist*" rules, and upgrade to fixed automake' \
+           'see http://bugzilla.redhat.com/542609 for details'         \
+               1>&2; exit 1; } || :;                                   \
+       else :;                                                         \
+       fi
+
 vc-diff-check:
-       $(VC) diff > vc-diffs || :
+       (unset CDPATH; cd $(srcdir) && $(VC) diff) > vc-diffs || :
        if test -s vc-diffs; then                               \
          cat vc-diffs;                                         \
          echo "Some files are locally modified:" 1>&2;         \
@@ -602,179 +793,28 @@ vc-diff-check:
          rm vc-diffs;                                          \
        fi
 
-cvs-check: vc-diff-check
-
-ALL_RECURSIVE_TARGETS += maintainer-distcheck
-maintainer-distcheck:
-       $(MAKE) distcheck
-       $(MAKE) taint-distcheck
-       $(MAKE) my-distcheck
-
-
-# Don't make a distribution if checks fail.
-# Also, make sure the NEWS file is up-to-date.
-ALL_RECURSIVE_TARGETS += vc-dist
-vc-dist: $(local-check) cvs-check maintainer-distcheck
-       XZ_OPT=-9ev $(MAKE) dist
-
-# Use this to make sure we don't run these programs when building
-# from a virgin tgz file, below.
-null_AM_MAKEFLAGS = \
-  ACLOCAL=false \
-  AUTOCONF=false \
-  AUTOMAKE=false \
-  AUTOHEADER=false \
-  MAKEINFO=false
-
-built_programs = $$(cd src && MAKEFLAGS= $(MAKE) -s built_programs.list)
-
-warn_cflags = -Dlint -O -Werror -Wall -Wformat -Wshadow -Wpointer-arith
-bin=bin-$$$$
-
-write_loser = printf '\#!%s\necho $$0: bad path 1>&2; exit 1\n' '$(SHELL)'
-
-TMPDIR ?= /tmp
-t=$(TMPDIR)/$(PACKAGE)/test
-pfx=$(t)/i
-
-# More than once, tainted build and source directory names would
-# have caused at least one "make check" test to apply "chmod 700"
-# to all directories under $HOME.  Make sure it doesn't happen again.
-tp := $(shell echo "$(TMPDIR)/$(PACKAGE)-$$$$")
-t_prefix = $(tp)/a
-t_taint = '$(t_prefix) b'
-fake_home = $(tp)/home
-
-# Ensure that tests run from tainted build and src dir names work,
-# and don't affect anything in $HOME.  Create witness files in $HOME,
-# record their attributes, and build/test.  Then ensure that the
-# witnesses were not affected.
-ALL_RECURSIVE_TARGETS += taint-distcheck
-taint-distcheck: $(DIST_ARCHIVES)
-       test -d $(t_taint) && chmod -R 700 $(t_taint) || :
-       -rm -rf $(t_taint) $(fake_home)
-       mkdir -p $(t_prefix) $(t_taint) $(fake_home)
-       GZIP=$(GZIP_ENV) $(AMTAR) -C $(t_taint) -zxf $(distdir).tar.gz
-       mkfifo $(fake_home)/fifo
-       touch $(fake_home)/f
-       mkdir -p $(fake_home)/d/e
-       ls -lR $(fake_home) $(t_prefix) > $(tp)/.ls-before
-       cd $(t_taint)/$(distdir)                        \
-         && ./configure                                \
-         && $(MAKE)                                    \
-         && HOME=$(fake_home) $(MAKE) check            \
-         && ls -lR $(fake_home) $(t_prefix) > $(tp)/.ls-after \
-         && diff $(tp)/.ls-before $(tp)/.ls-after      \
-         && test -d $(t_prefix)
-       rm -rf $(tp)
-
-# Verify that a twisted use of --program-transform-name=PROGRAM works.
-define install-transform-check
-  echo running install-transform-check                 \
-    && rm -rf $(pfx)                                   \
-    && $(MAKE) program_transform_name='s/.*/zyx/'      \
-      prefix=$(pfx) install                            \
-    && test "$$(echo $(pfx)/bin/*)" = "$(pfx)/bin/zyx" \
-    && test "$$(find $(pfx)/share/man -type f|sed 's,.*/,,;s,\..*,,')" = "zyx"
-endef
-
-# Install, then verify that all binaries and man pages are in place.
-# Note that neither the binary, ginstall, nor the ].1 man page is installed.
-define my-instcheck
-  $(MAKE) prefix=$(pfx) install                                \
-    && test ! -f $(pfx)/bin/ginstall                   \
-    && { fail=0;                                       \
-      for i in $(built_programs); do                   \
-        test "$$i" = ginstall && i=install;            \
-        for j in "$(pfx)/bin/$$i"                      \
-                 "$(pfx)/share/man/man1/$$i.1"; do     \
-          case $$j in *'[.1') continue;; esac;         \
-          test -f "$$j" && :                           \
-            || { echo "$$j not installed"; fail=1; };  \
-        done;                                          \
-      done;                                            \
-      test $$fail = 1 && exit 1 || :;                  \
-    }
-endef
-
-define coreutils-path-check
-  {                                                    \
-    if test -f $(srcdir)/src/true.c; then              \
-      fail=1;                                          \
-      mkdir $(bin)                                     \
-       && ($(write_loser)) > $(bin)/loser              \
-       && chmod a+x $(bin)/loser                       \
-       && for i in $(built_programs); do               \
-              case $$i in                              \
-                rm|expr|basename|echo|sort|ls|tr);;    \
-                cat|dirname|mv|wc);;                   \
-                *) ln $(bin)/loser $(bin)/$$i;;        \
-              esac;                                    \
-            done                                       \
-         && ln -sf ../src/true $(bin)/false            \
-         && PATH=`pwd`/$(bin)$(PATH_SEPARATOR)$$PATH   \
-               $(MAKE) -C tests check                  \
-         && { test -d gnulib-tests                     \
-                && $(MAKE) -C gnulib-tests check       \
-                || :; }                                \
-         && rm -rf $(bin)                              \
-         && fail=0;                                    \
-    else                                               \
-      fail=0;                                          \
-    fi;                                                        \
-    test $$fail = 1 && exit 1 || :;                    \
-  }
-endef
-
-# Use -Wformat -Werror to detect format-string/arg-list mismatches.
-# Also, check for shadowing problems with -Wshadow, and for pointer
-# arithmetic problems with -Wpointer-arith.
-# These CFLAGS are pretty strict.  If you build this target, you probably
-# have to have a recent version of gcc and glibc headers.
-# The hard-linking for-loop below ensures that there is a bin/ directory
-# full of all of the programs under test (except the ones that are required
-# for basic Makefile rules), all symlinked to the just-built "false" program.
-# This is to ensure that if ever a test neglects to make PATH include
-# the build srcdir, these always-failing programs will run.
-# Otherwise, it is too easy to test the wrong programs.
-# Note that "false" itself is a symlink to true, so it too will malfunction.
-ALL_RECURSIVE_TARGETS += my-distcheck
-my-distcheck: $(DIST_ARCHIVES) $(local-check)
-       $(MAKE) syntax-check
-       $(MAKE) check
-       -rm -rf $(t)
-       mkdir -p $(t)
-       GZIP=$(GZIP_ENV) $(AMTAR) -C $(t) -zxf $(distdir).tar.gz
-       cd $(t)/$(distdir)                              \
-         && ./configure --disable-nls                  \
-         && $(MAKE) CFLAGS='$(warn_cflags)'            \
-             AM_MAKEFLAGS='$(null_AM_MAKEFLAGS)'       \
-         && $(MAKE) dvi                                \
-         && $(install-transform-check)                 \
-         && $(my-instcheck)                            \
-         && $(coreutils-path-check)                    \
-         && $(MAKE) distclean
-       (cd $(t) && mv $(distdir) $(distdir).old        \
-         && $(AMTAR) -zxf - ) < $(distdir).tar.gz
-       diff -ur $(t)/$(distdir).old $(t)/$(distdir)
-       -rm -rf $(t)
-       @echo "========================"; \
-       echo "$(distdir).tar.gz is ready for distribution"; \
-       echo "========================"
-
 rel-files = $(DIST_ARCHIVES)
 
+gnulib_dir ?= $(srcdir)/gnulib
 gnulib-version = $$(cd $(gnulib_dir) && git describe)
+bootstrap-tools ?= autoconf,automake,gnulib
+
+# If it's not already specified, derive the GPG key ID from
+# the signed tag we've just applied to mark this release.
+gpg_key_ID ?= \
+  $$(git cat-file tag v$(VERSION) > .ann-sig \
+     && gpgv .ann-sig - < /dev/null 2>&1 \
+         | sed -n '/.*key ID \([0-9A-F]*\)/s//\1/p'; rm -f .ann-sig)
 
 announcement: NEWS ChangeLog $(rel-files)
-       @./build-aux/announce-gen                                       \
+       @$(build_aux)/announce-gen                                      \
            --release-type=$(RELEASE_TYPE)                              \
            --package=$(PACKAGE)                                        \
            --prev=$(PREV_VERSION)                                      \
            --curr=$(VERSION)                                           \
            --gpg-key-id=$(gpg_key_ID)                                  \
-           --news=NEWS                                                 \
-           --bootstrap-tools=autoconf,automake,bison,gnulib            \
+           --news=$(srcdir)/NEWS                                       \
+           --bootstrap-tools=$(bootstrap-tools)                        \
            --gnulib-version=$(gnulib-version)                          \
            --no-print-checksums                                        \
            $(addprefix --url-dir=, $(url_dir_list))
@@ -786,16 +826,14 @@ announcement: NEWS ChangeLog $(rel-files)
 ftp-gnu = ftp://ftp.gnu.org/gnu
 www-gnu = http://www.gnu.org
 
-# Use mv, if you don't have/want move-if-change.
-move_if_change ?= move-if-change
-
+upload_dest_dir_ ?= $(PACKAGE)
 emit_upload_commands:
        @echo =====================================
        @echo =====================================
-       @echo "$(srcdir)/build-aux/gnupload $(GNUPLOADFLAGS) \\"
-       @echo "    --to $(gnu_rel_host):$(PACKAGE) \\"
+       @echo "$(build_aux)/gnupload $(GNUPLOADFLAGS) \\"
+       @echo "    --to $(gnu_rel_host):$(upload_dest_dir_) \\"
        @echo "  $(rel-files)"
-       @echo '# send the /tmp/announcement e-mail'
+       @echo '# send the ~/announce-$(my_distdir) e-mail'
        @echo =====================================
        @echo =====================================
 
@@ -807,26 +845,51 @@ define emit-commit-log
     '* cfg.mk (old_NEWS_hash): Auto-update.'
 endef
 
-.PHONY: alpha beta major
-ALL_RECURSIVE_TARGETS += alpha beta major
-alpha beta major: $(local-check) writable-files
-       test $@ = major                                         \
-         && { echo $(VERSION) | grep -E '^[0-9]+(\.[0-9]+)+$$' \
+.PHONY: no-submodule-changes
+no-submodule-changes:
+       if test -d $(srcdir)/.git; then                                 \
+         diff=$$(cd $(srcdir) && git submodule -q foreach              \
+                 git diff-index --name-only HEAD)                      \
+           || exit 1;                                                  \
+         case $$diff in '') ;;                                         \
+           *) echo '$(ME): submodule files are locally modified:';     \
+               echo "$$diff"; exit 1;; esac;                           \
+       else                                                            \
+         : ;                                                           \
+       fi
+
+.PHONY: alpha beta stable
+ALL_RECURSIVE_TARGETS += alpha beta stable
+alpha beta stable: $(local-check) writable-files no-submodule-changes
+       test $@ = stable                                                \
+         && { echo $(VERSION) | grep -E '^[0-9]+(\.[0-9]+)+$$'         \
               || { echo "invalid version string: $(VERSION)" 1>&2; exit 1;};}\
          || :
-       $(MAKE) vc-dist
-       $(MAKE) news-date-check
-       $(MAKE) -s announcement RELEASE_TYPE=$@ > /tmp/announce-$(my_distdir)
+       $(MAKE) vc-diff-check
+       $(MAKE) news-check
+       $(MAKE) distcheck
+       $(MAKE) dist XZ_OPT=-9ev
+       $(MAKE) $(release-prep-hook) RELEASE_TYPE=$@
+       $(MAKE) -s emit_upload_commands RELEASE_TYPE=$@
+
+# Override this in cfg.mk if you follow different procedures.
+release-prep-hook ?= release-prep
+
+.PHONY: release-prep
+release-prep:
+       case $$RELEASE_TYPE in alpha|beta|stable) ;; \
+         *) echo "invalid RELEASE_TYPE: $$RELEASE_TYPE" 1>&2; exit 1;; esac
+       $(MAKE) -s announcement > ~/announce-$(my_distdir)
        if test -d $(release_archive_dir); then                 \
          ln $(rel-files) $(release_archive_dir);               \
          chmod a-w $(rel-files);                               \
        fi
-       $(MAKE) -s emit_upload_commands RELEASE_TYPE=$@
        echo $(VERSION) > $(prev_version_file)
        $(MAKE) update-NEWS-hash
        perl -pi -e '$$. == 3 and print "$(noteworthy)\n\n\n"' NEWS
        $(emit-commit-log) > .ci-msg
        $(VC) commit -F .ci-msg -a
+       rm .ci-msg
 
 .PHONY: web-manual
 web-manual:
@@ -879,3 +942,20 @@ INDENT_SOURCES ?= $(C_SOURCES)
 .PHONY: indent
 indent:
        indent $(INDENT_SOURCES)
+
+# If you want to set UPDATE_COPYRIGHT_* environment variables,
+# put the assignments in this variable.
+update-copyright-env ?=
+
+# Run this rule once per year (usually early in January)
+# to update all FSF copyright year lists in your project.
+# If you have an additional project-specific rule,
+# add it in cfg.mk along with a line 'update-copyright: prereq'.
+# By default, exclude all variants of COPYING; you can also
+# add exemptions (such as ChangeLog..* for rotated change logs)
+# in the file .x-update-copyright.
+.PHONY: update-copyright
+update-copyright:
+       grep -l -w Copyright                                             \
+         $$(export VC_LIST_EXCEPT_DEFAULT=COPYING && $(VC_LIST_EXCEPT)) \
+         | $(update-copyright-env) xargs $(build_aux)/$@