branch: master
commit b31819c7231def3f61d50c49bbe9da494460ec0c
Author: Bruno Haible <br...@clisp.org>
AuthorDate: Wed Feb 26 15:07:39 2025 +0100

    New program libtool-next-version.
    
    * libtool-next-version.in: New file, based on
    gnulib/build-aux/libtool-next-version.
    * doc/libtool.texi (Updating version info): Add sub-nodes
    'Manual version info update', 'Guided version info update',
    'Invoking libtool-next-version'. Note, line breaks use
    partial semantic newlines.
    * Makefile.am (BUILT_SOURCES): Add libtool-next-version.
    (libtoolnextv_in): New variable.
    (EXTRA_DIST): Add it.
    (bin_SCRIPTS): Add libtool-next-version.
    (libtool-next-version): New target.
    (libtoolnextv_1): New variable. New target.
    (dist_man1_MANS): Add it.
    * NEWS: Update.
---
 .gitignore              |   2 +-
 Makefile.am             |  21 +++-
 NEWS                    |   3 +
 doc/libtool.texi        | 110 ++++++++++++++++--
 libtool-next-version.in | 297 ++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 419 insertions(+), 14 deletions(-)

diff --git a/.gitignore b/.gitignore
index ab4fdcbe..fc825165 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,7 +43,7 @@
 /gnulib-local
 /gnulib-tests
 /libtoolize
-/libtoolize.in
+/libtool-next-version
 /maint.mk
 /patches
 /release
diff --git a/Makefile.am b/Makefile.am
index 39b35bb7..0f32a52c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -28,7 +28,7 @@ SUBDIRS                       = . gnulib-tests
 DIST_SUBDIRS           = $(SUBDIRS)
 EXTRA_DIST             =
 
-BUILT_SOURCES          = libtool libtoolize
+BUILT_SOURCES          = libtool libtoolize libtool-next-version
 
 CLEANFILES             =
 MOSTLYCLEANFILES       =
@@ -64,7 +64,7 @@ build_scripts = $(srcdir)/$(aux_dir)/announce-gen \
 
 EXTRA_DIST     += bootstrap bootstrap.conf $(build_scripts) cfg.mk maint.mk \
                  GNUmakefile
-CLEANFILES     += libtool libtoolize
+CLEANFILES     += libtool libtoolize libtool-next-version
 
 ## If a file is named several times below, and especially if it
 ## is a distributed file created during Libtool bootstrap, we
@@ -78,6 +78,7 @@ extract_trace = $(srcdir)/$(aux_dir)/extract-trace
 funclib_sh     = $(srcdir)/$(aux_dir)/funclib.sh
 inline_source  = $(srcdir)/$(aux_dir)/inline-source
 libtoolize_in  = $(srcdir)/libtoolize.in
+libtoolnextv_in        = $(srcdir)/libtool-next-version.in
 ltmain_sh      = $(srcdir)/$(aux_dir)/ltmain.sh
 ltmain_in      = $(srcdir)/$(aux_dir)/ltmain.in
 libtool_m4     = $(srcdir)/$(macro_dir)/libtool.m4
@@ -88,7 +89,8 @@ options_parser        = $(srcdir)/$(aux_dir)/options-parser
 u2d_copyright  = $(srcdir)/$(aux_dir)/update-copyright
 
 EXTRA_DIST     += $(extract_trace) $(funclib_sh) $(inline_source) \
-                 $(libtoolize_in) $(ltmain_in) $(ltmain_sh) \
+                 $(libtoolize_in) $(libtoolnextv_in) \
+                 $(ltmain_in) $(ltmain_sh) \
                  $(ltversion_in) $(ltversion_m4) $(no_bogus_macros) \
                  $(options_parser) $(u2d_copyright)
 
@@ -280,6 +282,8 @@ configure_edit = $(bootstrap_edit) \
 
 # The libtool distributor and the standalone libtool script.
 bin_SCRIPTS = libtool
