xref: /petsc/config/PETSc/petsc.py (revision 2205254efee3a00a594e5e2a3a70f74dcb40bc03)
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,'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.utilities.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('PETSc.packages.BlasLapack', self)
98    self.mpi        = framework.require('PETSc.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);', cxxMangle = not self.languages.cSupport) and
238               self.libraries.check(libraries, 'VecDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_Vec *Vec;int VecDestroy(Vec*);', call = 'VecDestroy((Vec*) 0)', cxxMangle = not self.languages.cSupport) and
239               self.libraries.check(libraries, 'MatDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_Mat *Mat;int MatDestroy(Mat*);', call = 'MatDestroy((Mat*) 0)', cxxMangle = not self.languages.cSupport) and
240               self.libraries.check(libraries, 'DMDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_DM *DA;int DMDestroy(DA*);', call = 'DMDestroy((DA*) 0)', cxxMangle = not self.languages.cSupport) and
241               self.libraries.check(libraries, 'KSPDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_KSP *KSP;int KSPDestroy(KSP*);', call = 'KSPDestroy((KSP*) 0)', cxxMangle = not self.languages.cSupport) and
242               self.libraries.check(libraries, 'SNESDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_SNES *SNES;int SNESDestroy(SNES*);', call = 'SNESDestroy((SNES*) 0)', cxxMangle = not self.languages.cSupport) and
243               self.libraries.check(libraries, 'TSDestroy', otherLibs = self.otherLibs, prototype = 'typedef struct _p_TS *TS;int TSDestroy(TS*);', call = 'TSDestroy((TS*) 0)', cxxMangle = not self.languages.cSupport))
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 <petsclog.h>\n', 'PetscLogDouble time;\nPetscErrorCode ierr;\n\nierr = PetscGetTime(&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('#define PETSC_USE_EXTERN_CXX\n#include <petscsys.h>\n', 'PetscLogDouble time;\nPetscErrorCode ierr;\n\nierr = PetscGetTime(&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 PetscGetTime(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():
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    patchRE    = re.compile(r'^#define PETSC_VERSION_PATCH([\s]+)(?P<patchNum>\d+)[\s]*$');
322    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]*$');
323    input   = file(os.path.join(self.dir, 'include', 'petscversion.h'))
324    lines   = []
325    majorNum = 'Unknown'
326    minorNum = 'Unknown'
327    subminorNum = 'Unknown'
328    patchNum = 'Unknown'
329    self.date = 'Unknown'
330    for line in input.readlines():
331      m1 = majorRE.match(line)
332      m2 = minorRE.match(line)
333      m3 = subminorRE.match(line)
334      m4 = patchRE.match(line)
335      m5 = dateRE.match(line)
336      if m1:
337        majorNum = int(m1.group('versionNum'))
338      elif m2:
339        minorNum = int(m2.group('versionNum'))
340      elif m3:
341        subminorNum = int(m3.group('versionNum'))
342
343      if m4:
344        patchNum = int(m4.group('patchNum'))+1
345        lines.append('#define PETSC_VERSION_PATCH'+m4.group(1)+str(patchNum)+'\n')
346      elif m5:
347        self.date = time.strftime('%b %d, %Y', time.localtime(time.time()))
348        lines.append('#define PETSC_VERSION_DATE'+m5.group(1)+'"'+self.date+'"\n')
349      else:
350        lines.append(line)
351    input.close()
352    self.logPrint('Found PETSc version (%s,%s,%s) patch %s on %s' % (majorNum, minorNum, subminorNum, patchNum, self.date))
353    return '%d.%d.%d' % (majorNum, minorNum, subminorNum)
354
355  def includeGuesses(self, path = None):
356    '''Return all include directories present in path or its ancestors'''
357    if not path:
358      yield []
359    while path:
360      dir = os.path.join(path, 'include')
361      if os.path.isdir(dir):
362        yield [dir, os.path.join(path, self.arch,'include')]
363      if path == '/':
364        return
365      path = os.path.dirname(path)
366    return
367
368  def libraryGuesses(self, root = None):
369    '''Return standard library name guesses for a given installation root'''
370    libs = ['ts', 'snes', 'ksp', 'dm', 'mat', 'vec', '']
371    if root:
372      d = os.path.join(root, 'lib', self.arch)
373      if not os.path.isdir(d):
374        self.logPrint('', 3, 'petsc')
375        return
376      yield [os.path.join(d, 'libpetsc'+lib+'.a') for lib in libs]
377    else:
378      yield ['libpetsc'+lib+'.a' for lib in libs]
379    return
380
381  def configureLibrary(self):
382    '''Find a working PETSc
383       - Right now, C++ builds are required to use PETSC_USE_EXTERN_CXX'''
384    for location, name in self.trial.items():
385      self.framework.logPrintDivider()
386      self.framework.logPrint('Checking for a functional PETSc in '+name+', location/origin '+str(location))
387      lib     = None
388      include = None
389      found   = 0
390      for libraries in self.libraryGuesses(location[0]):
391        if self.checkLib(libraries):
392          lib = libraries
393          for includeDir in self.includeGuesses(location[0]):
394            if self.checkInclude(includeDir):
395              include = includeDir
396              self.trial[location] = (name, include, lib, 'Unknown')
397              if self.executeTest(self.checkWorkingLink):
398                found = 1
399                break
400              else:
401                self.framework.logPrintDivider(single = 1)
402                self.framework.logPrint('PETSc in '+name+', location/origin '+str(location)+' failed checkWorkingLink test')
403            else:
404              self.framework.logPrintDivider(single = 1)
405              self.framework.logPrint('PETSc in '+name+', location/origin '+str(location)+' failed checkInclude test with includeDir: '+str(includeDir))
406          if not found:
407            self.framework.logPrintDivider(single = 1)
408            self.framework.logPrint('PETSc in '+name+', location/origin '+str(location)+' failed checkIncludes test')
409            continue
410        else:
411          self.framework.logPrintDivider(single = 1)
412          self.framework.logPrint('PETSc in '+name+', location/origin '+str(location)+' failed checkLib test with libraries: '+str(libraries))
413          continue
414        if self.framework.argDB['with-petsc-shared']:
415          if not self.executeTest(self.checkSharedLibrary, [libraries]):
416            self.framework.logPrintDivider(single = 1)
417            self.framework.logPrint('PETSc in '+name+', location/origin '+str(location)+' failed checkSharedLibrary test with libraries: '+str(libraries))
418            found = 0
419        if found:
420          break
421      if found:
422        version = self.executeTest(self.configureVersion)
423        self.working[location] = (name, include, lib, version)
424        break
425    if found:
426      self.logPrint('Choose PETSc '+self.version+' in '+self.name)
427    else:
428      raise RuntimeError('Could not locate any functional PETSc')
429    return
430
431  def setOutput(self):
432    '''Add defines and substitutions
433       - HAVE_PETSC is defined if a working PETSc is found
434       - PETSC_INCLUDE and PETSC_LIB are command line arguments for the compile and link'''
435    if self.found:
436      self.addDefine('HAVE_PETSC', 1)
437      self.addSubstitution('PETSC_INCLUDE', ' '.join([self.headers.getIncludeArgument(inc) for inc in self.include]))
438      self.addSubstitution('PETSC_LIB', ' '.join(map(self.libraries.getLibArgument, self.lib)))
439    return
440
441  def configure(self):
442    self.executeTest(self.configureLibrary)
443    self.setOutput()
444    return
445
446if __name__ == '__main__':
447  import config.framework
448  import sys
449  framework = config.framework.Framework(sys.argv[1:])
450  framework.setup()
451  framework.addChild(Configure(framework))
452  framework.configure()
453  framework.dumpSubstitutions()
454