DIST_SUBDIRS = $(SUBDIRS)
EXTRA_DIST =
-BUILT_SOURCES = libtool libtoolize
+BUILT_SOURCES = libtool libtoolize libtool-next-version
CLEANFILES =
MOSTLYCLEANFILES =
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
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
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)
# 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 '$@'
$(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.
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
$(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; \
$(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
@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
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.
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
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:
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
--- /dev/null
+#! /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"