+# The "Update version info" wizard.
+bin_SCRIPTS += libtool-next-version
 
 libtoolize: $(libtoolize_in) $(config_status)
        $(AM_V_at)rm -f '$@'
@@ -287,6 +291,12 @@ libtoolize: $(libtoolize_in) $(config_status)
        $(AM_V_at)chmod a+x '$@'
        $(AM_V_at)chmod a-w '$@'
 
+libtool-next-version: $(libtoolnextv_in) $(config_status)
+       $(AM_V_at)rm -f '$@'
+       $(AM_V_GEN)$(bootstrap_edit) '$(libtoolnextv_in)' > '$@'
+       $(AM_V_at)chmod a+x '$@'
+       $(AM_V_at)chmod a-w '$@'
+
 # We used to do this with a 'stamp-vcl' file, but non-gmake builds
 # would rerun configure on every invocation, so now we manually
 # check the version numbers from the build rule when necessary.
@@ -383,6 +393,7 @@ doc_dir             = $(srcdir)/doc
 
 libtool_1      = $(doc_dir)/libtool.1
 libtoolize_1   = $(doc_dir)/libtoolize.1
+libtoolnextv_1 = $(doc_dir)/libtool-next-version.1
 notes_texi     = $(doc_dir)/notes.texi
 notes_txt      = $(doc_dir)/notes.txt
 
@@ -408,7 +419,7 @@ $(notes_txt): $(notes_texi)
        $(AM_V_GEN)$(MAKEINFO) -P '$(srcdir)/doc' --no-headers \
            $(MAKEINFOFLAGS) -o '$@' '$(notes_texi)'
 
-dist_man1_MANS         = $(libtool_1) $(libtoolize_1)
+dist_man1_MANS         = $(libtool_1) $(libtoolize_1) $(libtoolnextv_1)
 MAINTAINERCLEANFILES   += $(dist_man1_MANS)
 update_mans = \
   PATH=".$(PATH_SEPARATOR)$$PATH"; export PATH; \
@@ -422,6 +433,8 @@ $(libtool_1): $(ltmain_sh)
        $(AM_V_GEN)$(update_mans) -n 'Provide generalized library-building 
support services' --help-option=--help-all libtool
 $(libtoolize_1): $(libtoolize_in)
        $(AM_V_GEN)$(update_mans) -n 'Prepare a package to use libtool' 
libtoolize
+$(libtoolnextv_1): $(libtoolnextv_in)
+       $(AM_V_GEN)$(update_mans) -n 'Determines next version to use for a 
libtool library' libtool-next-version
 
 
 
diff --git a/NEWS b/NEWS
index ed6fb977..0a613563 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,9 @@ NEWS - list of user-visible changes between releases of GNU 
Libtool
 
 ** New features:
 
+  - Add a new tool, libtool-next-version, to guide users through updating
+    library versions.
+
   - Add tagging for Objective-C and Objective-C++, OBJC and OBJCXX.
 
   - Increase 5 digit limit on revision value for libraries to 19 digits,
diff --git a/doc/libtool.texi b/doc/libtool.texi
index 21bb8908..710f238f 100644
--- a/doc/libtool.texi
+++ b/doc/libtool.texi
@@ -40,6 +40,7 @@ the section entitled ``GNU Free Documentation License''.
 @direntry
 * libtool-invocation: (libtool)Invoking libtool. Running the @code{libtool} 
script.
 * libtoolize: (libtool)Invoking libtoolize.      Adding libtool support.
+* libtool-next-version: (libtool)Invoking libtool-next-version. Running the 
@code{libtool-next-version} wizard.
 @end direntry
 
 @titlepage
@@ -3158,7 +3159,7 @@ interface number.
 Here are a set of rules to help you update your library version
 information:
 
-@enumerate 1
+@itemize @bullet
 @item
 Start with version information of @samp{0:0:0} for each libtool library.
 
