xref: /llvm-project/llvm/utils/TableGen/tdtags (revision 2946cd701067404b99c39fb29dc9c74bd7193eb3)
1#!/bin/sh
2#===-- tdtags - TableGen tags wrapper ---------------------------*- sh -*-===#
3# vim:set sts=2 sw=2 et:
4#===----------------------------------------------------------------------===#
5#
6# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
7# See https://llvm.org/LICENSE.txt for license information.
8# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
9#
10#===----------------------------------------------------------------------===#
11#
12# This is a wrapper script to simplify generating ctags(1)-compatible index
13# files for target .td files. Run tdtags -H for more documentation.
14#
15# For portability, this script is intended to conform to IEEE Std 1003.1-2008.
16#
17#===----------------------------------------------------------------------===#
18
19SELF=${0##*/}
20
21usage() {
22cat <<END
23Usage: $SELF [ <options> ] tdfile
24   or: $SELF [ <options> ] -x recipe [arg ...]
25OPTIONS
26  -H          Display further help.
27  -a          Append the tags to an existing tags file.
28  -f <file>   Write tags to the specified file (defaults to 'tags').
29  -I <dir>    Add the directory to the search path for tblgen include files.
30  -x <recipe> Generate tags file(s) for a common use case:
31  -q          Suppress $TBLGEN error messages.
32  -v          Be verbose; report progress.
33END
34  usage_recipes
35}
36
37usage_recipes() {
38cat <<END
39     all      - Generate an index in each directory that contains .td files
40                in the LLVM source tree.
41     here     - Generate an index for all .td files in the current directory.
42     recurse  - Generate an index in each directory that contains .td files
43                in and under the current directory.
44     target [<target> ...]
45              - Generate a tags file for each specified LLVM code generator
46                target, or if none are specified, all targets.
47END
48}
49
50help() {
51cat <<END
52NAME
53  $SELF - generate ctags(1)-compatible index files for tblgen .td source
54
55SYNOPSIS
56  $SELF [ options ] -x recipe [arg ...]
57  $SELF [ options ] [file ...]
58
59DESCRIPTION
60  With the '-x' option, $SELF produces one or more tags files for a
61  particular common use case. See the RECIPES section below for details.
62
63  Without the '-x' option, $SELF provides a ctags(1)-like interface to
64  $TBLGEN.
65
66OPTIONS
67  -a          Append newly generated tags to those already in an existing
68              tags file. Without ths option, any and all existing tags are
69              replaced. NOTE: When building a mixed tags file, using ${SELF}
70              for tblgen tags and ctags(1) for other languages, it is best
71              to run ${SELF} first without '-a', and ctags(1) second with '-a',
72              because ctags(1) handling is more capable.
73  -f <file>   Use the name <file> for the tags file, rather than the default
74              "tags". If the <file> is "-", then the tag index is written to
75              standard output.
76  -H          Display this document.
77  -I <dir>    Add the directory <dir> to the search path for 'include'
78              statements in tblgen source.
79  -x          Run a canned recipe, rather than operate on specified files.
80              When '-x' is present, the first non-option argument is the
81              name of a recipe, and any further arguments are arguments to
82              that recipe. With no arguments, lists the available recipes.
83  -q          Suppress $TBLGEN error messages. Not all .td files are well-
84              formed outside a specific context, so recipes will sometimes
85              produce error messages for certain .td files. These errors
86              do not affect the indices produced for valid files.
87  -v          Be verbose; report progress.
88
89RECIPES
90  $SELF -x all
91              Produce a tags file in every directory in the LLVM source tree
92              that contains any .td files.
93  $SELF -x here
94              Produce a tags file from .td files in the current directory.
95  $SELF -x recurse
96              Produce a tags file in every directory that contains any .td
97              files, in and under the current directory.
98  $SELF -x target [<target> ...]
99              Produce a tags file for each named code generator target, or
100              if none are named, for all code generator targets.
101END
102}
103
104# Temporary file management.
105#
106# Since SUS sh(1) has no arrays, this script makes extensive use of
107# temporary files. The follow are 'global' and used to carry information
108# across functions:
109#   $TMP:D    Include directories.
110#   $TMP:I    Included files.
111#   $TMP:T    Top-level files, that are not included by another.
112#   $TMP:W    Directories in which to generate tags (Worklist).
113# For portability to OS X, names must not differ only in case.
114#
115TMP=${TMPDIR:-/tmp}/$SELF:$$
116trap "rm -f $TMP*" 0
117trap exit 1 2 13 15
118>$TMP:D
119
120td_dump()
121{
122  if [ $OPT_VERBOSE -gt 1 ]
123  then
124    printf '===== %s =====\n' "$1"
125    cat <"$1"
126  fi
127}
128
129# Escape the arguments, taken as a whole.
130e() {
131  printf '%s' "$*" |
132    sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/"
133}
134
135# Determine whether the given directory contains at least one .td file.
136dir_has_td() {
137  for i in $1/*.td
138  do
139    [ -f "$i" ] && return 0
140  done
141  return 1
142}
143
144# Partition the supplied list of files, plus any files included from them,
145# into two groups:
146#   $TMP:T    Top-level files, that are not included by another.
147#   $TMP:I    Included files.
148# Add standard directories to the include paths in $TMP:D if this would
149# benefit the any of the included files.
150td_prep() {
151  >$TMP:E
152  >$TMP:J
153  for i in *.td
154  do
155    [ "x$i" = 'x*.td' ] && return 1
156    if [ -f "$i" ]
157    then
158      printf '%s\n' "$i" >>$TMP:E
159      sed -n -e 's/include[[:space:]]"\(.*\)".*/\1/p' <"$i" >>$TMP:J
160    else
161      printf >&2 '%s: "%s" not found.\n' "$SELF" "$i"
162      exit 7
163    fi
164  done
165  sort -u <$TMP:E >$TMP:X
166  sort -u <$TMP:J >$TMP:I
167  # A file that exists but is not included is toplevel.
168  comm -23 $TMP:X $TMP:I >$TMP:T
169  td_dump $TMP:T
170  td_dump $TMP:I
171  # Check include files.
172  while read i
173  do
174    [ -f "$i" ] && continue
175    while read d
176    do
177      [ -f "$d/$i" ] && break
178    done <$TMP:D
179    if [ -z "$d" ]
180    then
181      # See whether this include file can be found in a common location.
182      for d in $LLVM_SRC_ROOT/include \
183               $LLVM_SRC_ROOT/tools/clang/include
184      do
185        if [ -f "$d/$i" ]
186        then
187          printf '%s\n' "$d" >>$TMP:D
188          break
189        fi
190      done
191    fi
192  done <$TMP:I
193  td_dump $TMP:D
194}
195
196# Generate tags for the list of files in $TMP:T.
197td_tag() {
198  # Collect include directories.
199  inc=
200  while read d
201  do
202    inc="${inc}${inc:+ }$(e "-I=$d")"
203  done <$TMP:D
204
205  if [ $OPT_VERBOSE -ne 0 ]
206  then
207    printf >&2 'In "%s",\n' "$PWD"
208  fi
209
210  # Generate tags for each file.
211  n=0
212  while read i
213  do
214    if [ $OPT_VERBOSE -ne 0 ]
215    then
216      printf >&2 '  generating tags from "%s"\n' "$i"
217    fi
218    n=$((n + 1))
219    t=$(printf '%s:A:%05u' "$TMP" $n)
220    eval $TBLGEN --gen-ctags $inc "$i" >$t 2>$TMP:F
221    [ $OPT_NOTBLGENERR -eq 1 ] || cat $TMP:F
222  done <$TMP:T
223
224  # Add existing tags if requested.
225  if [ $OPT_APPEND -eq 1 -a -f "$OPT_TAGSFILE" ]
226  then
227    if [ $OPT_VERBOSE -ne 0 ]
228    then
229      printf >&2 '  and existing tags from "%s"\n' "$OPT_TAGSFILE"
230    fi
231    n=$((n + 1))
232    t=$(printf '%s:A:%05u' "$TMP" $n)
233    sed -e '/^!_TAG_/d' <"$OPT_TAGSFILE" | sort -u >$t
234  fi
235
236  # Merge tags.
237  if [ $n = 1 ]
238  then
239    mv -f "$t" $TMP:M
240  else
241    sort -m -u $TMP:A:* >$TMP:M
242  fi
243
244  # Emit tags.
245  if [ x${OPT_TAGSFILE}x = x-x ]
246  then
247    cat $TMP:M
248  else
249    if [ $OPT_VERBOSE -ne 0 ]
250    then
251      printf >&2 '  into "%s".\n' "$OPT_TAGSFILE"
252    fi
253    mv -f $TMP:M "$OPT_TAGSFILE"
254  fi
255}
256
257# Generate tags for the current directory.
258td_here() {
259  td_prep
260  [ -s $TMP:T ] || return 1
261  td_tag
262}
263
264# Generate tags for the current directory, and report an error if there are
265# no .td files present.
266do_here()
267{
268  if ! td_here
269  then
270    printf >&2 '%s: Nothing to do here.\n' "$SELF"
271    exit 1
272  fi
273}
274
275# Generate tags for all .td files under the current directory.
276do_recurse()
277{
278  td_find "$PWD"
279  td_dirs
280}
281
282# Generate tags for all .td files in LLVM.
283do_all()
284{
285  td_find "$LLVM_SRC_ROOT"
286  td_dirs
287}
288
289# Generate tags for each directory in the worklist $TMP:W.
290td_dirs()
291{
292  while read d
293  do
294    (cd "$d" && td_here)
295  done <$TMP:W
296}
297
298# Find directories containing .td files within the specified directory,
299# and record them in the worklist $TMP:W.
300td_find()
301{
302  find -L "$1" -type f -name '*.td' |
303    sed -e 's:/[^/]*$::' |
304    sort -u >$TMP:W
305  td_dump $TMP:W
306}
307
308# Generate tags for the specified code generator targets, or
309# if there are no arguments, all targets.
310do_targets() {
311  cd $LLVM_SRC_ROOT/lib/Target
312  if [ -z "$*" ]
313  then
314    td_find "$PWD"
315  else
316    # Check that every specified argument is a target directory;
317    # if not, list all target directories.
318    for d
319    do
320      if [ -d "$d" ] && dir_has_td "$d"
321      then
322        printf '%s/%s\n' "$PWD" "$d"
323      else
324        printf >&2 '%s: "%s" is not a target. Targets are:\n' "$SELF" "$d"
325        for d in *
326        do
327          [ -d "$d" ] || continue
328          dir_has_td "$d" && printf >&2 '  %s\n' "$d"
329        done
330        exit 2
331      fi
332    done >$TMP:W
333  fi
334  td_dirs
335}
336
337# Change to the directory at the top of the enclosing LLVM source tree,
338# if possible.
339llvm_src_root() {
340  while [ "$PWD" != / ]
341  do
342    # Use this directory if multiple notable subdirectories are present.
343    [ -d include/llvm -a -d lib/Target ] && return 0
344    cd ..
345  done
346  return 1
347}
348
349# Ensure sort(1) behaves consistently.
350LC_ALL=C
351export LC_ALL
352
353# Globals.
354TBLGEN=llvm-tblgen
355LLVM_SRC_ROOT=
356
357# Command options.
358OPT_TAGSFILE=tags
359OPT_RECIPES=0
360OPT_APPEND=0
361OPT_VERBOSE=0
362OPT_NOTBLGENERR=0
363
364while getopts 'af:hxqvHI:' opt
365do
366  case $opt in
367  a)
368    OPT_APPEND=1
369    ;;
370  f)
371    OPT_TAGSFILE="$OPTARG"
372    ;;
373  x)
374    OPT_RECIPES=1
375    ;;
376  q)
377    OPT_NOTBLGENERR=1
378    ;;
379  v)
380    OPT_VERBOSE=$((OPT_VERBOSE + 1))
381    ;;
382  I)
383    printf '%s\n' "$OPTARG" >>$TMP:D
384    ;;
385  [hH])
386    help
387    exit 0
388    ;;
389  *)
390    usage >&2
391    exit 4
392    ;;
393  esac
394done
395shift $((OPTIND - 1))
396
397# Handle the case where tdtags is a simple ctags(1)-like wrapper for tblgen.
398if [ $OPT_RECIPES -eq 0 ]
399then
400  if [ -z "$*" ]
401  then
402    help >&2
403    exit 5
404  fi
405  for i
406  do
407    printf '%s\n' "$i"
408  done >$TMP:T
409  td_tag
410  exit $?
411fi
412
413# Find the directory at the top of the enclosing LLVM source tree.
414if ! LLVM_SRC_ROOT=$(llvm_src_root && pwd)
415then
416  printf >&2 '%s: Run from within the LLVM source tree.\n' "$SELF"
417  exit 3
418fi
419
420# Select canned actions.
421RECIPE="$1"
422case "$RECIPE" in
423all)
424  shift
425  do_all
426  ;;
427.|cwd|here)
428  shift
429  do_here
430  ;;
431recurse)
432  shift
433  do_recurse
434  ;;
435target)
436  shift
437  do_targets "$@"
438  ;;
439*)
440  if [ -n "$RECIPE" ]
441  then
442    shift
443    printf >&2 '%s: Unknown recipe "-x %s". ' "$SELF" "$RECIPE"
444  fi
445  printf >&2 'Recipes:\n'
446  usage_recipes >&2
447  printf >&2 'Run "%s -H" for help.\n' "$SELF"
448  exit 6
449  ;;
450esac
451
452exit $?
453