announce-gen: add comments
[gnulib.git] / gnulib-tool
blob3dad968bc97681ab91c5d4069d7f26ae4989fece
1 #! /bin/sh
3 # Copyright (C) 2002-2025 Free Software Foundation, Inc.
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <https://www.gnu.org/licenses/>.
19 # This program dispatches among
20 # - the shell implementation and
21 # - the Python implementation
22 # of gnulib-tool, according to the environment variable GNULIB_TOOL_IMPL.
24 # The environment variable GNULIB_TOOL_IMPL can have four possible values:
25 # - GNULIB_TOOL_IMPL=sh chooses the shell implementation.
26 # - GNULIB_TOOL_IMPL=py chooses the Python implementation.
27 # - GNULIB_TOOL_IMPL= chooses the default (the Python implementation
28 # if a suitable Python version is found, the shell
29 # implementation otherwise).
30 # - GNULIB_TOOL_IMPL=sh+py runs both implementations and compares the
31 # results.
33 progname=$0
35 # func_exit STATUS
36 # exits with a given status.
37 # This function needs to be used, rather than 'exit', when a 'trap' handler is
38 # in effect that refers to $?.
39 func_exit ()
41 (exit $1); exit $1
44 # func_fatal_error message
45 # outputs to stderr a fatal error message, and terminates the program.
46 # Input:
47 # - progname name of this program
48 func_fatal_error ()
50 echo "$progname: *** $1" 1>&2
51 echo "$progname: *** Stop." 1>&2
52 func_exit 1
55 # func_readlink SYMLINK
56 # outputs the target of the given symlink.
57 if (type readlink) > /dev/null 2>&1; then
58 func_readlink ()
60 # Use the readlink program from GNU coreutils.
61 readlink "$1"
63 else
64 func_readlink ()
66 # Use two sed invocations. A single sed -n -e 's,^.* -> \(.*\)$,\1,p'
67 # would do the wrong thing if the link target contains " -> ".
68 LC_ALL=C ls -l "$1" | sed -e 's, -> ,#%%#,' | sed -n -e 's,^.*#%%#\(.*\)$,\1,p'
72 # func_gnulib_dir
73 # locates the directory where the gnulib repository lives
74 # Input:
75 # - progname name of this program
76 # Sets variables
77 # - self_abspathname absolute pathname of gnulib-tool
78 # - gnulib_dir absolute pathname of gnulib repository
79 func_gnulib_dir ()
81 case "$progname" in
82 /* | ?:*) self_abspathname="$progname" ;;
83 */*) self_abspathname=`pwd`/"$progname" ;;
85 # Look in $PATH.
86 # Iterate through the elements of $PATH.
87 # We use IFS=: instead of
88 # for d in `echo ":$PATH:" | sed -e 's/:::*/:.:/g' | sed -e 's/:/ /g'`
89 # because the latter does not work when some PATH element contains spaces.
90 # We use a canonicalized $pathx instead of $PATH, because empty PATH
91 # elements are by definition equivalent to '.', however field splitting
92 # according to IFS=: loses empty fields in many shells:
93 # - /bin/sh on OSF/1 and Solaris loses all empty fields (at the
94 # beginning, at the end, and in the middle),
95 # - /bin/sh on IRIX and /bin/ksh on IRIX and OSF/1 lose empty fields
96 # at the beginning and at the end,
97 # - GNU bash, /bin/sh on AIX and HP-UX, and /bin/ksh on AIX, HP-UX,
98 # Solaris lose empty fields at the end.
99 # The 'case' statement is an optimization, to avoid evaluating the
100 # explicit canonicalization command when $PATH contains no empty fields.
101 self_abspathname=
102 if test "$PATH_SEPARATOR" = ";"; then
103 # On Windows, programs are searched in "." before $PATH.
104 pathx=".;$PATH"
105 else
106 # On Unix, we have to convert empty PATH elements to ".".
107 pathx="$PATH"
108 case :$PATH: in
109 *::*)
110 pathx=`echo ":$PATH:" | sed -e 's/:::*/:.:/g' -e 's/^://' -e 's/:\$//'`
112 esac
114 saved_IFS="$IFS"
115 IFS="$PATH_SEPARATOR"
116 for d in $pathx; do
117 IFS="$saved_IFS"
118 test -z "$d" && d=.
119 if test -x "$d/$progname" && test ! -d "$d/$progname"; then
120 self_abspathname="$d/$progname"
121 break
123 done
124 IFS="$saved_IFS"
125 if test -z "$self_abspathname"; then
126 func_fatal_error "could not locate the gnulib-tool program - how did you invoke it?"
129 esac
130 while test -h "$self_abspathname"; do
131 # Resolve symbolic link.
132 linkval=`func_readlink "$self_abspathname"`
133 test -n "$linkval" || break
134 case "$linkval" in
135 /* | ?:* ) self_abspathname="$linkval" ;;
136 * ) self_abspathname=`echo "$self_abspathname" | sed -e 's,/[^/]*$,,'`/"$linkval" ;;
137 esac
138 done
139 gnulib_dir=`echo "$self_abspathname" | sed -e 's,/[^/]*$,,'`
142 # If $progname contains '/' and is not a symlink, it suffices for $prog to be
143 # the same as $progname with except with basename 'gnulib-tool’; this
144 # speeds startup and might avoid problems in oddball development environments.
145 # Otherwise, $prog is the absolute name of the gnulib-tool executable.
146 if case $progname in
147 */*) test -h "$0" ;;
148 esac
149 then
150 func_gnulib_dir
151 prog=$gnulib_dir/gnulib-tool
152 else
153 prog=${progname%/*}/gnulib-tool
156 case "$GNULIB_TOOL_IMPL" in
158 # Use the Python implementation if a suitable Python version is found
159 # in $PATH. This is the same Python version test as in gnulib-tool.py.
160 if (python3 -c 'import sys; sys.exit(not sys.version_info >= (3,7))') 2>/dev/null; then
161 exec "$prog.py" "$@"
162 else
163 echo "gnulib-tool: warning: python3 not found or too old, using the slow shell-based implementation" 1>&2
164 exec "$prog.sh" "$@"
168 exec "$prog.sh" "$@" ;;
170 exec "$prog.py" "$@" ;;
171 sh+py)
172 case " $* " in
173 *" --import"* | *" --add-import"* | *" --remove-import"* | *" --update"* | *" --copy-file"*)
174 # A gnulib-tool invocation that produces files in the current directory.
175 # Create a temporary directory in the parent directory.
176 tmpdir=`cd .. && pwd`
178 # Use the mktemp program if available. If not available, hide the error
179 # message.
180 tmp=`(umask 077 && mktemp -d "$tmpdir/glpyXXXXXX") 2>/dev/null` &&
181 test -n "$tmp" && test -d "$tmp"
182 } ||
184 # Use a simple mkdir command. It is guaranteed to fail if the directory
185 # already exists.
186 tmp=$tmpdir/glpy$$
187 (umask 077 && mkdir "$tmp")
188 } ||
190 echo "$progname: cannot create a temporary directory in $tmpdir" >&2
191 func_exit 1
193 # Copy the current directory into the the temporary directory.
194 { tar cf - . | (cd "$tmp" && tar xf -); } ||
196 echo "$progname: failed to clone the current directory" >&2
197 func_exit 1
199 # Execute gnulib-tool.py in the clone directory.
200 case $prog in
201 /*) absprog=$prog ;;
202 *) absprog=$PWD/prog ;;
203 esac
204 (cd "$tmp" && "$absprog.py" "$@" >"$tmp-py-out" 2>"$tmp-py-err")
205 pyrc=$?
206 # Execute gnulib-tool.sh in the current directory.
207 "$prog.sh" "$@" >"$tmp-sh-out" 2>"$tmp-sh-err"
208 shrc=$?
209 if test $shrc != 0; then
210 if test $pyrc = 0; then
211 func_fatal_error "gnulib-tool.sh failed but gnulib-tool.py succeeded! Inspect $tmp-sh-err and $tmp-py-err."
212 else
213 cat "$tmp-sh-out"
214 cat "$tmp-sh-err" >&2
215 rm -rf "$tmp" "$tmp-sh-out" "$tmp-sh-err" "$tmp-py-out" "$tmp-py-err"
216 exit $shrc
219 if test $pyrc != 0; then
220 func_fatal_error "gnulib-tool.sh succeeded but gnulib-tool.py failed! Inspect $tmp/ and $tmp-py-err."
222 # Compare the two results on the file system.
223 # GNU diffutils 3.3 or newer support option --no-dereference. This
224 # option avoids errors on dangling links.
225 if LC_ALL=C diff --help 2>/dev/null | grep no-dereference >/dev/null; then
226 diff_options='--no-dereference'
227 else
228 diff_options=
230 diff -r $diff_options --exclude=__pycache__ --exclude=autom4te.cache -q . "$tmp" >/dev/null ||
231 func_fatal_error "gnulib-tool.py produced different files than gnulib-tool.sh! Compare `pwd` and $tmp."
232 # Compare the two outputs.
233 diff -q "$tmp-sh-out" "$tmp-py-out" >/dev/null ||
234 func_fatal_error "gnulib-tool.py produced different output than gnulib-tool.sh! Compare $tmp-sh-out and $tmp-py-out."
235 # Same results.
236 cat "$tmp-sh-out"
237 cat "$tmp-sh-err" >&2
238 rm -rf "$tmp" "$tmp-sh-out" "$tmp-sh-err" "$tmp-py-out" "$tmp-py-err"
239 exit 0
241 *" --create-testdir"* | *" --create-megatestdir"*)
242 # A gnulib-tool invocation that produces a new directory with files.
243 # Extract the --dir value.
244 dir=`echo " $* " | sed -n -e 's/^.* --dir=//p' | sed -e 's/ .*//'`
245 if test -z "$dir"; then
246 dir=`echo " $* " | sed -n -e 's/^.* --dir *//p' | sed -e 's/ .*//'`
247 if test -z "$dir"; then
248 func_fatal_error "could not extract --dir value"
251 # Find another directory name.
252 tmp="$dir-glpy$$"
253 # Execute gnulib-tool.py, creating a different directory.
254 "$prog.py" "$@" --dir="$tmp" >"$tmp-py-out" 2>"$tmp-py-err"
255 pyrc=$?
256 # Execute gnulib-tool.sh, creating the intended directory.
257 "$prog.sh" "$@" >"$tmp-sh-out" 2>"$tmp-sh-err"
258 shrc=$?
259 if test $shrc != 0; then
260 if test $pyrc = 0; then
261 func_fatal_error "gnulib-tool.sh failed but gnulib-tool.py succeeded! Inspect $tmp-sh-err and $tmp-py-err."
262 else
263 cat "$tmp-sh-out"
264 cat "$tmp-sh-err" >&2
265 rm -rf "$tmp" "$tmp-sh-out" "$tmp-sh-err" "$tmp-py-out" "$tmp-py-err"
266 exit $shrc
269 if test $pyrc != 0; then
270 func_fatal_error "gnulib-tool.sh succeeded but gnulib-tool.py failed! Inspect $tmp/ and $tmp-py-err."
272 # Compare the two results on the file system.
273 # GNU diffutils 3.3 or newer support option --no-dereference. This
274 # option avoids errors on dangling links.
275 if LC_ALL=C diff --help 2>/dev/null | grep no-dereference >/dev/null; then
276 diff_options='--no-dereference'
277 else
278 diff_options=
280 diff -r $diff_options -q "$dir" "$tmp" >/dev/null ||
281 func_fatal_error "gnulib-tool.py produced different files than gnulib-tool.sh! Compare $dir and $tmp."
282 # Compare the two outputs.
283 diff -q "$tmp-sh-out" "$tmp-py-out" >/dev/null ||
284 func_fatal_error "gnulib-tool.py produced different output than gnulib-tool.sh! Compare $tmp-sh-out and $tmp-py-out."
285 # Same results.
286 cat "$tmp-sh-out"
287 cat "$tmp-sh-err" >&2
288 rm -rf "$tmp" "$tmp-sh-out" "$tmp-sh-err" "$tmp-py-out" "$tmp-py-err"
289 exit 0
292 # A gnulib-tool invocation that produces only output, no files.
293 tmp="glpy$$"
294 # Execute gnulib-tool.py.
295 "$prog.py" "$@" >"$tmp-py-out" 2>"$tmp-py-err"
296 pyrc=$?
297 # Execute gnulib-tool.sh.
298 "$prog.sh" "$@" >"$tmp-sh-out" 2>"$tmp-sh-err"
299 shrc=$?
300 if test $shrc != 0; then
301 if test $pyrc = 0; then
302 func_fatal_error "gnulib-tool.sh failed but gnulib-tool.py succeeded! Inspect $tmp-sh-err and $tmp-py-err."
303 else
304 cat "$tmp-sh-out"
305 cat "$tmp-sh-err" >&2
306 rm -rf "$tmp-sh-out" "$tmp-sh-err" "$tmp-py-out" "$tmp-py-err"
307 exit $shrc
310 if test $pyrc != 0; then
311 func_fatal_error "gnulib-tool.sh succeeded but gnulib-tool.py failed! Inspect $tmp-py-err."
313 # Compare the two outputs.
314 diff -q "$tmp-sh-out" "$tmp-py-out" >/dev/null ||
315 func_fatal_error "gnulib-tool.py produced different output than gnulib-tool.sh! Compare $tmp-sh-out and $tmp-py-out."
316 # Same results.
317 cat "$tmp-sh-out"
318 cat "$tmp-sh-err" >&2
319 rm -rf "$tmp-sh-out" "$tmp-sh-err" "$tmp-py-out" "$tmp-py-err"
320 exit 0
322 esac
325 func_fatal_error "invalid value of GNULIB_TOOL_IMPL: $GNULIB_TOOL_IMPL" ;;
326 esac