xref: /petsc/config/PETSc/petsc.py (revision 55b0329ccc0dccfffe4a1aa6e4e68d35d0fa021e)
1#!/usr/bin/env python
2'''
3  This is the first try for a hierarchically configured module. The idea is to
4add the configure objects from a previously executed framework into the current
5framework. However, this necessitates a reorganization of the activities in the
6module.
7
8  We must now have three distinct phases: location, construction, and testing.
9This is very similar to the current compiler checks. The construction phase is
10optional, and only necessary when the package has not been previously configured.
11The phases will necessarily interact, as an installtion must be located before
12testing, however anothe should be located if the testing fails.
13
14  We will give each installation a unique key, which is returned by the location
15method. This will allow us to identify working installations, as well as those
16that failed testing.
17
18  There is a wierd role reversal that can happen. If we look for PETSc, but
19cannot find it, it is reasonable to ask to have it automatically downloaded.
20However, in this case, rather than using the configure objects from the existing
21PETSc, we contribute objects to the PETSc which will be built.
22
23'''
24from __future__ import generators
25import user
26import config.base
27
28import re
29import os
30
31class InvalidPETScError(RuntimeError):
32  pass
33
34class Configure(config.base.Configure):
35  def __init__(self, framework):
36    config.base.Configure.__init__(self, framework)
37    self.headerPrefix = ''
38    self.substPrefix  = ''
39    self.location     = None
40    self.trial        = {}
41    self.working      = {}
42    return
43
44  def __str__(self):
45    if self.found:
46      desc = ['PETSc:']
47      desc.append('  Type: '+self.name)
48      desc.append('  Version: '+self.version)
49      desc.append('  Includes: '+str(self.include))
50      desc.append('  Library: '+str(self.lib))
51      return '\n'.join(desc)+'\n'
52    else:
53      return ''
54
55  def setupHelp(self, help):
56    import nargs
57    help.addArgument('PETSc', '-with-petsc=<bool>',                nargs.ArgBool(None, 1, 'Activate PETSc'))
58    # Location options
59    help.addArgument('PETSc', '-with-petsc-dir=<root dir>',        nargs.ArgDir(None, None, 'Specify the root directory of the PETSc installation'))
60    help.addArgument('PETSc', '-with-petsc-arch=<arch>',           nargs.Arg(None, None, 'Specify PETSC_ARCH'))
61    # Construction options
62    help.addArgument('PETSc', '-download-petsc=<bool>',          nargs.ArgBool(None, 0, 'Install PETSc'))
63    # Testing options
64    help.addArgument('PETSc', '-with-petsc-shared=<bool>',         nargs.ArgBool(None, 1, 'Require that the PETSc library be shared'))
65    return
66
67  def setupPackageDependencies(self, framework):
68    import sys
69
70    petscConf = None
71    for (name, (petscDir, petscArch)) in self.getLocations():
72      petscPythonDir = os.path.join(petscDir, 'config')
73      sys.path.append(petscPythonDir)
74      confPath = os.path.join(petscDir, petscArch,'lib','petsc','conf')
75      petscConf = framework.loadFramework(confPath)
76      if petscConf:
77        self.logPrint('Loaded PETSc-AS configuration ('+name+') from '+confPath)
78        self.location = (petscDir, petscArch)
79        self.trial[self.location] = name
80        break
81      else:
82        self.logPrint('PETSc-AS has no cached configuration in '+confPath)
83        sys.path.reverse()
84        sys.path.remove(petscPythonDir)
85        sys.path.reverse()
86    if not petscConf:
87      self.downloadPETSc()
88    framework.addPackageDependency(petscConf, confPath)
89    return
90
91  def setupDependencies(self, framework):
92    config.base.Configure.setupDependencies(self, framework)
93    self.languages  = framework.require('PETSc.options.languages', self)
94    self.compilers  = framework.require('config.compilers', self)
95    self.headers    = framework.require('config.headers', self)
96    self.libraries  = framework.require('config.libraries', self)
97    self.blaslapack = framework.require('config.packages.BlasLapack', self)
98    self.mpi        = framework.require('config.packages.MPI', self)
99    return
100
101  def getPETScArch(self, petscDir):
102    '''Return the allowable PETSc architectures for a given root'''
103    if 'with-petsc-arch' in self.framework.argDB:
104      yield self.framework.argDB['with-petsc-arch']
105    elif 'PETSC_ARCH' in os.environ:
106      yield os.environ['PETSC_ARCH']
107    else:
108      raise InvalidPETScError('Must set PETSC_ARCH or use --with-petsc-arch')
109    return
110
111  def getLocations(self):
112    '''Return all allowable locations for PETSc'''
113    if hasattr(self, '_configured'):
114      key =(self.dir, self.arch)
115      yield (self.working[key], key)
116      raise InvalidPETScError('Configured PETSc is not usable')
117    if self.framework.argDB['download-petsc'] == 1:
118      yield self.downloadPETSc()
119      raise InvalidPETScError('Downloaded PETSc is not usable')
120    if 'with-petsc-dir' in self.framework.argDB:
121      petscDir = self.framework.argDB['with-petsc-dir']
122      for petscArch in self.getPETScArch(petscDir):
123        yield ('User specified installation root', (petscDir, petscArch))
124      raise InvalidPETScError('No working architecitures in '+str(petscDir))
125    elif 'PETSC_DIR' in os.environ:
126      petscDir = os.environ['PETSC_DIR']
127      for petscArch in self.getPETScArch(petscDir):
128        yield ('User specified installation root', (petscDir, petscArch))
129      raise InvalidPETScError('No working architecitures in '+str(petscDir))
130    else:
131      for petscArch in self.getPETScArch(petscDir):
132        yield ('Default compiler locations', ('', petscArch))
133      petscDirRE = re.compile(r'(PETSC|pets)c(-.*)?')
134      trialDirs = []
135      for packageDir in self.framework.argDB['package-dirs']:
136        if os.path.isdir(packageDir):
137          for d in os.listdir(packageDir):
138            if petscDirRE.match(d):
139              trialDirs.append(('Package directory installation root', os.path.join(packageDir, d)))
140      usrLocal = os.path.join('/usr', 'local')
141      if os.path.isdir(os.path.join('/usr', 'local')):
142        trialDirs.append(('Frequent user install location (/usr/local)', usrLocal))
143        for d in os.listdir(usrLocal):
144          if petscDirRE.match(d):
145            trialDirs.append(('Frequent user install location (/usr/local/'+d+')', os.path.join(usrLocal, d)))
146      if 'HOME' in os.environ and os.path.isdir(os.environ['HOME']):
147        for d in os.listdir(os.environ['HOME']):
148          if petscDirRE.match(d):
149            trialDirs.append(('Frequent user install location (~/'+d+')', os.path.join(os.environ['HOME'], d)))
150    return
151
152  def downloadPETSc(self):
153    if self.framework.argDB['download-petsc'] == 0:
154      raise RuntimeError('No functioning PETSc located')
155    # Download and build PETSc
156    #   Use only the already configured objects from this run
157    raise RuntimeError('Not implemented')
158
159  def getDir(self):
160    if self.location:
161      return self.location[0]
162    return None
163  dir = property(getDir, doc = 'The PETSc root directory')
164
165  def getArch(self):
166    if self.location:
167      return self.location[1]
168    return None
169  arch = property(getArch, doc = 'The PETSc architecture')
170
171  def getFound(self):
172    return self.location and self.location in self.working
173  found = property(getFound, doc = 'Did we find a valid PETSc installation')
174
175  def getName(self):
176    if self.location and self.location in self.working:
177      return self.working[self.location][0]
178    return None
179  name = property(getName, doc = 'The PETSc installation type')
180
181  def getInclude(self, useTrial = 0):
182    if self.location and self.location in self.working:
183      return self.working[self.location][1]
184    elif useTrial and self.location and self.location in self.trial:
185      return self.trial[self.location][1]
186    return None
187  include = property(getInclude, doc = 'The PETSc include directories')
188
189  def getLib(self, useTrial = 0):
190    if self.location and self.location in self.working:
191      return self.working[self.location][2]
192    elif useTrial and self.location and self.location in self.trial:
193      return self.trial[self.location][2]
194    return None
195  lib = property(getLib, doc = 'The PETSc libraries')
196
197  def getVersion(self):
198    if self.location and self.location in self.working:
199      return self.working[self.location][3]
200    return None
201  version = property(getVersion, doc = 'The PETSc version')
202
203  def getOtherIncludes(self):
204    if not hasattr(self, '_otherIncludes'):
205      includes = []
206      includes.extend([self.headers.getIncludeArgument(inc) for inc in self.mpi.include])
207      return ' '.join(includes)
208    return self._otherIncludes
209  def setOtherIncludes(self, otherIncludes):
210    self._otherIncludes = otherIncludes
211  otherIncludes = property(getOtherIncludes, setOtherIncludes, doc = 'Includes needed to compile PETSc')
212
213  def getOtherLibs(self):
214    if not hasattr(self, '_otherLibs'):
215      libs = self.compilers.flibs[:]
216      libs.extend(self.mpi.lib)
217      libs.extend(self.blaslapack.lib)
218      return libs
219    return self._otherLibs
220  def setOtherLibs(self, otherLibs):
221    self._otherLibs = otherLibs
222  otherLibs = property(getOtherLibs, setOtherLibs, doc = 'Libraries needed to link PETSc')
223
224  def checkLib(self, libraries):
225    '''Check for PETSc creation functions in libraries, which can be a list of libraries or a single library
226       - PetscInitialize from libpetsc
227       - VecCreate from libpetscvec
228       - MatCreate from libpetscmat
229       - DMDestroy from libpetscdm
230       - KSPCreate from libpetscksp
231       - SNESCreate from libpetscsnes
232       - TSCreate from libpetscts
233       '''
234    if not isinstance(libraries, list): libraries = [libraries]
235    oldLibs = self.compilers.LIBS
236    self.libraries.pushLanguage(self.languages.clanguage)
237    found   = (self.libraries.check(libraries, 'PetscInitializeNoArguments', otherLibs = self.otherLibs, prototype = 'int PetscInitializeNoArguments(void);') and
238               self.libraries.check(libraries, 'VecDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_Vec *Vec;int VecDestroy(Vec*);', call = 'VecDestroy((Vec*) 0)') and
239               self.libraries.check(libraries, 'MatDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_Mat *Mat;int MatDestroy(Mat*);', call = 'MatDestroy((Mat*) 0)') and
240               self.libraries.check(libraries, 'DMDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_DM *DA;int DMDestroy(DA*);', call = 'DMDestroy((DA*) 0)') and
241               self.libraries.check(libraries, 'KSPDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_KSP *KSP;int KSPDestroy(KSP*);', call = 'KSPDestroy((KSP*) 0)') and
242               self.libraries.check(libraries, 'SNESDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_SNES *SNES;int SNESDestroy(SNES*);', call = 'SNESDestroy((SNES*) 0)') and
243               self.libraries.check(libraries, 'TSDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_TS *TS;int TSDestroy(TS*);', call = 'TSDestroy((TS*) 0)'))
244    self.libraries.popLanguage()
245    self.compilers.LIBS = oldLibs
246    return found
247
248  def checkInclude(self, includeDir):
249    '''Check that petscsys.h is present'''
250    oldFlags = self.compilers.CPPFLAGS
251    self.compilers.CPPFLAGS += ' '.join([self.headers.getIncludeArgument(inc) for inc in includeDir])
252    if self.otherIncludes:
253      self.compilers.CPPFLAGS += ' '+self.otherIncludes
254    self.pushLanguage(self.languages.clanguage)
255    found = self.checkPreprocess('#include <petscsys.h>\n')
256    self.popLanguage()
257    self.compilers.CPPFLAGS = oldFlags
258    return found
259
260  def checkPETScLink(self, includes, body, cleanup = 1, codeBegin = None, codeEnd = None, shared = None):
261    '''Analogous to checkLink(), but the PETSc includes and libraries are automatically provided'''
262    success  = 0
263    oldFlags = self.compilers.CPPFLAGS
264    self.compilers.CPPFLAGS += ' '.join([self.headers.getIncludeArgument(inc) for inc in self.getInclude(useTrial = 1)])
265    if self.otherIncludes:
266      self.compilers.CPPFLAGS += ' '+self.otherIncludes
267    oldLibs  = self.compilers.LIBS
268    self.compilers.LIBS = ' '.join([self.libraries.getLibArgument(lib) for lib in self.getLib(useTrial = 1)+self.otherLibs])+' '+self.compilers.LIBS
269    if self.checkLink(includes, body, cleanup, codeBegin, codeEnd, shared):
270      success = 1
271    self.compilers.CPPFLAGS = oldFlags
272    self.compilers.LIBS     = oldLibs
273    return success
274
275  def checkWorkingLink(self):
276    '''Checking that we can link a PETSc executable'''
277    self.pushLanguage(self.languages.clanguage)
278    if not self.checkPETScLink('#include <petsctime.h>\n', 'PetscLogDouble time;\nPetscErrorCode ierr;\n\nierr = PetscTime(&time);CHKERRQ(ierr);\n'):
279      self.logPrint('PETSc cannot link, which indicates a problem with the PETSc installation')
280      return 0
281    self.logPrint('PETSc can link with '+self.languages.clanguage)
282    self.popLanguage()
283
284    if hasattr(self.compilers, 'CXX') and self.languages.clanguage == 'C':
285      self.pushLanguage('C++')
286      self.sourceExtension = '.C'
287      if not self.checkPETScLink('#include <petsctime.h>\n', 'PetscLogDouble time;\nPetscErrorCode ierr;\n\nierr = PetscTime(&time);CHKERRQ(ierr);\n'):
288        self.logPrint('PETSc cannot link C++ but can link C, which indicates a problem with the PETSc installation')
289        self.popLanguage()
290        return 0
291      self.popLanguage()
292      self.logPrint('PETSc can link with C++')
293
294    if hasattr(self.compilers, 'FC'):
295      self.pushLanguage('FC')
296      self.sourceExtension = '.F'
297      if not self.checkPETScLink('', '          integer ierr\n          real time\n          call PetscTime(time, ierr)\n'):
298        self.logPrint('PETSc cannot link Fortran, but can link C, which indicates a problem with the PETSc installation\nRun with -with-fc=0 if you do not wish to use Fortran')
299        self.popLanguage()
300        return 0
301      self.popLanguage()
302      self.logPrint('PETSc can link with Fortran')
303    return 1
304
305  def checkSharedLibrary(self, libraries):
306    '''Check that the libraries for PETSc are shared libraries'''
307    if config.setCompilers.Configure.isDarwin(self.log):
308      # on Apple if you list the MPI libraries again you will generate multiply defined errors
309      # since they are already copied into the PETSc dynamic library.
310      self.setOtherLibs([])
311    self.pushLanguage(self.languages.clanguage)
312    isShared = self.libraries.checkShared('#include <petscsys.h>\n', 'PetscInitialize', 'PetscInitialized', 'PetscFinalize', checkLink = self.checkPETScLink, libraries = libraries, initArgs = '&argc, &argv, 0, 0', boolType = 'PetscBool ', executor = self.mpi.mpiexec)
313    self.popLanguage()
314    return isShared
315
316  def configureVersion(self):
317    '''Determine the PETSc version'''
318    majorRE    = re.compile(r'^#define PETSC_VERSION_MAJOR([\s]+)(?P<versionNum>\d+)[\s]*$');
319    minorRE    = re.compile(r'^#define PETSC_VERSION_MINOR([\s]+)(?P<versionNum>\d+)[\s]*$');
320    subminorRE = re.compile(r'^#define PETSC_VERSION_SUBMINOR([\s]+)(?P<versionNum>\d+)[\s]*$');
321    dateRE     = re.compile(r'^#define PETSC_VERSION_DATE([\s]+)"(?P<date>(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d\d?, \d\d\d\d)"[\s]*$');
322    input   = file(os.path.join(self.dir, 'include', 'petscversion.h'))
323    lines   = []
324    majorNum = 'Unknown'
325    minorNum = 'Unknown'
326    subminorNum = 'Unknown'
327    self.date = 'Unknown'
328    for line in input.readlines():
329      m1 = majorRE.match(line)
330      m2 = minorRE.match(line)
331      m3 = subminorRE.match(line)
332      m5 = dateRE.match(line)
333      if m1:
334        majorNum = int(m1.group('versionNum'))
335      elif m2:
336        minorNum = int(m2.group('versionNum'))
337      elif m3:
338        subminorNum = int(m3.group('versionNum'))
339
340      if m5:
341        self.date = time.strftime('%b %d, %Y', time.localtime(time.time()))
342        lines.append('#define PETSC_VERSION_DATE'+m5.group(1)+'"'+self.date+'"\n')
343      else:
344        lines.append(line)
345    input.close()
346    self.logPrint('Found PETSc version (%s,%s,%s) on %s' % (majorNum, minorNum, subminorNum, self.date))
347    return '%d.%d.%d' % (majorNum, minorNum, subminorNum)
348
349  def includeGuesses(self, path = None):
350    '''Return all include directories present in path or its ancestors'''
351    if not path:
352      yield []
353    while path:
354      dir = os.path.join(path, 'include')
355      if os.path.isdir(dir):
356        yield [dir, os.path.join(path, self.arch,'include')]
357      if path == '/':
358        return
359      path = os.path.dirname(path)
360    return
361
362  def libraryGuesses(self, root = None):
363    '''Return standard library name guesses for a given installation root'''
364    libs = ['ts', 'snes', 'ksp', 'dm', 'mat', 'vec', '']
365    if root:
366      d = os.path.join(root, 'lib', self.arch)
367      if not os.path.isdir(d):
368        self.logPrint('', 3, 'petsc')
369        return
370      yield [os.path.join(d, 'libpetsc'+lib+'.a') for lib in libs]
371    else:
372      yield ['libpetsc'+lib+'.a' for lib in libs]
373    return
374
375  def configureLibrary(self):
376    '''Find a working PETSc'''
377    for location, name in self.trial.items():
378      self.framework.logPrintDivider()
379      self.framework.logPrint('Checking for a functional PETSc in '+name+', location/origin '+str(location))
380      lib     = None
381      include = None
382      found   = 0
383      for libraries in self.libraryGuesses(location[0]):
384        if self.checkLib(libraries):
385          lib = libraries
386          for includeDir in self.includeGuesses(location[0]):
387            if self.checkInclude(includeDir):
388              include = includeDir
389              self.trial[location] = (name, include, lib, 'Unknown')
390              if self.executeTest(self.checkWorkingLink):
391                found = 1
392                break
393              else:
394                self.framework.logPrintDivider(single = 1)
395                self.framework.logPrint('PETSc in '+name+', location/origin '+str(location)+' failed checkWorkingLink test')
396            else:
397              self.framework.logPrintDivider(single = 1)
398              self.framework.logPrint('PETSc in '+name+', location/origin '+str(location)+' failed checkInclude test with includeDir: '+str(includeDir))
399          if not found:
400            self.framework.logPrintDivider(single = 1)
401            self.framework.logPrint('PETSc in '+name+', location/origin '+str(location)+' failed checkIncludes test')
402            continue
403        else:
404          self.framework.logPrintDivider(single = 1)
405          self.framework.logPrint('PETSc in '+name+', location/origin '+str(location)+' failed checkLib test with libraries: '+str(libraries))
406          continue
407        if self.framework.argDB['with-petsc-shared']:
408          if not self.executeTest(self.checkSharedLibrary, [libraries]):
409            self.framework.logPrintDivider(single = 1)
410            self.framework.logPrint('PETSc in '+name+', location/origin '+str(location)+' failed checkSharedLibrary test with libraries: '+str(libraries))
411            found = 0
412        if found:
413          break
414      if found:
415        version = self.executeTest(self.configureVersion)
416        self.working[location] = (name, include, lib, version)
417        break
418    if found:
419      self.logPrint('Choose PETSc '+self.version+' in '+self.name)
420    else:
421      raise RuntimeError('Could not locate any functional PETSc')
422    return
423
424  def setOutput(self):
425    '''Add defines and substitutions
426       - HAVE_PETSC is defined if a working PETSc is found
427       - PETSC_INCLUDE and PETSC_LIB are command line arguments for the compile and link'''
428    if self.found:
429      self.addDefine('HAVE_PETSC', 1)
430      self.addSubstitution('PETSC_INCLUDE', ' '.join([self.headers.getIncludeArgument(inc) for inc in self.include]))
431      self.addSubstitution('PETSC_LIB', ' '.join(map(self.libraries.getLibArgument, self.lib)))
432    return
433
434  def configure(self):
435    self.executeTest(self.configureLibrary)
436    self.setOutput()
437    return
438
439if __name__ == '__main__':
440  import config.framework
441  import sys
442  framework = config.framework.Framework(sys.argv[1:])
443  framework.setup()
444  framework.addChild(Configure(framework))
445  framework.configure()
446  framework.dumpSubstitutions()
447