xref: /petsc/config/install.py (revision f3d3cd90648576fafae91a24e2611daaef7bcd2e)
1#!/usr/bin/env python3
2from __future__ import print_function
3import os, re, shutil, sys
4import sysconfig
5
6if 'PETSC_DIR' in os.environ:
7  PETSC_DIR = os.environ['PETSC_DIR']
8else:
9  fd = open(os.path.join('lib','petsc','conf','petscvariables'))
10  a = fd.readline()
11  a = fd.readline()
12  PETSC_DIR = a.split('=')[1][0:-1]
13  fd.close()
14
15if 'PETSC_ARCH' in os.environ:
16  PETSC_ARCH = os.environ['PETSC_ARCH']
17else:
18  fd = open(os.path.join('lib','petsc','conf','petscvariables'))
19  a = fd.readline()
20  PETSC_ARCH = a.split('=')[1][0:-1]
21  fd.close()
22
23print('*** Using PETSC_DIR='+PETSC_DIR+' PETSC_ARCH='+PETSC_ARCH+' ***')
24sys.path.insert(0, os.path.join(PETSC_DIR, 'config'))
25sys.path.insert(0, os.path.join(PETSC_DIR, 'config', 'BuildSystem'))
26
27import script
28
29try:
30  WindowsError
31except NameError:
32  WindowsError = None
33
34class Installer(script.Script):
35  def __init__(self, clArgs = None):
36    import RDict
37    argDB = RDict.RDict(None, None, 0, 0, readonly = True)
38    argDB.saveFilename = os.path.join(PETSC_DIR, PETSC_ARCH, 'lib','petsc','conf', 'RDict.db')
39    argDB.load()
40    script.Script.__init__(self, argDB = argDB)
41    if not clArgs is None: self.clArgs = clArgs
42    self.copies = []
43    return
44
45  def setupHelp(self, help):
46    import nargs
47    script.Script.setupHelp(self, help)
48    help.addArgument('Installer', '-destDir=<path>', nargs.Arg(None, '', 'Destination Directory for install'))
49    help.addArgument('Installer', '-no-examples', nargs.Arg(None, '', 'Skip installing examples'))
50    return
51
52
53  def setupModules(self):
54    self.setCompilers  = self.framework.require('config.setCompilers',         None)
55    self.arch          = self.framework.require('PETSc.options.arch',          None)
56    self.petscdir      = self.framework.require('PETSc.options.petscdir',      None)
57    self.compilers     = self.framework.require('config.compilers',            None)
58    self.mpi           = self.framework.require('config.packages.MPI',         None)
59    return
60
61  def setup(self):
62    script.Script.setup(self)
63    self.framework = self.loadConfigure()
64    self.setupModules()
65    return
66
67  def setupDirectories(self):
68    self.rootDir    = self.petscdir.dir
69    self.installDir = os.path.abspath(os.path.expanduser(self.framework.argDB['prefix']))
70    self.destDir    = os.path.abspath(self.argDB['destDir']+self.installDir)
71    self.arch       = self.arch.arch
72    self.archDir           = os.path.join(self.rootDir, self.arch)
73    self.rootIncludeDir    = os.path.join(self.rootDir, 'include')
74    self.archIncludeDir    = os.path.join(self.rootDir, self.arch, 'include')
75    self.rootConfDir       = os.path.join(self.rootDir, 'lib','petsc','conf')
76    self.archConfDir       = os.path.join(self.rootDir, self.arch, 'lib','petsc','conf')
77    self.rootBinDir        = os.path.join(self.rootDir, 'lib','petsc','bin')
78    self.archBinDir        = os.path.join(self.rootDir, self.arch, 'bin')
79    self.archLibDir        = os.path.join(self.rootDir, self.arch, 'lib')
80    self.destIncludeDir    = os.path.join(self.destDir, 'include')
81    self.destConfDir       = os.path.join(self.destDir, 'lib','petsc','conf')
82    self.destLibDir        = os.path.join(self.destDir, 'lib')
83    self.destBinDir        = os.path.join(self.destDir, 'lib','petsc','bin')
84    self.installIncludeDir = os.path.join(self.installDir, 'include')
85    self.installLibDir     = os.path.join(self.installDir, 'lib')
86    self.installBinDir     = os.path.join(self.installDir, 'lib','petsc','bin')
87    self.rootShareDir      = os.path.join(self.rootDir, 'share')
88    self.destShareDir      = os.path.join(self.destDir, 'share')
89    self.rootSrcDir        = os.path.join(self.rootDir, 'src')
90
91    self.ranlib      = self.compilers.RANLIB
92    self.arLibSuffix = self.compilers.AR_LIB_SUFFIX
93    return
94
95  def checkPrefix(self):
96    if not self.installDir:
97      print('********************************************************************')
98      print('PETSc is built without prefix option. So "make install" is not appropriate.')
99      print('If you need a prefix install of PETSc - rerun configure with --prefix option.')
100      print('********************************************************************')
101      sys.exit(1)
102    return
103
104  def checkDestdir(self):
105    if os.path.exists(self.destDir):
106      if os.path.samefile(self.destDir, self.rootDir):
107        print('********************************************************************')
108        print('Incorrect prefix usage. Specified destDir same as current PETSC_DIR')
109        print('********************************************************************')
110        sys.exit(1)
111      if os.path.samefile(self.destDir, os.path.join(self.rootDir,self.arch)):
112        print('********************************************************************')
113        print('Incorrect prefix usage. Specified destDir same as current PETSC_DIR/PETSC_ARCH')
114        print('********************************************************************')
115        sys.exit(1)
116      if not os.path.isdir(os.path.realpath(self.destDir)):
117        print('********************************************************************')
118        print('Specified destDir', self.destDir, 'is not a directory. Cannot proceed!')
119        print('********************************************************************')
120        sys.exit(1)
121      if not os.access(self.destDir, os.W_OK):
122        print('********************************************************************')
123        print('Unable to write to ', self.destDir, 'Perhaps you need to do "sudo make install"')
124        print('********************************************************************')
125        sys.exit(1)
126    return
127
128  def setupBuild(self):
129    self.using_build_backend = any(
130      os.environ.get(prefix + '_BUILD_BACKEND')
131      for prefix in ('_PYPROJECT_HOOKS', 'PEP517')
132    )
133    self.relocate_py_env = os.environ.get('CIBUILDWHEEL', '0') == '1'
134
135  def copyfile(self, src, dst, symlinks = False, copyFunc = shutil.copy2):
136    """Copies a single file    """
137    copies = []
138    errors = []
139    if not os.path.exists(dst):
140      os.makedirs(dst)
141    elif not os.path.isdir(dst):
142      raise shutil.Error('Destination is not a directory')
143    srcname = src
144    dstname = os.path.join(dst, os.path.basename(src))
145    try:
146      if symlinks and os.path.islink(srcname):
147        linkto = os.readlink(srcname)
148        os.symlink(linkto, dstname)
149      else:
150        copyFunc(srcname, dstname)
151        copies.append((srcname, dstname))
152    except (IOError, os.error) as why:
153      errors.append((srcname, dstname, str(why)))
154    except shutil.Error as err:
155      errors.append((srcname,dstname,str(err.args[0])))
156    if errors:
157      raise shutil.Error(errors)
158    return copies
159
160  def fixExamplesMakefile(self, src):
161    '''Change ././${PETSC_ARCH} in makefile in root PETSc directory with ${PETSC_DIR}'''
162    lines   = []
163    oldFile = open(src, 'r')
164    alllines=oldFile.read()
165    oldFile.close()
166    newlines=alllines.split('\n')[0]+'\n'  # Firstline
167    # Hardcode PETSC_DIR and PETSC_ARCH to avoid users doing the wrong thing
168    newlines+='PETSC_DIR='+self.installDir+'\n'
169    newlines+='PETSC_ARCH=\n'
170    for line in alllines.split('\n')[1:]:
171      if line.startswith('TESTLOGFILE'):
172        newlines+='TESTLOGFILE = $(TESTDIR)/examples-install.log\n'
173      elif line.startswith('CONFIGDIR'):
174        newlines+='CONFIGDIR:=$(PETSC_DIR)/$(PETSC_ARCH)/share/petsc/examples/config\n'
175      elif line.startswith('$(generatedtest)') and 'petscvariables' in line:
176        newlines+='all: test\n\n'+line+'\n'
177      else:
178        newlines+=line+'\n'
179    newFile = open(src, 'w')
180    newFile.write(newlines)
181    newFile.close()
182    return
183
184  def copyConfig(self, src, dst):
185    """Copy configuration/testing files
186    """
187    if not os.path.isdir(dst):
188      raise shutil.Error('Destination is not a directory')
189
190    self.copies.extend(self.copyfile('gmakefile.test',dst))
191    newConfigDir=os.path.join(dst,'config')  # Am not renaming at present
192    if not os.path.isdir(newConfigDir): os.mkdir(newConfigDir)
193    testConfFiles="gmakegentest.py gmakegen.py testparse.py example_template.py".split()
194    testConfFiles+="petsc_harness.sh report_tests.py query_tests.py".split()
195    for tf in testConfFiles:
196      self.copies.extend(self.copyfile(os.path.join('config',tf),newConfigDir))
197    return
198
199  def copyExamples(self, src, dst):
200    """copy the examples directories
201    """
202    for root, dirs, files in os.walk(src, topdown=False):
203      if os.path.basename(root) not in ("tests", "tutorials"): continue
204      self.copies.extend(self.copytree(root, root.replace(src,dst)))
205    return
206
207  def copytree(self, src, dst, symlinks = False, copyFunc = shutil.copy2, exclude = [], exclude_ext= ['.DSYM','.o','.pyc'], recurse = 1):
208    """Recursively copy a directory tree using copyFunc, which defaults to shutil.copy2().
209
210       The copyFunc() you provide is only used on the top level, lower levels always use shutil.copy2
211
212    The destination directory must not already exist.
213    If exception(s) occur, an shutil.Error is raised with a list of reasons.
214
215    If the optional symlinks flag is true, symbolic links in the
216    source tree result in symbolic links in the destination tree; if
217    it is false, the contents of the files pointed to by symbolic
218    links are copied.
219    """
220    copies = []
221    names  = os.listdir(src)
222    if not os.path.exists(dst):
223      os.makedirs(dst)
224    elif not os.path.isdir(dst):
225      raise shutil.Error('Destination is not a directory')
226    errors = []
227    srclinks = []
228    dstlinks = []
229    for name in names:
230      srcname = os.path.join(src, name)
231      dstname = os.path.join(dst, name)
232      try:
233        if symlinks and os.path.islink(srcname):
234          linkto = os.readlink(srcname)
235          os.symlink(linkto, dstname)
236        elif os.path.isdir(srcname) and recurse and not os.path.basename(srcname) in exclude:
237          copies.extend(self.copytree(srcname, dstname, symlinks,exclude = exclude, exclude_ext = exclude_ext))
238        elif os.path.isfile(srcname) and not os.path.basename(srcname) in exclude and os.path.splitext(name)[1] not in exclude_ext :
239          if os.path.islink(srcname):
240            srclinks.append(srcname)
241            dstlinks.append(dstname)
242          else:
243            copyFunc(srcname, dstname)
244            copies.append((srcname, dstname))
245        # XXX What about devices, sockets etc.?
246      except (IOError, os.error) as why:
247        errors.append((srcname, dstname, str(why)))
248      # catch the Error from the recursive copytree so that we can
249      # continue with other files
250      except shutil.Error as err:
251        errors.append((srcname,dstname,str(err.args[0])))
252    for srcname, dstname in zip(srclinks, dstlinks):
253      try:
254        copyFunc(srcname, dstname)
255        copies.append((srcname, dstname))
256      except (IOError, os.error) as why:
257        errors.append((srcname, dstname, str(why)))
258      # catch the Error from the recursive copytree so that we can
259      # continue with other files
260      except shutil.Error as err:
261        errors.append((srcname,dstname,str(err.args[0])))
262    try:
263      shutil.copystat(src, dst)
264    except OSError as e:
265      if WindowsError is not None and isinstance(e, WindowsError):
266        # Copying file access times may fail on Windows
267        pass
268      else:
269        errors.append((src, dst, str(e)))
270    if errors:
271      raise shutil.Error(errors)
272    return copies
273
274
275  def fixConfFile(self, src):
276    lines   = []
277    oldFile = open(src, 'r')
278    for line in oldFile.readlines():
279      if line.startswith('PETSC_CC_INCLUDES =') or line.startswith('PETSC_FC_INCLUDES ='):
280        continue
281      line = line.replace('PETSC_CC_INCLUDES_INSTALL', 'PETSC_CC_INCLUDES')
282      line = line.replace('PETSC_FC_INCLUDES_INSTALL', 'PETSC_FC_INCLUDES')
283      # remove PETSC_DIR/PETSC_ARCH variables from conf-makefiles. They are no longer necessary
284      line = line.replace('${PETSC_DIR}/${PETSC_ARCH}', self.installDir)
285      line = line.replace('PETSC_ARCH=${PETSC_ARCH}', '')
286      line = line.replace('${PETSC_DIR}', self.installDir)
287      # replace PETSC_DIR/PETSC_ARCH/lib (i.e., build location) with prefix/lib
288      line = line.replace(self.archLibDir,self.installLibDir)
289      # replace PETSC_DIR/lib/petsc/bin with prefix/lib/petsc/bin
290      line = line.replace(self.rootBinDir,self.destBinDir)
291      lines.append(line)
292    oldFile.close()
293    newFile = open(src, 'w')
294    newFile.write(''.join(lines))
295    newFile.close()
296    return
297
298  def fixConf(self):
299    import shutil
300    for file in ['rules', 'rules_doc.mk', 'rules_util.mk', 'variables', 'petscrules', 'petscvariables']:
301      self.fixConfFile(os.path.join(self.destConfDir,file))
302    return
303
304  def fixPythonWheel(self):
305    import glob
306    import shutil
307    #
308    for pattern in (
309        self.destLibDir  + '/*.a',
310        self.destLibDir  + '/*.la',
311        self.destLibDir  + '/pkgconfig',  # TODO: keep?
312        self.destConfDir + '/configure-hash',
313        self.destConfDir + '/uninstall.py',
314        self.destConfDir + '/reconfigure-*.py',
315        self.destConfDir + '/pkg.conf.*',
316        self.destConfDir + '/pkg.git*.*',
317        self.destConfDir + '/modules',  # TODO: keep?
318        self.destShareDir + '/*/examples/src/*',
319        self.destShareDir + '/*/datafiles',
320    ):
321      for pathname in glob.glob(pattern):
322        if os.path.isdir(pathname):
323          shutil.rmtree(pathname)
324        elif os.path.exists(pathname):
325          os.remove(pathname)
326    #
327    if self.relocate_py_env:
328      pydir = sys.prefix
329      pylibdir = os.path.join(pydir, 'lib')
330      pysitedir = sysconfig.get_paths()["platlib"]
331      # petsc is installed in site-packages
332      petscdir = os.path.join(pysitedir, 'petsc')
333      petsclibdir = os.path.join(petscdir, 'lib')
334    for filename in (
335      self.destIncludeDir + '/petscconf.h',
336      self.destIncludeDir + '/petscconfiginfo.h',
337      self.destIncludeDir + '/petscmachineinfo.h',
338      self.destShareDir + '/petsc/examples/gmakefile.test',
339      self.destConfDir + '/rules',
340      self.destConfDir + '/rules_doc.mk',
341      self.destConfDir + '/rules_util.mk',
342      self.destConfDir + '/petscrules',
343      self.destConfDir + '/variables',
344      self.destConfDir + '/petscvariables',
345    ):
346      with open(filename, 'r') as oldFile:
347        contents = oldFile.read()
348      contents = contents.replace(self.installDir, '${PETSC_DIR}')
349      contents = contents.replace(self.rootDir, '${PETSC_DIR}')
350      if self.relocate_py_env:
351        pydir_from_petsc = os.path.relpath(pydir, petscdir)
352        contents = contents.replace(pydir, os.path.join('${PETSC_DIR}', pydir_from_petsc))
353      contents = re.sub(
354        r'^(PYTHON(_EXE)?) = (.*)$',
355        r'\1 = python%d' % sys.version_info[0],
356        contents, flags=re.MULTILINE,
357      )
358      with open(filename, 'w') as newFile:
359        newFile.write(contents)
360    #
361    def lsdir(dirname, *patterns):
362      return glob.glob(os.path.join(dirname, *patterns))
363    def shell(*args):
364      return self.executeShellCommand(' '.join(args))[0]
365    libdir = os.path.join(self.installDir, 'lib')
366    if sys.platform == 'linux':
367      libraries = [
368        lib for lib in lsdir(self.destLibDir, 'lib*.so*')
369        if not os.path.islink(lib)
370      ]
371      for shlib in libraries:
372        # fix shared library rpath
373        rpath = shell('patchelf', '--print-rpath', shlib)
374        rpath = rpath.split(os.path.pathsep)
375        if not self.relocate_py_env:
376          if libdir in rpath:
377            rpath.insert(0, '$ORIGIN')
378            while libdir in rpath:
379              rpath.remove(libdir)
380        else:
381          rpathold = rpath
382          rpath = []
383          # strip all rpath info, except for libraries in Python
384          # sys prefix, site-packages, or petsc/lib
385          rpath.insert(0, '$ORIGIN')
386          for libdir in rpathold:
387            if libdir.startswith(pysitedir):
388              libdir_from_petsc = os.path.relpath(libdir, petsclibdir)
389              rpath.insert(0, os.path.join('$ORIGIN',libdir_from_petsc))
390          pylibdir_from_petsc = os.path.relpath(pylibdir, petsclibdir)
391          rpath.insert(0, os.path.join('$ORIGIN',pylibdir_from_petsc))
392        if rpath:
393          rpath = os.path.pathsep.join(rpath)
394          shell('patchelf', '--set-rpath', "'%s'" % rpath, shlib)
395        # fix shared library file and symlink
396        basename = os.path.basename(shlib)
397        libname, ext, _ = basename.partition('.so')
398        liblink = libname + ext
399        soname = shell('patchelf', '--print-soname', shlib)
400        for symlink in lsdir(self.destLibDir, liblink + '*'):
401          if os.path.islink(symlink):
402            os.unlink(symlink)
403        curdir = os.getcwd()
404        try:
405          os.chdir(os.path.dirname(shlib))
406          if soname != basename:
407            os.rename(basename, soname)
408          if soname != liblink:
409            with open(liblink, 'w') as f:
410              f.write('INPUT(' + soname + ')\n')
411        finally:
412          os.chdir(curdir)
413    if sys.platform == 'darwin':
414      def otool(cmd, dylib):
415        pattern = r'''
416          ^\s+ cmd \s %s$\n
417          ^\s+ cmdsize \s \d+$\n
418          ^\s+ (?:name|path) \s (.*) \s \(offset \s \d+\)$
419        ''' % cmd
420        return re.findall(
421          pattern, shell('otool', '-l', dylib),
422          flags=re.VERBOSE | re.MULTILINE,
423        )
424      libraries = [
425        lib for lib in lsdir(self.destLibDir, 'lib*.dylib')
426        if not os.path.islink(lib)
427      ]
428      for dylib in libraries:
429        install_name = otool('LC_ID_DYLIB', dylib)[0]
430        dependencies = otool('LC_LOAD_DYLIB', dylib)
431        runtime_path = otool('LC_RPATH', dylib)
432        # fix shared library install name and rpath
433        install_name = '@rpath/' + os.path.basename(install_name)
434        shell('install_name_tool', '-id', install_name, dylib)
435        if libdir in runtime_path:
436          shell('install_name_tool', '-delete_rpath', libdir, dylib)
437        for rpath in ('@loader_path',):
438          if rpath not in runtime_path:
439            shell('install_name_tool', '-add_rpath', rpath, dylib)
440        for dep in dependencies:
441          if os.path.dirname(dep) in (libdir,):
442            newid = '@rpath/' + os.path.basename(dep)
443            shell('install_name_tool', '-change', dep, newid, dylib)
444        # fix shared library file and symlink
445        basename = os.path.basename(dylib)
446        libname, ext = os.path.splitext(basename)
447        libname = libname.partition('.')[0]
448        liblink = libname + ext
449        dyname = os.path.basename(install_name)
450        for symlink in lsdir(self.destLibDir, libname + '*' + ext):
451          if os.path.islink(symlink):
452            os.unlink(symlink)
453        curdir = os.getcwd()
454        try:
455          os.chdir(os.path.dirname(dylib))
456          if dyname != basename:
457            os.rename(basename, dyname)
458          if dyname != liblink:
459            os.symlink(dyname, liblink)
460        finally:
461          os.chdir(curdir)
462    #
463    return
464
465  def createUninstaller(self):
466    uninstallscript = os.path.join(self.destConfDir, 'uninstall.py')
467    f = open(uninstallscript, 'w')
468    # Could use the Python AST to do this
469    f.write('#!'+sys.executable+'\n')
470    f.write('import os\n')
471    f.write('prefixdir = "'+self.installDir+'"\n')
472    files = [dst.replace(self.destDir,self.installDir) for src, dst in self.copies]
473    files.append(uninstallscript.replace(self.destDir,self.installDir))
474    f.write('files = '+repr(files))
475    f.write('''
476for file in files:
477  if os.path.exists(file) or os.path.islink(file):
478    os.remove(file)
479    dir = os.path.dirname(file)
480    while dir not in [os.path.dirname(prefixdir),'/']:
481      try: os.rmdir(dir)
482      except: break
483      dir = os.path.dirname(dir)
484''')
485    f.close()
486    os.chmod(uninstallscript,0o744)
487    return
488
489  def installIncludes(self):
490    exclude = ['makefile']
491    if not hasattr(self.compilers.setCompilers, 'FC'):
492      exclude.append('finclude')
493    if not self.mpi.usingMPIUni:
494      exclude.append('mpiuni')
495    self.copies.extend(self.copytree(self.rootIncludeDir, self.destIncludeDir,exclude = exclude))
496    self.copies.extend(self.copytree(self.archIncludeDir, self.destIncludeDir))
497    return
498
499  def installConf(self):
500    self.copies.extend(self.copytree(self.rootConfDir, self.destConfDir, exclude = ['test.log']))
501    self.copies.extend(self.copytree(self.archConfDir, self.destConfDir, exclude = ['sowing', 'configure.log.bkp','configure.log','make.log','gmake.log','test.log','error.log','memoryerror.log','files','testfiles','RDict.db']))
502    return
503
504  def installBin(self):
505    exclude = ['bib2html','doc2lt','doctext','mapnames', 'pstogif','pstoxbm','tohtml']
506    self.copies.extend(self.copytree(self.archBinDir, self.destBinDir, exclude = exclude ))
507    exclude = ['maint']
508    if not self.mpi.usingMPIUni:
509      exclude.append('petsc-mpiexec.uni')
510    self.setCompilers.pushLanguage('C')
511    if self.setCompilers.getCompiler().find('win32fe') < 0:
512      exclude.append('win32fe')
513    self.setCompilers.popLanguage()
514    self.copies.extend(self.copytree(self.rootBinDir, self.destBinDir, exclude = exclude ))
515    return
516
517  def installShare(self):
518    if self.argDB['no-examples']: exclude = ['datafiles']
519    else: exclude = []
520    self.copies.extend(self.copytree(self.rootShareDir, self.destShareDir, exclude=exclude))
521    examplesdir=os.path.join(self.destShareDir,'petsc','examples')
522    if os.path.exists(examplesdir):
523      shutil.rmtree(examplesdir)
524    os.mkdir(examplesdir)
525    os.mkdir(os.path.join(examplesdir,'src'))
526    self.copyConfig(self.rootDir,examplesdir)
527    if not self.argDB['no-examples']:
528        self.copyExamples(self.rootSrcDir,os.path.join(examplesdir,'src'))
529        self.fixExamplesMakefile(os.path.join(examplesdir,'gmakefile.test'))
530    return
531
532  def copyLib(self, src, dst):
533    '''Run ranlib on the destination library if it is an archive. Also run install_name_tool on dylib on Mac'''
534    # Symlinks (assumed local) are recreated at dst
535    if os.path.islink(src):
536      linkto = os.readlink(src)
537      try:
538        os.remove(dst)            # In case it already exists
539      except OSError:
540        pass
541      os.symlink(linkto, dst)
542      return
543    shutil.copy2(src, dst)
544    if self.setCompilers.getCompiler().find('win32fe') < 0 and os.path.splitext(dst)[1] == '.'+self.arLibSuffix:
545      import shlex
546      self.executeShellCommand(shlex.split(self.ranlib) + [dst])
547    if os.path.splitext(dst)[1] == '.dylib' and shutil.which('otool') and shutil.which('install_name_tool'):
548      [output,err,flg] = self.executeShellCommand(['otool', '-D', src])
549      oldname = output[output.find("\n")+1:]
550      installName = oldname.replace(os.path.realpath(self.archDir), self.installDir)
551      self.executeShellCommand(['install_name_tool', '-id', installName, dst])
552    # preserve the original timestamps - so that the .a vs .so time order is preserved
553    shutil.copystat(src,dst)
554    return
555
556  def installLib(self):
557    self.copies.extend(self.copytree(self.archLibDir, self.destLibDir, copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
558    self.copies.extend(self.copytree(os.path.join(self.archLibDir,'pkgconfig'), os.path.join(self.destLibDir,'pkgconfig'), copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
559    return
560
561
562  def outputInstallDone(self):
563    from config.packages.make import getMakeUserPath
564    print('''\
565====================================
566Install complete.
567Now to check if the libraries are working do (in current directory):
568%s PETSC_DIR=%s PETSC_ARCH="" check
569====================================\
570''' % (getMakeUserPath(self.arch), self.installDir))
571    return
572
573  def outputDestDirDone(self):
574    print('''\
575====================================
576Copy to DESTDIR %s is now complete.
577Before use - please copy/install over to specified prefix: %s
578====================================\
579''' % (self.destDir,self.installDir))
580    return
581
582  def runsetup(self):
583    self.setup()
584    self.setupDirectories()
585    self.setupBuild()
586    self.checkPrefix()
587    self.checkDestdir()
588    return
589
590  def runcopy(self):
591    if self.destDir == self.installDir:
592      print('*** Installing PETSc at prefix location:',self.destDir, ' ***')
593    else:
594      print('*** Copying PETSc to DESTDIR location:',self.destDir, ' ***')
595    if not os.path.exists(self.destDir):
596      try:
597        os.makedirs(self.destDir)
598      except:
599        print('********************************************************************')
600        print('Unable to create', self.destDir, 'Perhaps you need to do "sudo make install"')
601        print('********************************************************************')
602        sys.exit(1)
603    self.installIncludes()
604    self.installConf()
605    self.installBin()
606    self.installLib()
607    self.installShare()
608    self.createUninstaller()
609    return
610
611  def runfix(self):
612    self.fixConf()
613    if self.using_build_backend:
614      self.fixPythonWheel()
615    return
616
617  def rundone(self):
618    if self.destDir == self.installDir:
619      self.outputInstallDone()
620    else:
621      self.outputDestDirDone()
622    return
623
624  def run(self):
625    self.runsetup()
626    self.runcopy()
627    self.runfix()
628    self.rundone()
629    return
630
631if __name__ == '__main__':
632  Installer(sys.argv[1:]).run()
633  # temporary hack - delete log files created by BuildSystem - when 'sudo make install' is invoked
634  delfiles=['RDict.db','RDict.log','buildsystem.log','default.log','buildsystem.log.bkp','default.log.bkp']
635  for delfile in delfiles:
636    if os.path.exists(delfile) and (os.stat(delfile).st_uid==0):
637      os.remove(delfile)
638