@@ -3167,6 +3168,31 @@ Update the version information only immediately before a 
public release
 of your software.  More frequent updates are unnecessary, and only
 guarantee that the current interface number gets larger faster.
 
+@item
+Do the update either manually, or guided by the @samp{libtool-next-version}
+wizard.
+@end itemize
+
+@strong{@emph{Never}} try to set the interface numbers so that they
+correspond to the release number of your package.  This is an abuse that
+only fosters misunderstanding of the purpose of library versions.
+Instead, use the @option{-release} flag (@pxref{Release numbers}), but be
+warned that every release of your package will not be binary compatible
+with any other release.
+
+@menu
+* Manual version info update::    Updating version info manually.
+* Guided version info update::    Using the libtool-next-version program.
+* Invoking libtool-next-version:: @code{libtool-next-version} command line 
options.
+@end menu
+
+@node Manual version info update
+@subsection Updating the version info manually
+
+Here are the steps that you need to do, to update your library version
+information:
+
+@enumerate 1
 @item
 If the library source code has changed at all since the last update,
 then increment @var{revision} (@samp{@var{c}:@var{r}:@var{a}} becomes
@@ -3185,14 +3211,7 @@ If any interfaces have been removed or changed since the 
last public
 release, then set @var{age} to 0.
 @end enumerate
 
-@strong{@emph{Never}} try to set the interface numbers so that they
-correspond to the release number of your package.  This is an abuse that
-only fosters misunderstanding of the purpose of library versions.
-Instead, use the @option{-release} flag (@pxref{Release numbers}), but be
-warned that every release of your package will not be binary compatible
-with any other release.
-
-The following explanation may help to understand the above rules a bit
+The following explanation may help to understand the steps above a bit
 better: consider that there are three possible kinds of reactions from
 users of your library to changes in a shared library:
 
@@ -3222,6 +3241,79 @@ to 0.
 In the above description, @emph{programs} using the library in question
 may also be replaced by other libraries using it.
 
+@node Guided version info update
+@subsection Updating the version info, guided by the libtool-next-version 
program
+
+The @samp{libtool-next-version} program is a wizard-like interactive tool,
+that is designed to avoid mistakes in the process of the manual update:
+
+@itemize @bullet
+@item
+It asks you questions, one by one, so that you can focus on one thing at a 
time.
+@item
+It prepares default answers, based on the set of symbols exported by library.
+@item
+It gives a couple of additional explanations and hints.
+@end itemize
+
+Before invoking the wizard,
+you need to build and install the previous release and the current release
+candidate of your package into different directories.
+For example, assume the last release was @code{libfoo-1.4.tar.gz},
+and before preparing @code{libfoo-1.5.tar.gz},
+your current release candidate is @code{libfoo-1.4.99.tar.gz}.
+You build them like this:
+@example
+$ rm -rf /tmp/prev /tmp/curr
+$ tar xfz libfoo-1.4.tar.gz
+$ (cd libfoo-1.4
+   && ./configure --prefix=/tmp/prev
+   && make
+   && make install)
+$ tar xfz libfoo-1.4.99.tar.gz
+$ (cd libfoo-1.4.99
+   && ./configure --prefix=/tmp/curr
+   && make
+   && make install)
+@end example
+
+Then you are ready to invoke the wizard like this:
+@example
+$ libtool-next-version /tmp/prev/lib/libfoo.so /tmp/curr/lib/libfoo.so
+@end example
+
+@node Invoking libtool-next-version
+@subsection Invoking @command{libtool-next-version}
+@pindex libtool-next-version
+@cindex libtool-next-version command options
+@cindex command options, libtool-next-version
+@cindex options, libtool-next-version command
+
+The @code{libtool-next-version} program determines the next version to use
+for a libtool library.
+
+It is invoked as follows:
+@example
+libtool-next-version [@var{option}]... @var{previous-library} 
@var{current-library}
+@end example
+
+@var{previous-library} is the installed library (in @code{.a} or @code{.so}
+format) of the previous release.
+
+@var{current-library} is the installed library (in @code{.a} or @code{.so}
+format) of the current release candidate.
+
+It accepts the following options:
+
+@table @option
+
+@item --help
+Print a help message and exit.
+
+@item --version
+Print version information and exit.
+
+@end table
 
 @node Release numbers
 @section Managing release information
diff --git a/libtool-next-version.in b/libtool-next-version.in
new file mode 100644
index 00000000..36b02fb9
--- /dev/null
+++ b/libtool-next-version.in
@@ -0,0 +1,297 @@
+#! /bin/sh
+#
+# Copyright (C) 2019-2025 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/>.
+#
+
+# This program is a wizard that helps a maintainer update the libtool
+# version of a shared library, according to the documentation section
+# 'Updating version info'
+# 
<https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html>.
+#
+# Let's call the three parts of the version
+#   LTV_CURRENT
+#   LTV_REVISION
+#   LTV_AGE
+#
+# The list of steps given in this documentation section
+#   - If the library source code has changed at all since the last update,
+#     then increment LTV_REVISION.
+#   - If any interfaces have been added, removed, or changed since the last
+#     update, increment LTV_CURRENT and set LTV_REVISION to 0.
+#   - If any interfaces have been added since the last public release, then
+#     increment LTV_AGE.
+#   - If any interfaces have been removed or changed since the last public
+#     release, then set LTV_AGE to 0.
+# leads to mistakes, because
+#   - It does not say what "interfaces" are.
+#   - It does not enforce that applying the second, third, or fourth rule
+#     is only possible after applying the first rule.
+#   - It does not enforce that applying the third or fourth rule is only
+#     possible after applying the second rule.
+
+# func_usage
+# outputs to stdout the --help usage message.
+func_usage ()
+{
+  echo "\
+Usage: libtool-next-version [OPTION]... PREVIOUS-LIBRARY CURRENT-LIBRARY
+
+Determines the next version to use for a libtool library.
+
+PREVIOUS-LIBRARY is the installed library (in .a or .so format) of the
+previous release.
+
+CURRENT-LIBRARY is the installed library (in .a or .so format) of the current
+release candidate.
+
+Options:
+      --help           print this help and exit
+      --version        print version information and exit
+
+Send patches and bug reports to <@PACKAGE_BUGREPORT@>."
+}
+
+# func_version
+# outputs to stdout the --version message.
+func_version ()
+{
+  sed_extract_copyright_year='/Copyright (C)/{
+s/.*\([0-9][0-9][0-9][0-9]\).*/\1/p
+q
+}'
+  copyright_year=`sed -n -e "$sed_extract_copyright_year" < "$0"`
+  echo "libtool-next-version (GNU @PACKAGE@) @VERSION@"
+  echo "Copyright (C) $copyright_year Free Software Foundation, Inc.
+License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law."
+  echo
+  printf 'Written by %s.\n' "Bruno Haible"
+}
+
+# func_fatal_error message
+# outputs to stderr a fatal error message, and terminates the program.
+func_fatal_error ()
+{
+  echo "libtool-next-version: *** $1" 1>&2
+  echo "libtool-next-version: *** Stop." 1>&2
+  exit 1
+}
+
+# func_tmpdir
+# creates a temporary directory.
+# Sets variable
+# - tmp             pathname of freshly created temporary directory
+func_tmpdir ()
+{
+  # Use the environment variable TMPDIR, falling back to /tmp. This allows
+  # users to specify a different temporary directory, for example, if their
+  # /tmp is filled up or too small.
+  : "${TMPDIR=/tmp}"
+  {
+    # Use the mktemp program if available. If not available, hide the error
+    # message.
+    tmp=`(umask 077 && mktemp -d -q "$TMPDIR/gtXXXXXX") 2>/dev/null` &&
+    test -n "$tmp" && test -d "$tmp"
+  } ||
+  {
+    # Use a simple mkdir command. It is guaranteed to fail if the directory
+    # already exists.  $RANDOM is bash specific and expands to empty in shells
+    # other than bash, ksh and zsh.  Its use does not increase security;
+    # rather, it minimizes the probability of failure in a very cluttered /tmp
+    # directory.
+    tmp=$TMPDIR/gt$$-$RANDOM
+    (umask 077 && mkdir "$tmp")
+  } ||
+  {
+    echo "$0: cannot create a temporary directory in $TMPDIR" >&2
+    { (exit 1); exit 1; }
+  }
+}
+
+# func_read_yesno
+# reads an answer (yes or no).
+# Sets variable
+# - ans             yes or no
+func_read_yesno ()
+{
+  while true; do
+    read ans
+    if test yes = "$ans" || test no = "$ans"; then
+      break
+    fi
+    echo "Invalid answer. Please answer yes or no."
+  done
+}
+
+# Command-line option processing.
+while test $# -gt 0; do
+  case $1 in
+    --help | --hel | --he | --h )
+      func_usage
+      exit 0 ;;
+   --version | --versio | --versi | --vers | --ver | --ve | --v )
+      func_version
+      exit 0 ;;
+    -- )      # Stop option processing
+      shift; break ;;
+    -* )
+      func_fatal_error "unrecognized option: $option"
+      ;;
+    * )
+      break ;;
+  esac
+done
+
+test $# = 2 || {
+  if test $# -gt 2; then
+    func_fatal_error "too many arguments"
+  else
+    func_fatal_error "Usage: libtool-next-version [OPTION]... PREVIOUS-LIBRARY 
CURRENT-LIBRARY"
+  fi
+}
+
+test -f "$1" || func_fatal_error "file $1 not found"
+test -f "$2" || func_fatal_error "file $2 not found"
+
+(type nm) >/dev/null || func_fatal_error "program 'nm' not found"
+# Determine how to extract a symbol list from the 'nm' output.
+case `uname -s` in
+  Linux | FreeBSD | NetBSD | OpenBSD) nm_filter="sed -n -e 's/^.* [TWDRB] 
//p'" ;;
+  Darwin) nm_filter="sed -n -e 's/^.* [TWDRB] _//p'" ;;
+  Minix) nm_filter="sed -n -e 's/^.* [TDC] _//p'" ;;
+  AIX) nm_filter="sed -n -e 's/  *[UD] .*//p' | sed -e 's/^\\.//'" ;;
+  HP-UX) nm_filter="grep '|extern|\\(code\\|data\\)   |' | sed -e 's/|.*//' | 
sed -e 's/ *$//'" ;;
+  IRIX) nm_filter="grep '|\\(GLOB\\|WEAK\\)' | sed -e 's/^.*|//'" ;;
+  SunOS)
+    case `uname -r` in
+      5.10) nm_filter="sed -n -e 's/^.* [ATWDRBV] //p'" ;;
+      *) nm_filter="grep '|\\(GLOB\\|WEAK\\)' | grep -v '|UNDEF' | grep -v 
'|SUNW' | sed -e 's/^.*|//'" ;;
+    esac
+    ;;
+  CYGWIN*) nm_filter="sed -n -e 's/^.* T _//p'" ;;
+  *) func_fatal_error "unknown OS - don't know how to interpret the 'nm' 
output" ;;
+esac
+nm_filter="$nm_filter | LC_ALL=C sort -u"
+
+func_tmpdir
+eval "nm '$1' | $nm_filter > '$tmp/symlist1'"
+eval "nm '$2' | $nm_filter > '$tmp/symlist2'"
+
+echo "Please enter the libtool version of the library in the previous release."
+
+printf "LTV_CURRENT="; read current
+nondigits=`echo "$current" | tr -d '0123456789'`
+{ test -n "$current" && test -z "$nondigits"; } \
+  || func_fatal_error "LTV_CURRENT is invalid. It should be a nonnegative 
integer."
+
+printf "LTV_REVISION="; read revision
+nondigits=`echo "$revision" | tr -d '0123456789'`
+{ test -n "$revision" && test -z "$nondigits"; } \
+  || func_fatal_error "LTV_REVISION is invalid. It should be a nonnegative 
integer."
+
+printf "LTV_AGE="; read age
+nondigits=`echo "$age" | tr -d '0123456789'`
+{ test -n "$age" && test -z "$nondigits"; } \
+  || func_fatal_error "LTV_AGE is invalid. It should be a nonnegative integer."
+
+echo
+echo 
"-------------------------------------------------------------------------------"
+echo "Did the library's code change at all since the previous version?"
+echo "You can usually detect this by looking at the source code changes in 
git;"
+echo "don't forget source code that is imported from other projects."
+if cmp "$tmp/symlist1" "$tmp/symlist2" >/dev/null; then
+  echo "Please answer yes or no."
+else
+  echo "The symbol list changed. Here are the differences:"
+  (cd "$tmp" && diff symlist1 symlist2 | grep '^[<>]' | sed -e 's/^/  /')
+  echo "Please answer yes or no (probably yes)."
+fi
+func_read_yesno
+if test yes = "$ans"; then
+
+  revision=`expr $revision + 1`
+
+  echo
+  echo 
"-------------------------------------------------------------------------------"
+  echo "Have any interfaces (functions, variables, classes) been removed since 
the"
+  echo "previous release? What matters here are interfaces at the linker 
level;"
+  echo "whether macros have been removed from the include files does not 
matter."
+  if diff "$tmp/symlist1" "$tmp/symlist2" | grep '^< ' >/dev/null; then
+    echo "Some symbols have been removed:"
+    diff "$tmp/symlist1" "$tmp/symlist2" | grep '^< ' | sed -e 's/^< /  /'
+    echo "Please answer yes or no (probably yes)."
+  else
+    echo "Please answer yes or no."
+  fi
+  func_read_yesno
+
+  if test yes = "$ans"; then
+
+    current=`expr $current + 1`
+    revision=0
+    age=0
+
+  else
+
+    echo
+    echo 
"-------------------------------------------------------------------------------"
+    echo "Have any interfaces (functions, variables, classes) been changed 
since the"
+    echo "previous release? This includes signature changes. It includes also 
details of"
+    echo "how functions produce their results and the values of variables, IF 
AND ONLY IF"
+    echo "users of the library are likely use these details in their test 
suite."
+    echo "Please answer yes or no."
+    func_read_yesno
+
+    if test yes = "$ans"; then
+
+      current=`expr $current + 1`
+      revision=0
+      age=0
+
+    else
+
+      echo
+      echo 
"-------------------------------------------------------------------------------"
+      echo "Have any interfaces (functions, variables, classes) been added 
since the"
+      echo "previous release? What matters here are interfaces at the linker 
level;"
+      echo "whether macros have been added to the include files does not 
matter."
+      if diff "$tmp/symlist1" "$tmp/symlist2" | grep '^> ' >/dev/null; then
+        echo "Some symbols have been added:"
+        diff "$tmp/symlist1" "$tmp/symlist2" | grep '^> ' | sed -e 's/^> /  /'
+        echo "Please answer yes or no (probably yes)."
+      else
+        echo "Please answer yes or no."
+      fi
+      func_read_yesno
+
+      if test yes = "$ans"; then
+
+        current=`expr $current + 1`
+        revision=0
+        age=`expr $age + 1`
+
+      fi
+    fi
+  fi
+fi
+
+echo
+echo 
"-------------------------------------------------------------------------------"
+echo "This is the libtool version of the library for the new release:"
+echo "LTV_CURRENT=$current"
+echo "LTV_REVISION=$revision"
+echo "LTV_AGE=$age"

Reply via email to