1#! /bin/sh
2
3# Extract macro arguments from autotools input with GNU M4.
4# Written by Gary V. Vaughan, 2010
5#
6# Copyright (C) 2010-2015 Free Software Foundation, Inc.
7# This is free software; see the source for copying conditions.  There is NO
8# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
10# Make sure we've evaluated scripts we depend on.
11test -z "$progpath" && . `echo "$0" |${SED-sed} 's|[^/]*$||'`/funclib.sh
12test extract-trace = "$progname" && . `echo "$0" |${SED-sed} 's|[^/]*$||'`/options-parser
13
14# Set a version string.
15scriptversion=2015-01-20.17; # UTC
16
17# This program is free software: you can redistribute it and/or modify
18# it under the terms of the GNU General Public License as published by
19# the Free Software Foundation, either version 3 of the License, or
20# (at your option) any later version.
21
22# This program is distributed in the hope that it will be useful,
23# but WITHOUT ANY WARRANTY; without even the implied warranty of
24# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25# GNU General Public License for more details.
26
27# You should have received a copy of the GNU General Public License
28# along with this program.  If not, see <http://www.gnu.org/licenses/>.
29
30# Please report bugs or propose patches to gary@gnu.org.
31
32
33## ------ ##
34## Usage. ##
35## ------ ##
36
37# Run './extract-trace --help' for help with using this script from the
38# command line.
39#
40# Or source first 'options-parser' and then this file into your own
41# scripts in order to make use of the function and variable framework
42# they define, and also to avoid the overhead of forking to run this
43# script in its own process on every call.
44
45
46
47## ----------------- ##
48## Helper functions. ##
49## ----------------- ##
50
51# This section contains the helper functions used by the rest of
52# 'extract-trace'.
53
54
55# func_autoconf_configure MAYBE-CONFIGURE-FILE
56# --------------------------------------------
57# Ensure that MAYBE-CONFIGURE-FILE is the name of a file in the current
58# directory that contains an uncommented call to AC_INIT.
59func_autoconf_configure ()
60{
61    $debug_cmd
62
63    _G_sed_no_comment='
64      s|#.*$||
65      s|^dnl .*$||
66      s| dnl .*$||'
67    _G_ac_init=
68
69    # If we were passed a genuine file, make sure it calls AC_INIT.
70    test -f "$1" \
71      && _G_ac_init=`$SED "$_G_sed_no_comment" "$1" |$GREP AC_INIT`
72
73    # Otherwise it is not a genuine Autoconf input file.
74    test -n "$_G_ac_init"
75    _G_status=$?
76
77    test 0 -ne "$_G_status" \
78      && func_verbose "'$1' not using Autoconf"
79
80    (exit $_G_status)
81}
82
83
84# func_tool_version_output CMD [FATAL-ERROR-MSG]
85# ----------------------------------------------
86# Attempt to run 'CMD --version', discarding errors.  The output can be
87# ignored by redirecting stdout, and this function used simply to test
88# whether the command exists and exits normally when passed a
89# '--version' argument.
90# When FATAL-ERROR-MSG is given, then this function will display the
91# message and exit if running 'CMD --version' returns a non-zero exit
92# status.
93func_tool_version_output ()
94{
95    $debug_cmd
96
97    _G_cmd=$1
98    _G_fatal_error_msg=$2
99
100    # Some tools, like 'git2cl' produce thousands of lines of output
101    # unless stdin is /dev/null - in that case we want to return
102    # successfully without saving all of that output.  Other tools,
103    # such as 'help2man' exit with a non-zero status when stdin comes
104    # from /dev/null, so we re-execute without /dev/null if that
105    # happens.  This means that occasionally, the output from both calls
106    # ends up in the result, but the alternative would be to discard the
107    # output from one call, and hope the other produces something useful.
108    { $_G_cmd --version </dev/null || $_G_cmd --version; } 2>/dev/null
109    _G_status=$?
110
111    test 0 -ne "$_G_status" && test -n "$_G_fatal_error_msg" \
112        && func_fatal_error "$_G_fatal_error_msg"
113
114    (exit $_G_status)
115}
116
117
118# func_tool_version_number CMD [FATAL-ERROR-MSG]
119# ----------------------------------------------
120# Pass arguments to func_tool_version_output, but set
121# $func_tool_version_number_result to the last dot delimited digit string
122# on the first line of output.
123func_tool_version_number ()
124{
125    $debug_cmd
126
127    _G_verout=`func_tool_version_output "$@"`
128    _G_status=$?
129
130    # A version number starts with a digit following a space on the first
131    # line of output from `--version`.
132    _G_verout=`echo "$_G_verout" |sed 1q`
133    if test -n "$_G_verout"; then
134      _G_vernum=`expr "$_G_verout" : '.* \([0-9][^ ]*\)'`
135    fi
136
137    if test -n "$_G_vernum"; then
138      printf '%s\n' "$_G_vernum"
139    else
140      printf '%s\n' "$_G_verout"
141    fi
142
143    (exit $_G_status)
144}
145
146
147# func_find_tool ENVVAR NAMES...
148# ------------------------------
149# Search for a required program.  Use the value of ENVVAR, if set,
150# otherwise find the first of the NAMES that can be run (i.e.,
151# supports --version).  If found, set ENVVAR to the program name,
152# die otherwise.
153func_find_tool ()
154{
155    $debug_cmd
156
157    _G_find_tool_envvar=$1
158    shift
159    _G_find_tool_names=$@
160    eval "_G_find_tool_res=\$$_G_find_tool_envvar"
161    if test -n "$_G_find_tool_res"; then
162      _G_find_tool_error_prefix="\$$find_tool_envvar: "
163    else
164      _G_find_tool_res=
165      _G_bestver=
166      for _G_prog
167      do
168        _G_find_tool_save_IFS=$IFS
169        IFS=${PATH_SEPARATOR-:}
170	for _G_dir in $PATH; do
171	  IFS=$_G_find_tool_save_IFS
172	  _G_progpath=$_G_dir/$_G_prog
173          test -r "$_G_progpath" && {
174            _G_curver=`func_tool_version_number $_G_progpath`
175	    case $_G_bestver,$_G_curver in
176	    ,)
177	      # first non--version responsive prog sticks!
178              test -n "$_G_progpath" || _G_find_tool_res=$_G_progpath
179              ;;
180            ,*)
181	      # first --version responsive prog beats non--version responsive!
182	      _G_find_tool_res=$_G_progpath
183	      _G_bestver=$_G_curver
184	      ;;
185	    *,*)
186	      # another --version responsive prog must be newer to beat previous one!
187	      test "x$_G_curver" = "x$_G_bestver" \
188		|| func_lt_ver "$_G_curver" "$_G_bestver" \
189		|| {
190		     _G_find_tool_res=$_G_progpath
191		     _G_bestver=$_G_curver
192	           }
193	      ;;
194	    esac
195          }
196	done
197	IFS=$_G_find_tool_save_IFS
198      done
199    fi
200    if test -n "$_G_find_tool_res"; then
201      func_tool_version_number >/dev/null $_G_find_tool_res "\
202${_G_find_tool_error_prefix}Cannot run '$_G_find_tool_res --version'"
203
204      # Make sure the result is exported to the environment for children
205      # to use.
206      eval "$_G_find_tool_envvar=\$_G_find_tool_res"
207      eval "export $_G_find_tool_envvar"
208    else
209      func_error "\
210One of these is required:
211       $_G_find_tool_names"
212    fi
213}
214
215
216
217## -------------------- ##
218## Resource management. ##
219## -------------------- ##
220
221# This section contains definitions for functions that each ensure a
222# particular resource (a file, or a non-empty configuration variable for
223# example) is available, and if appropriate to extract default values
224# from pertinent package files.  Where a variable already has a non-
225# empty value (as set by the package's 'bootstrap.conf'), that value is
226# used in preference to deriving the default. Call them using their
227# associated 'require_*' variable to ensure that they are executed, at
228# most, once.
229#
230# It's entirely deliberate that calling these functions can set
231# variables that don't obey the namespace limitations obeyed by the rest
232# of this file, in order that that they be as useful as possible to
233# callers.
234
235
236# require_configure_ac
237# --------------------
238# Ensure that there is a 'configure.ac' or 'configure.in' file in the
239# current directory that contains an uncommented call to AC_INIT, and
240# that '$configure_ac' contains its name.
241require_configure_ac=func_require_configure_ac
242func_require_configure_ac ()
243{
244    $debug_cmd
245
246    test -z "$configure_ac" \
247      && func_autoconf_configure configure.ac && configure_ac=configure.ac
248    test -z "$configure_ac" \
249      && func_autoconf_configure configure.in && configure_ac=configure.in
250    test -z "$configure_ac" \
251      || func_verbose "found '$configure_ac'"
252
253    require_configure_ac=:
254}
255
256
257# require_gnu_m4
258# --------------
259# Search for GNU M4, and export it in $M4.
260require_gnu_m4=func_require_gnu_m4
261func_require_gnu_m4 ()
262{
263    $debug_cmd
264
265    test -n "$M4" || {
266      # Find the first m4 binary that responds to --version.
267      func_find_tool M4 gm4 gnum4 m4
268    }
269
270    test -n "$M4" || func_fatal_error "\
271Please install GNU M4, or 'export M4=/path/to/gnu/m4'."
272
273    func_verbose "export M4='$M4'"
274
275    # Make sure the search result is visible to subshells
276    export M4
277
278    require_gnu_m4=:
279}
280
281
282## --------------- ##
283## Core functions. ##
284## --------------- ##
285
286# This section contains the high level functions used when calling this
287# file as a script. 'func_extract_trace' is probably the only one that you
288# won't want to replace if you source this file into your own script.
289
290
291# func_extract_trace MACRO_NAMES [FILENAME]...
292# --------------------------------------------
293# set '$func_extract_trace_result' to a colon delimited list of arguments
294# to any of the comma separated list of MACRO_NAMES in FILENAME. If no
295# FILENAME is given, then '$configure_ac' is assumed.
296func_extract_trace ()
297{
298    $debug_cmd
299
300    $require_configure_ac
301    $require_gnu_m4
302
303    _G_m4_traces=`$ECHO "--trace=$1" |$SED 's%,% --trace=%g'`
304    _G_re_macros=`$ECHO "($1)" |$SED 's%,%|%g'`
305    _G_macros="$1"; shift
306    test $# -gt 0 || {
307      set dummy $configure_ac
308      shift
309    }
310
311    # Generate an error if the first file is missing
312    <"$1"
313
314    # Sadly, we can't use 'autom4te' tracing to extract macro arguments,
315    # because it complains about things we want to ignore at bootstrap
316    # time - like missing m4_include files; AC_PREREQ being newer than
317    # the installed autoconf; and returns nothing when tracing
318    # 'AM_INIT_AUTOMAKE' when aclocal hasn't been generated yet.
319    #
320    # The following tries to emulate a less persnickety version of (and
321    # due to not having to wait for Perl startup on every invocation,
322    # it's probably faster too):
323    #
324    #    autom4te --language=Autoconf --trace=$my_macro:\$% "$@"
325    #
326    # First we give a minimal set of macro declarations to M4 to prime
327    # it for reading Autoconf macros, while still providing some of the
328    # functionality generally used at m4-time to supply dynamic
329    # arguments to Autocof functions, but without following
330    # 'm4_s?include' files.
331    _G_mini='
332        # Initialisation.
333        m4_changequote([,])
334        m4_define([m4_copy],   [m4_define([$2], m4_defn([$1]))])
335        m4_define([m4_rename], [m4_copy([$1], [$2])m4_undefine([$1])])
336
337        # Disable these macros.
338        m4_undefine([m4_dnl])
339        m4_undefine([m4_include])
340        m4_undefine([m4_m4exit])
341        m4_undefine([m4_m4wrap])
342        m4_undefine([m4_maketemp])
343
344        # Copy and rename macros not handled by "m4 --prefix".
345        m4_define([dnl],         [m4_builtin([dnl])])
346        m4_copy([m4_define],     [m4_defun])
347        m4_rename([m4_ifelse],   [m4_if])
348        m4_ifdef([m4_mkstemp],   [m4_undefine([m4_mkstemp])])
349        m4_rename([m4_patsubst], [m4_bpatsubst])
350        m4_rename([m4_regexp],   [m4_bregexp])
351
352        # "m4sugar.mini" - useful m4-time macros for dynamic arguments.
353        # If we discover packages that need more m4 macros defined in
354        # order to bootstrap correctly, add them here:
355        m4_define([m4_bmatch],
356            [m4_if([$#], 0, [], [$#], 1, [], [$#], 2, [$2],
357                   [m4_if(m4_bregexp([$1], [$2]), -1,
358                          [$0([$1], m4_shift3($@))], [$3])])])
359        m4_define([m4_ifndef], [m4_ifdef([$1], [$3], [$2])])
360        m4_define([m4_ifset],
361            [m4_ifdef([$1], [m4_ifval(m4_defn([$1]), [$2], [$3])], [$3])])
362        m4_define([m4_require], [$1])
363        m4_define([m4_shift3], [m4_shift(m4shift(m4shift($@)))])
364
365        # "autoconf.mini" - things from autoconf macros we care about.
366        m4_copy([m4_defun], [AC_DEFUN])
367
368        # Dummy definitions for the macros we want to trace.
369        # AM_INIT_AUTOMAKE at least produces no trace without this.
370    '
371
372    _G_save=$IFS
373    IFS=,
374    for _G_macro in $_G_macros; do
375      IFS=$_G_save
376      func_append _G_mini "AC_DEFUN([$_G_macro])$nl"
377    done
378    IFS=$_G_save
379
380    # We discard M4's stdout, but the M4 trace output from reading our
381    # "autoconf.mini" followed by any other files passed to this
382    # function is then scanned by sed to transform it into a colon
383    # delimited argument list assigned to a shell variable.
384    _G_transform='s|#.*$||; s|^dnl .*$||; s| dnl .*$||;'
385
386    # Unfortunately, alternation in regexp addresses doesn't work in at
387    # least BSD (and hence Mac OS X) sed, so we have to append a capture
388    # and print block for each traced macro to the sed transform script.
389    _G_save=$IFS
390    IFS=,
391    for _G_macro in $_G_macros; do
392      IFS=$_G_save
393      func_append _G_transform '
394        /^m4trace: -1- '"$_G_macro"'/ {
395          s|^m4trace: -1- '"$_G_macro"'[([]*||
396          s|], [[]|:|g
397          s|[])]*$|:|
398          s|\(.\):$|\1|
399          p
400        }'
401    done
402    IFS=$_G_save
403
404    # Save the command pipeline results for further use by callers of
405    # this function.
406    func_extract_trace_result=`$ECHO "$_G_mini" \
407      |$M4 -daq --prefix $_G_m4_traces - "$@" 2>&1 1>/dev/null \
408      |$SED -n -e "$_G_transform"`
409}
410
411
412# func_extract_trace_first MACRO_NAMES [FILENAME]...
413# --------------------------------------------------
414# Exactly like func_extract_trace, except that only the first argument
415# to the first invocation of one of the comma separated MACRO_NAMES is
416# returned in '$func_extract_trace_first_result'.
417func_extract_trace_first ()
418{
419    $debug_cmd
420
421    func_extract_trace ${1+"$@"}
422    func_extract_trace_first_result=`$ECHO "$func_extract_trace_result" \
423      |$SED -e 's|:.*$||g' -e 1q`
424}
425
426
427# func_main [ARG]...
428# ------------------
429func_main ()
430{
431    $debug_cmd
432
433    # Configuration.
434    usage='$progname MACRO_NAME FILE [...]'
435
436    long_help_message='
437The first argument to this program is the name of an autotools macro
438whose arguments you want to extract by examining the files listed in the
439remaining arguments using the same tool that Autoconf and Automake use,
440GNU M4.
441
442The arguments are returned separated by colons, with each traced call
443on a separate line.'
444
445    # Option processing.
446    func_options "$@"
447    eval set dummy "$func_options_result"; shift
448
449    # Validate remaining non-option arguments.
450    test $# -gt 1 \
451        || func_fatal_help "not enough arguments"
452
453    # Pass non-option arguments to extraction function.
454    func_extract_trace "$@"
455
456    # Display results.
457    test -n "$func_extract_trace_result" \
458        && $ECHO "$func_extract_trace_result"
459
460    # The End.
461    exit $EXIT_SUCCESS
462}
463
464
465## --------------------------- ##
466## Actually perform the trace. ##
467## --------------------------- ##
468
469# Only call 'func_main' if this script was called directly.
470test extract-trace = "$progname" && func_main "$@"
471
472# Local variables:
473# mode: shell-script
474# sh-indentation: 2
475# eval: (add-hook 'before-save-hook 'time-stamp)
476# time-stamp-pattern: "20/scriptversion=%:y-%02m-%02d.%02H; # UTC"
477# time-stamp-time-zone: "UTC"
478# End:
479