xref: /petsc/lib/petsc/bin/petscnagfor (revision f14a7c29b82d1117d8e3de344377442be395a55f)
1#!/usr/bin/env python3
2import os
3import re
4import sys
5import subprocess
6import time
7from pathlib import Path
8#
9#             This script replaces the nagfor compiler wrapper that has design features
10#           that make it impossible to work directly with PETSc (or any standard Unix compile system)
11#
12#      It uses the nagfor option -dryrun to determine the programs nagfor would run and fixes the arguments to the calls
13#
14#      It can be used with --download-mpich and --download-fblaslapack; PETSc configure automatically adds the option
15#      -mismatch when building these packages (but not when building PETSc)
16#
17#      -verbose and  -v run the compile printing the commands used. -dryrun does NOT run the compiler just prints what nagfor would do
18#
19#      -V or -version cause the version to be printed and then the compiler ends
20#
21#   This script does multiple tries when the NAG license server does not respond
22#
23#   nagfor puts temporary files in /tmp with a process id, if a different user runs and gets the same process id the compiler will fail
24#      thus petscnagfor appends the /tmp file names with the USER id to generate unique file names.
25#   nagfor does not handle -Wl,-rpath,path correctly (MPICH claims -Wl,-Wl,, works, but we can't change all of PETSc for this))
26#   nagfor links against two of its own .o files, if the link results in multiple definitions then it removes a set and tries again
27#      this can cause problems when the linking is done by the C compiler which doesn't know about these libraries
28#   nagfor cannot always handle -O0 so remove it
29#   nagfor checks the length of all character strings; PETSc does not pass in the character string lengths to BLAS/LAPACK
30#      hence this script removes the lines of generated C code that check the lengths.
31#   nagfor does not handle -verbose or -v; so this script provides support
32#   nagfor does not handle -shared; this script passes it to linker with -Wl,-shared
33#   nagfor does not handle --whole-archive, --no-whole-archive that MPICH insists on putting in so they are removed
34#
35def runnagfor(args):
36  if not isinstance(args, list): args = args.strip().split(' ')
37  for i in range(1,4):
38    try:
39      sub = subprocess.Popen(args, stdout=subprocess.PIPE,stderr=subprocess.PIPE)
40      sub.wait()
41      output = sub.stdout.read().decode('UTF-8')
42      error = sub.stderr.read().decode('UTF-8')
43      status = sub.returncode
44    except:
45      print('petscnagfor: Unable to run process with job: '+' '.join(args))
46      exit(1)
47    if (output+error).find('No licences currently available') == -1:
48      break;
49    time.sleep(30)
50  if (output+error).find('No licences currently available') > -1:
51    Path(os.path.join(os.path.dirname(__file__),'..','..','..','naglicenseproblem')).touch()
52  return (output,error,status)
53
54if __name__ == '__main__':
55  # Process options passed into compiler suite
56  verbose = 0
57  linkerargs = ['-rpath', '--whole-archive', '--no-whole-archive', '-soname' ]
58  args = sys.argv[1:]
59  argst = args
60  args = []
61  i = 0
62  while i < len(argst):
63    flag = 1
64    if argst[i] == '-verbose':
65      verbose = 1
66      i = i + 1
67      continue
68    if argst[i] == '-v':
69      verbose = 1
70      i = i + 1
71      continue
72    if argst[i] == '-shared':
73      args.append('-Wl,-shared')
74      i = i + 1
75      continue
76    for j in linkerargs:
77      if argst[i].startswith(j):
78        args.append('-Wl,'+ argst[i] + ',' + argst[i+1])
79        i = i + 1
80        flag = 0
81        break
82    if flag: args.append(argst[i])
83    i = i + 1
84  if '-mismatch' in args or '-dusty' in args:
85    args = [x for x in args if not x.startswith('-C=')]
86
87  if '-version' in args or '-dryrun' in args or '-V' in args:
88    (output,error,status) = runnagfor(['nagfor']+args)
89    error = error.replace('-rpath ','-Wl,-rpath')
90    print(output)
91    print(error,file=sys.stderr)
92    exit(0)
93
94  if verbose: print(' '.join(['nagfor','-dryrun']+args))
95  (output,error,status) = runnagfor(['nagfor','-dryrun']+args)
96  if status:
97    print(output)
98    print(error,file=sys.stderr)
99    exit(status)
100
101  # Run through the up to four commands that nagfor passes out from -dryrun
102  for i in (output+error).split('\n'):
103    import re
104    if not i or i.startswith('stdout') or i.startswith('stderr') or i.startswith('NAG') or i.startswith('Loading'): continue
105    if os.path.isfile(i[0:-1]): continue
106    i = i.replace(';',' ')
107    i = re.sub(r'/tmp/([a-zA-Z0-9_]*)\.([0-9]*)\.([fF90co]*)','/tmp/'+os.getenv('USER')+r"-\1.\2.\3",i)
108    for j in linkerargs:
109      i = i.replace(j+' ','-Wl,'+j+',').strip()
110    if i.find('forcomp') > -1:
111      i = i.replace(' -PIC','').strip()
112      # each option needs its own -options in front of it
113      i = i.replace(' -options','').strip()
114      i = i.replace(' -C=',' -options -C=')   # note -C=xxx requires options but -C does not
115      i = i.replace(' -O',' -options -O')
116      i = i.replace(' -gline',' -options -gline') # note this is actually not needed
117      #  add more compiler options here
118    js = [x for x in i.split(' ') if not x == '-Bstatic' and not x == '-Bdynamic']
119
120    # Save all the .c files generated so they may be seen in the debugger
121    if (i.find('/fpp') == -1 and i.find('quickfit.o') == -1):
122      if i.find('forcomp') > -1:
123        last = js[-2][5:]
124      else:
125        last = js[-1][5:]
126      f1 = last.find('.')
127      f2 = last.rfind('.')
128      last = last[0:f1] + last[f2:]
129      if i.find('forcomp') > -1:
130        js[-2] = last
131      else:
132        js[-1] = last
133
134    if verbose: print(' '.join(js))
135    (suboutput,error,status) = runnagfor(js)
136    if js[0].endswith('forcomp'):
137      lerror = ''
138      for k in error.split('\n'):
139        if k.find("Unused dummy variable") > -1: continue
140        if k.find("Fixed source form") > -1: continue
141        if k.find("[NAG Fortran Compiler normal termination") > -1: continue
142        if k.find("Line longer than") > -1: continue
143        lerror += k
144      if lerror: print("\n"+lerror,file=sys.stderr)
145
146    if (i.find('/fpp') == -1 and i.find('quickfit.o') == -1):
147      if i.find('forcomp') > -1:
148        if status:
149          print(suboutput)
150          print(error,file=sys.stderr)
151          exit(status)
152        fd = open(last)
153        f = fd.read()
154        fd.close()
155        fd = open(last,'w')
156        for k in f.split('\n'):
157          # comment out the next line if you want to see the generated C code in the debugger, not the Fortran
158          if k.find('# line') > -1: k = '/* removed hash line */'
159          if k.find('Len) __NAGf90_rterr') > -1: k = '/* removed length check code */'
160          fd.write(k+'\n')
161        fd.close()
162
163    if status and (suboutput+error).find('multiple') > -1:
164      js = i.strip().split(' ')
165      ks = []
166      foundinit = 0
167      foundquickfit = 0
168      for x in js:
169        if not foundinit and x.endswith('init.o'):
170          foundinit = 1
171          continue
172        if not foundquickfit and x.endswith('quickfit.o'):
173          foundquickfit = 1
174          continue
175        ks.append(x)
176      (suboutput,error,status) = runnagfor(ks)
177      if status and (suboutput+error).find('multiple') > -1:
178        js = [x for x in js if not x.endswith('init.o') and not x.endswith('quickfit.o')]
179        (suboutput,error,status) = runnagfor(js)
180    if status:
181      print(suboutput)
182      print(error,file=sys.stderr)
183      exit(status)
184
185  exit(0)
186
187