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