xref: /petsc/lib/petsc/bin/maint/abicheck.py (revision f14a7c29b82d1117d8e3de344377442be395a55f)
1#!/usr/bin/python
2#
3#
4# Check ABI/API compatibility of two PETSc versions (the old and the new). Old and new PETSc should have already been built using GCC and with -g
5#
6# Usage:
7#   ./abicheck.py -old_dir        <old PETSC_DIR>
8#                 -old_arch       <old PETSC_ARCH>
9#                 -new_dir        <new PETSC_DIR>
10#                 -new_arch       <new PETSC_ARCH>
11#                 -report_format  <format of the report file, either xml or html. Optional with default=html>
12#
13
14import os, sys
15import subprocess
16import argparse
17
18# Copy from config/BuildSystem/config/setCompilers.py
19def isGNU(compiler):
20  '''Returns true if the compiler is a GNU compiler'''
21  try:
22    output = subprocess.check_output([compiler,'--help'], stderr=subprocess.STDOUT)
23    return (any([s in output for s in ['www.gnu.org',
24                                       'bugzilla.redhat.com',
25                                       'gcc.gnu.org',
26                                       'gcc version',
27                                       '-print-libgcc-file-name',
28                                       'passed on to the various sub-processes invoked by gcc',
29                                       'passed on to the various sub-processes invoked by cc',
30                                       'passed on to the various sub-processes invoked by gfortran',
31                                       'passed on to the various sub-processes invoked by g++',
32                                       'passed on to the various sub-processes invoked by c++',
33                                       ]])
34            and not any([s in output for s in ['Intel(R)',
35                                               'Unrecognised option --help passed to ld', # NAG f95 compiler
36                                               ' clang '
37                                               ]]))
38  except:
39    pass
40  return 0
41
42def gen_xml_desc(petsc_dir, petsc_arch, libs, xmlfile):
43  '''Generate an ABI descriptor file for the given PETSC_DIR and PETSC_ARCH'''
44
45  # Get branch and hash of the source used to build the library. We use them as an informative version number of the library
46  petscconf = os.path.join(petsc_dir, petsc_arch, 'include', 'petscconf.h')
47
48  branch = subprocess.check_output(['grep', '^#define PETSC_VERSION_BRANCH_GIT', petscconf])
49  branch = branch.split("\"")[1]
50  hash   = subprocess.check_output(['grep', '^#define PETSC_VERSION_GIT', petscconf])
51  hash   = hash.split("\"")[1]
52
53  petsclibs = ''
54  petscheaders = ''
55
56  for name in libs:
57    name = name.lower()
58    if not name.startswith('petsc'): # Support petsc, petscsys, or sys etc
59      name = 'petsc'+name
60    libname       = 'lib'+name
61    libname_so    = os.path.join(petsc_dir, petsc_arch, 'lib', libname+'.so')
62    libname_dylib = os.path.join(petsc_dir, petsc_arch, 'lib', libname+'.dylib')
63    libname_a     = os.path.join(petsc_dir, petsc_arch, 'lib', libname+'.a')
64    lib = ''
65    if os.path.isfile(libname_so):
66      lib = libname_so
67    elif os.path.isfile(libname_dylib):
68      lib = libname_dylib
69    elif os.path.isfile(libname_a):
70      lib = libname_a
71
72    if lib == '':
73      raise RuntimeError('Could not find library %s for PETSC_DIR=%s PETSC_ARCH=%s. Please configure and build it before doing ABI/API checking\n'% (name,petsc_dir,petsc_arch))
74    petsclibs += lib+'\n'
75
76    header = os.path.join(petsc_dir,'include',name+'.h')
77    if not os.path.isfile(header):
78      raise RuntimeError('File %s does not exist.\n'% (header))
79    petscheaders += header+'\n'
80
81  with open(xmlfile, "w") as file:
82    file.write("<version>\n")
83    file.write(branch + ' (' + hash +')')
84    file.write("</version>\n\n")
85
86    file.write("<headers>\n")
87    file.write(petscheaders)
88    file.write("\n")
89    file.write("</headers>\n\n")
90
91    file.write("<libs>\n")
92    file.write(petsclibs)
93    file.write("\n")
94    file.write("</libs>\n\n")
95
96    file.write('<include_paths>\n')
97    file.write(os.path.join(petsc_dir,'include'))
98    file.write("\n")
99    file.write(os.path.join(petsc_dir,petsc_arch,'include'))
100    file.write("\n")
101    file.write("</include_paths>\n")
102
103def run_abi_checker(petsc_dir, petsc_arch, abi_dir, oldxml, newxml, cc, rformat):
104  if rformat == 'html':
105    report = 'report.html'
106  else:
107    report = 'report.xml'
108  reportfile   = os.path.join(abi_dir, report)
109
110  abichecker     = os.path.join(petsc_dir, 'lib', 'petsc', 'bin', 'maint', 'abi-compliance-checker', 'abi-compliance-checker.pl')
111  ierr = subprocess.call([abichecker, '-l', 'petsc', '--lang=C', '-old', oldxml, '-new', newxml, '--gcc-path', cc, '--report-path', reportfile, '--report-format', rformat])
112  return ierr
113
114def main():
115  parser = argparse.ArgumentParser()
116  parser.add_argument("-old_dir",  help="Old PETSC_DIR, which defines the PETSc library to compare with", required=True)
117  parser.add_argument("-old_arch", help="Old PETSC_ARCH, which defines the PETSc library to compare with", required=True)
118  parser.add_argument("-new_dir",  help="New PETSC_DIR", required=True)
119  parser.add_argument("-new_arch", help="New PETSC_ARCH", required=True)
120  parser.add_argument("-report_format", help="Format of the report file", default='html', required=False)
121
122  args     = parser.parse_args()
123  old_dir  = args.old_dir
124  old_arch = args.old_arch
125  new_dir  = args.new_dir
126  new_arch = args.new_arch
127  rformat  = args.report_format
128
129  # Get the compiler and raise error if it is not GNU
130  cc = ''
131  for [petsc_dir, petsc_arch] in [[old_dir, old_arch],[new_dir, new_arch]]:
132    petscvariables = os.path.join(petsc_dir, petsc_arch, 'lib', 'petsc','conf', 'petscvariables')
133    if not os.path.isfile(petscvariables):
134      raise RuntimeError('File %s does not exist.\n'% (petscvariables))
135    ccstring = subprocess.check_output(['grep', '^CC =', petscvariables])
136    cc       = ccstring.split('=')[1].strip()
137    if not isGNU(cc):
138      raise RuntimeError('The compiler %s is not a GNU compiler.\n'% (cc))
139
140  # Check the PETSC_USE_SINGLE_LIBRARY status
141  petscconf_h = os.path.join(new_dir, new_arch, 'include', 'petscconf.h')
142  if not os.path.isfile(petscconf_h):
143    raise RuntimeError('File %s does not exist.\n'% (petscconf_h))
144
145  if 'PETSC_USE_SINGLE_LIBRARY' in open(petscconf_h).read():
146    libs=['petsc']
147  else:
148    libs=['sys', 'vec', 'mat', 'dm', 'ksp', 'snes', 'ts', 'tao']
149
150  # Check report format
151  if rformat != 'html' and rformat != 'xml':
152     raise RuntimeError('Unsupported report format "%s". Only html and xml are supported.\n'% (rformat))
153
154  # We generate report under new_dir/abi
155  abi_dir = os.path.join(new_dir, new_arch, 'abi')
156  if not os.path.isdir(abi_dir):
157    os.makedirs (abi_dir)
158
159  print("\nChecking libraries %s ..." %(libs))
160  oldxml = os.path.join(abi_dir, 'old.xml')
161  newxml = os.path.join(abi_dir, 'new.xml')
162  gen_xml_desc(old_dir,old_arch, libs, oldxml)
163  gen_xml_desc(new_dir,new_arch, libs, newxml)
164  ierr = run_abi_checker(new_dir, new_arch, abi_dir, oldxml, newxml, cc, rformat)
165
166  print("=========================================================================================")
167  if (ierr):
168    print("Error: ABI/API compatibility check failed. Open the compatibility report file to see details.")
169  else:
170    print("ABI/API of the two versions are compatible.")
171
172if __name__ == '__main__':
173    main()
174