xref: /petsc/lib/petsc/bin/maint/generateetags.py (revision 10bd3113d5951075367803e10ffec15cc811bc9b)
1#!/usr/bin/env python3
2#
3#    Generates etag and ctag (use -noctags to skip generation of ctags) files for PETSc
4#    Adds file names to list of tags in a TAGS file
5#    Also removes the #define somefunction_ somefunction from the tags list
6#
7#
8#
9#   Walks through the PETSc tree generating the TAGS file
10#
11import os
12import re
13import sys
14from string import *
15import subprocess
16try:
17  from subprocess import check_output
18except ImportError:
19  def check_output(*popenargs, **kwargs):
20    """Implementation from Python-2.7 subprocess.check_output for use with
21    Python-2.6 which does not provide check_output.
22    """
23    if 'stdout' in kwargs:
24      raise ValueError('stdout argument not allowed, it will be overridden.')
25    process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
26    output, unused_err = process.communicate()
27    retcode = process.poll()
28    if retcode:
29      cmd = kwargs.get("args")
30      if cmd is None:
31        cmd = popenargs[0]
32      raise subprocess.CalledProcessError(retcode, cmd, output=output)
33    return output
34
35DEVNULL = open(os.devnull, 'w')
36#
37#  Copies structs from filename to filename.tmp
38
39def addFileNameTags(filename):
40  removedefines = 0
41  f = open(filename, 'rb')
42  g = open('TAGS', 'wb')
43  line = f.readline()
44  while line:
45    if not (removedefines and line.startswith(b'#define ')): g.write(line)
46    if line.startswith(b'\f'):
47      line = f.readline()
48      g.write(line)
49      line = line[0:line.index(b',')]
50      if os.path.dirname(line).endswith(b'custom') and not line.endswith(b'.h'):
51        removedefines = 1
52      else: removedefines = 0
53      line = os.path.basename(line)
54      g.write(line + b':^?' + line + b'^A,1\n')
55    line = f.readline()
56  f.close()
57  g.close()
58  return
59
60def createTags(flist,etagfile,ctagfile):
61  # split up the flist into blocks of 1000 - and call etags on each chunk
62  nfiles = len(flist)
63  niter  = nfiles//1000
64  nrem   = nfiles%1000
65  blocks = [i*1000 for i in range(niter+1)]
66  if nrem: blocks.append(nfiles)
67  for i in range(len(blocks)-1):
68    createTagsBlock(flist[blocks[i]:blocks[i+1]],etagfile,ctagfile)
69  return
70
71def createTagsBlock(flist,etagfile,ctagfile):
72  # error check for each parameter?
73  frlist = [os.path.relpath(path,os.getcwd()) for path in flist]
74
75  try:
76    response = subprocess.run('etags -a -o '+etagfile+' '+' '.join(frlist), capture_output = True, shell=True, check=True)
77  except subprocess.CalledProcessError as e:
78    # do not raise exception since it spews a huge file list to the screen obscuring the problem
79    print('Unable to run etags command. Likely it is not in your path. You may need to install etags or Emacs')
80    exit(1)
81  except e:
82    print('Unable to run etags command. Likely it is not in your path. You may need to install etags or Emacs')
83    exit(1)
84
85  # linux can use '--tag-relative=yes --langmap=c:+.cu'. For others [Mac,bsd] try running ctags in root directory - with relative path to file
86  if ctagfile:
87    status = subprocess.call('ctags --fields=+l --tag-relative=yes --langmap=c:+.cu -I PeNS,PeOP -a -f '+ctagfile+' '+' '.join(frlist), shell=True, stdout=DEVNULL, stderr=subprocess.STDOUT)
88    if status:
89      status = subprocess.call('/usr/local/bin/ctags -a -f '+ctagfile+' '+' '.join(frlist), shell=True, stdout=DEVNULL, stderr=subprocess.STDOUT)
90      if status:
91        status = subprocess.call('ctags -a -f '+ctagfile+' '+' '.join(frlist), shell=True, stdout=DEVNULL, stderr=subprocess.STDOUT)
92        if status:
93          raise RuntimeError("Error running ctags")
94  return
95
96def endsWithSuffix(file,suffixes):
97  # returns 1 if any of the suffixes match - else return 0
98  for suffix in suffixes:
99    if file.endswith(suffix):
100      return 1
101  return 0
102
103def startsWithPrefix(file,prefixes):
104  # returns 1 if any of the prefix match - else return 0
105  for prefix in prefixes:
106    if file.startswith(prefix):
107      return 1
108  return 0
109
110def badWebIndex(dirname,file):
111  # checks if the file is bad index.html document [i.e not generated]
112  if file != 'index.html':
113    return 0
114  elif file == 'index.html' and dirname.find('docs/website') >=0:
115    return 0
116  else:
117    return 1
118
119def processDir(flist, dirpath, dirnames, filenames):
120  newls = []
121  gsfx = ['.py','.c','.cu','.F','.F90','.h','.h90','.tex','.cxx','.hh','makefile','.bib','.jl']
122  bpfx = ['.#']
123  hsfx = ['.html']
124  bsfx = ['.py.html','.c.html','.F.html','.h.html','.tex.html','.cxx.html','.hh.html','makefile.html','.gcov.html','.cu.html','.cache.html']
125  for l in filenames:
126    if endsWithSuffix(l,gsfx) and not startsWithPrefix(l,bpfx):
127      newls.append(l)
128    elif endsWithSuffix(l,hsfx)  and not endsWithSuffix(l,bsfx) and not badWebIndex(dirpath,l):
129      # if html - and not bad suffix - and not badWebIndex - then add to etags-list
130      newls.append(l)
131  if newls: flist.extend([os.path.join(dirpath,name) for name in newls])
132
133  # exclude 'petsc/docs/' only (and not docs/ in other locations)
134  for exname in ['docs']:
135    if exname in dirnames and os.path.realpath(dirpath) == os.path.realpath(os.getcwd()):
136      dirnames.remove(exname)
137
138  # One-level unique dirs
139  for exname in ['.git','.hg','SCCS', 'output', 'BitKeeper', 'externalpackages', 'bilinear', 'ftn-auto','lib','systems']:
140    if exname in dirnames:
141      dirnames.remove(exname)
142  #  Multi-level unique dirs - specify from toplevel
143  for exname in ['src/python/PETSc','client/c++','client/c','client/python']:
144    for name in dirnames:
145      filename=os.path.join(dirpath,name)
146      if filename.find(exname) >=0:
147        dirnames.remove(name)
148  # check for configure generated PETSC_ARCHes
149  rmnames=[]
150  for name in dirnames:
151    if os.path.isdir(os.path.join(dirpath,name,'petsc','conf')):
152      rmnames.append(name)
153  for rmname in rmnames:
154    dirnames.remove(rmname)
155  return
156
157def processFiles(dirname,flist):
158  # list files that can't be done with global match [as above] with complete paths
159  import glob
160  files= []
161  lists=['petsc/conf/*']
162
163  for glist in lists:
164    gfiles = glob.glob(glist)
165    for file in gfiles:
166      if not (file.endswith('pyc') or file.endswith('/SCCS') or file.endswith('~')):
167        files.append(file)
168  if files: flist.extend([os.path.join(dirname,name) for name in files])
169  return
170
171def main(ctags):
172  try: os.unlink('TAGS')
173  except: pass
174  etagfile = os.path.join(os.getcwd(),'ETAGS')
175  if ctags:
176    try: os.unlink('CTAGS')
177    except: pass
178    ctagfile = os.path.join(os.getcwd(),'CTAGS')
179  else:
180    ctagfile = None
181  flist = []
182  if os.path.isdir('.git'):
183    output = check_output(r'git ls-files | grep -E -v \(^\(systems/\|share/petsc/datafiles/\)\|/output/\|\.\(png\|pdf\|ps\|ppt\|jpg\)$\)', shell=True)
184    flist = output.decode(sys.getfilesystemencoding()).splitlines()
185  else:
186    for dirpath, dirnames, filenames in os.walk(os.getcwd()):
187      processDir(flist, dirpath, dirnames, filenames)
188    processFiles(os.getcwd(),flist)
189  createTags(flist,etagfile,ctagfile)
190  addFileNameTags(etagfile)
191  try: os.unlink('ETAGS')
192  except: pass
193#
194# The classes in this file can also be used in other python-programs by using 'import'
195#
196if __name__ ==  '__main__':
197    if (len(sys.argv) > 1 and sys.argv[1] == "-noctags"): ctags = 0
198    else: ctags = 1
199    main(ctags)
200
201