xref: /petsc/config/gmakegentest.py (revision 96d2aba5c40a07bc26ef1954a3f1cfdaabadb7a8)
1#!/usr/bin/env python
2
3import os,shutil, string, re
4from distutils.sysconfig import parse_makefile
5import sys
6import logging, time
7import types
8sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
9from cmakegen import Mistakes, stripsplit, AUTODIRS, SKIPDIRS
10from cmakegen import defaultdict # collections.defaultdict, with fallback for python-2.4
11from gmakegen import *
12
13import inspect
14thisscriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
15sys.path.insert(0,thisscriptdir)
16import testparse
17import example_template
18
19class generateExamples(Petsc):
20  """
21    gmakegen.py has basic structure for finding the files, writing out
22      the dependencies, etc.
23  """
24  def __init__(self,petsc_dir=None, petsc_arch=None, testdir=None, verbose=False, single_ex=False, srcdir=None):
25    super(generateExamples, self).__init__(petsc_dir, petsc_arch, verbose)
26
27    self.single_ex=single_ex
28
29    # Set locations to handle movement
30    self.inInstallDir=self.getInInstallDir(thisscriptdir)
31    if not self.inInstallDir:
32      self.arch_dir=os.path.join(self.petsc_dir,self.petsc_arch)
33      self.srcdir=os.path.join(self.petsc_dir,'src')
34    else:
35      # set PETSC_ARCH to install directory to allow script to work in both
36      dirlist=thisscriptdir.split(os.path.sep)
37      installdir=os.path.sep.join(dirlist[0:len(dirlist)-4])
38      self.arch_dir=installdir
39      self.srcdir=os.path.join(os.path.dirname(thisscriptdir),'src')
40
41    # Do some initialization
42    if testdir:
43      # If full path given, then use it, otherwise assume relative to arch_dir
44      if testdir.strip().startswith(os.path.sep):
45        self.testroot_dir=testdir.strip()
46      else:
47        self.testroot_dir=os.path.join(self.arch_dir,testdir.strip())
48    else:
49      self.testroot_dir=os.path.join(self.arch_dir,"tests")
50
51    self.ptNaming=True
52    self.verbose=verbose
53    # Whether to write out a useful debugging
54    self.summarize=True if verbose else False
55
56    # For help in setting the requirements
57    self.precision_types="single double __float128 int32".split()
58    self.integer_types="int32 int64".split()
59    self.languages="fortran cuda cxx".split()    # Always requires C so do not list
60
61    # Things that are not test
62    self.buildkeys=testparse.buildkeys
63
64    # Adding a dictionary for storing sources, objects, and tests
65    # to make building the dependency tree easier
66    self.sources={}
67    self.objects={}
68    self.tests={}
69    for pkg in PKGS:
70      self.sources[pkg]={}
71      self.objects[pkg]=[]
72      self.tests[pkg]={}
73      for lang in LANGS:
74        self.sources[pkg][lang]={}
75        self.sources[pkg][lang]['srcs']=[]
76        self.tests[pkg][lang]={}
77
78    if not os.path.isdir(self.testroot_dir): os.makedirs(self.testroot_dir)
79
80    self.indent="   "
81    if self.verbose: print('Finishing the constructor')
82    return
83
84  def srcrelpath(self,rdir):
85    """
86    Get relative path to source directory
87    """
88    return os.path.join('src',os.path.relpath(rdir,self.srcdir))
89
90  def getInInstallDir(self,thisscriptdir):
91    """
92    When petsc is installed then this file in installed in:
93         <PREFIX>/share/petsc/examples/config/gmakegentest.py
94    otherwise the path is:
95         <PETSC_DIR>/config/gmakegentest.py
96    We use this difference to determine if we are in installdir
97    """
98    dirlist=thisscriptdir.split(os.path.sep)
99    if len(dirlist)>4:
100      lastfour=os.path.sep.join(dirlist[len(dirlist)-4:])
101      if lastfour==os.path.join('share','petsc','examples','config'):
102        return True
103      else:
104        return False
105    else:
106      return False
107
108  def nameSpace(self,srcfile,srcdir):
109    """
110    Because the scripts have a non-unique naming, the pretty-printing
111    needs to convey the srcdir and srcfile.  There are two ways of doing this.
112    """
113    if self.ptNaming:
114      cdir=srcdir.split('src')[1].lstrip("/").rstrip("/")
115      prefix=cdir.replace('/examples/','_').replace("/","_")+"-"
116      nameString=prefix+srcfile
117    else:
118      #nameString=srcdir+": "+srcfile
119      nameString=srcfile
120    return nameString
121
122  def getLanguage(self,srcfile):
123    """
124    Based on the source, determine associated language as found in gmakegen.LANGS
125    Can we just return srcext[1:\] now?
126    """
127    langReq=None
128    srcext=os.path.splitext(srcfile)[-1]
129    if srcext in ".F90".split(): langReq="F90"
130    if srcext in ".F".split(): langReq="F"
131    if srcext in ".cxx".split(): langReq="cxx"
132    if srcext == ".cu": langReq="cu"
133    if srcext == ".c": langReq="c"
134    #if not langReq: print "ERROR: ", srcext, srcfile
135    return langReq
136
137  def _getLoopVars(self,inDict,testname, isSubtest=False):
138    """
139    Given: 'args: -bs {{1 2 3 4 5}} -pc_type {{cholesky sor}} -ksp_monitor'
140    Return:
141      inDict['args']: -ksp_monitor
142      inDict['subargs']: -bs ${bs} -pc_type ${pc_type}
143      loopVars['subargs']['varlist']=['bs' 'pc_type']   # Don't worry about OrderedDict
144      loopVars['subargs']['bs']=[["bs"],["1 2 3 4 5"]]
145      loopVars['subargs']['pc_type']=[["pc_type"],["cholesky sor"]]
146    subst should be passed in instead of inDict
147    """
148    loopVars={}; newargs=""
149    lkeys=inDict.keys()
150    lsuffix='_'
151    from testparse import parseLoopArgs
152    for key in lkeys:
153      if type(inDict[key])!=types.StringType: continue
154      keystr = str(inDict[key])
155      akey=('subargs' if key=='args' else key)  # what to assign
156      if akey not in inDict: inDict[akey]=''
157      varlist=[]
158      for varset in re.split('-(?=[a-zA-Z])',keystr):
159        if not varset.strip(): continue
160        if '{{' in varset:
161          keyvar,lvars,ftype=parseLoopArgs(varset)
162          if akey not in loopVars: loopVars[akey]={}
163          varlist.append(keyvar)
164          loopVars[akey][keyvar]=[keyvar,lvars]
165          if akey=='nsize':
166            inDict[akey] = '${' + keyvar + '}'
167            lsuffix+=akey+'-'+inDict[akey]+'_'
168          else:
169            inDict[akey] += ' -'+keyvar+' ${' + keyvar + '}'
170            lsuffix+=keyvar+'-${' + keyvar + '}_'
171        else:
172          if key=='args': newargs+=" -"+varset.strip()
173        if varlist: loopVars[akey]['varlist']=varlist
174
175
176    # For subtests, args are always substituted in (not top level)
177    if isSubtest:
178      inDict['subargs']+=" "+newargs.strip()
179      inDict['args']=''
180      if 'label_suffix' in inDict:
181        inDict['label_suffix']+=lsuffix.rstrip('_')
182      else:
183        inDict['label_suffix']=lsuffix.rstrip('_')
184    else:
185      if loopVars.keys():
186        inDict['args']=newargs.strip()
187        inDict['label_suffix']=lsuffix.rstrip('_')
188    if loopVars.keys():
189      return loopVars
190    else:
191      return None
192
193  def getArgLabel(self,testDict):
194    """
195    In all of the arguments in the test dictionary, create a simple
196    string for searching within the makefile system.  For simplicity in
197    search, remove "-", for strings, etc.
198    Also, concatenate the arg commands
199    For now, ignore nsize -- seems hard to search for anyway
200    """
201    # Collect all of the args associated with a test
202    argStr=("" if 'args' not in testDict else testDict['args'])
203    if 'subtests' in testDict:
204      for stest in testDict["subtests"]:
205         sd=testDict[stest]
206         argStr=argStr+("" if 'args' not in sd else sd['args'])
207
208    # Now go through and cleanup
209    argStr=re.sub('{{(.*?)}}',"",argStr)
210    argStr=re.sub('-'," ",argStr)
211    for digit in string.digits: argStr=re.sub(digit," ",argStr)
212    argStr=re.sub("\.","",argStr)
213    argStr=re.sub(",","",argStr)
214    argStr=re.sub('\+',' ',argStr)
215    argStr=re.sub(' +',' ',argStr)  # Remove repeated white space
216    return argStr.strip()
217
218  def addToSources(self,exfile,root,srcDict):
219    """
220      Put into data structure that allows easy generation of makefile
221    """
222    rpath=self.srcrelpath(root)
223    pkg=rpath.split(os.path.sep)[1]
224    relpfile=os.path.join(rpath,exfile)
225    lang=self.getLanguage(exfile)
226    if not lang: return
227    self.sources[pkg][lang]['srcs'].append(relpfile)
228    self.sources[pkg][lang][relpfile] = []
229    if 'depends' in srcDict:
230      depSrcList=srcDict['depends'].split()
231      for depSrc in depSrcList:
232        depObj=os.path.splitext(depSrc)[0]+".o"
233        self.sources[pkg][lang][relpfile].append(os.path.join(rpath,depObj))
234
235    # In gmakefile, ${TESTDIR} var specifies the object compilation
236    testsdir=self.srcrelpath(root)+"/"
237    objfile="${TESTDIR}/"+testsdir+os.path.splitext(exfile)[0]+".o"
238    self.objects[pkg].append(objfile)
239    return
240
241  def addToTests(self,test,root,exfile,execname,testDict):
242    """
243      Put into data structure that allows easy generation of makefile
244      Organized by languages to allow testing of languages
245    """
246    rpath=self.srcrelpath(root)
247    pkg=rpath.split("/")[1]
248    #nmtest=self.nameSpace(test,root)
249    nmtest=os.path.join(rpath,test)
250    lang=self.getLanguage(exfile)
251    if not lang: return
252    self.tests[pkg][lang][nmtest]={}
253    self.tests[pkg][lang][nmtest]['exfile']=os.path.join(rpath,exfile)
254    self.tests[pkg][lang][nmtest]['exec']=execname
255    self.tests[pkg][lang][nmtest]['argLabel']=self.getArgLabel(testDict)
256    return
257
258  def getExecname(self,exfile,root):
259    """
260      Generate bash script using template found next to this file.
261      This file is read in at constructor time to avoid file I/O
262    """
263    rpath=self.srcrelpath(root)
264    if self.single_ex:
265      execname=rpath.split("/")[1]+"-ex"
266    else:
267      execname=os.path.splitext(exfile)[0]
268    return execname
269
270  def getSubstVars(self,testDict,rpath,testname):
271    """
272      Create a dictionary with all of the variables that get substituted
273      into the template commands found in example_template.py
274    """
275    subst={}
276
277    # Handle defaults of testparse.acceptedkeys (e.g., ignores subtests)
278    if 'nsize' not in testDict: testDict['nsize']=1
279    if 'timeoutfactor' not in testDict: testDict['timeoutfactor']="1"
280    for ak in testparse.acceptedkeys:
281      if ak=='test': continue
282      subst[ak]=(testDict[ak] if ak in testDict else '')
283
284    # Now do other variables
285    subst['execname']=testDict['execname']
286    if 'filter' in testDict:
287      subst['filter']="'"+testDict['filter']+"'"   # Quotes are tricky - overwrite
288
289    # Others
290    subst['subargs']=''  # Default.  For variables override
291    subst['srcdir']=os.path.join(os.path.dirname(self.srcdir),rpath)
292    subst['label_suffix']=''
293    subst['comments']="\n#".join(subst['comments'].split("\n"))
294    if subst['comments']: subst['comments']="#"+subst['comments']
295    subst['exec']="../"+subst['execname']
296    subst['testroot']=self.testroot_dir
297    subst['testname']=testname
298    dp = self.conf.get('DATAFILESPATH','')
299    subst['datafilespath_line'] = 'DATAFILESPATH=${DATAFILESPATH:-"'+dp+'"}'
300
301    # This is used to label some matrices
302    subst['petsc_index_size']=str(self.conf['PETSC_INDEX_SIZE'])
303    subst['petsc_scalar_size']=str(self.conf['PETSC_SCALAR_SIZE'])
304
305    # These can have for loops and are treated separately later
306    subst['nsize']=str(subst['nsize'])
307
308    #Conf vars
309    if self.petsc_arch.find('valgrind')>=0:
310      subst['mpiexec']='petsc_mpiexec_valgrind ' + self.conf['MPIEXEC']
311    else:
312      subst['mpiexec']=self.conf['MPIEXEC']
313    subst['petsc_dir']=self.petsc_dir # not self.conf['PETSC_DIR'] as this could be windows path
314    subst['petsc_arch']=self.petsc_arch
315    if not self.inInstallDir:
316      subst['CONFIG_DIR']=os.path.join(self.petsc_dir,'config')
317      subst['PETSC_BINDIR']=os.path.join(self.petsc_dir,'bin')
318    else:
319      subst['CONFIG_DIR']=os.path.join(os.path.dirname(self.srcdir),'config')
320      subst['PETSC_BINDIR']=os.path.join(self.petsc_dir,self.petsc_arch,'bin')
321    subst['diff']=self.conf['DIFF']
322    subst['rm']=self.conf['RM']
323    subst['grep']=self.conf['GREP']
324    subst['petsc_lib_dir']=self.conf['PETSC_LIB_DIR']
325    subst['wpetsc_dir']=self.conf['wPETSC_DIR']
326
327    # Output file is special because of subtests override
328    defroot=(re.sub("run","",testname) if testname.startswith("run") else testname)
329    if not "_" in defroot: defroot=defroot+"_1"
330    subst['defroot']=defroot
331    subst['label']=self.nameSpace(defroot,subst['srcdir'])
332    subst['redirect_file']=defroot+".tmp"
333    if 'output_file' not in testDict:
334      subst['output_file']="output/"+defroot+".out"
335    # Add in the full path here.
336    subst['output_file']=os.path.join(subst['srcdir'],subst['output_file'])
337    if not os.path.isfile(os.path.join(self.petsc_dir,subst['output_file'])):
338      if not subst['TODO']:
339        print "Warning: "+subst['output_file']+" not found."
340    # Worry about alt files here -- see
341    #   src/snes/examples/tutorials/output/ex22*.out
342    altlist=[subst['output_file']]
343    for i in range(1,3):
344      altroot=defroot+"_alt"
345      if i==2: altroot=altroot+"_2"
346      af="output/"+altroot+".out"
347      srcaf=os.path.join(subst['srcdir'],af)
348      fullaf=os.path.join(self.petsc_dir,srcaf)
349      if os.path.isfile(fullaf): altlist.append(srcaf)
350    if len(altlist)>1: subst['altfiles']=altlist
351    #if len(altlist)>1: print "Found alt files: ",altlist
352
353    return subst
354
355  def getCmds(self,subst,i):
356    """
357      Generate bash script using template found next to this file.
358      This file is read in at constructor time to avoid file I/O
359    """
360    nindnt=i # the start and has to be consistent with below
361    cmdindnt=self.indent*nindnt
362    cmdLines=""
363
364    # MPI is the default -- but we have a few odd commands
365    if not subst['command']:
366      cmd=cmdindnt+self._substVars(subst,example_template.mpitest)
367    else:
368      cmd=cmdindnt+self._substVars(subst,example_template.commandtest)
369    cmdLines+=cmd+"\n"+cmdindnt+"res=$?\n\n"
370
371    cmdLines+=cmdindnt+'if test $res = 0; then\n'
372    diffindnt=self.indent*(nindnt+1)
373    if not subst['filter_output']:
374      if 'altfiles' not in subst:
375        cmd=diffindnt+self._substVars(subst,example_template.difftest)
376      else:
377        # Have to do it by hand a bit because of variable number of alt files
378        rf=subst['redirect_file']
379        cmd=diffindnt+example_template.difftest.split('@')[0]
380        for i in range(len(subst['altfiles'])):
381          af=subst['altfiles'][i]
382          cmd+=af+' '+rf+' > diff-${testname}-'+str(i)+'.out 2> diff-${testname}-'+str(i)+'.out'
383          if i!=len(subst['altfiles'])-1:
384            cmd+=' || ${diff_exe} '
385          else:
386            cmd+='" diff-${testname}.out diff-${testname}.out diff-${label}'
387            cmd+=subst['label_suffix']+' ""'  # Quotes are painful
388    else:
389      cmd=diffindnt+self._substVars(subst,example_template.filterdifftest)
390    cmdLines+=cmd+"\n"
391    cmdLines+=cmdindnt+'else\n'
392    cmdLines+=diffindnt+'printf "ok ${label} # SKIP Command failed so no diff\\n"\n'
393    cmdLines+=cmdindnt+'fi\n'
394    return cmdLines
395
396  def _substVars(self,subst,origStr):
397    """
398      Substitute variables
399    """
400    Str=origStr
401    for subkey in subst:
402      if type(subst[subkey])!=types.StringType: continue
403      patt="@"+subkey.upper()+"@"
404      Str=re.sub(patt,subst[subkey],Str)
405    return Str
406
407  def _writeTodoSkip(self,fh,tors,reasons,footer):
408    """
409    Write out the TODO and SKIP lines in the file
410    The TODO or SKIP variable, tors, should be lower case
411    """
412    TORS=tors.upper()
413    template=eval("example_template."+tors+"line")
414    tsStr=re.sub("@"+TORS+"COMMENT@",', '.join(reasons),template)
415    tab = ''
416    if reasons:
417      fh.write('if ! $force; then\n')
418      tab = tab + '    '
419    if reasons == ["Requires DATAFILESPATH"]:
420      # The only reason not to run is DATAFILESPATH, which we check at run-time
421      fh.write(tab + 'if test -z "${DATAFILESPATH}"; then\n')
422      tab = tab + '    '
423    if reasons:
424      fh.write(tab+tsStr+"\n" + tab + "total=1; "+tors+"=1\n")
425      fh.write(tab+footer+"\n")
426      fh.write(tab+"exit\n")
427    if reasons == ["Requires DATAFILESPATH"]:
428      fh.write('    fi\n')
429    if reasons:
430      fh.write('fi\n')
431    fh.write('\n\n')
432    return
433
434  def getLoopVarsHead(self,loopVars,i):
435    """
436    Generate a nicely indented string with the format loops
437    Here is what the data structure looks like
438      loopVars['subargs']['varlist']=['bs' 'pc_type']   # Don't worry about OrderedDict
439      loopVars['subargs']['bs']=["i","1 2 3 4 5"]
440      loopVars['subargs']['pc_type']=["j","cholesky sor"]
441    """
442    outstr=''; indnt=self.indent
443    for key in loopVars:
444      for var in loopVars[key]['varlist']:
445        varval=loopVars[key][var]
446        outstr += indnt * i + "for "+varval[0]+" in "+varval[1]+"; do\n"
447        i = i + 1
448    return (outstr,i)
449
450  def getLoopVarsFoot(self,loopVars,i):
451    outstr=''; indnt=self.indent
452    for key in loopVars:
453      for var in loopVars[key]['varlist']:
454        i = i - 1
455        outstr += indnt * i + "done\n"
456    return (outstr,i)
457
458  def genRunScript(self,testname,root,isRun,srcDict):
459    """
460      Generate bash script using template found next to this file.
461      This file is read in at constructor time to avoid file I/O
462    """
463    # runscript_dir directory has to be consistent with gmakefile
464    testDict=srcDict[testname]
465    rpath=self.srcrelpath(root)
466    runscript_dir=os.path.join(self.testroot_dir,rpath)
467    if not os.path.isdir(runscript_dir): os.makedirs(runscript_dir)
468    fh=open(os.path.join(runscript_dir,testname+".sh"),"w")
469    petscvarfile=os.path.join(self.arch_dir,'lib','petsc','conf','petscvariables')
470
471    # Get variables to go into shell scripts.  last time testDict used
472    subst=self.getSubstVars(testDict,rpath,testname)
473    loopVars = self._getLoopVars(subst,testname)  # Alters subst as well
474    #if '33_' in testname: print subst['subargs']
475
476    #Handle runfiles
477    for lfile in subst.get('localrunfiles','').split():
478      fullfile=os.path.join(root,lfile)
479      shutil.copy(fullfile,runscript_dir)
480    # Check subtests for local runfiles
481    for stest in subst.get("subtests",[]):
482      for lfile in testDict[stest].get('localrunfiles','').split():
483        fullfile=os.path.join(root,lfile)
484        shutil.copy(fullfile,self.runscript_dir)
485
486    # Now substitute the key variables into the header and footer
487    header=self._substVars(subst,example_template.header)
488    # The header is done twice to enable @...@ in header
489    header=self._substVars(subst,header)
490    footer=re.sub('@TESTROOT@',subst['testroot'],example_template.footer)
491
492    # Start writing the file
493    fh.write(header+"\n")
494
495    # If there is a TODO or a SKIP then we do it before writing out the
496    # rest of the command (which is useful for working on the test)
497    # SKIP and TODO can be for the source file or for the runs
498    self._writeTodoSkip(fh,'todo',[s for s in [srcDict.get('TODO',''), testDict.get('TODO','')] if s],footer)
499    self._writeTodoSkip(fh,'skip',srcDict.get('SKIP',[]) + testDict.get('SKIP',[]),footer)
500
501    j=0  # for indentation
502
503    if loopVars:
504      (loopHead,j) = self.getLoopVarsHead(loopVars,j)
505      if (loopHead): fh.write(loopHead+"\n")
506
507    # Subtests are special
508    if 'subtests' in testDict:
509      substP=subst   # Subtests can inherit args but be careful
510      k=0  # for label suffixes
511      for stest in testDict["subtests"]:
512        subst=substP.copy()
513        subst.update(testDict[stest])
514        # nsize is special because it is usually overwritten
515        if 'nsize' in testDict[stest]:
516          fh.write("nsize="+str(testDict[stest]['nsize'])+"\n")
517        else:
518          fh.write("nsize=1\n")
519        subst['label_suffix']='-'+string.ascii_letters[k]; k+=1
520        sLoopVars = self._getLoopVars(subst,testname,isSubtest=True)
521        #if '10_9' in testname: print sLoopVars
522        if sLoopVars:
523          (sLoopHead,j) = self.getLoopVarsHead(sLoopVars,j)
524          fh.write(sLoopHead+"\n")
525        fh.write(self.getCmds(subst,j)+"\n")
526        if sLoopVars:
527          (sLoopFoot,j) = self.getLoopVarsFoot(sLoopVars,j)
528          fh.write(sLoopFoot+"\n")
529    else:
530      fh.write(self.getCmds(subst,j)+"\n")
531
532    if loopVars:
533      (loopFoot,j) = self.getLoopVarsFoot(loopVars,j)
534      fh.write(loopFoot+"\n")
535
536    fh.write(footer+"\n")
537    os.chmod(os.path.join(runscript_dir,testname+".sh"),0755)
538    #if '10_9' in testname: sys.exit()
539    return
540
541  def  genScriptsAndInfo(self,exfile,root,srcDict):
542    """
543    Generate scripts from the source file, determine if built, etc.
544     For every test in the exfile with info in the srcDict:
545      1. Determine if it needs to be run for this arch
546      2. Generate the script
547      3. Generate the data needed to write out the makefile in a
548         convenient way
549     All tests are *always* run, but some may be SKIP'd per the TAP standard
550    """
551    debug=False
552    execname=self.getExecname(exfile,root)
553    isBuilt=self._isBuilt(exfile,srcDict)
554    for test in srcDict:
555      if test in self.buildkeys: continue
556      if debug: print self.nameSpace(exfile,root), test
557      srcDict[test]['execname']=execname   # Convenience in generating scripts
558      isRun=self._isRun(srcDict[test])
559      self.genRunScript(test,root,isRun,srcDict)
560      srcDict[test]['isrun']=isRun
561      self.addToTests(test,root,exfile,execname,srcDict[test])
562
563    # This adds to datastructure for building deps
564    if isBuilt: self.addToSources(exfile,root,srcDict)
565    return
566
567  def _isBuilt(self,exfile,srcDict):
568    """
569    Determine if this file should be built.
570    """
571    # Get the language based on file extension
572    srcDict['SKIP'] = []
573    lang=self.getLanguage(exfile)
574    if (lang=="F" or lang=="F90"):
575      if not self.have_fortran:
576        srcDict["SKIP"].append("Fortran required for this test")
577      elif lang=="F90" and 'PETSC_USING_F90FREEFORM' not in self.conf:
578        srcDict["SKIP"].append("Fortran f90freeform required for this test")
579    if lang=="cu" and 'PETSC_HAVE_CUDA' not in self.conf:
580      srcDict["SKIP"].append("CUDA required for this test")
581    if lang=="cxx" and 'PETSC_HAVE_CXX' not in self.conf:
582      srcDict["SKIP"].append("C++ required for this test")
583
584    # Deprecated source files
585    if srcDict.get("TODO"):
586      return False
587
588    # isRun can work with srcDict to handle the requires
589    if "requires" in srcDict:
590      if srcDict["requires"]:
591        return self._isRun(srcDict)
592
593    return srcDict['SKIP'] == []
594
595
596  def _isRun(self,testDict):
597    """
598    Based on the requirements listed in the src file and the petscconf.h
599    info, determine whether this test should be run or not.
600    """
601    indent="  "
602    debug=False
603
604    if 'SKIP' not in testDict:
605      testDict['SKIP'] = []
606    # MPI requirements
607    if testDict.get('nsize',1)>1 and 'MPI_IS_MPIUNI' in self.conf:
608      if debug: print indent+"Cannot run parallel tests"
609      testDict['SKIP'].append("Parallel test with serial build")
610
611    # The requirements for the test are the sum of all the run subtests
612    if 'subtests' in testDict:
613      if 'requires' not in testDict: testDict['requires']=""
614      for stest in testDict['subtests']:
615        if 'requires' in testDict[stest]:
616          testDict['requires']+=" "+testDict[stest]['requires']
617
618
619    # Now go through all requirements
620    if 'requires' in testDict:
621      for requirement in testDict['requires'].split():
622        requirement=requirement.strip()
623        if not requirement: continue
624        if debug: print indent+"Requirement: ", requirement
625        isNull=False
626        if requirement.startswith("!"):
627          requirement=requirement[1:]; isNull=True
628        # Precision requirement for reals
629        if requirement in self.precision_types:
630          if self.conf['PETSC_PRECISION']==requirement:
631            if isNull:
632              testDict['SKIP'].append("not "+requirement+" required")
633              continue
634            continue  # Success
635          elif not isNull:
636            testDict['SKIP'].append(requirement+" required")
637            continue
638        # Precision requirement for ints
639        if requirement in self.integer_types:
640          if requirement=="int32":
641            if self.conf['PETSC_SIZEOF_INT']==4:
642              if isNull:
643                testDict['SKIP'].append("not int32 required")
644                continue
645              continue  # Success
646            elif not isNull:
647              testDict['SKIP'].append("int32 required")
648              continue
649          if requirement=="int64":
650            if self.conf['PETSC_SIZEOF_INT']==8:
651              if isNull:
652                testDict['SKIP'].append("NOT int64 required")
653                continue
654              continue  # Success
655            elif not isNull:
656              testDict['SKIP'].append("int64 required")
657              continue
658        # Datafilespath
659        if requirement=="datafilespath" and not isNull:
660          testDict['SKIP'].append("Requires DATAFILESPATH")
661          continue
662        # Defines -- not sure I have comments matching
663        if "define(" in requirement.lower():
664          reqdef=requirement.split("(")[1].split(")")[0]
665          if reqdef in self.conf:
666            if isNull:
667              testDict['SKIP'].append("Null requirement not met: "+requirement)
668              continue
669            continue  # Success
670          elif not isNull:
671            testDict['SKIP'].append("Required: "+requirement)
672            continue
673
674        # Rest should be packages that we can just get from conf
675        if requirement == "complex":
676          petscconfvar="PETSC_USE_COMPLEX"
677        else:
678          petscconfvar="PETSC_HAVE_"+requirement.upper()
679        if self.conf.get(petscconfvar):
680          if isNull:
681            testDict['SKIP'].append("Not "+petscconfvar+" requirement not met")
682            continue
683          continue  # Success
684        elif not isNull:
685          if debug: print "requirement not found: ", requirement
686          testDict['SKIP'].append(petscconfvar+" requirement not met")
687          continue
688
689    return testDict['SKIP'] == []
690
691  def genPetscTests_summarize(self,dataDict):
692    """
693    Required method to state what happened
694    """
695    if not self.summarize: return
696    indent="   "
697    print(self.testroot_dir, os.path.realpath(os.curdir))
698    fhname=os.path.join(self.testroot_dir,'GenPetscTests_summarize.txt')
699    fh=open(fhname,"w")
700    #print "See ", fhname
701    for root in dataDict:
702      relroot=self.srcrelpath(root)
703      pkg=relroot.split("/")[1]
704      fh.write(relroot+"\n")
705      allSrcs=[]
706      for lang in LANGS: allSrcs+=self.sources[pkg][lang]['srcs']
707      for exfile in dataDict[root]:
708        # Basic  information
709        rfile=os.path.join(relroot,exfile)
710        builtStatus=(" Is built" if rfile in allSrcs else " Is NOT built")
711        fh.write(indent+exfile+indent*4+builtStatus+"\n")
712
713        for test in dataDict[root][exfile]:
714          if test in self.buildkeys: continue
715          line=indent*2+test
716          fh.write(line+"\n")
717          # Looks nice to have the keys in order
718          #for key in dataDict[root][exfile][test]:
719          for key in "isrun abstracted nsize args requires script".split():
720            if key not in dataDict[root][exfile][test]: continue
721            line=indent*3+key+": "+str(dataDict[root][exfile][test][key])
722            fh.write(line+"\n")
723          fh.write("\n")
724        fh.write("\n")
725      fh.write("\n")
726    #fh.write("\nClass Sources\n"+str(self.sources)+"\n")
727    #fh.write("\nClass Tests\n"+str(self.tests)+"\n")
728    fh.close()
729    return
730
731  def genPetscTests(self,root,dirs,files,dataDict):
732    """
733     Go through and parse the source files in the directory to generate
734     the examples based on the metadata contained in the source files
735    """
736    debug=False
737    # Use examplesAnalyze to get what the makefles think are sources
738    #self.examplesAnalyze(root,dirs,files,anlzDict)
739
740    dataDict[root]={}
741
742    for exfile in files:
743      #TST: Until we replace files, still leaving the orginals as is
744      #if not exfile.startswith("new_"+"ex"): continue
745      #if not exfile.startswith("ex"): continue
746
747      # Ignore emacs files
748      if exfile.startswith("#") or exfile.startswith(".#"): continue
749
750      # Convenience
751      fullex=os.path.join(root,exfile)
752      if self.verbose: print('   --> '+fullex)
753      dataDict[root].update(testparse.parseTestFile(fullex,0))
754      if exfile in dataDict[root]:
755        self.genScriptsAndInfo(exfile,root,dataDict[root][exfile])
756
757    return
758
759  def walktree(self,top):
760    """
761    Walk a directory tree, starting from 'top'
762    """
763    #print "action", action
764    # Goal of action is to fill this dictionary
765    dataDict={}
766    for root, dirs, files in os.walk(top, topdown=False):
767      if not "examples" in root: continue
768      if not os.path.isfile(os.path.join(root,"makefile")): continue
769      if self.verbose: print(root)
770      bname=os.path.basename(root.rstrip("/"))
771      self.genPetscTests(root,dirs,files,dataDict)
772    # Now summarize this dictionary
773    self.genPetscTests_summarize(dataDict)
774    return dataDict
775
776  def gen_gnumake(self, fd):
777    """
778     Overwrite of the method in the base PETSc class
779    """
780    def write(stem, srcs):
781      for lang in LANGS:
782        if srcs[lang]['srcs']:
783          fd.write('%(stem)s.%(lang)s := %(srcs)s\n' % dict(stem=stem, lang=lang, srcs=' '.join(srcs[lang]['srcs'])))
784    for pkg in PKGS:
785        srcs = self.gen_pkg(pkg)
786        write('testsrcs-' + pkg, srcs)
787        # Handle dependencies
788        for lang in LANGS:
789            for exfile in srcs[lang]['srcs']:
790                if exfile in srcs[lang]:
791                    ex='$(TESTDIR)/'+os.path.splitext(exfile)[0]
792                    exfo='$(TESTDIR)/'+os.path.splitext(exfile)[0]+'.o'
793                    for dep in srcs[lang][exfile]:
794                        fd.write(exfile+": $(TESTDIR)/"+ dep+'\n')
795                        fd.write(ex    +": "+exfo+" $(TESTDIR)/"+dep +'\n')
796
797    return self.gendeps
798
799  def gen_pkg(self, pkg):
800    """
801     Overwrite of the method in the base PETSc class
802    """
803    return self.sources[pkg]
804
805  def write_gnumake(self,dataDict):
806    """
807     Write out something similar to files from gmakegen.py
808
809     Test depends on script which also depends on source
810     file, but since I don't have a good way generating
811     acting on a single file (oops) just depend on
812     executable which in turn will depend on src file
813    """
814    # Different options for how to set up the targets
815    compileExecsFirst=False
816
817    # Open file
818    arch_files = os.path.join(self.arch_dir,'lib','petsc','conf', 'testfiles')
819    fd = open(arch_files, 'w')
820
821    # Write out the sources
822    gendeps = self.gen_gnumake(fd)
823
824    # Write out the tests and execname targets
825    fd.write("\n#Tests and executables\n")    # Delimiter
826
827    for pkg in PKGS:
828      # These grab the ones that are built
829      for lang in LANGS:
830        testdeps=[]
831        for ftest in self.tests[pkg][lang]:
832          test=os.path.basename(ftest)
833          basedir=os.path.dirname(ftest)
834          testdeps.append(self.nameSpace(test,basedir))
835        fd.write("test-"+pkg+"."+lang+" := "+' '.join(testdeps)+"\n")
836        fd.write('test-%s.%s : $(test-%s.%s)\n' % (pkg, lang, pkg, lang))
837
838        # test targets
839        for ftest in self.tests[pkg][lang]:
840          test=os.path.basename(ftest)
841          basedir=os.path.dirname(ftest)
842          testdir="${TESTDIR}/"+basedir+"/"
843          nmtest=self.nameSpace(test,basedir)
844          rundir=os.path.join(testdir,test)
845          #print test, nmtest
846          script=test+".sh"
847
848          # Deps
849          exfile=self.tests[pkg][lang][ftest]['exfile']
850          fullex=os.path.join(os.path.dirname(self.srcdir),exfile)
851          localexec=self.tests[pkg][lang][ftest]['exec']
852          execname=os.path.join(testdir,localexec)
853          fullscript=os.path.join(testdir,script)
854          tmpfile=os.path.join(testdir,test,test+".tmp")
855
856          # *.counts depends on the script and either executable (will
857          # be run) or the example source file (SKIP or TODO)
858          fd.write('%s.counts : %s %s'
859              % (os.path.join('$(TESTDIR)/counts', nmtest),
860                 fullscript,
861                 execname if exfile in self.sources[pkg][lang]['srcs'] else fullex)
862              )
863          if exfile in self.sources[pkg][lang]:
864            for dep in self.sources[pkg][lang][exfile]:
865              fd.write(' %s' % os.path.join('$(TESTDIR)',dep))
866          fd.write('\n')
867
868          # Now write the args:
869          fd.write(nmtest+"_ARGS := '"+self.tests[pkg][lang][ftest]['argLabel']+"'\n")
870
871    fd.close()
872    return
873
874  def writeHarness(self,output,dataDict):
875    """
876     This is set up to write out multiple harness even if only gnumake
877     is supported now
878    """
879    eval("self.write_"+output+"(dataDict)")
880    return
881
882def main(petsc_dir=None, petsc_arch=None, output=None, verbose=False, single_ex=False, srcdir=None, testdir=None):
883    if output is None:
884        output = 'gnumake'
885
886    # Allow petsc_arch to have both petsc_dir and petsc_arch for convenience
887    if petsc_arch:
888        if len(petsc_arch.split(os.path.sep))>1:
889            petsc_dir,petsc_arch=os.path.split(petsc_arch.rstrip(os.path.sep))
890
891    pEx=generateExamples(petsc_dir=petsc_dir, petsc_arch=petsc_arch,
892                         verbose=verbose, single_ex=single_ex, srcdir=srcdir,
893                         testdir=testdir)
894    dataDict=pEx.walktree(os.path.join(pEx.srcdir))
895    pEx.writeHarness(output,dataDict)
896
897if __name__ == '__main__':
898    import optparse
899    parser = optparse.OptionParser()
900    parser.add_option('--verbose', help='Show mismatches between makefiles and the filesystem', action='store_true', default=False)
901    parser.add_option('--petsc-arch', help='Set PETSC_ARCH different from environment', default=os.environ.get('PETSC_ARCH'))
902    parser.add_option('--srcdir', help='Set location of sources different from PETSC_DIR/src', default=None)
903    parser.add_option('--output', help='Location to write output file', default=None)
904    parser.add_option('-s', '--single_executable', dest='single_executable', action="store_false", help='Whether there should be single executable per src subdir.  Default is false')
905    parser.add_option('-t', '--testdir', dest='testdir',  help='Test directory: PETSC_DIR/PETSC_ARCH/testdir.  Default is "tests"')
906    opts, extra_args = parser.parse_args()
907    opts, extra_args = parser.parse_args()
908    if extra_args:
909        import sys
910        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
911        exit(1)
912    main(petsc_arch=opts.petsc_arch, output=opts.output, verbose=opts.verbose,
913         single_ex=opts.single_executable, srcdir=opts.srcdir, testdir=opts.testdir)
914