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