xref: /petsc/config/gmakegentest.py (revision aa573868d1d60da4c7bb767e1b7f519ab7b30e26)
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, verbose=False, single_ex=False):
25    super(generateExamples, self).__init__(petsc_dir=None, petsc_arch=None, verbose=False)
26
27    self.single_ex=single_ex
28    self.arch_dir=os.path.join(self.petsc_dir,self.petsc_arch)
29    self.ptNaming=True
30    # Whether to write out a useful debugging
31    #if verbose: self.summarize=True
32    self.summarize=True
33
34    # For help in setting the requirements
35    self.precision_types="single double quad int32".split()
36    self.integer_types="int32 int64".split()
37    self.languages="fortran cuda cxx".split()    # Always requires C so do not list
38
39    # Things that are not test
40    self.buildkeys=testparse.buildkeys
41
42    # Adding a dictionary for storing sources, objects, and tests
43    # to make building the dependency tree easier
44    self.sources={}
45    self.objects={}
46    self.tests={}
47    for pkg in PKGS:
48      self.sources[pkg]={}
49      self.objects[pkg]=[]
50      self.tests[pkg]={}
51      for lang in LANGS:
52        self.sources[pkg][lang]={}
53        self.sources[pkg][lang]['srcs']=[]
54        self.tests[pkg][lang]={}
55
56    # Do some initialization
57    self.testroot_dir=os.path.join(self.arch_dir,"tests")
58    if not os.path.isdir(self.testroot_dir): os.makedirs(self.testroot_dir)
59    return
60
61  def nameSpace(self,srcfile,srcdir):
62    """
63    Because the scripts have a non-unique naming, the pretty-printing
64    needs to convey the srcdir and srcfile.  There are two ways of doing this.
65    """
66    if self.ptNaming:
67      cdir=srcdir.split('src')[1].lstrip("/").rstrip("/")
68      prefix=cdir.replace('/examples/','_').replace("/","_")+"-"
69      nameString=prefix+srcfile
70    else:
71      #nameString=srcdir+": "+srcfile
72      nameString=srcfile
73    return nameString
74
75  def getLanguage(self,srcfile):
76    """
77    Based on the source, determine associated language as found in gmakegen.LANGS
78    Can we just return srcext[1:\] now?
79    """
80    langReq=None
81    srcext=os.path.splitext(srcfile)[-1]
82    if srcext in ".F90".split(): langReq="F90"
83    if srcext in ".F".split(): langReq="F"
84    if srcext in ".cxx".split(): langReq="cxx"
85    if srcext == ".cu": langReq="cu"
86    if srcext == ".c": langReq="c"
87    if not langReq: print "ERROR: ", srcext, srcfile
88    return langReq
89
90  def getArgLabel(self,testDict):
91    """
92    In all of the arguments in the test dictionary, create a simple
93    string for searching within the makefile system.  For simplicity in
94    search, remove "-", for strings, etc.
95    Also, concatenate the arg commands
96    For now, ignore nsize -- seems hard to search for anyway
97    """
98    # Collect all of the args associated with a test
99    argStr=("" if not testDict.has_key('args') else testDict['args'])
100    if testDict.has_key('subtests'):
101      for stest in testDict["subtests"]:
102         sd=testDict[stest]
103         argStr=argStr+("" if not sd.has_key('args') else sd['args'])
104
105    # Now go through and cleanup
106    argStr=re.sub('{{(.*?)}}',"",argStr)
107    argStr=re.sub('-'," ",argStr)
108    for digit in string.digits: argStr=re.sub(digit," ",argStr)
109    argStr=re.sub("\.","",argStr)
110    argStr=re.sub(",","",argStr)
111    argStr=re.sub('\+',' ',argStr)
112    argStr=re.sub(' +',' ',argStr)  # Remove repeated white space
113    return argStr.strip()
114
115  def addToSources(self,exfile,root,srcDict):
116    """
117      Put into data structure that allows easy generation of makefile
118    """
119    pkg=self.relpath(self.petsc_dir,root).split("/")[1]
120    fullfile=os.path.join(root,exfile)
121    relpfile=self.relpath(self.petsc_dir,fullfile)
122    lang=self.getLanguage(exfile)
123    self.sources[pkg][lang]['srcs'].append(relpfile)
124    if srcDict.has_key('depends'):
125      depSrc=srcDict['depends']
126      depObj=os.path.splitext(depSrc)[0]+".o"
127      self.sources[pkg][lang][exfile]=depObj
128
129    # In gmakefile, ${TESTDIR} var specifies the object compilation
130    testsdir=self.relpath(self.petsc_dir,root)+"/"
131    objfile="${TESTDIR}/"+testsdir+os.path.splitext(exfile)[0]+".o"
132    self.objects[pkg].append(objfile)
133    return
134
135  def addToTests(self,test,root,exfile,execname,testDict):
136    """
137      Put into data structure that allows easy generation of makefile
138      Organized by languages to allow testing of languages
139    """
140    pkg=self.relpath(self.petsc_dir,root).split("/")[1]
141    #nmtest=self.nameSpace(test,root)
142    rpath=self.relpath(self.petsc_dir,root)
143    nmtest=os.path.join(rpath,test)
144    lang=self.getLanguage(exfile)
145    self.tests[pkg][lang][nmtest]={}
146    self.tests[pkg][lang][nmtest]['exfile']=os.path.join(rpath,exfile)
147    self.tests[pkg][lang][nmtest]['exec']=execname
148    self.tests[pkg][lang][nmtest]['argLabel']=self.getArgLabel(testDict)
149    return
150
151  def getFor(self,subst,i,j):
152    """
153      Get the for and done lines
154    """
155    forlines=""
156    donlines=""
157    indent="   "
158    nsizeStr=subst['nsize']
159    for loop in re.findall('{{(.*?)}}',subst['nsize']):
160      lindex=string.ascii_lowercase[i]
161      forline=indent*j+"for "+lindex+" in '"+loop+"'; do"
162      nsizeStr=re.sub("{{"+loop+"}}","${"+lindex+"}",nsizeStr)
163      donline=indent*j+"done"
164      forlines=forlines+forline+"\n"
165      donlines=donlines+donline+"\n"
166      i=i+1
167      j=j+1
168    subst['nsize']=nsizeStr
169    argStr=subst['args']
170    for loop in re.findall('{{(.*?)}}',subst['args']):
171      lindex=string.ascii_lowercase[i]
172      forline=indent*j+"for "+lindex+" in '"+loop+"'; do"
173      argStr=re.sub("{{"+loop+"}}","${"+lindex+"}",argStr)
174      donline=indent*j+"done"
175      forlines=forlines+forline+"\n"
176      donlines=donlines+donline+"\n"
177      i=i+1
178      j=j+1
179    subst['args']=argStr
180
181    # The do lines have reverse order with respect to indentation
182    dl=donlines.rstrip("\n").split("\n")
183    dl.reverse()
184    donlines="\n".join(dl)+"\n"
185
186    return forlines,donlines,i,j
187
188
189  def getExecname(self,exfile,root):
190    """
191      Generate bash script using template found next to this file.
192      This file is read in at constructor time to avoid file I/O
193    """
194    rpath=self.relpath(self.petsc_dir,root)
195    if self.single_ex:
196      execname=rpath.split("/")[1]+"-ex"
197    else:
198      execname=os.path.splitext(exfile)[0]
199    return execname
200
201  def getSubstVars(self,testDict,rpath,testname):
202    """
203      Create a dictionary with all of the variables that get substituted
204      into the template commands found in example_template.py
205      TODO: Cleanup
206    """
207    subst={}
208    # Handle defaults
209    if not testDict.has_key('nsize'): testDict['nsize']=1
210    if not testDict.has_key('filter'): testDict['filter']=""
211    if not testDict.has_key('filter_output'): testDict['filter_output']=""
212    if not testDict.has_key('localrunfiles'): testDict['localrunfiles']=""
213    if not testDict.has_key('args'): testDict['args']=""
214    defroot=(re.sub("run","",testname) if testname.startswith("run") else testname)
215    if not testDict.has_key('redirect_file'): testDict['redirect_file']=defroot+".tmp"
216    if not testDict.has_key('output_file'): testDict['output_file']="output/"+defroot+".out"
217
218    # Setup the variables in template_string that need to be substituted
219    subst['srcdir']=os.path.join(self.petsc_dir,rpath)
220    subst['label']=self.nameSpace(defroot,subst['srcdir'])
221    subst['redirect_file']=testDict['redirect_file']
222    subst['output_file']=os.path.join(subst['srcdir'],testDict['output_file'])
223    subst['exec']="../"+testDict['execname']
224    subst['filter']="'"+testDict['filter']+"'"   # Quotes are tricky
225    subst['filter_output']=testDict['filter_output']
226    subst['localrunfiles']=testDict['localrunfiles']
227    subst['testroot']=self.testroot_dir
228    subst['testname']=testname
229
230    # Be careful with this
231    if testDict.has_key('command'): subst['command']=testDict['command']
232
233    # These can have for loops and are treated separately later
234    if testDict.has_key('nsize'): subst['nsize']=str(testDict['nsize'])
235    if testDict.has_key('args'):  subst['args']=testDict['args']
236
237    #Conf vars
238    subst['mpiexec']=self.conf['MPIEXEC']  # make sure PETSC_DIR is defined!
239    subst['diff']=self.conf['DIFF']
240    subst['rm']=self.conf['RM']
241    subst['grep']=self.conf['GREP']
242
243    return subst
244
245  def getCmds(self,subst,i):
246    """
247      Generate bash script using template found next to this file.
248      This file is read in at constructor time to avoid file I/O
249    """
250    indent="   "
251    nindent=i # the start and has to be consistent with below
252    cmdLines=""
253    # MPI is the default -- but we have a few odd commands
254    if not subst.has_key('command'):
255      cmd=indent*nindent+self._substVars(subst,example_template.mpitest)
256    else:
257      cmd=indent*nindent+self._substVars(subst,example_template.commandtest)
258    cmdLines=cmdLines+cmd+"\n\n"
259
260    if not subst['filter_output']:
261      cmd=indent*nindent+self._substVars(subst,example_template.difftest)
262    else:
263      cmd=indent*nindent+self._substVars(subst,example_template.filterdifftest)
264    cmdLines=cmdLines+cmd+"\n"
265    return cmdLines
266
267  def _substVars(self,subst,origStr):
268    """
269      Substitute varial
270    """
271    Str=origStr
272    for subkey in subst:
273      if type(subst[subkey])!=types.StringType: continue
274      patt="@"+subkey.upper()+"@"
275      Str=re.sub(patt,subst[subkey],Str)
276    return Str
277
278  def genRunScript(self,testname,root,isRun,srcDict):
279    """
280      Generate bash script using template found next to this file.
281      This file is read in at constructor time to avoid file I/O
282    """
283    # runscript_dir directory has to be consistent with gmakefile
284    testDict=srcDict[testname]
285    rpath=self.relpath(self.petsc_dir,root)
286    runscript_dir=os.path.join(self.testroot_dir,rpath)
287    if not os.path.isdir(runscript_dir): os.makedirs(runscript_dir)
288    fh=open(os.path.join(runscript_dir,testname+".sh"),"w")
289    petscvarfile=os.path.join(self.arch_dir,'lib','petsc','conf','petscvariables')
290
291    subst=self.getSubstVars(testDict,rpath,testname)
292
293    #Handle runfiles
294    if subst['localrunfiles']:
295      for lfile in subst['localrunfiles'].split():
296        fullfile=os.path.join(self.petsc_dir,rpath,lfile)
297        shutil.copy(fullfile,runscript_dir)
298    # Check subtests for local runfiles
299    if testDict.has_key("subtests"):
300      for stest in testDict["subtests"]:
301        if testDict[stest].has_key('localrunfiles'):
302          for lfile in testDict[stest]['localrunfiles'].split():
303            fullfile=os.path.join(self.petsc_dir,rpath,lfile)
304            shutil.copy(fullfile,self.runscript_dir)
305
306    # Now substitute the key variables into the header and footer
307    header=self._substVars(subst,example_template.header)
308    footer=re.sub('@TESTROOT@',subst['testroot'],example_template.footer)
309
310    # Start writing the file
311    fh.write(header+"\n")
312
313    # If there is a TODO or a SKIP then we do it before writing out the
314    # rest of the command (which is useful for working on the test)
315    # SKIP and TODO can be for the source file or for the runs
316    if srcDict.has_key("SKIP") or srcDict.has_key("TODO"):
317      if srcDict.has_key("TODO"):
318        todo=re.sub("@TODOCOMMENT@",srcDict['TODO'],example_template.todoline)
319        fh.write(todo+"\ntotal=1; todo=1\n")
320        fh.write(footer+"\n")
321        fh.write("exit\n\n\n")
322      elif srcDict.has_key("SKIP") or srcDict.has_key("TODO"):
323        skip=re.sub("@SKIPCOMMENT@",srcDict['SKIP'],example_template.skipline)
324        fh.write(skip+"\ntotal=1; skip=1\n")
325        fh.write(footer+"\n")
326        fh.write("exit\n\n\n")
327    elif not isRun:
328      skip=re.sub("@SKIPCOMMENT@",testDict['SKIP'],example_template.skipline)
329      fh.write(skip+"\ntotal=1; skip=1\n")
330      fh.write(footer+"\n")
331      fh.write("exit\n\n\n")
332    elif testDict.has_key('TODO'):
333      todo=re.sub("@TODOCOMMENT@",testDict['TODO'],example_template.todoline)
334      fh.write(todo+"\ntotal=1; todo=1\n")
335      fh.write(footer+"\n")
336      fh.write("exit\n\n\n")
337
338    # Need to handle loops
339    i=8  # for loop counters
340    j=0  # for indentation
341
342    doForP=False
343    if "{{" in subst['args']+subst['nsize']:
344      doForP=True
345      flinesP,dlinesP,i,j=self.getFor(subst,i,j)
346      fh.write(flinesP+"\n")
347
348    # Subtests are special
349    if testDict.has_key("subtests"):
350      substP=subst   # Subtests can inherit args but be careful
351      if not substP.has_key("arg"): substP["arg"]=""
352      jorig=j
353      for stest in testDict["subtests"]:
354        j=jorig
355        subst=substP
356        subst.update(testDict[stest])
357        subst['nsize']=str(subst['nsize'])
358        if not testDict[stest].has_key('args'): testDict[stest]['args']=""
359        subst['args']=substP['args']+testDict[stest]['args']
360        doFor=False
361        if "{{" in subst['args']+subst['nsize']:
362          doFor=True
363          flines,dlines,i,j=self.getFor(subst,i,j)
364          fh.write(flines+"\n")
365        fh.write(self.getCmds(subst,j)+"\n")
366        if doFor: fh.write(dlines+"\n")
367    else:
368      fh.write(self.getCmds(subst,j)+"\n")
369      if doForP: fh.write(dlinesP+"\n")
370
371    fh.write(footer+"\n")
372    os.chmod(os.path.join(runscript_dir,testname+".sh"),0755)
373    return
374
375  def  genScriptsAndInfo(self,exfile,root,srcDict):
376    """
377    Generate scripts from the source file, determine if built, etc.
378     For every test in the exfile with info in the srcDict:
379      1. Determine if it needs to be run for this arch
380      2. Generate the script
381      3. Generate the data needed to write out the makefile in a
382         convenient way
383     All tests are *always* run, but some may be SKIP'd per the TAP standard
384    """
385    debug=False
386    fileIsTested=False
387    execname=self.getExecname(exfile,root)
388    isBuilt=self._isBuilt(exfile,srcDict)
389    for test in srcDict:
390      if test in self.buildkeys: continue
391      if debug: print self.nameSpace(exfile,root), test
392      srcDict[test]['execname']=execname   # Convenience in generating scripts
393      isRun=self._isRun(srcDict[test])
394      self.genRunScript(test,root,isRun,srcDict)
395      srcDict[test]['isrun']=isRun
396      if isRun: fileIsTested=True
397      self.addToTests(test,root,exfile,execname,srcDict[test])
398
399    # This adds to datastructure for building deps
400    if fileIsTested and isBuilt: self.addToSources(exfile,root,srcDict)
401    #print self.nameSpace(exfile,root), fileIsTested
402    return
403
404  def _isBuilt(self,exfile,srcDict):
405    """
406    Determine if this file should be built.
407    """
408    # Get the language based on file extension
409    lang=self.getLanguage(exfile)
410    if (lang=="F" or lang=="F90") and not self.have_fortran:
411      srcDict["SKIP"]="Fortran required for this test"
412      return False
413    if lang=="cu" and not self.conf.has_key('PETSC_HAVE_CUDA'):
414      srcDict["SKIP"]="CUDA required for this test"
415      return False
416    if lang=="cxx" and not self.conf.has_key('PETSC_HAVE_CXX'):
417      srcDict["SKIP"]="C++ required for this test"
418      return False
419
420    # Deprecated source files
421    if srcDict.has_key("TODO"): return False
422
423    # isRun can work with srcDict to handle the requires
424    if srcDict.has_key("requires"):
425      if len(srcDict["requires"])>0:
426        return self._isRun(srcDict)
427
428    return True
429
430
431  def _isRun(self,testDict):
432    """
433    Based on the requirements listed in the src file and the petscconf.h
434    info, determine whether this test should be run or not.
435    """
436    indent="  "
437    debug=False
438
439    # MPI requirements
440    if testDict.has_key('nsize'):
441      if testDict['nsize']>1 and self.conf.has_key('MPI_IS_MPIUNI'):
442        if debug: print indent+"Cannot run parallel tests"
443        testDict['SKIP']="Parallel test with serial build"
444        return False
445
446    # The requirements for the test are the sum of all the run subtests
447    if testDict.has_key('subtests'):
448      if not testDict.has_key('requires'): testDict['requires']=""
449      for stest in testDict['subtests']:
450        if testDict[stest].has_key('requires'):
451          testDict['requires']=testDict['requires']+" "+testDict[stest]['requires']
452
453
454    # Now go through all requirements
455    if testDict.has_key('requires'):
456      for requirement in testDict['requires'].split():
457        requirement=requirement.strip()
458        if not requirement: continue
459        if debug: print indent+"Requirement: ", requirement
460        isNull=False
461        if requirement.startswith("!"):
462          requirement=requirement[1:]; isNull=True
463        # Precision requirement for reals
464        if requirement in self.precision_types:
465          if self.conf['PETSC_PRECISION']==requirement:
466            testDict['SKIP']="not "+requirement+" required"
467            if isNull: return False
468          else:
469            testDict['SKIP']=requirement+" required"
470            return False
471        # Precision requirement for ints
472        if requirement in self.integer_types:
473          if requirement=="int32":
474            if self.conf['PETSC_SIZEOF_INT']==4:
475              testDict['SKIP']="not int32 required"
476              if isNull: return False
477            else:
478              testDict['SKIP']="int32 required"
479              return False
480          if requirement=="int64":
481            if self.conf['PETSC_SIZEOF_INT']==8:
482              testDict['SKIP']="NOT int64 required"
483              if isNull: return False
484            else:
485              testDict['SKIP']="int64 required"
486              return False
487        # Datafilespath
488        if requirement=="datafilespath":
489          testDict['SKIP']="Requires DATAFILESPATH"
490          return False
491        # Defines -- not sure I have comments matching
492        if "define(" in requirement.lower():
493          reqdef=requirement.split("(")[1].split(")")[0]
494          val=(reqdef.split()[1] if " " in reqdef else "")
495          if self.conf.has_key(reqdef):
496            if val:
497              if self.conf[reqdef]==val:
498                if isNull:
499                  testDict['SKIP']="Null requirement not met: "+requirement
500                  return False
501              else:
502                testDict['SKIP']="Required: "+requirement
503                return False
504            else:
505              if isNull:
506                testDict['SKIP']="Null requirement not met: "+requirement
507                return False
508              else:
509                return True
510          else:
511            testDict['SKIP']="Requirement not met: "+requirement
512            return False
513
514        # Rest should be packages that we can just get from conf
515        if requirement == "complex":  petscconfvar="PETSC_USE_COMPLEX"
516        else:   petscconfvar="PETSC_HAVE_"+requirement.upper()
517        if self.conf.get(petscconfvar):
518          if isNull:
519            testDict['SKIP']="Not "+petscconfvar+" requirement not met"
520            return False
521        elif not isNull:
522          if debug: print "requirement not found: ", requirement
523          testDict['SKIP']=petscconfvar+" requirement not met"
524          return False
525
526    return True
527
528  def genPetscTests_summarize(self,dataDict):
529    """
530    Required method to state what happened
531    """
532    if not self.summarize: return
533    indent="   "
534    fhname="GenPetscTests_summarize.txt"
535    fh=open(fhname,"w")
536    #print "See ", fhname
537    for root in dataDict:
538      relroot=self.relpath(self.petsc_dir,root)
539      pkg=relroot.split("/")[1]
540      fh.write(relroot+"\n")
541      allSrcs=[]
542      for lang in LANGS: allSrcs=allSrcs+self.sources[pkg][lang]['srcs']
543      for exfile in dataDict[root]:
544        # Basic  information
545        fullfile=os.path.join(root,exfile)
546        rfile=self.relpath(self.petsc_dir,fullfile)
547        builtStatus=(" Is built" if rfile in allSrcs else " Is NOT built")
548        fh.write(indent+exfile+indent*4+builtStatus+"\n")
549
550        for test in dataDict[root][exfile]:
551          if test in self.buildkeys: continue
552          line=indent*2+test
553          fh.write(line+"\n")
554          # Looks nice to have the keys in order
555          #for key in dataDict[root][exfile][test]:
556          for key in "isrun abstracted nsize args requires script".split():
557            if not dataDict[root][exfile][test].has_key(key): continue
558            line=indent*3+key+": "+str(dataDict[root][exfile][test][key])
559            fh.write(line+"\n")
560          fh.write("\n")
561        fh.write("\n")
562      fh.write("\n")
563    #fh.write("\nClass Sources\n"+str(self.sources)+"\n")
564    #fh.write("\nClass Tests\n"+str(self.tests)+"\n")
565    fh.close()
566    return
567
568  def genPetscTests(self,root,dirs,files,dataDict):
569    """
570     Go through and parse the source files in the directory to generate
571     the examples based on the metadata contained in the source files
572    """
573    debug=False
574    # Use examplesAnalyze to get what the makefles think are sources
575    #self.examplesAnalyze(root,dirs,files,anlzDict)
576
577    dataDict[root]={}
578
579    for exfile in files:
580      #TST: Until we replace files, still leaving the orginals as is
581      #if not exfile.startswith("new_"+"ex"): continue
582      if not exfile.startswith("ex"): continue
583
584      # Convenience
585      fullex=os.path.join(root,exfile)
586      relpfile=self.relpath(self.petsc_dir,fullex)
587      if debug: print relpfile
588      dataDict[root].update(testparse.parseTestFile(fullex))
589      # Need to check and make sure tests are in the file
590      # if verbosity>=1: print relpfile
591      if dataDict[root].has_key(exfile):
592        self.genScriptsAndInfo(exfile,root,dataDict[root][exfile])
593
594    return
595
596  def walktree(self,top,action="printFiles"):
597    """
598    Walk a directory tree, starting from 'top'
599    """
600    #print "action", action
601    # Goal of action is to fill this dictionary
602    dataDict={}
603    for root, dirs, files in os.walk(top, topdown=False):
604      if not "examples" in root: continue
605      if not os.path.isfile(os.path.join(root,"makefile")): continue
606      bname=os.path.basename(root.rstrip("/"))
607      if bname=="tests" or bname=="tutorials":
608        eval("self."+action+"(root,dirs,files,dataDict)")
609      if type(top) != types.StringType:
610          raise TypeError("top must be a string")
611    # Now summarize this dictionary
612    eval("self."+action+"_summarize(dataDict)")
613    return dataDict
614
615  def gen_gnumake(self, fd,prefix='srcs-'):
616    """
617     Overwrite of the method in the base PETSc class
618    """
619    def write(stem, srcs):
620        fd.write('%s :=\n' % stem)
621        for lang in LANGS:
622            fd.write('%(stem)s.%(lang)s := %(srcs)s\n' % dict(stem=stem, lang=lang, srcs=' '.join(srcs[lang]['srcs'])))
623            fd.write('%(stem)s += $(%(stem)s.%(lang)s)\n' % dict(stem=stem, lang=lang))
624    for pkg in PKGS:
625        srcs = self.gen_pkg(pkg)
626        write(prefix + pkg, srcs)
627    return self.gendeps
628
629  def gen_pkg(self, pkg):
630    """
631     Overwrite of the method in the base PETSc class
632    """
633    return self.sources[pkg]
634
635  def write_gnumake(self,dataDict):
636    """
637     Write out something similar to files from gmakegen.py
638
639     There is not a lot of has_key type checking because
640     should just work and need to know if there are bugs
641
642     Test depends on script which also depends on source
643     file, but since I don't have a good way generating
644     acting on a single file (oops) just depend on
645     executable which in turn will depend on src file
646    """
647    # Different options for how to set up the targets
648    compileExecsFirst=False
649    alltargets=""
650
651    # Open file
652    arch_files = self.arch_path('lib','petsc','conf', 'testfiles')
653    arg_files = self.arch_path('lib','petsc','conf', 'testargfiles')
654    fd = open(arch_files, 'w')
655    fa = open(arg_files, 'w')
656
657    # Write out the sources
658    gendeps = self.gen_gnumake(fd,prefix="testsrcs-")
659
660    # Write out the tests and execname targets
661    fd.write("\n#Tests and executables\n")    # Delimiter
662    fd.write("test: testinit testex testpkgs report_tests\n")    # Main test target
663    testdeps=" ".join(["test-"+pkg for pkg in PKGS])
664    testexdeps=" ".join(["test-ex-"+pkg for pkg in PKGS])
665    fd.write("testpkgs: "+testdeps+"\n")    # The test for the pkgs
666    # Testinit handles the logging
667    fd.write("testinit:\n")
668    fd.write("\t-@rm -f ${PETSC_ARCH}/tests/test.log\n")
669    fd.write("\t-@touch ${PETSC_ARCH}/tests/test.log\n")
670    # I like compiling executables first, but this is problematic for
671    # systems that static compile with limited disk space. Somewhat
672    # redundant
673    if compileExecsFirst:
674      fd.write("testex: "+testexdeps+"\n")    # Main test target
675    else:
676      fd.write("testex: \n")    # Main test target
677
678    for pkg in PKGS:
679      # These grab the ones that are built
680      # Package tests
681      testdeps=" ".join(["test-"+pkg+"-"+lang for lang in LANGS])
682      fd.write("test-"+pkg+": "+testdeps+"\n")
683      testexdeps=" ".join(["test-ex-"+pkg+"-"+lang for lang in LANGS])
684      fd.write("test-ex-"+pkg+": "+testexdeps+"\n")
685      # This needs work
686      if self.single_ex:
687        execname=pkg+"-ex"
688        fd.write(execname+": "+" ".join(self.objects[pkg])+"\n\n")
689      for lang in LANGS:
690        testdeps=""
691        for ftest in self.tests[pkg][lang]:
692          test=os.path.basename(ftest)
693          basedir=os.path.dirname(ftest)
694          testdeps=testdeps+" "+self.nameSpace(test,basedir)
695        fd.write("test-"+pkg+"-"+lang+":"+testdeps+"\n")
696        alltargets=alltargets+testdeps+" "
697
698        # test targets
699        for ftest in self.tests[pkg][lang]:
700          test=os.path.basename(ftest)
701          basedir=os.path.dirname(ftest)
702          testdir="${TESTDIR}/"+basedir+"/"
703          nmtest=self.nameSpace(test,basedir)
704          rundir=os.path.join(testdir,test)
705          #print test, nmtest
706          script=test+".sh"
707
708          # Deps
709          exfile=self.tests[pkg][lang][ftest]['exfile']
710          fullex=os.path.join(self.petsc_dir,exfile)
711          localexec=self.tests[pkg][lang][ftest]['exec']
712          execname=os.path.join(testdir,localexec)
713          fullscript=os.path.join(testdir,script)
714          tmpfile=os.path.join(testdir,test,test+".tmp")
715
716          # SKIP and TODO tests do not depend on exec
717          if exfile in self.sources[pkg][lang]['srcs']:
718            #print "Found dep: "+exfile, execname
719            #fd.write(nmtest+": "+execname+"\n")
720
721            # Can't get implicit rules for running the scripts working
722            fd.write(nmtest+": "+execname+" "+tmpfile+"\n")
723            #fd.write(nmtest+": "+execname+" "+fullscript+"\n")
724          else:
725            # Still add dependency to file
726            #fd.write(nmtest+": "+fullex+" "+"\n")
727            # Can't get implicit rules for running the scripts working
728            fd.write(nmtest+": "+fullex+" "+tmpfile+"\n")
729            #fd.write(nmtest+": "+fullex+" "+fullscript+"\n")
730          cmd=fullscript+" ${TESTFLAGS}"
731          # Work on enabling implicit rules for running the script
732          # Can't get implicit rules for running the scripts working
733          #fd.write(".PHONY: "+fullscript+"\n")
734          fd.write(tmpfile+":\n")
735          fd.write("\t-@"+cmd+"\n")
736          # Now write the args:
737          fa.write(nmtest+"_ARGS='"+self.tests[pkg][lang][ftest]['argLabel']+"'\n")
738    fd.write("alltesttargets='"+alltargets+"'\n")
739    fd.write("helptests:\n\t -@grep '^[a-z]' ${generatedtest} | cut -f1 -d:\n")
740    # Write out tests
741    return
742
743  def writeHarness(self,output,dataDict):
744    """
745     This is set up to write out multiple harness even if only gnumake
746     is supported now
747    """
748    eval("self.write_"+output+"(dataDict)")
749    return
750
751def main(petsc_dir=None, petsc_arch=None, output=None, verbose=False, single_ex=False):
752    if output is None:
753        output = 'gnumake'
754
755
756    pEx=generateExamples(petsc_dir=petsc_dir, petsc_arch=petsc_arch, verbose=verbose, single_ex=single_ex)
757    dataDict=pEx.walktree(os.path.join(pEx.petsc_dir,'src'),action="genPetscTests")
758    pEx.writeHarness(output,dataDict)
759
760if __name__ == '__main__':
761    import optparse
762    parser = optparse.OptionParser()
763    parser.add_option('--verbose', help='Show mismatches between makefiles and the filesystem', action='store_true', default=False)
764    parser.add_option('--petsc-arch', help='Set PETSC_ARCH different from environment', default=os.environ.get('PETSC_ARCH'))
765    parser.add_option('--output', help='Location to write output file', default=None)
766    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')
767    opts, extra_args = parser.parse_args()
768    if extra_args:
769        import sys
770        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
771        exit(1)
772    main(petsc_arch=opts.petsc_arch, output=opts.output, verbose=opts.verbose, single_ex=opts.single_executable)
773