xref: /petsc/config/BuildSystem/config/package.py (revision a9c6532420f486c00016f218f8bc5f5b7ec1b67a)
1from __future__ import generators
2import config.base
3
4import os
5import sys
6import re
7import itertools
8from hashlib import sha256 as checksum_algo
9
10def sliding_window(seq, n=2):
11  """
12  Returns a sliding window (of width n) over data from the iterable
13  s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...
14  """
15  it     = iter(seq)
16  result = tuple(itertools.islice(it, n))
17  if len(result) == n:
18    yield result
19  for elem in it:
20    result = result[1:] + (elem,)
21    yield result
22
23class FakePETScDir:
24  def __init__(self):
25    self.dir = 'UNKNOWN'
26
27class Package(config.base.Configure):
28  def __init__(self, framework):
29    config.base.Configure.__init__(self, framework)
30    self.headerPrefix        = 'PETSC'
31    self.substPrefix         = 'PETSC'
32    self.arch                = None # The architecture identifier
33    self.externalPackagesDir = os.path.abspath('externalpackages')
34
35    # These are determined by the configure tests
36    self.found            = 0
37    self.setNames()
38    self.include          = []
39    self.dinclude         = []   # all includes in this package and all those it depends on
40    self.lib              = []
41    self.dlib             = []   # all libraries in this package and all those it depends on
42    self.directory        = None # path of the package installation point; for example /usr/local or /home/bsmith/mpich-2.0.1
43
44    self.version          = ''   # the version of the package that PETSc will build with the --download-package option
45    self.versionname      = ''   # string name that appears in package include file, for example HYPRE_RELEASE_VERSION
46    self.versioninclude   = ''   # include file that contains package version information; if not provided uses includes[0]
47    self.minversion       = ''   # minimum version of the package that is supported
48    self.maxversion       = ''   # maximum version of the package that is supported
49    self.foundversion     = ''   # version of the package actually found
50    self.version_tuple    = ''   # version of the package actually found (tuple)
51    self.requiresversion  = 0    # error if the version information is not found
52    self.requirekandr     = 0    # package requires KandR compiler flags to build
53    self.brokengnu23      = 0    # package requires a C standard lower than GNU23
54
55    # These are specified for the package
56    self.required               = 0    # 1 means the package is required
57    self.devicePackage          = 0    # 1 if PETSC_HAVE_DEVICE should be defined by
58                                       # inclusion of this package
59    self.lookforbydefault       = 0    # 1 means the package is not required, but always look for and use if found
60                                       # cannot tell the difference between user requiring it with --with-PACKAGE=1 and
61                                       # this flag being one so hope user never requires it. Needs to be fixed in an overhaul of
62                                       # args database so it keeps track of what the user set vs what the program set
63    self.useddirectly           = 1    # 1 indicates used by PETSc directly, 0 indicates used by a package used by PETSc
64    self.linkedbypetsc          = 1    # 1 indicates PETSc shared libraries (and PETSc executables) need to link against this library
65    self.gitcommit              = None # Git commit to use for downloads
66    self.gitcommitmain          = None # Git commit to use for petsc/main or similar non-release branches
67    self.gcommfile              = None # File within the git clone - that has the gitcommit for the current build - saved
68    self.gitsubmodules          = []   # List of git submodues that should be cloned along with the repo
69    self.download               = []   # list of URLs where repository or tarballs may be found (git is tested before tarballs)
70    self.deps                   = []   # other packages whose dlib or include we depend on, usually we also use self.framework.require()
71    self.odeps                  = []   # dependent packages that are optional
72    self.defaultLanguage        = 'C'  # The language in which to run tests
73    self.liblist                = [[]] # list of libraries we wish to check for (packages can override with their own generateLibList() method)
74    self.extraLib               = []   # additional libraries needed to link
75    self.includes               = []   # headers to check for
76    self.macros                 = []   # optional macros we wish to check for in the headers
77    self.functions              = []   # functions we wish to check for in the libraries
78    self.functionsDefine        = []   # optional functions we wish to check for in the libraries that should generate a PETSC_HAVE_ define
79    self.functionsFortran       = 0    # 1 means the symbols in self.functions are Fortran symbols, so name-mangling is done
80    self.functionsCxx           = [0, '', ''] # 1 means the symbols in self.functions symbol are C++ symbol, so name-mangling with prototype/call is done
81    self.buildLanguages         = ['C']  # Languages the package is written in, hence also the compilers needed to build it. Normally only contains one
82                                         # language, but can have multiple, such as ['FC', 'Cxx']. In PETSc's terminology, languages are C, Cxx, FC, CUDA, HIP, SYCL.
83                                         # We use the first language in the list to check include headers, library functions and versions.
84    self.noMPIUni               = 0    # 1 means requires a real MPI
85    self.libDirs                = ['lib', 'lib64']   # search locations of libraries in the package directory tree; self.libDir is self.installDir + self.libDirs[0]
86    self.includedir             = 'include' # location of includes in the package directory tree
87    self.license                = None # optional license text
88    self.excludedDirs           = []   # list of directory names that could be false positives, SuperLU_DIST when looking for SuperLU
89    self.downloadonWindows      = 0  # 1 means the --download-package works on Microsoft Windows
90    self.minCxxVersion          = framework.compilers.setCompilers.cxxDialectRange['Cxx'][0] # minimum c++ standard version required by the package, e.g. 'c++11'
91    self.maxCxxVersion          = framework.compilers.setCompilers.cxxDialectRange['Cxx'][1] # maximum c++ standard version allowed by the package, e.g. 'c++14', must be greater than self.minCxxVersion
92    self.publicInstall          = 1  # Installs the package in the --prefix directory if it was given. Packages that are only used
93                                     # during the configuration/installation process such as sowing, make etc should be marked as 0
94    self.parallelMake           = 1  # 1 indicates the package supports make -j np option
95
96    self.precisions             = ['__fp16','single','double','__float128']; # Floating point precision package works with
97    self.complex                = 1  # 0 means cannot use complex
98    self.requires32bitint       = 0  # 1 means that the package will not work with 64-bit integers
99    self.requires32bitintblas   = 1  # 1 means that the package will not work with 64-bit integer BLAS/LAPACK
100    self.skippackagewithoptions = 0  # packages like fblaslapack and MPICH do not support --with-package* options so do not print them in help
101    self.skippackagelibincludedirs = 0 # packages like make do not support --with-package-lib and --with-package-include so do not print them in help
102    self.alternativedownload    = [] # Used by, for example mpi.py to print useful error messages, which does not support --download-mpi but one can use --download-mpich
103    self.usesopenmp             = 'no'  # yes, no, unknown package is built to use OpenMP
104    self.usespthreads           = 'no'  # yes, no, unknown package is built to use Pthreads
105    self.cmakelistsdir          = '' # Location of CMakeLists.txt - if not located at the top level of the package dir
106
107    # Outside coupling
108    self.defaultInstallDir      = ''
109    self.PrefixWriteCheck       = 1 # check if specified prefix location is writable for 'make install'
110
111    self.skipMPIDependency      = 0 # Does this package need to skip adding a dependency on MPI? Most packages work with MPI dependency - some libraries (ex. metis) don't care, while some build tools (ex. make, bison) and some MPI library dependencies (ex. hwloc, ucx) need to be built before it, so they can set/use this flag.
112    self.hastests               = 0 # indicates that PETSc make alltests has tests for this package
113    self.hastestsdatafiles      = 0 # indicates that PETSc make alltests has tests for this package that require DATAFILESPATH to be set
114    self.makerulename           = '' # some packages do too many things with the make stage; this allows a package to limit to, for example, just building the libraries
115    self.installedpetsc         = 0  # configure actually compiled and installed PETSc
116    self.installwithbatch       = 1  # install the package even though configure in the batch mode; f2blaslapack and fblaslapack for example
117    self.builtafterpetsc        = 0  # package is compiled/installed after PETSc is compiled
118
119    self.downloaded             = 0  # 1 indicates that this package is being downloaded during this run (internal use only)
120    self.testoptions            = '' # Any PETSc options that should be used when this package is installed and the test harness is run
121    self.executablename         = '' # full path of executable, for example cmake
122    return
123
124  def __str__(self):
125    '''Prints the location of the packages includes and libraries'''
126    output = ''
127    if self.found:
128      output = self.name+':\n'
129      if self.foundversion:
130        if hasattr(self,'versiontitle'):
131          output += '  '+self.versiontitle+':  '+self.foundversion+'\n'
132        else:
133          output += '  Version:    '+self.foundversion+'\n'
134      else:
135        if self.version:      output += '  Version:    '+self.version+'\n'
136      if self.include:        output += '  Includes:   '+self.headers.toStringNoDupes(self.include)+'\n'
137      if self.lib:            output += '  Libraries:  '+self.libraries.toStringNoDupes(self.lib)+'\n'
138      if self.executablename: output += '  Executable: '+getattr(self,self.executablename)+'\n'
139      if self.usesopenmp == 'yes': output += '  uses OpenMP; use export OMP_NUM_THREADS=<p> or -omp_num_threads <p> to control the number of threads\n'
140      if self.usesopenmp == 'unknown': output += '  Unknown if this uses OpenMP (try export OMP_NUM_THREADS=<1-4> yourprogram -log_view) \n'
141      if self.usespthreads == 'yes': output += '  uses PTHREADS; please consult the documentation on how to control the number of threads\n'
142    return output
143
144  def setupDependencies(self, framework):
145    config.base.Configure.setupDependencies(self, framework)
146    self.setCompilers    = framework.require('config.setCompilers', self)
147    self.compilers       = framework.require('config.compilers', self)
148    self.fortran         = framework.require('config.compilersFortran', self)
149    self.compilerFlags   = framework.require('config.compilerFlags', self)
150    self.types           = framework.require('config.types', self)
151    self.headers         = framework.require('config.headers', self)
152    self.libraries       = framework.require('config.libraries', self)
153    self.programs        = framework.require('config.programs', self)
154    self.sourceControl   = framework.require('config.sourceControl',self)
155    self.python          = framework.require('config.packages.Python',self)
156    try:
157      import PETSc.options
158      self.sharedLibraries = framework.require('PETSc.options.sharedLibraries', self)
159      self.petscdir        = framework.require('PETSc.options.petscdir', self.setCompilers)
160      self.petscclone      = framework.require('PETSc.options.petscclone',self.setCompilers)
161      self.havePETSc       = True
162    except ImportError:
163      self.havePETSc       = False
164      self.petscdir        = FakePETScDir()
165    # All packages depend on make
166    self.make          = framework.require('config.packages.make',self)
167    if not self.skipMPIDependency:
168      self.mpi         = framework.require('config.packages.MPI',self)
169    return
170
171  def setupHelp(self,help):
172    '''Prints help messages for the package'''
173    import nargs
174    if not self.skippackagewithoptions:
175      help.addArgument(self.PACKAGE,'-with-'+self.package+'=<bool>',nargs.ArgBool(None,self.required+self.lookforbydefault,'Indicate if you wish to test for '+self.name))
176      help.addArgument(self.PACKAGE,'-with-'+self.package+'-dir=<dir>',nargs.ArgDir(None,None,'Indicate the root directory of the '+self.name+' installation',mustExist = 1))
177      help.addArgument(self.PACKAGE,'-with-'+self.package+'-pkg-config=<dir>', nargs.ArgDir(None, None, 'Look for '+self.name+' using pkg-config utility optional directory to look in',mustExist = 1))
178      if not self.skippackagelibincludedirs:
179        help.addArgument(self.PACKAGE,'-with-'+self.package+'-include=<dirs>',nargs.ArgDirList(None,None,'Indicate the directory of the '+self.name+' include files'))
180        help.addArgument(self.PACKAGE,'-with-'+self.package+'-lib=<libraries: e.g. [/Users/..../lib'+self.package+'.a,...]>',nargs.ArgLibrary(None,None,'Indicate the '+self.name+' libraries'))
181    if self.download:
182      help.addArgument(self.PACKAGE, '-download-'+self.package+'=<no,yes,filename,url>', nargs.ArgDownload(None, 0, 'Download and install '+self.name))
183      if hasattr(self, 'download_git'):
184        help.addArgument(self.PACKAGE, '-download-'+self.package+'-commit=commitid', nargs.ArgString(None, 0, 'Switch from installing release tarballs to git repo - using the specified commit of '+self.name))
185      else:
186        help.addArgument(self.PACKAGE, '-download-'+self.package+'-commit=commitid', nargs.ArgString(None, 0, 'The commit id from a git repository to use for the build of '+self.name))
187      help.addDownload(self.package,self.download)
188    return
189
190  def setNames(self):
191    '''Setup various package names
192    name:         The module name (usually the filename)
193    package:      The lowercase name
194    PACKAGE:      The uppercase name
195    pkgname:      The name of pkg-config (.pc) file
196    downloadname:     Name for download option (usually name)
197    downloaddirnames: names for downloaded directory (first part of string) (usually downloadname)
198    '''
199    import sys
200    if hasattr(sys.modules.get(self.__module__), '__file__'):
201      self.name       = os.path.splitext(os.path.basename(sys.modules.get(self.__module__).__file__))[0]
202    else:
203      self.name           = 'DEBUGGING'
204    self.PACKAGE          = self.name.upper()
205    self.package          = self.name.lower()
206    self.pkgname          = self.package
207    self.downloadname     = self.name
208    self.downloaddirnames = [self.downloadname]
209    return
210
211  def getDefaultPrecision(self):
212    '''The precision of the library'''
213    if hasattr(self, 'precisionProvider'):
214      if hasattr(self.precisionProvider, 'precision'):
215        return self.precisionProvider.precision
216    if hasattr(self, '_defaultPrecision'):
217      return self._defaultPrecision
218    return 'double'
219  def setDefaultPrecision(self, defaultPrecision):
220    '''The precision of the library'''
221    self._defaultPrecision = defaultPrecision
222    return
223  defaultPrecision = property(getDefaultPrecision, setDefaultPrecision, doc = 'The precision of the library')
224
225  def getDefaultScalarType(self):
226    '''The scalar type for the library'''
227    if hasattr(self, 'precisionProvider'):
228      if hasattr(self.precisionProvider, 'scalartype'):
229        return self.precisionProvider.scalartype
230    return self._defaultScalarType
231  def setDefaultScalarType(self, defaultScalarType):
232    '''The scalar type for the library'''
233    self._defaultScalarType = defaultScalarType
234    return
235  defaultScalarType = property(getDefaultScalarType, setDefaultScalarType, doc = 'The scalar type for of the library')
236
237  def getDefaultIndexSize(self):
238    '''The index size for the library'''
239    if hasattr(self, 'indexProvider'):
240      if hasattr(self.indexProvider, 'integerSize'):
241        return self.indexProvider.integerSize
242    return self._defaultIndexSize
243  def setDefaultIndexSize(self, defaultIndexSize):
244    '''The index size for the library'''
245    self._defaultIndexSize = defaultIndexSize
246    return
247  defaultIndexSize = property(getDefaultIndexSize, setDefaultIndexSize, doc = 'The index size for of the library')
248
249  def checkNoOptFlag(self):
250    flags = ''
251    flag = '-O0 '
252    if self.setCompilers.checkCompilerFlag(flag): flags = flags+flag
253    flag = '-mfp16-format=ieee'
254    if self.setCompilers.checkCompilerFlag(flag): flags = flags+flag
255    return flags
256
257  def getSharedFlag(self,cflags):
258    for flag in ['-PIC', '-fPIC', '-KPIC', '-qpic', '-fpic']:
259      if cflags.find(flag) >= 0: return flag
260    return ''
261
262  def getPointerSizeFlag(self,cflags):
263    for flag in ['-m32', '-m64', '-xarch=v9','-q64','-mmic']:
264      if cflags.find(flag) >=0: return flag
265    return ''
266
267  def getDebugFlags(self,cflags):
268    outflags = []
269    for flag in cflags.split():
270      if flag in ['-g','-g3','-Z7']:
271        outflags.append(flag)
272    return ' '.join(outflags)
273
274  def getWindowsNonOptFlags(self,cflags):
275    outflags = []
276    for flag in cflags.split():
277      if flag in ['-MT','-MTd','-MD','-MDd','-threads']:
278        outflags.append(flag)
279    return ' '.join(outflags)
280
281  def rmArgs(self,args,rejects):
282    self.logPrint('Removing configure arguments '+str(rejects))
283    return [arg for arg in args if not arg in rejects]
284
285  def rmArgsPair(self,args,rejects,remove_ahead=True):
286    '''Remove an argument and the next argument from a list of arguments'''
287    '''For example: --ccbin compiler'''
288    '''If remove_ahead is true, arguments rejects specifies the first entry in the pair and the following argument is removed, otherwise rejects specifies the second entry in the pair and the previous argument is removed as well.'''
289    self.logPrint('Removing paired configure arguments '+str(rejects))
290    rejects = set(rejects)
291    nargs   = []
292    skip    = -1
293    for flag, next_flag in sliding_window(args):
294      if skip == 1:
295        skip = 0
296        continue
297      skip = 0
298
299      flag_to_check = flag if remove_ahead else next_flag
300      if flag_to_check in rejects:
301        skip = 1
302      else:
303        nargs.append(flag)
304    if remove_ahead is False and skip == 0: # append last flag
305      nargs.append(next_flag)
306    return nargs
307
308  def rmArgsStartsWith(self,args,rejectstarts):
309    '''Remove an argument that starts with given strings'''
310    rejects = []
311    if not isinstance(rejectstarts, list): rejectstarts = [rejectstarts]
312    for i in rejectstarts:
313      rejects.extend([arg for arg in args if arg.startswith(i)])
314    return self.rmArgs(args,rejects)
315
316  def addArgStartsWith(self,args,sw,value):
317    '''Adds another value with the argument that starts with sw, create sw if it does not exist'''
318    keep = []
319    found = 0
320    for i in args:
321      if i.startswith(sw+'="'):
322        i = i[:-1] + ' ' + value + '"'
323        found = 1
324      keep.append(i)
325    if not found:
326      keep.append(sw+'="' + value + '"')
327    return keep
328
329  def rmValueArgStartsWith(self,args,sw,value):
330    '''Remove a value from arguments that start with sw'''
331    if not isinstance(sw, list): sw = [sw]
332    keep = []
333    for i in args:
334      for j in sw:
335        if i.startswith(j+'="'):
336          i = i.replace(value,'')
337      keep.append(i)
338    return keep
339
340  def removeWarningFlags(self,flags):
341    flags = self.rmArgs(
342      flags,
343      {
344        '-Werror', '-Wall', '-Wwrite-strings', '-Wno-strict-aliasing', '-Wno-unknown-pragmas',
345        '-Wno-unused-variable', '-Wno-unused-dummy-argument', '-std=c89', '-pedantic','--coverage',
346        '-Mfree', '-fdefault-integer-8', '-fsanitize=address', '-fstack-protector', '-Wconversion'
347      }
348    )
349    return ['-g' if f == '-g3' else f for f in flags]
350
351  def __remove_flag_pair(self, flags, flag_to_remove, pair_prefix):
352    """
353    Remove FLAG_TO_REMOVE from FLAGS
354
355    Parameters
356    ----------
357    - flags          - iterable (or string) of flags to remove from
358    - flag_to_remove - the flag to remove
359    - pair_prefix    - (Optional) if not None, indicates that FLAG_TO_REMOVE is in a pair, and
360                       is prefixed by str(pair_prefix). For example, pair_prefix='-Xcompiler' indicates
361                       that the flag is specified as <COMPILER_NAME> -Xcompiler FLAG_TO_REMOVE
362
363    Return
364    ------
365    flags - list of post-processed flags
366    """
367    if isinstance(flags, str):
368      flags = flags.split()
369
370    if pair_prefix is None:
371      return self.rmArgs(flags, {flag_to_remove})
372    assert isinstance(pair_prefix, str)
373    # deals with bare PAIR_PREFIX FLAG_TO_REMOVE
374    flag_str = ' '.join(self.rmArgsPair(flags, {flag_to_remove}, remove_ahead=False))
375    # handle PAIR_PREFIX -fsome_other_flag,FLAG_TO_REMOVE
376    flag_str = re.sub(r',{}\s'.format(flag_to_remove), ' ', flag_str)
377    # handle PAIR_PREFIX -fsome_other_flag,FLAG_TO_REMOVE,-fyet_another_flag
378    flag_str = re.sub(r',{},'.format(flag_to_remove), ',', flag_str)
379    # handle PAIR_PREFIX FLAG_TO_REMOVE,-fsome_another_flag
380    flag_str = re.sub(r'\s{},'.format(flag_to_remove), ' ', flag_str)
381    return flag_str.split()
382
383  def removeVisibilityFlag(self, flags, pair_prefix=None):
384    """Remove -fvisibility=hidden from flags."""
385    return self.__remove_flag_pair(flags, '-fvisibility=hidden', pair_prefix)
386
387  def removeCoverageFlag(self, flags, pair_prefix=None):
388    """Remove --coverage from flags."""
389    return self.__remove_flag_pair(flags, '--coverage', pair_prefix)
390
391  def removeOpenMPFlag(self, flags, pair_prefix=None):
392    """Remove -fopenmp from flags."""
393    if hasattr(self,'openmp') and hasattr(self.openmp,'ompflag'):
394      return self.__remove_flag_pair(flags, self.openmp.ompflag, pair_prefix)
395    else:
396      return flags
397
398  def removeStdCxxFlag(self,flags):
399    '''Remove the -std=[CXX_VERSION] flag from the list of flags, but only for CMake packages'''
400    if issubclass(type(self),config.package.CMakePackage):
401      # only cmake packages get their std flags removed since they use
402      # -DCMAKE_CXX_STANDARD to set the std flag
403      cmakeLists = os.path.join(self.packageDir,self.cmakelistsdir,'CMakeLists.txt')
404      with open(cmakeLists,'r') as fd:
405        refcxxstd = re.compile(r'^\s*(?!#)(set\()(CMAKE_CXX_STANDARD\s[A-z0-9\s]*)')
406        for line in fd:
407          match = refcxxstd.search(line)
408          if match:
409            # from set(CMAKE_CXX_STANDARD <val> [CACHE <type> <docstring> [FORCE]]) extract
410            # <val> CACHE <type> <docstring> [FORCE]
411            cmakeSetCmd = match.groups()[1].split()[1:]
412            if (len(cmakeSetCmd) == 1) or 'CACHE' not in cmakeSetList:
413              # The worst behaved, we have a pure "set". we shouldn't rely on
414              # CMAKE_CXX_STANDARD, since the package overrides it unconditionally. Thus
415              # we leave the std flag in the compiler flags.
416              self.logPrint('removeStdCxxFlag: CMake Package {pkg} had an overriding \'set\' command in their CMakeLists.txt:\n\t{cmd}\nLeaving std flags in'.format(pkg=self.name,cmd=line.strip()),indent=1)
417              return flags
418            self.logPrint('removeStdCxxFlag: CMake Package {pkg} did NOT have an overriding \'set\' command in their CMakeLists.txt:\n\t{cmd}\nRemoving std flags'.format(pkg=self.name,cmd=line.strip()),indent=1)
419            # CACHE was found in the set command, meaning we can override it from the
420            # command line. So we continue on to remove the std flags.
421            break
422      stdFlags = ('-std=c++','-std=gnu++')
423      return [f for f in flags if not f.startswith(stdFlags)]
424    return flags
425
426  def updatePackageCFlags(self,flags):
427    '''To turn off various warnings or errors the compilers may produce with external packages, remove or add appropriate compiler flags'''
428    outflags = self.removeVisibilityFlag(flags.split())
429    outflags = self.removeWarningFlags(outflags)
430    outflags = self.removeCoverageFlag(outflags)
431    if self.requirekandr:
432      outflags += self.setCompilers.KandRFlags
433    with self.Language('C'):
434      if self.brokengnu23 and config.setCompilers.Configure.isGcc150plus(self.getCompiler(), self.log):
435        outflags.append('-std=gnu17')
436    return ' '.join(outflags)
437
438  def updatePackageFFlags(self,flags):
439    outflags = self.removeVisibilityFlag(flags.split())
440    outflags = self.removeWarningFlags(outflags)
441    outflags = self.removeCoverageFlag(outflags)
442    with self.Language('FC'):
443      if config.setCompilers.Configure.isNAG(self.getLinker(), self.log):
444         outflags.extend(['-mismatch','-dusty','-dcfuns'])
445      if config.setCompilers.Configure.isGfortran100plus(self.getCompiler(), self.log):
446        outflags.append('-fallow-argument-mismatch')
447    return ' '.join(outflags)
448
449  def updatePackageCxxFlags(self,flags):
450    outflags = self.removeVisibilityFlag(flags.split())
451    outflags = self.removeWarningFlags(outflags)
452    outflags = self.removeCoverageFlag(outflags)
453    outflags = self.removeStdCxxFlag(outflags)
454    return ' '.join(outflags)
455
456  def updatePackageCUDAFlags(self, flags):
457    outflags = self.removeVisibilityFlag(flags, pair_prefix='-Xcompiler')
458    outflags = self.removeCoverageFlag(outflags, pair_prefix='-Xcompiler')
459    return ' '.join(outflags)
460
461  def getDefaultLanguage(self):
462    '''The language in which to run tests'''
463    if hasattr(self, 'forceLanguage'):
464      return self.forceLanguage
465    if hasattr(self, 'languageProvider'):
466      if hasattr(self.languageProvider, 'defaultLanguage'):
467        return self.languageProvider.defaultLanguage
468      elif hasattr(self.languageProvider, 'clanguage'):
469        return self.languageProvider.clanguage
470    return self._defaultLanguage
471  def setDefaultLanguage(self, defaultLanguage):
472    '''The language in which to run tests'''
473    if hasattr(self, 'languageProvider'):
474      del self.languageProvider
475    self._defaultLanguage = defaultLanguage
476    return
477  defaultLanguage = property(getDefaultLanguage, setDefaultLanguage, doc = 'The language in which to run tests')
478
479  def getArch(self):
480    '''The architecture identifier'''
481    if hasattr(self, 'archProvider'):
482      if hasattr(self.archProvider, 'arch'):
483        return self.archProvider.arch
484    return self._arch
485  def setArch(self, arch):
486    '''The architecture identifier'''
487    self._arch = arch
488    return
489  arch = property(getArch, setArch, doc = 'The architecture identifier')
490
491  # This construct should be removed and just have getInstallDir() handle the process
492  def getDefaultInstallDir(self):
493    '''The installation directory of the library'''
494    if hasattr(self, 'installDirProvider'):
495      if hasattr(self.installDirProvider, 'dir'):
496        return self.installDirProvider.dir
497    return self._defaultInstallDir
498  def setDefaultInstallDir(self, defaultInstallDir):
499    '''The installation directory of the library'''
500    self._defaultInstallDir = defaultInstallDir
501    return
502  defaultInstallDir = property(getDefaultInstallDir, setDefaultInstallDir, doc = 'The installation directory of the library')
503
504  def getExternalPackagesDir(self):
505    '''The directory for downloaded packages'''
506    if hasattr(self, 'externalPackagesDirProvider'):
507      if hasattr(self.externalPackagesDirProvider, 'dir'):
508        return self.externalPackagesDirProvider.dir
509    elif not self.framework.externalPackagesDir is None:
510      return os.path.abspath('externalpackages')
511    return self._externalPackagesDir
512  def setExternalPackagesDir(self, externalPackagesDir):
513    '''The directory for downloaded packages'''
514    self._externalPackagesDir = externalPackagesDir
515    return
516  externalPackagesDir = property(getExternalPackagesDir, setExternalPackagesDir, doc = 'The directory for downloaded packages')
517
518  def getSearchDirectories(self):
519    '''By default, do not search any particular directories, but try compiler default paths'''
520    return ['']
521
522  def getInstallDir(self):
523    '''Calls self.Install() to install the package'''
524    '''Returns --prefix (or the value computed from --package-prefix-hash) if provided otherwise $PETSC_DIR/$PETSC_ARCH'''
525    '''Special case for packages such as sowing that are have self.publicInstall == 0 it always locates them in $PETSC_DIR/$PETSC_ARCH'''
526    '''Special case if --package-prefix-hash then even self.publicInstall == 0 are installed in the prefix location'''
527    self.confDir    = self.installDirProvider.confDir  # private install location; $PETSC_DIR/$PETSC_ARCH for PETSc
528    self.packageDir = self.getDir()
529    self.setupDownload()
530    if not self.packageDir: self.packageDir = self.downLoad()
531    self.updateGitDir()
532    self.updatehgDir()
533    self.applyPatches()
534    if (self.publicInstall or 'package-prefix-hash' in self.argDB) and not ('package-prefix-hash' in self.argDB and (hasattr(self,'postProcess') or self.builtafterpetsc)):
535      self.installDir = self.defaultInstallDir
536    else:
537      self.installDir = self.confDir
538    if self.PrefixWriteCheck and self.publicInstall and not 'package-prefix-hash' in self.argDB and self.installDirProvider.installSudo:
539      if self.installDirProvider.dir in ['/usr','/usr/local']: prefixdir = os.path.join(self.installDirProvider.dir,'petsc')
540      else: prefixdir = self.installDirProvider.dir
541      msg='''\
542Specified prefix-dir: %s is read-only! "%s" cannot install at this location! Suggest:
543      sudo mkdir %s
544      sudo chown $USER %s
545Now rerun configure''' % (self.installDirProvider.dir, '--download-'+self.package, prefixdir, prefixdir)
546      raise RuntimeError(msg)
547    self.includeDir = os.path.join(self.installDir, 'include')
548    self.libDir = os.path.join(self.installDir, self.libDirs[0])
549    installDir = self.Install()
550    if not installDir:
551      raise RuntimeError(self.package+' forgot to return the install directory from the method Install()\n')
552    return os.path.abspath(installDir)
553
554  def getChecksum(self,source, chunkSize = 1024*1024):
555    '''Return the checksum for a given file, which may also be specified by its filename
556       - The chunkSize argument specifies the size of blocks read from the file'''
557    if hasattr(source, 'close'):
558      f = source
559    else:
560      f = open(source, 'rb')
561    m = checksum_algo()
562    size = chunkSize
563    buf  = f.read(size)
564    while buf:
565      m.update(buf)
566      buf = f.read(size)
567    f.close()
568    return m.hexdigest()
569
570  def generateLibList(self, directory, liblist = None):
571    '''Generates full path list of libraries from self.liblist'''
572    if liblist == None: liblist = self.liblist
573    if [] in liblist: liblist.remove([]) # process null list later
574    if liblist == []: # most packages don't have a liblist - so return an empty list
575      return [[]]
576    alllibs = []
577    if not directory:  # compiler default path - so also check compiler default libs.
578      alllibs.insert(0,[])
579    elif directory in self.libraries.sysDirs:
580      self.logPrint('generateLibList: systemDir detected! skipping: '+str(directory))
581      directory = ''
582    for libSet in liblist:
583      libs = []
584      use_L = 0
585      for library in libSet:
586        # if the library name starts with 'lib' or uses '-lfoo' then add in -Lpath. Otherwise - add the fullpath
587        if library.startswith('-l') or library.startswith('lib'):
588          libs.append(library)
589          use_L = 1
590        else:
591          libs.append(os.path.join(directory, library))
592      if use_L and directory:
593        libs.insert(0,'-L'+directory)
594      libs.extend(self.extraLib)
595      alllibs.append(libs)
596    return alllibs
597
598  def getIncludeDirs(self, prefix, includeDirs):
599    if not isinstance(includeDirs, list):
600      includeDirs = [includeDirs]
601    iDirs = [inc for inc in includeDirs if os.path.isabs(inc)] + [os.path.join(prefix, inc) for inc in includeDirs if not os.path.isabs(inc)]
602    return [inc for inc in iDirs if os.path.exists(inc)]
603
604  def addToArgs(self,args,key,value):
605    found = 0
606    for i in range(0,len(args)):
607      if args[i].startswith(key+'='):
608        args[i] = args[i][0:-1] + ' '+ value +'"'
609        found = 1
610    if not found: args.append(key+'="'+value+'"')
611
612  def generateGuesses(self):
613    d = self.checkDownload()
614    if d:
615      if not self.liblist or not self.liblist[0] or self.builtafterpetsc :
616        yield('Download '+self.PACKAGE, d, [], self.getIncludeDirs(d, self.includedir))
617      for libdir in self.libDirs:
618        libdirpath = os.path.join(d, libdir)
619        if not os.path.isdir(libdirpath):
620          self.logPrint(self.PACKAGE+': Downloaded DirPath not found.. skipping: '+libdirpath)
621          continue
622        for l in self.generateLibList(libdirpath):
623          yield('Download '+self.PACKAGE, d, l, self.getIncludeDirs(d, self.includedir))
624      raise RuntimeError('Downloaded '+self.package+' could not be used. Please check install in '+d+'\n')
625
626    if 'with-'+self.package+'-pkg-config' in self.argDB:
627      if self.argDB['with-'+self.package+'-pkg-config']:
628        #  user provided path to look for pkg info
629        if 'PKG_CONFIG_PATH' in os.environ: path = os.environ['PKG_CONFIG_PATH']
630        else: path = None
631        os.environ['PKG_CONFIG_PATH'] = self.argDB['with-'+self.package+'-pkg-config']
632
633      l,err,ret  = config.base.Configure.executeShellCommand('pkg-config '+self.pkgname+' --libs', timeout=60, log = self.log)
634      l = l.strip()
635      i,err,ret  = config.base.Configure.executeShellCommand('pkg-config '+self.pkgname+' --cflags', timeout=60, log = self.log)
636      i = i.strip()
637      if self.argDB['with-'+self.package+'-pkg-config']:
638        if path: os.environ['PKG_CONFIG_PATH'] = path
639        else: os.environ['PKG_CONFIG_PATH'] = ''
640      yield('pkg-config located libraries and includes '+self.PACKAGE, None, l.split(), i)
641      raise RuntimeError('pkg-config could not locate correct includes and libraries for '+self.package)
642
643    if 'with-'+self.package+'-dir' in self.argDB:
644      d = self.argDB['with-'+self.package+'-dir']
645      # error if package-dir is in externalpackages
646      if os.path.realpath(d).find(os.path.realpath(self.externalPackagesDir)) >=0:
647        fakeExternalPackagesDir = d.replace(os.path.realpath(d).replace(os.path.realpath(self.externalPackagesDir),''),'')
648        raise RuntimeError('Bad option: '+'--with-'+self.package+'-dir='+self.argDB['with-'+self.package+'-dir']+'\n'+
649                           fakeExternalPackagesDir+' is reserved for --download-package scratch space. \n'+
650                           'Do not install software in this location nor use software in this directory.')
651
652      if not self.liblist or not self.liblist[0]:
653          yield('User specified root directory '+self.PACKAGE, d, [], self.getIncludeDirs(d, self.includedir))
654
655      for libdir in self.libDirs:
656        libdirpath = os.path.join(d, libdir)
657        if not os.path.isdir(libdirpath):
658          self.logPrint(self.PACKAGE+': UserSpecified DirPath not found.. skipping: '+libdirpath)
659          continue
660        for l in self.generateLibList(libdirpath):
661          yield('User specified root directory '+self.PACKAGE, d, l, self.getIncludeDirs(d, self.includedir))
662
663      if 'with-'+self.package+'-include' in self.argDB:
664        raise RuntimeError('Do not set --with-'+self.package+'-include if you set --with-'+self.package+'-dir')
665      if 'with-'+self.package+'-lib' in self.argDB:
666        raise RuntimeError('Do not set --with-'+self.package+'-lib if you set --with-'+self.package+'-dir')
667      raise RuntimeError('--with-'+self.package+'-dir='+self.argDB['with-'+self.package+'-dir']+' did not work')
668
669    if 'with-'+self.package+'-include' in self.argDB and not 'with-'+self.package+'-lib' in self.argDB:
670      if self.liblist and self.liblist[0]:
671        raise RuntimeError('If you provide --with-'+self.package+'-include you must also supply with-'+self.package+'-lib\n')
672    if 'with-'+self.package+'-lib' in self.argDB and not 'with-'+self.package+'-include' in self.argDB:
673      if self.includes:
674        raise RuntimeError('If you provide --with-'+self.package+'-lib you must also supply with-'+self.package+'-include\n')
675    if 'with-'+self.package+'-include-dir' in self.argDB:
676        raise RuntimeError('Use --with-'+self.package+'-include; not --with-'+self.package+'-include-dir')
677
678    if 'with-'+self.package+'-include' in self.argDB or 'with-'+self.package+'-lib' in self.argDB:
679      if self.liblist and self.liblist[0]:
680        libs  = self.argDB['with-'+self.package+'-lib']
681        slibs = str(self.argDB['with-'+self.package+'-lib'])
682      else:
683        libs  = []
684        slibs = 'NoneNeeded'
685      inc  = []
686      d  = None
687      if self.includes:
688        inc = self.argDB['with-'+self.package+'-include']
689        # hope that package root is one level above first include directory specified
690        if inc:
691          d   = os.path.dirname(inc[0])
692
693      if not isinstance(inc, list): inc = inc.split(' ')
694      if not isinstance(libs, list): libs = libs.split(' ')
695      inc = [os.path.abspath(i) for i in inc]
696      yield('User specified '+self.PACKAGE+' libraries', d, libs, inc)
697      msg = '--with-'+self.package+'-lib='+slibs
698      if self.includes:
699        msg += ' and \n'+'--with-'+self.package+'-include='+str(self.argDB['with-'+self.package+'-include'])
700      msg += ' did not work'
701      raise RuntimeError(msg)
702
703    for d in self.getSearchDirectories():
704      if d:
705        if not os.path.isdir(d):
706          self.logPrint(self.PACKAGE+': SearchDir DirPath not found.. skipping: '+d)
707          continue
708        includedir = self.getIncludeDirs(d, self.includedir)
709        for libdir in self.libDirs:
710          libdirpath = os.path.join(d, libdir)
711          if not os.path.isdir(libdirpath):
712            self.logPrint(self.PACKAGE+': DirPath not found.. skipping: '+libdirpath)
713            continue
714          for l in self.generateLibList(libdirpath):
715            yield('Package specific search directory '+self.PACKAGE, d, l, includedir)
716      else:
717        includedir = ''
718        for l in self.generateLibList(d): # d = '' i.e search compiler libraries
719            yield('Compiler specific search '+self.PACKAGE, d, l, includedir)
720
721    if not self.lookforbydefault or ('with-'+self.package in self.framework.clArgDB and self.argDB['with-'+self.package]):
722      mesg = 'Unable to find '+self.package+' in default locations!\nPerhaps you can specify with --with-'+self.package+'-dir=<directory>\nIf you do not want '+self.name+', then give --with-'+self.package+'=0'
723      if self.download: mesg +='\nYou might also consider using --download-'+self.package+' instead'
724      if self.alternativedownload: mesg +='\nYou might also consider using --download-'+self.alternativedownload+' instead'
725      raise RuntimeError(mesg)
726
727  def checkDownload(self):
728    '''Check if we should download the package, returning the install directory or the empty string indicating installation'''
729    if not self.download:
730      return ''
731    if self.argDB['with-batch'] and self.argDB['download-'+self.package] and not (hasattr(self.setCompilers,'cross_cc') or self.installwithbatch): raise RuntimeError('--download-'+self.name+' cannot be used on batch systems. You must either\n\
732    1) load the appropriate module on your system and use --with-'+self.name+' or \n\
733    2) locate its installation on your machine or install it yourself and use --with-'+self.name+'-dir=path\n')
734
735    if self.argDB['download-'+self.package] and 'package-prefix-hash' in self.argDB and self.argDB['package-prefix-hash'] == 'reuse' and not hasattr(self,'postProcess') and not self.builtafterpetsc: # package already built in prefix hash location so reuse it
736      self.installDir = self.defaultInstallDir
737      return self.defaultInstallDir
738    if self.argDB['download-'+self.package]:
739      if self.license and not os.path.isfile('.'+self.package+'_license'):
740        self.logClear()
741        self.logPrint("**************************************************************************************************", debugSection='screen')
742        self.logPrint('Please register to use '+self.downloadname+' at '+self.license, debugSection='screen')
743        self.logPrint("**************************************************************************************************\n", debugSection='screen')
744        fd = open('.'+self.package+'_license','w')
745        fd.close()
746      return self.getInstallDir()
747    else:
748      # check if download option is set for MPI dependent packages - if so flag an error.
749      mesg=''
750      if hasattr(self,'mpi') and self.mpi in self.deps:
751        if 'download-mpich' in self.argDB and self.argDB['download-mpich'] or 'download-openmpi' in self.argDB and self.argDB['download-openmpi']:
752          mesg+='Cannot use --download-mpich or --download-openmpi when not using --download-%s. Perhaps you want --download-%s.\n' % (self.package,self.package)
753      if mesg:
754        raise RuntimeError(mesg)
755    return ''
756
757  def installNeeded(self, mkfile):
758    makefile       = os.path.join(self.packageDir, mkfile)
759    makefileSaved  = os.path.join(self.confDir, 'lib','petsc','conf','pkg.conf.'+self.package)
760    gcommfileSaved = os.path.join(self.confDir,'lib','petsc','conf', 'pkg.gitcommit.'+self.package)
761    if self.downloaded:
762      self.log.write(self.PACKAGE+' was just downloaded, forcing a rebuild because cannot determine if package has changed\n')
763      return 1
764    if not os.path.isfile(makefileSaved) or not (self.getChecksum(makefileSaved) == self.getChecksum(makefile)):
765      self.log.write('Have to rebuild '+self.PACKAGE+', '+makefile+' != '+makefileSaved+'\n')
766      return 1
767    else:
768      self.log.write('Makefile '+makefileSaved+' has correct checksum\n')
769    if self.gcommfile and os.path.isfile(self.gcommfile):
770      if not os.path.isfile(gcommfileSaved) or not (self.getChecksum(gcommfileSaved) == self.getChecksum(self.gcommfile)):
771        self.log.write('Have to rebuild '+self.PACKAGE+', '+self.gcommfile+' != '+gcommfileSaved+'\n')
772        return 1
773      else:
774        self.log.write('Commit file '+gcommfileSaved+' has correct checksum\n')
775    self.log.write('Do not need to rebuild '+self.PACKAGE+'\n')
776    return 0
777
778  def postInstall(self, output, mkfile):
779    '''Dump package build log into configure.log - also copy package config to prevent unnecessary rebuild'''
780    self.log.write('********Output of running make on '+self.PACKAGE+' follows *******\n')
781    self.log.write(output)
782    self.log.write('********End of Output of running make on '+self.PACKAGE+' *******\n')
783    subconfDir = os.path.join(self.confDir, 'lib', 'petsc', 'conf')
784    if not os.path.isdir(subconfDir):
785      os.makedirs(subconfDir)
786    makefile       = os.path.join(self.packageDir, mkfile)
787    makefileSaved  = os.path.join(subconfDir, 'pkg.conf.'+self.package)
788    gcommfileSaved = os.path.join(subconfDir, 'pkg.gitcommit.'+self.package)
789    import shutil
790    shutil.copyfile(makefile,makefileSaved)
791    if self.gcommfile and os.path.exists(self.gcommfile):
792      shutil.copyfile(self.gcommfile,gcommfileSaved)
793    self.framework.actions.addArgument(self.PACKAGE, 'Install', 'Installed '+self.PACKAGE+' into '+self.installDir)
794
795  def matchExcludeDir(self,dir):
796    '''Check is the dir matches something in the excluded directory list'''
797    for exdir in self.excludedDirs:
798      if dir.lower().startswith(exdir.lower()):
799        return 1
800    return 0
801
802  def gitPreReqCheck(self):
803    '''Some packages may need addition prerequisites if the package comes from a git repository'''
804    return 1
805
806  def applyPatches(self):
807    '''Patch the package's files with needed (likely portability) corrections'''
808
809  def updatehgDir(self):
810    '''Checkout the correct hash'''
811    if hasattr(self.sourceControl, 'hg') and (self.packageDir == os.path.join(self.externalPackagesDir,'hg.'+self.package)):
812      if hasattr(self,'hghash'):
813        config.base.Configure.executeShellCommand([self.sourceControl.hg, 'update', '-c', self.hghash], cwd=self.packageDir, log = self.log)
814
815  def updateGitDir(self):
816    '''Checkout the correct gitcommit for the gitdir - and update pkg.gitcommit'''
817    if hasattr(self.sourceControl, 'git') and (self.packageDir == os.path.join(self.externalPackagesDir,'git.'+self.package)):
818      if not (hasattr(self, 'gitcommit') and self.gitcommit):
819        if hasattr(self, 'download_git'):
820          self.gitcommit = 'HEAD'
821        else:
822          raise RuntimeError('Trying to update '+self.package+' package source directory '+self.packageDir+' which is supposed to be a git repository, but no gitcommit is set for this package.\n\
823Try to delete '+self.packageDir+' and rerun configure.\n\
824If the problem persists, please send your configure.log to petsc-maint@mcs.anl.gov')
825      # verify that packageDir is actually a git clone
826      if not os.path.isdir(os.path.join(self.packageDir,'.git')):
827        raise RuntimeError(self.packageDir +': is not a git repository! '+os.path.join(self.packageDir,'.git')+' not found!')
828      gitdir,err,ret = config.base.Configure.executeShellCommand([self.sourceControl.git, 'rev-parse','--git-dir'], cwd=self.packageDir, log = self.log)
829      if gitdir != '.git':
830        raise RuntimeError(self.packageDir +': is not a git repository! "git rev-parse --gitdir" gives: '+gitdir)
831
832      prefetch = 0
833      if self.gitcommit.startswith('origin/'):
834        prefetch = self.gitcommit.replace('origin/','')
835      else:
836        try:
837          config.base.Configure.executeShellCommand([self.sourceControl.git, 'cat-file', '-e', self.gitcommit+'^{commit}'], cwd=self.packageDir, log = self.log)
838          gitcommit_hash,err,ret = config.base.Configure.executeShellCommand([self.sourceControl.git, 'rev-parse', self.gitcommit], cwd=self.packageDir, log = self.log)
839          if self.gitcommit != 'HEAD':
840            # check if origin/branch exists - if so warn user that we are using the remote branch
841            try:
842              rbranch = 'origin/'+self.gitcommit
843              config.base.Configure.executeShellCommand([self.sourceControl.git, 'cat-file', '-e', rbranch+'^{commit}'], cwd=self.packageDir, log = self.log)
844              gitcommit_hash,err,ret = config.base.Configure.executeShellCommand([self.sourceControl.git, 'rev-parse', self.gitcommit], cwd=self.packageDir, log = self.log)
845              self.logPrintWarning('Branch "%s" is specified, however remote branch "%s" also exists! Proceeding with using the remote branch. \
846To use the local branch (manually checkout local branch and) - rerun configure with option --download-%s-commit=HEAD)' % (self.gitcommit, rbranch, self.name))
847              prefetch = self.gitcommit
848            except:
849              pass
850        except:
851          prefetch = self.gitcommit
852      if prefetch:
853        fetched = 0
854        self.logPrintBox('Attempting a "git fetch" commit/branch/tag: %s from Git repositor%s: %s' % (str(self.gitcommit), str('ies' if len(self.retriever.git_urls) > 1 else 'y'), str(self.retriever.git_urls)))
855        for git_url in self.retriever.git_urls:
856          try:
857            config.base.Configure.executeShellCommand([self.sourceControl.git, 'fetch', '--tags', git_url, prefetch], cwd=self.packageDir, log = self.log)
858            gitcommit_hash,err,ret = config.base.Configure.executeShellCommand([self.sourceControl.git, 'rev-parse', 'FETCH_HEAD'], cwd=self.packageDir, log = self.log)
859            fetched = 1
860            break
861          except:
862            continue
863        if not fetched:
864          raise RuntimeError('The above "git fetch" failed! Check if the specified "commit/branch/tag" is present in the remote git repo.\n\
865To use currently downloaded (local) git snapshot - use: --download-'+self.package+'-commit=HEAD')
866      if self.gitcommit != 'HEAD':
867        try:
868          config.base.Configure.executeShellCommand([self.sourceControl.git, '-c', 'user.name=petsc-configure', '-c', 'user.email=petsc@configure', 'stash'], cwd=self.packageDir, log = self.log)
869          config.base.Configure.executeShellCommand([self.sourceControl.git, 'clean', '-f', '-d', '-x'], cwd=self.packageDir, log = self.log)
870        except RuntimeError as e:
871          if str(e).find("Unknown option: -c") >= 0:
872            self.logPrintWarning('Unable to "git stash". Likely due to antique Git version (<1.8). Proceeding without stashing!')
873          else:
874            raise RuntimeError('Unable to run git stash/clean in repository: '+self.packageDir+'.\nPerhaps its a git error!')
875        try:
876          if self.gitsubmodules:
877            config.base.Configure.executeShellCommand([self.sourceControl.git, 'checkout', '--recurse-submodules', '-f', gitcommit_hash], cwd=self.packageDir, log = self.log)
878          else:
879            config.base.Configure.executeShellCommand([self.sourceControl.git, 'checkout', '-f', gitcommit_hash], cwd=self.packageDir, log = self.log)
880        except:
881          raise RuntimeError('Unable to checkout commit: '+self.gitcommit+' in repository: '+self.packageDir+'.\nPerhaps its a git error!')
882      # write a commit-tag file
883      self.gcommfile = os.path.join(self.packageDir,'pkg.gitcommit')
884      with open(self.gcommfile,'w') as fd:
885        fd.write(gitcommit_hash)
886    return
887
888  def getDir(self):
889    '''Find the directory containing the package'''
890    packages = self.externalPackagesDir
891    if not os.path.isdir(packages):
892      os.makedirs(packages)
893      self.framework.actions.addArgument('Framework', 'Directory creation', 'Created the external packages directory: '+packages)
894    Dir = []
895    pkgdirs = os.listdir(packages)
896    gitpkg  = 'git.'+self.package
897    hgpkg  = 'hg.'+self.package
898    self.logPrint('Looking for '+self.PACKAGE+' at '+gitpkg+ ', '+hgpkg+' or a directory starting with '+str(self.downloaddirnames))
899    if hasattr(self.sourceControl, 'git') and gitpkg in pkgdirs:
900      Dir.append(gitpkg)
901    if hasattr(self.sourceControl, 'hg') and hgpkg in pkgdirs:
902      Dir.append(hgpkg)
903    for d in pkgdirs:
904      for j in self.downloaddirnames:
905        if d.lower().startswith(j.lower()) and os.path.isdir(os.path.join(packages, d)) and not self.matchExcludeDir(d):
906          Dir.append(d)
907
908    if len(Dir) > 1:
909      raise RuntimeError('Located multiple directories with package '+self.package+' '+str(Dir)+'\nDelete directory '+self.arch+' and rerun ./configure')
910
911    if Dir:
912      self.logPrint('Found a copy of '+self.PACKAGE+' in '+str(Dir[0]))
913      return os.path.join(packages, Dir[0])
914    else:
915      self.logPrint('Could not locate an existing copy of '+self.PACKAGE+':')
916      self.logPrint('  '+str(pkgdirs))
917      return
918
919  def setupDownload(self):
920    import retrieval
921    self.retriever = retrieval.Retriever(self.sourceControl, argDB = self.argDB)
922    self.retriever.setup()
923    self.retriever.ver = self.petscdir.version
924    self.retriever.setupURLs(self.package,self.download,self.gitsubmodules,self.gitPreReqCheck())
925
926  def downLoad(self):
927    '''Downloads a package; using hg or ftp; opens it in the with-packages-build-dir directory'''
928    retriever = self.retriever
929    retriever.saveLog()
930    self.logPrint('Downloading '+self.name)
931    # now attempt to download each url until any one succeeds.
932    err =''
933    for proto, url in retriever.generateURLs():
934      self.logPrintBox('Trying to download '+url+' for '+self.PACKAGE)
935      try:
936        retriever.genericRetrieve(proto, url, self.externalPackagesDir)
937        self.logWrite(retriever.restoreLog())
938        retriever.saveLog()
939        pkgdir = self.getDir()
940        if not pkgdir:
941          raise RuntimeError('Could not locate downloaded package ' +self.PACKAGE +' in '+self.externalPackagesDir)
942        self.framework.actions.addArgument(self.PACKAGE, 'Download', 'Downloaded '+self.PACKAGE+' into '+pkgdir)
943        retriever.restoreLog()
944        self.downloaded = 1
945        return pkgdir
946      except RuntimeError as e:
947        self.logPrint('ERROR: '+str(e))
948        err += str(e)
949    self.logWrite(retriever.restoreLog())
950    raise RuntimeError('Error during download/extract/detection of '+self.PACKAGE+':\n'+err)
951
952  def Install(self):
953    raise RuntimeError('No custom installation implemented for package '+self.package+'\n')
954
955  def checkInclude(self, incl, hfiles, otherIncludes = [], timeout = 600.0):
956    self.headers.pushLanguage(self.buildLanguages[0]) # default is to use the first language in checking
957    self.headers.saveLog()
958    ret = self.executeTest(self.headers.checkInclude, [incl, hfiles], {'otherIncludes' : otherIncludes, 'macro' : None, 'timeout': timeout})
959    self.logWrite(self.headers.restoreLog())
960    self.headers.popLanguage()
961    return ret
962
963  def checkMacros(self, timeout = 600.0):
964    if not len(self.macros):
965      return
966    self.headers.pushLanguage(self.buildLanguages[0]) # default is to use the first language in checking
967    self.logPrint('Checking for macros ' + str(self.macros) + ' in ' + str(self.includes))
968    self.headers.saveLog()
969    for macro in self.macros:
970      self.executeTest(self.headers.checkInclude, [self.include, self.includes], {'macro' : macro, 'timeout' : timeout})
971    self.logWrite(self.headers.restoreLog())
972    self.headers.popLanguage()
973    return
974
975  def checkPackageLink(self, includes, body, cleanup = 1, codeBegin = None, codeEnd = None, shared = 0):
976    flagsArg = self.getPreprocessorFlagsArg()
977    oldFlags = getattr(self.compilers, flagsArg)
978    oldLibs  = self.compilers.LIBS
979    setattr(self.compilers, flagsArg, oldFlags+' '+self.headers.toString(self.include))
980    self.compilers.LIBS = self.libraries.toString(self.lib)+' '+self.compilers.LIBS
981    if 'FC' in self.buildLanguages:
982      self.compilers.LIBS = ' '.join([self.libraries.getLibArgument(lib) for lib in self.compilers.flibs])+' '+self.setCompilers.LIBS
983    if 'Cxx' in self.buildLanguages:
984      self.compilers.LIBS = ' '.join([self.libraries.getLibArgument(lib) for lib in self.compilers.cxxlibs])+' '+self.setCompilers.LIBS
985    result = self.checkLink(includes, body, cleanup, codeBegin, codeEnd, shared)
986    setattr(self.compilers, flagsArg,oldFlags)
987    self.compilers.LIBS = oldLibs
988    return result
989
990  @staticmethod
991  def sortPackageDependencies(startnode):
992    '''Create a dependency graph for all deps, and sort them'''
993    import graph
994    depGraph = graph.DirectedGraph()
995
996    def addGraph(Graph,node,nodesAdded=[]):
997      '''Recursively traverse the dependency graph - and add them as graph edges.'''
998      if not hasattr(node,'deps'): return
999      Graph.addVertex(node)
1000      nodesAdded.append(node)
1001      deps = list(node.deps)
1002      for odep in node.odeps:
1003        if odep.found: deps.append(odep)
1004      if deps:
1005        Graph.addEdges(node,outputs=deps)
1006      for dep in deps:
1007        if dep not in nodesAdded:
1008          addGraph(Graph,dep,nodesAdded)
1009      return
1010
1011    addGraph(depGraph,startnode)
1012    return [sortnode for sortnode in graph.DirectedGraph.topologicalSort(depGraph,start=startnode)]
1013
1014  def checkDependencies(self):
1015    '''Loop over declared dependencies of package and error if any are missing'''
1016    for package in self.deps:
1017      if not hasattr(package, 'found'):
1018        raise RuntimeError('Package '+package.name+' does not have found attribute!')
1019      if not package.found:
1020        if self.argDB['with-'+package.package] == 1:
1021          raise RuntimeError('Package '+package.PACKAGE+' needed by '+self.name+' failed to configure.\nMail configure.log to petsc-maint@mcs.anl.gov.')
1022        else:
1023          str = ''
1024          if package.download: str = ' or --download-'+package.package
1025          raise RuntimeError('Did not find package '+package.PACKAGE+' needed by '+self.name+'.\nEnable the package using --with-'+package.package+str)
1026    for package in self.odeps:
1027      if not hasattr(package, 'found'):
1028        raise RuntimeError('Package '+package.name+' does not have found attribute!')
1029      if not package.found:
1030        if 'with-'+package.package in self.framework.clArgDB and self.framework.clArgDB['with-'+package.package] == 1:
1031          raise RuntimeError('Package '+package.PACKAGE+' needed by '+self.name+' failed to configure.\nMail configure.log to petsc-maint@mcs.anl.gov.')
1032
1033    dpkgs = Package.sortPackageDependencies(self)
1034    dpkgs.remove(self)
1035    for package in dpkgs:
1036      if hasattr(package, 'lib'):     self.dlib += package.lib
1037      if hasattr(package, 'include'): self.dinclude += package.include
1038    return
1039
1040  def addPost(self, dir, rules):
1041    '''Adds make rules that are run after PETSc is built
1042
1043       Without a prefix the rules are run at the end of make all, otherwise they are run at the end of make install
1044    '''
1045    steps = ['@echo "=========================================="',\
1046             '@echo "Building/installing ' + self.name + '. This may take several minutes"',\
1047             '@${RM} ${PETSC_DIR}/${PETSC_ARCH}/lib/petsc/conf/' + self.package + '.build.log']
1048    if not isinstance(rules, list): rules = [rules]
1049    for rule in rules:
1050      steps.append('$(shell [ "$(V)" != "1" ] && echo @)cd ' + dir + ' && ' + rule + ' >> ${PETSC_DIR}/${PETSC_ARCH}/lib/petsc/conf/' + self.package + '.build.log 2>&1 ||\
1051                    (echo "***** Error building/installing ' + self.name + '. Check ${PETSC_DIR}/${PETSC_ARCH}/lib/petsc/conf/' + self.package + '.build.log" && exit 1)')
1052    self.addMakeRule(self.package + 'build', '', steps)
1053    if self.argDB['prefix'] and not 'package-prefix-hash' in self.argDB:
1054      self.framework.postinstalls.append(self.package + 'build')
1055    else:
1056      self.framework.postbuilds.append(self.package + 'build')
1057    self.addDefine('HAVE_' + self.PACKAGE.replace('-','_'), 1)
1058    if self.useddirectly:
1059      if not hasattr(self.framework, 'packages'):
1060        self.framework.packages = []
1061      self.framework.packages.append(self)
1062
1063  def addMakeCheck(self, dir, rule):
1064    '''Adds a small make check for the project'''
1065    self.addMakeRule(self.package + 'check','', \
1066                         ['@echo "*** Checking ' + self.name + ' ***"',\
1067                          '$(shell [ "$(V)" != "1" ] && echo @)cd ' + dir + ' && ' + rule + ' || (echo "***** Error checking ' + self.name + ' ******" && exit 1)'])
1068    self.framework.postchecks.append(self.package + 'check')
1069
1070  def addTest(self, dir, rule):
1071    '''Adds a large make test for the project'''
1072    self.addMakeRule(self.package + 'test','', \
1073                         ['@echo "*** Testing ' + self.name + ' ***"',\
1074                          '@${RM} ${PETSC_DIR}/${PETSC_ARCH}/lib/petsc/conf/' + self.package + '.errorflg',\
1075                          '$(shell [ "$(V)" != "1" ] && echo @)cd ' + dir + ' && ' + rule + ' || (echo "***** Error testing ' + self.name + ' ******" && exit 1)'])
1076
1077  def configureLibrary(self):
1078    '''Find an installation and check if it can work with PETSc'''
1079    self.log.write('==================================================================================\n')
1080    self.logPrint('Checking for a functional '+self.name)
1081    foundLibrary = 0
1082    foundHeader  = 0
1083
1084    for location, directory, lib, incl in self.generateGuesses():
1085      #  directory is not used in the search, it is used only in logging messages about where the
1086      #  searching is taking place. It has to already be embedded inside the lib argument
1087      if self.builtafterpetsc:
1088        self.found = 1
1089        return
1090
1091      if directory and not os.path.isdir(directory):
1092        self.logPrint('Directory does not exist: %s (while checking "%s" for "%r")' % (directory,location,lib))
1093        continue
1094      if lib == '': lib = []
1095      elif not isinstance(lib, list): lib = [lib]
1096      if incl == '': incl = []
1097      elif not isinstance(incl, list): incl = [incl]
1098      testedincl = list(incl)
1099      # weed out duplicates when adding fincs
1100      for loc in self.compilers.fincs:
1101        if not loc in incl:
1102          incl.append(loc)
1103      if self.functions:
1104        self.logPrint('Checking for library in '+location+': '+str(lib))
1105        if directory:
1106          self.logPrint('Contents of '+directory+': '+str(os.listdir(directory)))
1107          for libdir in self.libDirs:
1108            flibdir = os.path.join(directory, libdir)
1109            if os.path.isdir(flibdir):
1110              self.logPrint('Contents '+flibdir+': '+str(os.listdir(flibdir)))
1111      else:
1112        self.logPrint('Not checking for library in '+location+': '+str(lib)+' because no functions given to check for')
1113
1114      otherlibs = self.dlib
1115      if 'FC' in self.buildLanguages:
1116        otherlibs.extend(self.compilers.flibs)
1117      if 'Cxx' in self.buildLanguages:
1118        otherlibs.extend(self.compilers.cxxlibs)
1119      self.libraries.saveLog()
1120      if self.executeTest(self.libraries.check,[lib, self.functions],{'otherLibs' : self.dlib, 'fortranMangle' : self.functionsFortran, 'cxxMangle' : self.functionsCxx[0], 'prototype' : self.functionsCxx[1], 'call' : self.functionsCxx[2], 'cxxLink': 'Cxx' in self.buildLanguages}):
1121        self.lib = lib
1122        if self.functionsDefine:
1123          self.executeTest(self.libraries.check,[lib, self.functionsDefine],{'otherLibs' : self.dlib, 'fortranMangle' : self.functionsFortran, 'cxxMangle' : self.functionsCxx[0], 'prototype' : self.functionsCxx[1], 'call' : self.functionsCxx[2], 'cxxLink': 'Cxx' in self.buildLanguages, 'functionDefine': 1})
1124        self.logWrite(self.libraries.restoreLog())
1125        self.logPrint('Checking for headers '+str(self.includes)+' in '+location+': '+str(incl))
1126        if (not self.includes) or self.checkInclude(incl, self.includes, self.dinclude, timeout = 60.0):
1127          if self.includes:
1128            self.include = testedincl
1129          self.found     = 1
1130          self.dlib      = self.lib+self.dlib
1131          dinc = []
1132          [dinc.append(inc) for inc in incl+self.dinclude if inc not in dinc]
1133          self.dinclude = dinc
1134          self.checkMacros(timeout = 60.0)
1135          if not hasattr(self.framework, 'packages'):
1136            self.framework.packages = []
1137          self.directory = directory
1138          self.framework.packages.append(self)
1139          return
1140      else:
1141        self.logWrite(self.libraries.restoreLog())
1142    if not self.lookforbydefault or ('with-'+self.package in self.framework.clArgDB and self.argDB['with-'+self.package]):
1143      raise RuntimeError('Could not find a functional '+self.name+'\n')
1144    if self.lookforbydefault and 'with-'+self.package not in self.framework.clArgDB:
1145      self.argDB['with-'+self.package] = 0
1146
1147  def checkSharedLibrary(self):
1148    '''By default we don\'t care about checking if the library is shared'''
1149    return 1
1150
1151  def alternateConfigureLibrary(self):
1152    '''Called if --with-packagename=0; does nothing by default'''
1153    pass
1154
1155  def consistencyChecks(self):
1156    '''Checks run on the system and currently installed packages that need to be correct for the package now being configured'''
1157    def inVersionRange(myRange,reqRange):
1158      # my minimum needs to be less than the maximum and my maximum must be greater than
1159      # the minimum
1160      return (myRange[0].lower() <= reqRange[1].lower()) and (myRange[1].lower() >= reqRange[0].lower())
1161
1162    self.printTest(self.consistencyChecks)
1163    if 'with-'+self.package+'-dir' in self.argDB and ('with-'+self.package+'-include' in self.argDB or 'with-'+self.package+'-lib' in self.argDB):
1164      raise RuntimeError('Specify either "--with-'+self.package+'-dir" or "--with-'+self.package+'-lib --with-'+self.package+'-include". But not both!')
1165
1166    blaslapackconflict = 0
1167    for pkg in self.deps:
1168      if pkg.package == 'blaslapack':
1169        if pkg.has64bitindices and self.requires32bitintblas:
1170          blaslapackconflict = 1
1171
1172    cxxVersionRange = (self.minCxxVersion,self.maxCxxVersion)
1173    cxxVersionConflict = not inVersionRange(cxxVersionRange,self.setCompilers.cxxDialectRange[self.getDefaultLanguage()])
1174    # if user did not request option, then turn it off if conflicts with configuration
1175    if self.lookforbydefault and 'with-'+self.package not in self.framework.clArgDB:
1176      mess = None
1177      if 'Cxx' in self.buildLanguages and not hasattr(self.compilers, 'CXX'):
1178        mess = 'requires C++ but C++ compiler not set'
1179      if 'FC'  in self.buildLanguages and not hasattr(self.compilers, 'FC'):
1180        mess = 'requires Fortran but Fortran compiler not set'
1181      if self.noMPIUni and self.mpi.usingMPIUni:
1182        mess = 'requires real MPI but MPIUNI is being used'
1183      if cxxVersionConflict:
1184        mess = 'cannot work with C++ version being used'
1185      if not self.defaultPrecision.lower() in self.precisions:
1186        mess = 'does not support the current precision '+ self.defaultPrecision.lower()
1187      if not self.complex and self.defaultScalarType.lower() == 'complex':
1188        mess = 'does not support complex numbers but PETSc being build with complex'
1189      if self.defaultIndexSize == 64 and self.requires32bitint:
1190        mess = 'does not support 64-bit indices which PETSc is configured for'
1191      if blaslapackconflict:
1192        mess = 'requires 32-bit BLAS/LAPACK indices but configure is building with 64-bit'
1193
1194      if mess:
1195        self.logPrint('Turning off default package '+ self.package + ' because package ' + mess)
1196        self.argDB['with-'+self.package] = 0
1197
1198    if self.argDB['with-'+self.package]:
1199      if blaslapackconflict:
1200        raise RuntimeError('Cannot use '+self.name+' with 64-bit BLAS/LAPACK indices')
1201      if 'Cxx' in self.buildLanguages and not hasattr(self.compilers, 'CXX'):
1202        raise RuntimeError('Cannot use '+self.name+' without C++, make sure you do NOT have --with-cxx=0')
1203      if 'FC'  in self.buildLanguages and not hasattr(self.compilers, 'FC'):
1204        raise RuntimeError('Cannot use '+self.name+' without Fortran, make sure you do NOT have --with-fc=0')
1205      if self.noMPIUni and self.mpi.usingMPIUni:
1206        raise RuntimeError('Cannot use '+self.name+' with MPIUNI, you need a real MPI')
1207      if cxxVersionConflict:
1208        raise RuntimeError('Cannot use '+self.name+' as it requires -std=['+','.join(map(str,cxxVersionRange))+'], while your compiler seemingly only supports -std=['+','.join(map(str,self.setCompilers.cxxDialectRange[self.getDefaultLanguage()]))+']')
1209      if self.download and self.argDB.get('download-'+self.downloadname.lower()) and not self.downloadonWindows and (self.setCompilers.CC.find('win32fe') >= 0):
1210        raise RuntimeError('External package '+self.name+' does not support --download-'+self.downloadname.lower()+' with Microsoft compilers')
1211      if not self.defaultPrecision.lower() in self.precisions:
1212        raise RuntimeError('Cannot use '+self.name+' with '+self.defaultPrecision.lower()+', it is not available in this precision')
1213      if not self.complex and self.defaultScalarType.lower() == 'complex':
1214        raise RuntimeError('Cannot use '+self.name+' with complex numbers it is not coded for this capability')
1215      if self.defaultIndexSize == 64 and self.requires32bitint:
1216        raise RuntimeError('Cannot use '+self.name+' with 64-bit integers, it is not coded for this capability')
1217    if not self.download and 'download-'+self.downloadname.lower() in self.argDB and self.argDB['download-'+self.downloadname.lower()]:
1218      raise RuntimeError('External package '+self.name+' does not support --download-'+self.downloadname.lower())
1219    return
1220
1221  def versionToStandardForm(self,version):
1222    '''Returns original string'''
1223    '''This can be overloaded by packages that have their own unique representation of versions; for example CUDA'''
1224    return version
1225
1226  def versionToTuple(self,version):
1227    '''Converts string of the form x.y to (x,y)'''
1228    if not version: return ()
1229    vl = version.split('.')
1230    if len(vl) > 2:
1231      vl[-1] = re.compile(r'^[0-9]+').search(vl[-1]).group(0)
1232    return tuple(map(int,vl))
1233
1234  def checkVersion(self):
1235    '''Uses self.version, self.minversion, self.maxversion, self.versionname, and self.versioninclude to determine if package has required version'''
1236    def dropPatch(str):
1237      '''Drops the patch version number in a version if it exists'''
1238      if str.find('.') == str.rfind('.'): return str
1239      return str[0:str.rfind('.')]
1240    def zeroPatch(str):
1241      '''Replaces the patch version number in a version if it exists with 0'''
1242      if str.find('.') == str.rfind('.'): return str
1243      return str[0:str.rfind('.')]+'.0'
1244    def infinitePatch(str):
1245      '''Replaces the patch version number in a version if it exists with a very large number'''
1246      if str.find('.') == str.rfind('.'): return str
1247      return str[0:str.rfind('.')]+'.100000'
1248
1249    if not self.version and not self.minversion and not self.maxversion and not self.versionname: return
1250    if not self.versioninclude:
1251      if not self.includes:
1252        self.log.write('For '+self.package+' unable to find version information since includes and version includes are missing skipping version check\n')
1253        self.version = ''
1254        return
1255      self.versioninclude = self.includes[0]
1256    self.pushLanguage(self.buildLanguages[0]) # default is to use the first language in checking
1257    flagsArg = self.getPreprocessorFlagsArg()
1258    oldFlags = getattr(self.compilers, flagsArg)
1259    if self.language[-1] == 'HIP':
1260      extraFlags = ' -o -' # Force 'hipcc -E' to output to stdout, instead of *.cui files (as of hip-4.0. hip-4.1+ does not need it, but does not get hurt either).
1261    else:
1262      extraFlags = ''
1263    setattr(self.compilers, flagsArg, oldFlags+extraFlags+' '+self.headers.toString(self.dinclude))
1264    self.compilers.saveLog()
1265
1266    # Multiple headers are tried in order
1267    if not isinstance(self.versioninclude,list):
1268      headerList = [self.versioninclude]
1269    else:
1270      headerList = self.versioninclude
1271
1272    for header in headerList:
1273      try:
1274        # We once used '#include "'+self.versioninclude+'"\npetscpkgver('+self.versionname+');\n',
1275        # but some preprocessors are picky (ex. dpcpp -E), reporting errors on the code above even
1276        # it is just supposed to do preprocessing:
1277        #
1278        #  error: C++ requires a type specifier for all declarations
1279        #  petscpkgver(__SYCL_COMPILER_VERSION);
1280        #  ^
1281        #
1282        # So we instead use this compilable code.
1283        output = self.outputPreprocess(
1284'''
1285#include "{x}"
1286#define  PetscXstr_(s) PetscStr_(s)
1287#define  PetscStr_(s)  #s
1288const char *ver = "petscpkgver(" PetscXstr_({y}) ")";
1289'''.format(x=header, y=self.versionname))
1290         # Ex. char *ver = "petscpkgver(" "20211206" ")";
1291         # But after stripping spaces, quotes etc below, it becomes char*ver=petscpkgver(20211206);
1292      except:
1293        output = None
1294      self.logWrite(self.compilers.restoreLog())
1295      if output:
1296        break
1297    self.popLanguage()
1298    setattr(self.compilers, flagsArg,oldFlags)
1299    if not output:
1300        self.log.write('For '+self.package+' unable to run preprocessor to obtain version information, skipping version check\n')
1301        self.version = ''
1302        return
1303    # the preprocessor output might be very long, but the petscpkgver line should be at the end. Therefore, we partition it backwards
1304    [mid, right] = output.rpartition('petscpkgver')[1:]
1305    version = ''
1306    if mid: # if mid is not empty, then it should be 'petscpkgver', meaning we found the version string
1307      verLine = right.split(';',1)[0] # get the string before the first ';'. Preprocessor might dump multiline result.
1308      self.log.write('Found the raw version string: ' + verLine +'\n')
1309      # strip backslashes, spaces, and quotes. Note MUMPS' version macro has "" around it, giving output: (" "\"5.4.1\"" ")";
1310      for char in ['\\', ' ', '"']:
1311          verLine = verLine.replace(char, '')
1312      # get the string between the outer ()
1313      version = verLine.split('(', 1)[-1].rsplit(')',1)[0]
1314      self.log.write('This is the processed version string: ' + version +'\n')
1315    if not version:
1316      self.log.write('For '+self.package+' unable to find version information: output below, skipping version check\n')
1317      self.log.write(output)
1318      if self.requiresversion:
1319        raise RuntimeError('Configure must be able to determined the version information for '+self.name+'. It was unable to, please send configure.log to petsc-maint@mcs.anl.gov')
1320      return
1321    try:
1322      # 'version' could be in many formats, like '10007201', '3.23.0', or '((((1)<<24)|((18)<<16)))'. As long as it doesn't contain '.', we eval it to simplify it.
1323      if '.' not in version:
1324        try:
1325          version = str(eval(version)) # eval a potentially complex version expression
1326          self.log.write('This is the evaluated version string: ' + version +'\n')
1327        except:
1328          self.log.write('For '+self.package+' failed to eval its version string ('+version+') to a number\n')
1329      self.foundversion = self.versionToStandardForm(version)
1330    except:
1331      self.log.write('For '+self.package+' unable to convert version information ('+version+') to standard form, skipping version check\n')
1332      if self.requiresversion:
1333        raise RuntimeError('Configure must be able to determined the version information for '+self.name+'. It was unable to, please send configure.log to petsc-maint@mcs.anl.gov')
1334      return
1335
1336    self.log.write('For '+self.package+' need '+self.minversion+' <= '+self.foundversion+' <= '+self.maxversion+'\n')
1337
1338    try:
1339      self.version_tuple = self.versionToTuple(self.foundversion)
1340    except:
1341      self.log.write('For '+self.package+' unable to convert version string to tuple, skipping version check\n')
1342      if self.requiresversion:
1343        raise RuntimeError('Configure must be able to determined the version information for '+self.name+'; it appears to be '+self.foundversion+'. It was unable to, please send configure.log to petsc-maint@mcs.anl.gov')
1344      self.foundversion = ''
1345      return
1346
1347    suggest = ''
1348    if self.download:
1349      suggest = '. Suggest using --download-'+self.package+' for a compatible '+self.name
1350      if self.argDB['download-'+self.package]:
1351        rmdir = None
1352        try:
1353          rmdir = self.getDir()
1354        except:
1355          pass
1356        if rmdir:
1357          # this means that --download-package was requested, the package was not rebuilt, but there are newer releases of the package so it should be rebuilt
1358          suggest += ' after running "rm -rf ' + self.getDir() +'"\n'
1359          suggest += 'DO NOT DO THIS if you rely on the exact version of the currently installed ' + self.name
1360    if self.minversion:
1361      if self.versionToTuple(self.minversion) > self.version_tuple:
1362        raise RuntimeError(self.PACKAGE+' version is '+self.foundversion+', this version of PETSc needs at least '+self.minversion+suggest+'\n')
1363    elif self.version:
1364      if self.versionToTuple(zeroPatch(self.version)) > self.version_tuple:
1365        self.logPrintWarning('Using version '+self.foundversion+' of package '+self.PACKAGE+', PETSc is tested with '+dropPatch(self.version)+suggest)
1366    if self.maxversion:
1367      if self.versionToTuple(self.maxversion) < self.version_tuple:
1368        raise RuntimeError(self.PACKAGE+' version is '+self.foundversion+', this version of PETSc needs at most '+self.maxversion+suggest+'\n')
1369    elif self.version:
1370      if self.versionToTuple(infinitePatch(self.version)) < self.version_tuple:
1371        self.logPrintWarning('Using version '+self.foundversion+' of package '+self.PACKAGE+', PETSc is tested with '+dropPatch(self.version)+suggest)
1372    return
1373
1374  def configure(self):
1375    if hasattr(self, 'download_solaris') and config.setCompilers.Configure.isSolaris(self.log):
1376      self.download = self.download_solaris
1377    if hasattr(self, 'download_darwin') and config.setCompilers.Configure.isDarwin(self.log):
1378      self.download = self.download_darwin
1379    if hasattr(self, 'download_mingw') and config.setCompilers.Configure.isMINGW(self.framework.getCompiler(), self.log):
1380      self.download = self.download_mingw
1381    if self.download and self.argDB['download-'+self.downloadname.lower()] and (not self.framework.batchBodies or self.installwithbatch):
1382      self.argDB['with-'+self.package] = 1
1383      downloadPackageVal = self.argDB['download-'+self.downloadname.lower()]
1384      if isinstance(downloadPackageVal, str):
1385        self.download = [downloadPackageVal]
1386    if self.download and self.argDB['download-'+self.downloadname.lower()+'-commit']:
1387      self.gitcommit = self.argDB['download-'+self.downloadname.lower()+'-commit']
1388      if hasattr(self, 'download_git'):
1389        self.download = self.download_git
1390    elif self.gitcommitmain and not self.petscdir.versionRelease:
1391      self.gitcommit = self.gitcommitmain
1392    if not 'with-'+self.package in self.argDB:
1393      self.argDB['with-'+self.package] = 0
1394    if 'with-'+self.package+'-dir' in self.argDB or 'with-'+self.package+'-include' in self.argDB or 'with-'+self.package+'-lib' in self.argDB:
1395      self.argDB['with-'+self.package] = 1
1396    if 'with-'+self.package+'-pkg-config' in self.argDB:
1397      self.argDB['with-'+self.package] = 1
1398
1399    self.consistencyChecks()
1400    if self.argDB['with-'+self.package]:
1401      # If clanguage is c++, test external packages with the c++ compiler
1402      self.libraries.pushLanguage(self.defaultLanguage)
1403      self.executeTest(self.checkDependencies)
1404      self.executeTest(self.configureLibrary)
1405      if not self.builtafterpetsc:
1406        self.executeTest(self.checkVersion)
1407      self.executeTest(self.checkSharedLibrary)
1408      self.libraries.popLanguage()
1409    else:
1410      self.executeTest(self.alternateConfigureLibrary)
1411    return
1412
1413  def updateCompilers(self, installDir, mpiccName, mpicxxName, mpif77Name, mpif90Name):
1414    '''Check if mpicc, mpicxx etc binaries exist - and update setCompilers() database.
1415    The input arguments are the names of the binaries specified by the respective packages
1416    This should really be part of compilers.py but it also uses compilerFlags.configure() so
1417    I am putting it here and Matt can fix it'''
1418
1419    # Both MPICH and MVAPICH now depend on LD_LIBRARY_PATH for sharedlibraries.
1420    # So using LD_LIBRARY_PATH in configure - and -Wl,-rpath in makefiles
1421    if self.argDB['with-shared-libraries']:
1422      config.setCompilers.Configure.addLdPath(os.path.join(installDir,'lib'))
1423    # Initialize to empty
1424    mpicc=''
1425    mpicxx=''
1426    mpifc=''
1427
1428    mpicc = os.path.join(installDir,"bin",mpiccName)
1429    if not os.path.isfile(mpicc): raise RuntimeError('Could not locate installed MPI compiler: '+mpicc)
1430    try:
1431      self.logPrint('Showing compiler and options used by newly built MPI')
1432      self.executeShellCommand(mpicc + ' -show', log = self.log)[0]
1433    except:
1434      pass
1435    if hasattr(self.compilers, 'CXX'):
1436      mpicxx = os.path.join(installDir,"bin",mpicxxName)
1437      if not os.path.isfile(mpicxx): raise RuntimeError('Could not locate installed MPI compiler: '+mpicxx)
1438      try:
1439        self.executeShellCommand(mpicxx + ' -show', log = self.log)[0]
1440      except:
1441        pass
1442    if hasattr(self.compilers, 'FC'):
1443      if self.fortran.fortranIsF90:
1444        mpifc = os.path.join(installDir,"bin",mpif90Name)
1445      else:
1446        mpifc = os.path.join(installDir,"bin",mpif77Name)
1447      if not os.path.isfile(mpifc): raise RuntimeError('Could not locate installed MPI compiler: '+mpifc)
1448      try:
1449        self.executeShellCommand(mpifc + ' -show', log = self.log)[0]
1450      except:
1451        pass
1452    # redo compiler detection, copy the package cxx dialect restrictions though
1453    oldPackageRanges = self.setCompilers.cxxDialectPackageRanges
1454    self.setCompilers.updateMPICompilers(mpicc,mpicxx,mpifc)
1455    self.setCompilers.cxxDialectPackageRanges = oldPackageRanges
1456    self.compilers.__init__(self.framework)
1457    self.compilers.headerPrefix = self.headerPrefix
1458    self.compilers.setup()
1459    self.compilerFlags.saveLog()
1460    self.compilerFlags.configure()
1461    self.logWrite(self.compilerFlags.restoreLog())
1462    self.compilers.saveLog()
1463    self.compilers.configure()
1464    self.logWrite(self.compilers.restoreLog())
1465    if self.cuda.found:
1466      self.cuda.configureLibrary()
1467    return
1468
1469  def checkSharedLibrariesEnabled(self):
1470    if self.havePETSc:
1471      useShared = self.sharedLibraries.useShared
1472    else:
1473      useShared = True
1474    if 'download-'+self.package+'-shared' in self.framework.clArgDB and self.argDB['download-'+self.package+'-shared']:
1475      raise RuntimeError(self.package+' cannot use download-'+self.package+'-shared=1. This flag can only be used to disable '+self.package+' shared libraries')
1476    if not useShared or ('download-'+self.package+'-shared' in self.framework.clArgDB and not self.argDB['download-'+self.package+'-shared']):
1477      return False
1478    else:
1479      return True
1480
1481  def compilePETSc(self):
1482    try:
1483      self.logPrintBox('Compiling PETSc; this may take several minutes')
1484      output,err,ret  = config.package.Package.executeShellCommand(self.make.make+' all PETSC_DIR='+self.petscdir.dir+' PETSC_ARCH='+self.arch, cwd=self.petscdir.dir, timeout=1000, log = self.log)
1485      self.log.write(output+err)
1486    except RuntimeError as e:
1487      raise RuntimeError('Error running make all on PETSc: '+str(e))
1488    if self.framework.argDB['prefix']:
1489      try:
1490        self.logPrintBox('Installing PETSc; this may take several minutes')
1491        output,err,ret  = config.package.Package.executeShellCommand(self.make.make+' install PETSC_DIR='+self.petscdir.dir+' PETSC_ARCH='+self.arch, cwd=self.petscdir.dir, timeout=60, log = self.log)
1492        self.log.write(output+err)
1493      except RuntimeError as e:
1494        raise RuntimeError('Error running make install on PETSc: '+str(e))
1495    elif not self.argDB['with-batch']:
1496      try:
1497        self.logPrintBox('Testing PETSc; this may take several minutes')
1498        output,err,ret  = config.package.Package.executeShellCommand(self.make.make+' test PETSC_DIR='+self.petscdir.dir+' PETSC_ARCH='+self.arch, cwd=self.petscdir.dir, timeout=60, log = self.log)
1499        output = output+err
1500        self.log.write(output)
1501        if output.find('error') > -1 or output.find('Error') > -1:
1502          raise RuntimeError('Error running make check on PETSc: '+output)
1503      except RuntimeError as e:
1504        raise RuntimeError('Error running make check on PETSc: '+str(e))
1505    self.installedpetsc = 1
1506
1507'''
1508config.package.GNUPackage is a helper class whose intent is to simplify writing configure modules
1509for GNU-style packages that are installed using the "configure; make; make install" idiom.
1510
1511Brief overview of how BuildSystem\'s configuration of packages works.
1512---------------------------------------------------------------------
1513    Configuration is carried out by "configure objects": instances of classes desendant from config.base.Configure.
1514  These configure objects implement the "configure()" method, and are inserted into a "framework" object,
1515  which makes the "configure()" calls according to the dependencies between the configure objects.
1516    config.package.Package extends config.base.Configure and adds instance variables and methods that facilitate
1517  writing classes that configure packages.  Customized package configuration classes are written by subclassing
1518  config.package.Package -- the "parent class".
1519
1520    Packages essentially encapsulate libraries, that either
1521    (A) are already (prefix-)installed already somewhere on the system or
1522    (B) need to be downloaded, built and installed first
1523  If (A), the parent class provides a generic mechanism for locating the installation, by looking in user-specified and standard locations.
1524  If (B), the parent class provides a generic mechanism for determining whether a download is necessary, downloading and unpacking
1525  the source (if the download is, indeed, required), determining whether the package needs to be built, providing the build and
1526  installation directories, and a few other helper tasks.  The package subclass is responsible for implementing the "Install" hook,
1527  which is called by the parent class when the actual installation (building the source code, etc.) is done.  As an aside, BuildSystem-
1528  controlled build and install of a package at configuration time has a much better chance of guaranteeing language, compiler and library
1529  (shared or not) consistency among packages.
1530    No matter whether (A) or (B) is realized, the parent class control flow demands that the located or installed package
1531  be checked to ensure it is functional.  Since a package is conceptualized as a library, the check consists in testing whether
1532  a specified set of libraries can be linked against, and ahat the specified headers can be located.  The libraries and headers are specified
1533  by name, and the corresponding paths are supplied as a result of the process of locating or building the library.  The verified paths and
1534  library names are then are stored by the configure object as instance variables.  These can be used by other packages dependent on the package
1535  being configured; likewise, the package being configured will use the information from the packages it depends on by examining their instance
1536  variables.
1537
1538    Thus, the parent class provides for the overall control and data flow, which goes through several configuration stages:
1539  "init", "setup", "location/installation", "testing".  At each stage, various "hooks" -- methods -- are called.
1540  Some hooks (e.g., Install) are left unimplemented by the parent class and must be implemented by the package subclass;
1541  other hooks are implemented by the parent class and provide generic functionality that is likely to suit most packages,
1542  but can be overridden for custom purposes.  Each hook typically prepares the state -- instance variables -- of the configure object
1543  for the next phase of configuration.  Below we describe the stages, some of the more typically-used hooks and instance variables in some
1544  detail.
1545
1546  init:
1547  ----
1548  The init stage constructs the configure object; it is implemented by its __init__ method.
1549  Parent package class sets up the following useful state variables:
1550    self.name             - derived from module name                      [string]
1551    self.package          - lowercase name                                [string]
1552    self.PACKAGE          - uppercase name                                [string]
1553    self.downloadname     - same as self.name (usage a bit inconsistent)  [string]
1554    self.downloaddirnames - same as self.name (usage a bit inconsistent)  [string]
1555  Package subclass typically sets up the following state variables:
1556    self.download         - url to download source from                   [string]
1557    self.includes         - names of header files to locate               [list of strings]
1558    self.liblist          - names of library files to locate              [list of lists of strings]
1559    self.functions        - names of functions to locate in libraries     [list of strings]
1560    self.cxx              - whether C++ compiler, (this does not require that PETSc be built with C++, should it?) is required for this package      [bool]
1561    self.functionsFortran - whether to mangle self.functions symbols      [bool]
1562  Most of these instance variables determine the behavior of the location/installation and the testing stages.
1563  Ideally, a package subclass would extend only the __init__ method and parameterize the remainder of
1564  the configure process by the appropriate variables.  This is not always possible, since some
1565  of the package-specific choices depend on
1566
1567  setup:
1568  -----
1569  The setup stage follows init and is accomplished by the configure framework calling each configure objects
1570  setup hooks:
1571
1572    setupHelp:
1573    ---------
1574    This is used to define the command-line arguments expected by this configure object.
1575    The parent package class sets up generic arguments:
1576      --with-<package>         [bool]
1577      --with-<package>-dir     [string: directory]
1578      --download-<package>     [string:"yes","no","filename"]
1579      --with-<package>-include [string: directory]
1580      --with-<package>-lib     [string: directory]
1581    Here <package> is self.package defined in the init stage.
1582    The package subclass can add to these arguments.  These arguments\' values are set
1583    from the defaults specified in setupHelp or from the user-supplied command-line arguments.
1584    Their values can be queried at any time during the configure process.
1585
1586    setupDependencies:
1587    -----------------
1588    This is used to specify other configure objects that the package being configured depends on.
1589    This is done via the configure framework\'s "require" mechanism:
1590      self.framework.require(<dependentObject>, self)
1591    dependentObject is a string -- the name of the configure module this package depends on.
1592
1593    The parent package class by default sets up some of the common dependencies:
1594      config.compilers, config.types, config.headers, config.libraries, config.packages.MPI,
1595    among others.
1596    The package subclass should add package-specific dependencies via the "require" mechanism,
1597    as well as list them in self.deps [list].  This list is used during the location/installation
1598    stage to ensure that the package\'s dependencies have been configured correctly.
1599
1600  Location/installation:
1601  ---------------------
1602  These stages (somewhat mutually-exclusive), as well as the testing stage are carried out by the code in
1603  configureLibrary.  These stages calls back to certain hooks that allow the user to control the
1604  location/installation process by overriding these hooks in the package subclass.
1605
1606  Location:
1607  --------
1608  [Not much to say here, yet.]
1609
1610  Installation:
1611  ------------
1612  This stage is carried out by configure and functions called from it, most notably, configureLibrary
1613  The essential difficulty here is that the function calls are deeply nested (A-->B-->C--> ...),
1614  as opposed to a single driver repeatedly calling small single-purpose callback hooks.  This means that any
1615  customization would not be able to be self-contained by would need to know to call further down the chain.
1616  Moreover, the individual functions on the call stack combine generic code with the code that is naturally meant
1617  for customization by a package subclass.  Thus, a customization would have to reproduce this generic code.
1618  Some of the potentially customizable functionality is split between different parts of the code below
1619  configure (see, e.g., the comment at the end of this paragraph).
1620    Because of this, there are few opportunities for customization in the installation stage, without a substantial
1621  restructuring of configure, configureLibrary and/or its callees. Here we mention the main customizable callback
1622  Install along with two generic services, installNeeded and postInstall, which are provided by the parent class and
1623  can be used in implementing a custom Install.
1624    Comment: Note that configure decides whether to configure the package, in part, based on whether
1625             self.download is a non-empty list at the beginning of configure.
1626             This means that resetting self.download cannot take place later than this.
1627             On the other hand, constructing the correct self.download here might be premature, as it might result
1628             in unnecessary prompts for user input, only to discover later that a download is not required.
1629             Because of this a package configure class must always have at least dummy string for self.download, if
1630             a download is possible.
1631
1632  Here is a schematic description of the main point on the call chain:
1633
1634  configure:
1635    check whether to configure the package:
1636    package is configured only if
1637      self.download is not an empty string list and the command-line download flag is on
1638      OR if
1639      the command-line flag "-with-"self.package is present, prompting a search for the package on the system
1640      OR if
1641      the command-line flag(s) pointing to a package installation "-with-"self.package+"-dir or ...-lib, ...-include are present
1642    ...
1643    configureLibrary:
1644      consistencyChecks:
1645        ...
1646        check that appropriate language support is on:
1647          self.cxx            == 1 implies C++ compiler must be present
1648          self.fc             == 1 implies Fortran compiler must be present
1649          self.noMPIUni       == 1 implies real MPI must be present
1650      ...
1651      generateGuesses:
1652        ...
1653        checkDownload:
1654          ...
1655          check val = argDB[\'download-\'self.downloadname.tolower()\']
1656          /*
1657           note the inconsistency with setupHelp: it declares \'download-\'self.package
1658           Thus, in order for the correct variable to be queried here, we have to have
1659           self.downloadname.tolower() == self.package
1660          */
1661          if val is a string, set self.download = [val]
1662          check the package license
1663          getInstallDir:
1664            ...
1665            set the following instance variables, creating directories, if necessary:
1666            self.installDir   /* This is where the package will be installed, after it is built. */
1667            self.includeDir   /* subdir of self.installDir */
1668            self.libDir       /* subdir of self.installDir, defined as self.installDir + self.libDirs[0] */
1669            self.confDir      /* where packages private to the configure/build process are built, such as --download-make */
1670                              /* The subdirectory of this 'conf' is where the configuration information will be stored for the package */
1671            self.packageDir = /* this dir is where the source is unpacked and built */
1672            self.getDir():
1673              ...
1674              if a package dir starting with self.downloadname does not exist already
1675                create the package dir
1676                downLoad():
1677                  ...
1678                  download and unpack the source to self.packageDir,
1679            Install():
1680            /* This must be implemented by a package subclass */
1681
1682    Install:
1683    ------
1684    Note that it follows from the above pseudocode, that the package source is already in self.packageDir
1685    and the dir instance variables (e.g., installDir, confDir) already point to existing directories.
1686    The user can implement whatever actions are necessary to configure, build and install
1687    the package.  Typically, the package is built using GNU\'s "configure; make; make install"
1688    idiom, so the customized Install forms GNU configure arguments using the compilers,
1689    system libraries and dependent packages (their locations, libs and includes) discovered
1690    by configure up to this point.
1691
1692    It is important to check whether the package source in self.packageDir needs rebuilding, since it might
1693    have been downloaded in a previous configure run, as is checked by getDir() above.
1694    However, the package might now need to be built with different options.  For that reason,
1695    the parent class provides a helper method
1696      installNeeded(self, mkfile):
1697        This method compares two files: the file with name mkfile in self.packageDir and
1698        the file with name self.name in self.confDir (a subdir of the installation dir).
1699        If the former is absent or differs from the latter, this means the source has never
1700        been built or was built with different arguments, and needs to be rebuilt.
1701        This helper method should be run at the beginning of an Install implementation,
1702        to determine whether an install is actually needed.
1703    The other useful helper method provided by the parent class is
1704       postInstall(self, output,mkfile):
1705         This method will simply save string output in the file with name mkfile in self.confDir.
1706         Storing package configuration parameters there will enable installNeeded to do its job
1707         next time this package is being configured.
1708
1709  testing:
1710  -------
1711  The testing is carried out by part of the code in config.package.configureLibrary,
1712  after the package library has been located or installed.
1713  The library is considered functional if two conditions are satisfied:
1714   (1) all of the symbols in self.functions have been resolved when linking against the libraries in self.liblist,
1715       either located on the system or newly installed;
1716   (2) the headers in self.includes have been located.
1717  If no symbols are supplied in self.functions, no link OR header testing is done.
1718
1719  Extending package class:
1720  -----------------------
1721  Generally, extending the parent package configure class is done by overriding some
1722  or all of its methods (see config/BuildSystem/config/packages/hdf5.py, for example).
1723  Because convenient (i.e., localized) hooks are available onto to some parts of the
1724  configure process, frequently writing a custom configure class amounts to overriding
1725  configureLibrary so that pre- and post-code can be inserted before calling to
1726  config.package.Package.configureLibrary.
1727
1728  In any event, Install must be implemented anew for any package configure class extending
1729  config.package.Package.  Naturally, instance variables have to be set appropriately
1730  in __init__ (or elsewhere), package-specific help options and dependencies must be defined.
1731  Therefore, the general pattern for package configure subclassing is this:
1732    - override __init__ and set package-specific instance variables
1733    - override setupHelp and setupDependencies hooks to set package-specific command-line
1734      arguments and dependencies on demand
1735    - override Install, making use of the parent class\'s installNeeded and postInstall
1736    - override configureLibrary, if necessary, to insert pre- and post-configure fixup code.
1737
1738  GNUPackage class:
1739  ----------------
1740  This class is an attempt at making writing package configure classes easier for the packages
1741  that use the "configure; make; make install" idiom for the installation -- "GNU packages".
1742  The main contribution is in the implementation of a generic Install method, which attempts
1743  to automate the building of a package based on the mostly standard instance variables.
1744
1745  Besides running GNU configure, GNUPackage.Install runs installNeeded, make and postInstall
1746  at the appropriate times, automatically determining whether a rebuild is necessary, saving
1747  a GNU configure arguments stamp to perform the check in the future, etc.
1748
1749  setupHelp:
1750  ---------
1751  This method extends config.Package.setupHelp by adding two command-line arguments:
1752    "-download-"+self.package+"-version" with self.downloadversion as default or None, if it does not exist
1753    "-download-"+self.package+"-shared" with False as the default.
1754
1755  Summary:
1756  -------
1757  In order to customize GNUPackage:
1758    - set up the usual instance variables in __init__, plus the following instance variables, if necessary/appropriate:
1759        self.downloadpath
1760        self.downloadext
1761        self.downloadversion
1762    - override setupHelp to declare command-line arguments that can be used anywhere below
1763      (GNUPackage takes care of some of the basic args, including the download version)
1764    - override setupDependencies to process self.odeps and enable this optional package feature in the current externalpackage.
1765    - override setupDownload to control the precise download URL and/or
1766    - override setupDownloadVersion to control the self.downloadversion string inserted into self.download between self.downloadpath and self.downloadext
1767'''
1768
1769class GNUPackage(Package):
1770  def __init__(self, framework):
1771    Package.__init__(self,framework)
1772    self.builddir = 'no' # requires build be done in a subdirectory, not in the directory tree
1773    self.configureName = 'configure'
1774    return
1775
1776  def setupHelp(self, help):
1777    config.package.Package.setupHelp(self,help)
1778    import nargs
1779    help.addArgument(self.PACKAGE, '-download-'+self.package+'-shared=<bool>',     nargs.ArgBool(None, 0, 'Install '+self.PACKAGE+' with shared libraries'))
1780    help.addArgument(self.PACKAGE, '-download-'+self.package+'-configure-arguments=string', nargs.ArgString(None, 0, 'Additional GNU autoconf configure arguments for the build of '+self.name))
1781
1782  def formGNUConfigureArgs(self):
1783    '''This sets up the prefix, compiler flags, shared flags, and other generic arguments
1784       that are fed into the configure script supplied with the package.
1785       Override this to set options needed by a particular package'''
1786    args=[]
1787    ## prefix
1788    args.append('--prefix='+self.installDir)
1789    args.append('MAKE='+self.make.make)
1790    args.append('--libdir='+self.libDir)
1791    ## compiler args
1792    self.pushLanguage('C')
1793    if not self.installwithbatch and hasattr(self.setCompilers,'cross_cc'):
1794      args.append('CC="'+self.setCompilers.cross_cc+'"')
1795    else:
1796      args.append('CC="'+self.getCompiler()+'"')
1797    args.append('CFLAGS="'+self.updatePackageCFlags(self.getCompilerFlags())+'"')
1798    args.append('AR="'+self.setCompilers.AR+'"')
1799    args.append('ARFLAGS="'+self.setCompilers.AR_FLAGS+'"')
1800    if not self.installwithbatch and hasattr(self.setCompilers,'cross_LIBS'):
1801      args.append('LIBS="'+self.setCompilers.cross_LIBS+'"')
1802    if self.setCompilers.LDFLAGS:
1803      args.append('LDFLAGS="'+self.setCompilers.LDFLAGS+'"')
1804    self.popLanguage()
1805    if hasattr(self.compilers, 'CXX'):
1806      self.pushLanguage('Cxx')
1807      if not self.installwithbatch and hasattr(self.setCompilers,'cross_CC'):
1808        args.append('CXX="'+self.setCompilers.cross_CC+'"')
1809      else:
1810        args.append('CXX="'+self.getCompiler()+'"')
1811      args.append('CXXFLAGS="'+self.updatePackageCxxFlags(self.getCompilerFlags())+'"')
1812      self.popLanguage()
1813    else:
1814      args.append('--disable-cxx')
1815    if hasattr(self.compilers, 'FC'):
1816      self.pushLanguage('FC')
1817      fc = self.getCompiler()
1818      if self.fortran.fortranIsF90:
1819        try:
1820          output, error, status = self.executeShellCommand(fc+' -v', log = self.log)
1821          output += error
1822        except:
1823          output = ''
1824        if output.find('IBM') >= 0:
1825          fc = os.path.join(os.path.dirname(fc), 'xlf')
1826          self.log.write('Using IBM f90 compiler, switching to xlf for compiling ' + self.PACKAGE + '\n')
1827        # now set F90
1828        if not self.installwithbatch and hasattr(self.setCompilers,'cross_fc'):
1829          args.append('F90="'+self.setCompilers.cross_fc+'"')
1830        else:
1831          args.append('F90="'+fc+'"')
1832        args.append('F90FLAGS="'+self.updatePackageFFlags(self.getCompilerFlags())+'"')
1833      else:
1834        args.append('--disable-f90')
1835      args.append('FFLAGS="'+self.updatePackageFFlags(self.getCompilerFlags())+'"')
1836      if not self.installwithbatch and hasattr(self.setCompilers,'cross_fc'):
1837        args.append('FC="'+self.setCompilers.cross_fc+'"')
1838        args.append('F77="'+self.setCompilers.cross_fc+'"')
1839      else:
1840        args.append('FC="'+fc+'"')
1841        args.append('F77="'+fc+'"')
1842      args.append('FCFLAGS="'+self.updatePackageFFlags(self.getCompilerFlags())+'"')
1843      self.popLanguage()
1844    else:
1845      args.append('--disable-fortran')
1846      args.append('--disable-fc')
1847      args.append('--disable-f77')
1848      args.append('--disable-f90')
1849    if self.checkSharedLibrariesEnabled():
1850      args.append('--enable-shared')
1851    else:
1852      args.append('--disable-shared')
1853
1854    cuda_module = self.framework.findModule(self, config.packages.CUDA)
1855    if cuda_module and cuda_module.found:
1856      with self.Language('CUDA'):
1857        args.append('CUDAC='+self.getCompiler())
1858        args.append('CUDAFLAGS="'+self.updatePackageCUDAFlags(self.getCompilerFlags())+'"')
1859    return args
1860
1861  def preInstall(self):
1862    '''Run pre-install steps like generate configure script'''
1863    if not os.path.isfile(os.path.join(self.packageDir,self.configureName)):
1864      if not self.programs.autoreconf:
1865        raise RuntimeError('autoreconf required for ' + self.PACKAGE+' not found (or broken)! Use your package manager to install autoconf')
1866      if not self.programs.libtoolize:
1867        raise RuntimeError('libtoolize required for ' + self.PACKAGE+' not found! Use your package manager to install libtool')
1868      try:
1869        self.logPrintBox('Running libtoolize on ' +self.PACKAGE+'; this may take several minutes')
1870        output,err,ret  = config.base.Configure.executeShellCommand([self.programs.libtoolize, '--install'], cwd=self.packageDir, timeout=100, log=self.log)
1871        if ret:
1872          raise RuntimeError('Error in libtoolize: ' + output+err)
1873      except RuntimeError as e:
1874        raise RuntimeError('Error running libtoolize on ' + self.PACKAGE+': '+str(e))
1875      try:
1876        self.logPrintBox('Running autoreconf on ' +self.PACKAGE+'; this may take several minutes')
1877        output,err,ret  = config.base.Configure.executeShellCommand([self.programs.autoreconf, '--force', '--install'], cwd=self.packageDir, timeout=200, log = self.log)
1878        if ret:
1879          raise RuntimeError('Error in autoreconf: ' + output+err)
1880      except RuntimeError as e:
1881        raise RuntimeError('Error running autoreconf on ' + self.PACKAGE+': '+str(e))
1882
1883  def Install(self):
1884    ##### getInstallDir calls this, and it sets up self.packageDir (source download), self.confDir and self.installDir
1885    args = self.formGNUConfigureArgs()  # allow package to change self.packageDir
1886    if self.download and self.argDB['download-'+self.downloadname.lower()+'-configure-arguments']:
1887       args.append(self.argDB['download-'+self.downloadname.lower()+'-configure-arguments'])
1888    args = ' '.join(args)
1889    conffile = os.path.join(self.packageDir,self.package+'.petscconf')
1890    fd = open(conffile, 'w')
1891    fd.write(args)
1892    fd.close()
1893    ### Use conffile to check whether a reconfigure/rebuild is required
1894    if not self.installNeeded(conffile):
1895      return self.installDir
1896
1897    self.preInstall()
1898
1899    if self.builddir == 'yes':
1900      folder = os.path.join(self.packageDir, 'petsc-build')
1901      if os.path.isdir(folder):
1902        import shutil
1903        shutil.rmtree(folder)
1904      os.mkdir(folder)
1905      self.packageDir = folder
1906      dot = '..'
1907    else:
1908      dot = '.'
1909
1910    ### Configure and Build package
1911    try:
1912      self.logPrintBox('Running configure on ' +self.PACKAGE+'; this may take several minutes')
1913      output1,err1,ret1  = config.base.Configure.executeShellCommand(os.path.join(dot, self.configureName)+' '+args, cwd=self.packageDir, timeout=2000, log = self.log)
1914    except RuntimeError as e:
1915      self.logPrint('Error running configure on ' + self.PACKAGE+': '+str(e))
1916      try:
1917        with open(os.path.join(self.packageDir,'config.log')) as fd:
1918          conf = fd.read()
1919          fd.close()
1920          self.logPrint('Output in config.log for ' + self.PACKAGE+': '+conf)
1921      except:
1922        pass
1923      raise RuntimeError('Error running configure on ' + self.PACKAGE)
1924    try:
1925      self.logPrintBox('Running make on '+self.PACKAGE+'; this may take several minutes')
1926      if self.parallelMake: pmake = self.make.make_jnp+' '+self.makerulename+' '
1927      else: pmake = self.make.make+' '+self.makerulename+' '
1928
1929      output2,err2,ret2  = config.base.Configure.executeShellCommand(self.make.make+' clean', cwd=self.packageDir, timeout=200, log = self.log)
1930      output3,err3,ret3  = config.base.Configure.executeShellCommand(pmake, cwd=self.packageDir, timeout=6000, log = self.log)
1931      self.logPrintBox('Running make install on '+self.PACKAGE+'; this may take several minutes')
1932      output4,err4,ret4  = config.base.Configure.executeShellCommand(self.make.make+' install', cwd=self.packageDir, timeout=1000, log = self.log)
1933    except RuntimeError as e:
1934      self.logPrint('Error running make; make install on '+self.PACKAGE+': '+str(e))
1935      raise RuntimeError('Error running make; make install on '+self.PACKAGE)
1936    self.postInstall(output1+err1+output2+err2+output3+err3+output4+err4, conffile)
1937    return self.installDir
1938
1939  def Bootstrap(self,command):
1940    '''check for configure script - and run bootstrap - if needed'''
1941    import os
1942    if not os.path.isfile(os.path.join(self.packageDir,self.configureName)):
1943      if not self.programs.libtoolize:
1944        raise RuntimeError('Could not bootstrap '+self.PACKAGE+' using autotools: libtoolize not found')
1945      if not self.programs.autoreconf:
1946        raise RuntimeError('Could not bootstrap '+self.PACKAGE+' using autotools: autoreconf not found')
1947      self.logPrintBox('Bootstrapping '+self.PACKAGE+' using autotools; this may take several minutes')
1948      try:
1949        self.executeShellCommand(command,cwd=self.packageDir,log=self.log)
1950      except RuntimeError as e:
1951        raise RuntimeError('Could not bootstrap '+self.PACKAGE+': maybe autotools (or recent enough autotools) could not be found?\nError: '+str(e))
1952
1953class CMakePackage(Package):
1954  def __init__(self, framework):
1955    Package.__init__(self, framework)
1956    self.minCmakeVersion = (2,0,0)
1957    self.need35policy = False
1958    return
1959
1960  def setupHelp(self, help):
1961    config.package.Package.setupHelp(self,help)
1962    import nargs
1963    help.addArgument(self.PACKAGE, '-download-'+self.package+'-shared=<bool>',     nargs.ArgBool(None, 0, 'Install '+self.PACKAGE+' with shared libraries'))
1964    help.addArgument(self.PACKAGE, '-download-'+self.package+'-cmake-arguments=string', nargs.ArgString(None, 0, 'Additional CMake arguments for the build of '+self.name))
1965
1966  def setupDependencies(self, framework):
1967    Package.setupDependencies(self, framework)
1968    self.cmake = framework.require('config.packages.CMake',self)
1969    if self.argDB['download-'+self.downloadname.lower()]:
1970      self.cmake.maxminCmakeVersion = max(self.minCmakeVersion,self.cmake.maxminCmakeVersion)
1971    return
1972
1973  def formCMakeConfigureArgs(self):
1974    import os
1975    import shlex
1976    #  If user has set, for example, CMAKE_GENERATOR Ninja then CMake calls from configure will generate the wrong external package tools for building
1977    try:
1978      del os.environ['CMAKE_GENERATOR']
1979    except:
1980      pass
1981
1982    args = ['-DCMAKE_INSTALL_PREFIX='+self.installDir]
1983    args.append('-DCMAKE_INSTALL_NAME_DIR:STRING="'+self.libDir+'"')
1984    args.append('-DCMAKE_INSTALL_LIBDIR:STRING="lib"')
1985    args.append('-DCMAKE_VERBOSE_MAKEFILE=1')
1986    if self.compilerFlags.debugging:
1987      args.append('-DCMAKE_BUILD_TYPE=Debug')
1988    else:
1989      args.append('-DCMAKE_BUILD_TYPE=Release')
1990    args.append('-DCMAKE_AR="'+self.setCompilers.AR+'"')
1991    self.framework.pushLanguage('C')
1992    args.append('-DCMAKE_C_COMPILER="'+self.framework.getCompiler()+'"')
1993    # bypass CMake findMPI() bug that can find compilers later in the PATH before the first one in the PATH.
1994    # relevant lines of findMPI() begins with if(_MPI_BASE_DIR)
1995    self.getExecutable(self.framework.getCompiler(), getFullPath=1, resultName='mpi_C',setMakeMacro=0)
1996    args.append('-DMPI_C_COMPILER="'+self.mpi_C+'"')
1997    ranlib = shlex.split(self.setCompilers.RANLIB)[0]
1998    args.append('-DCMAKE_RANLIB='+ranlib)
1999    cflags = self.updatePackageCFlags(self.setCompilers.getCompilerFlags())
2000    args.append('-DCMAKE_C_FLAGS:STRING="'+cflags+'"')
2001    args.append('-DCMAKE_C_FLAGS_DEBUG:STRING="'+cflags+'"')
2002    args.append('-DCMAKE_C_FLAGS_RELEASE:STRING="'+cflags+'"')
2003    self.framework.popLanguage()
2004    if hasattr(self.compilers, 'CXX'):
2005      lang = self.framework.pushLanguage('Cxx').lower()
2006      args.append('-DCMAKE_CXX_COMPILER="'+self.framework.getCompiler()+'"')
2007      # bypass CMake findMPI() bug that can find compilers later in the PATH before the first one in the PATH.
2008      # relevant lines of findMPI() begins with if(_MPI_BASE_DIR)
2009      self.getExecutable(self.framework.getCompiler(), getFullPath=1, resultName='mpi_CC',setMakeMacro=0)
2010      args.append('-DMPI_CXX_COMPILER="'+self.mpi_CC+'"')
2011      cxxFlags = self.updatePackageCxxFlags(self.framework.getCompilerFlags())
2012
2013      cxxFlags = cxxFlags.split(' ')
2014      # next line is needed because CMAKE passes CXX flags even when linking an object file!
2015      cxxFlags = self.rmArgs(cxxFlags,['-TP'])
2016      cxxFlags = ' '.join(cxxFlags)
2017
2018      args.append('-DCMAKE_CXX_FLAGS:STRING="{cxxFlags}"'.format(cxxFlags=cxxFlags))
2019      args.append('-DCMAKE_CXX_FLAGS_DEBUG:STRING="{cxxFlags}"'.format(cxxFlags=cxxFlags))
2020      args.append('-DCMAKE_CXX_FLAGS_RELEASE:STRING="{cxxFlags}"'.format(cxxFlags=cxxFlags))
2021      langdialect = getattr(self.setCompilers,lang+'dialect',None)
2022      if langdialect:
2023        # langdialect is only set as an attribute if the user specifically chose a dialect
2024        # (see config/setCompilers.py::checkCxxDialect())
2025        args.append('-DCMAKE_CXX_STANDARD={stdver}'.format(stdver=langdialect[-2:])) # extract '17' from c++17
2026      self.framework.popLanguage()
2027
2028    if hasattr(self.compilers, 'FC'):
2029      self.framework.pushLanguage('FC')
2030      args.append('-DCMAKE_Fortran_COMPILER="'+self.framework.getCompiler()+'"')
2031      # bypass CMake findMPI() bug that can find compilers later in the PATH before the first one in the PATH.
2032      # relevant lines of findMPI() begins with if(_MPI_BASE_DIR)
2033      self.getExecutable(self.framework.getCompiler(), getFullPath=1, resultName='mpi_FC',setMakeMacro=0)
2034      args.append('-DMPI_Fortran_COMPILER="'+self.mpi_FC+'"')
2035      args.append('-DCMAKE_Fortran_FLAGS:STRING="'+self.updatePackageFFlags(self.framework.getCompilerFlags())+'"')
2036      args.append('-DCMAKE_Fortran_FLAGS_DEBUG:STRING="'+self.updatePackageFFlags(self.framework.getCompilerFlags())+'"')
2037      args.append('-DCMAKE_Fortran_FLAGS_RELEASE:STRING="'+self.updatePackageFFlags(self.framework.getCompilerFlags())+'"')
2038      self.framework.popLanguage()
2039
2040    if self.setCompilers.LDFLAGS:
2041      ldflags = self.setCompilers.LDFLAGS.replace('"','\\"') # escape double quotes (") in LDFLAGS
2042      args.append('-DCMAKE_EXE_LINKER_FLAGS:STRING="'+ldflags+'"')
2043      if self.checkSharedLibrariesEnabled():
2044        args.append('-DCMAKE_SHARED_LINKER_FLAGS:STRING="'+ldflags+'"')
2045
2046    if not config.setCompilers.Configure.isWindows(self.setCompilers.CC, self.log) and self.checkSharedLibrariesEnabled():
2047      args.append('-DBUILD_SHARED_LIBS:BOOL=ON')
2048      args.append('-DBUILD_STATIC_LIBS:BOOL=OFF')
2049    else:
2050      args.append('-DBUILD_SHARED_LIBS:BOOL=OFF')
2051      args.append('-DBUILD_STATIC_LIBS:BOOL=ON')
2052
2053    if self.checkSharedLibrariesEnabled():
2054      args.append('-DCMAKE_INSTALL_RPATH_USE_LINK_PATH:BOOL=ON')
2055      args.append('-DCMAKE_BUILD_WITH_INSTALL_RPATH:BOOL=ON')
2056
2057    if 'MSYSTEM' in os.environ:
2058      args.append('-G "MSYS Makefiles"')
2059    for package in self.deps + self.odeps:
2060      if package.found and package.name == 'CUDA':
2061        with self.Language('CUDA'):
2062          args.append('-DCMAKE_CUDA_COMPILER='+self.getCompiler())
2063          cuda_flags = self.updatePackageCUDAFlags(self.getCompilerFlags())
2064          args.append('-DCMAKE_CUDA_FLAGS:STRING="{}"'.format(cuda_flags))
2065          args.append('-DCMAKE_CUDA_FLAGS_DEBUG:STRING="{}"'.format(cuda_flags))
2066          args.append('-DCMAKE_CUDA_FLAGS_RELEASE:STRING="{}"'.format(cuda_flags))
2067          if hasattr(self.setCompilers,'CUDA_CXX'): # CUDA_CXX is set in cuda.py and might be mpicxx.
2068            args.append('-DCMAKE_CUDA_HOST_COMPILER="{}"'.format(self.setCompilers.CUDA_CXX))
2069          else:
2070            with self.Language('C++'):
2071              args.append('-DCMAKE_CUDA_HOST_COMPILER="{}"'.format(self.getCompiler()))
2072        break
2073    if self.need35policy:
2074      args.append('-DCMAKE_POLICY_VERSION_MINIMUM=3.5')
2075    return args
2076
2077  def updateControlFiles(self):
2078    # Override to change build control files
2079    return
2080
2081  def Install(self):
2082    import os
2083    args = self.formCMakeConfigureArgs()
2084    if self.download and 'download-'+self.downloadname.lower()+'-cmake-arguments' in self.framework.clArgDB:
2085       args.append(self.argDB['download-'+self.downloadname.lower()+'-cmake-arguments'])
2086    args = ' '.join(args)
2087    conffile = os.path.join(self.packageDir,self.package+'.petscconf')
2088    fd = open(conffile, 'w')
2089    fd.write(args)
2090    fd.close()
2091
2092    if self.installNeeded(conffile):
2093
2094      if not self.cmake.found:
2095        raise RuntimeError('CMake not found, needed to build '+self.PACKAGE+'. Rerun configure with --download-cmake.')
2096
2097      self.updateControlFiles()
2098
2099      # effectively, this is 'make clean'
2100      folder = os.path.join(self.packageDir, self.cmakelistsdir, 'petsc-build')
2101      if os.path.isdir(folder):
2102        import shutil
2103        shutil.rmtree(folder)
2104      os.mkdir(folder)
2105
2106      try:
2107        self.logPrintBox('Configuring '+self.PACKAGE+' with CMake; this may take several minutes')
2108        output1,err1,ret1  = config.package.Package.executeShellCommand(self.cmake.cmake+' .. '+args, cwd=folder, timeout=900, log = self.log)
2109      except RuntimeError as e:
2110        self.logPrint('Error configuring '+self.PACKAGE+' with CMake '+str(e))
2111        try:
2112          with open(os.path.join(folder, 'CMakeFiles', 'CMakeOutput.log')) as fd:
2113            conf = fd.read()
2114            fd.close()
2115            self.logPrint('Output in CMakeOutput.log for ' + self.PACKAGE+':\n'+conf)
2116        except:
2117          pass
2118        raise RuntimeError('Error configuring '+self.PACKAGE+' with CMake')
2119      try:
2120        self.logPrintBox('Compiling and installing '+self.PACKAGE+'; this may take several minutes')
2121        if self.parallelMake: pmake = self.make.make_jnp+' '+self.makerulename+' '
2122        else: pmake = self.make.make+' '+self.makerulename+' '
2123        output2,err2,ret2  = config.package.Package.executeShellCommand(pmake, cwd=folder, timeout=3000, log = self.log)
2124        output3,err3,ret3  = config.package.Package.executeShellCommand(self.make.make+' install', cwd=folder, timeout=3000, log = self.log)
2125      except RuntimeError as e:
2126        self.logPrint('Error running make on  '+self.PACKAGE+': '+str(e))
2127        raise RuntimeError('Error running make on  '+self.PACKAGE)
2128      self.postInstall(output1+err1+output2+err2+output3+err3,conffile)
2129      # CMake has no option to set the library name to .lib instead of .a so rename libraries
2130      if config.setCompilers.Configure.isWindows(self.setCompilers.AR, self.log):
2131        import pathlib
2132        path = pathlib.Path(os.path.join(self.installDir,'lib'))
2133        self.logPrint('Changing .a files to .lib files in'+str(path))
2134        for f in path.iterdir():
2135          if f.is_file() and f.suffix in ['.a']:
2136            self.logPrint('Changing '+str(f)+' to '+str(f.with_suffix('.lib')))
2137            f.rename(f.with_suffix('.lib'))
2138
2139      if os.path.isfile(os.path.join(self.packageDir,'pyproject.toml')):
2140        # this code is duplicated below for PythonPackage
2141        env = os.environ.copy()
2142        env["CMAKE_MODULE_PATH"] = folder
2143        env["CC"]                = self.compilers.CC
2144        if 'Cxx' in self.buildLanguages:
2145          self.pushLanguage('C++')
2146          env["CXX"]      = self.compilers.CXX
2147          env["CXXFLAGS"] = self.updatePackageCxxFlags(self.getCompilerFlags())
2148          self.popLanguage()
2149        try:
2150          # Uses --no-deps so does not install any listed dependencies of the package that Python pip would normally install
2151          output,err,ret = config.package.Package.executeShellCommandSeq([[self.python.pyexe, '-m', 'pip', 'install', '--no-build-isolation', '--no-deps', '--upgrade-strategy', 'only-if-needed', '--upgrade', '--target='+os.path.join(self.installDir,'lib'), '.']],cwd=self.packageDir, env=env, timeout=30, log = self.log)
2152        except RuntimeError as e:
2153          raise RuntimeError('Error running pip install on '+self.pkgname)
2154    return self.installDir
2155
2156class PythonPackage(Package):
2157  def __init__(self, framework):
2158    Package.__init__(self, framework)
2159    self.download = 'PyPi'
2160
2161  def setupDependencies(self, framework):
2162    config.package.Package.setupDependencies(self, framework)
2163    self.python = framework.require('config.packages.Python', self)
2164
2165  def __str__(self):
2166    if self.found:
2167      s =  self.name + ':\n'
2168      if hasattr(self,'pythonpath'):
2169        s += '  PYTHONPATH: '+self.pythonpath+'\n'
2170      return s
2171    return ''
2172
2173  def configureLibrary(self):
2174    import importlib
2175
2176    self.checkDownload()
2177    if self.builtafterpetsc: return
2178
2179    if self.argDB.get('with-' + self.name + '-dir'):
2180      dir = self.argDB['with-' + self.name + '-dir']
2181      sys.path.insert(0, dir)
2182      try:
2183        self.logPrint('Trying to import ' + self.pkgname + ' which was indicated with the --with-' + self.name + '-dir option')
2184        importlib.import_module(self.pkgname)
2185        self.python.path.add(dir)
2186        self.pythonpath = dir
2187      except:
2188        raise RuntimeError('--with-' + self.name + '-dir=' + dir + ' was not successful, check the directory or use --download-' + self.name)
2189    elif self.argDB.get('download-' + self.name):
2190      dir = os.path.join(self.installDir,'lib')
2191      sys.path.insert(0, dir)
2192      try:
2193        self.logPrint('Trying to import ' + self.pkgname + ' which was just installed with the --download-' + self.name + ' option')
2194        # importlib.import_module fails python 3.11 a newer
2195        #importlib.import_module(self.pkgname)
2196        self.python.path.add(dir)
2197        self.pythonpath = dir
2198      except:
2199        raise RuntimeError('--download-' + self.name + ' was not successful, send configure.log to petsc-maint@mcs.anl.gov')
2200    elif self.argDB.get('with-' + self.name):
2201      try:
2202        self.logPrint('Trying to import ' + self.pkgname + ' which was just included with the --with-' + self.name + ' option')
2203        importlib.import_module(self.pkgname)
2204      except:
2205        raise RuntimeError(self.name + ' not found in default Python PATH! Suggest --download-' + self.name + ' or --with-' + self.name + '-dir')
2206    self.found = 1
2207
2208  def downLoad(self):
2209    pass
2210
2211  def Install(self):
2212    pkgname = self.pkgname
2213    if hasattr(self,'pkgbuild'): pkgname = self.pkgbuild
2214    if hasattr(self,'version') and self.version: pkgname = pkgname + '==' + self.version
2215
2216    if not self.builtafterpetsc:
2217      env = os.environ.copy()
2218      env["CC"]      = self.compilers.CC
2219      if 'Cxx' in self.buildLanguages:
2220        self.pushLanguage('C++')
2221        env["CXX"]      = self.compilers.CXX
2222        env["CXXFLAGS"] = self.updatePackageCxxFlags(self.getCompilerFlags())
2223        self.popLanguage()
2224
2225      if 'PYTHONPATH' in env:
2226        env['PYTHONPATH'] = env['PYTHONPATH'] + ':' + os.path.join(self.installDir,'lib')
2227      else:
2228        env['PYTHONPATH'] = os.path.join(self.installDir,'lib')
2229
2230      try:
2231        output,err,ret = config.package.Package.executeShellCommandSeq([[self.python.pyexe, '-m', 'pip', 'install', '--no-build-isolation', '--no-deps', '--upgrade-strategy', 'only-if-needed', '--upgrade', '--target='+os.path.join(self.installDir,'lib'), pkgname]],env=env, timeout=30, log = self.log)
2232      except RuntimeError as e:
2233        raise RuntimeError('Error running pip install on '+self.pkgname)
2234    else:
2235      # provide access to previously built pip packages
2236      ppath = 'PYTHONPATH=' + os.path.join(self.installDir,'lib')
2237
2238      ccarg = 'CC=' + self.compilers.CC
2239      if 'Cxx' in self.buildLanguages:
2240        self.pushLanguage('C++')
2241        ccarg += ' CXX=' + self.compilers.CXX
2242        ccarg += ' CXXFLAGS="' +  self.updatePackageCxxFlags(self.getCompilerFlags()) + '"'
2243        self.popLanguage()
2244
2245      if hasattr(self,'env'):
2246        for i in self.env:
2247          ccarg += ' ' + i + '=' + self.env[i]
2248
2249      self.addPost('', [ccarg + ' ' + ppath + ' ' + ' ' + self.python.pyexe +  ' -m  pip install --no-build-isolation --no-deps --upgrade-strategy only-if-needed --upgrade --target=' + os.path.join(self.installDir,'lib') + ' ' + pkgname])
2250    return self.installDir
2251