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"