1#!/usr/local/bin/python3.8
2from __future__ import print_function
3import os, re, shutil, sys
4
5if 'PETSC_DIR' in os.environ:
6  PETSC_DIR = os.environ['PETSC_DIR']
7else:
8  fd = open(os.path.join('lib','petsc','conf','petscvariables'))
9  a = fd.readline()
10  a = fd.readline()
11  PETSC_DIR = a.split('=')[1][0:-1]
12  fd.close()
13
14if 'PETSC_ARCH' in os.environ:
15  PETSC_ARCH = os.environ['PETSC_ARCH']
16else:
17  fd = open(os.path.join('lib','petsc','conf','petscvariables'))
18  a = fd.readline()
19  PETSC_ARCH = a.split('=')[1][0:-1]
20  fd.close()
21
22print('*** Using PETSC_DIR='+PETSC_DIR+' PETSC_ARCH='+PETSC_ARCH+' ***')
23sys.path.insert(0, os.path.join(PETSC_DIR, 'config'))
24sys.path.insert(0, os.path.join(PETSC_DIR, 'config', 'BuildSystem'))
25
26import script
27
28try:
29  WindowsError
30except NameError:
31  WindowsError = None
32
33class Installer(script.Script):
34  def __init__(self, clArgs = None):
35    import RDict
36    argDB = RDict.RDict(None, None, 0, 0, readonly = True)
37    argDB.saveFilename = os.path.join(PETSC_DIR, PETSC_ARCH, 'lib','petsc','conf', 'RDict.db')
38    argDB.load()
39    script.Script.__init__(self, argDB = argDB)
40    if not clArgs is None: self.clArgs = clArgs
41    self.copies = []
42    return
43
44  def setupHelp(self, help):
45    import nargs
46    script.Script.setupHelp(self, help)
47    help.addArgument('Installer', '-destDir=<path>', nargs.Arg(None, '', 'Destination Directory for install'))
48    return
49
50
51  def setupModules(self):
52    self.setCompilers  = self.framework.require('config.setCompilers',         None)
53    self.arch          = self.framework.require('PETSc.options.arch',          None)
54    self.petscdir      = self.framework.require('PETSc.options.petscdir',      None)
55    self.compilers     = self.framework.require('config.compilers',            None)
56    self.mpi           = self.framework.require('config.packages.MPI',         None)
57    return
58
59  def setup(self):
60    script.Script.setup(self)
61    self.framework = self.loadConfigure()
62    self.setupModules()
63    return
64
65  def setupDirectories(self):
66    self.rootDir    = self.petscdir.dir
67    self.installDir = os.path.abspath(os.path.expanduser(self.framework.argDB['prefix']))
68    self.destDir    = os.path.abspath(self.argDB['destDir']+self.installDir)
69    self.arch       = self.arch.arch
70    self.archDir           = os.path.join(self.rootDir, self.arch)
71    self.rootIncludeDir    = os.path.join(self.rootDir, 'include')
72    self.archIncludeDir    = os.path.join(self.rootDir, self.arch, 'include')
73    self.rootConfDir       = os.path.join(self.rootDir, 'lib','petsc','conf')
74    self.archConfDir       = os.path.join(self.rootDir, self.arch, 'lib','petsc','conf')
75    self.rootBinDir        = os.path.join(self.rootDir, 'lib','petsc','bin')
76    self.archBinDir        = os.path.join(self.rootDir, self.arch, 'bin')
77    self.archLibDir        = os.path.join(self.rootDir, self.arch, 'lib')
78    self.destIncludeDir    = os.path.join(self.destDir, 'include')
79    self.destConfDir       = os.path.join(self.destDir, 'lib','petsc','conf')
80    self.destLibDir        = os.path.join(self.destDir, 'lib')
81    self.destBinDir        = os.path.join(self.destDir, 'lib','petsc','bin')
82    self.installIncludeDir = os.path.join(self.installDir, 'include')
83    self.installBinDir     = os.path.join(self.installDir, 'lib','petsc','bin')
84    self.rootShareDir      = os.path.join(self.rootDir, 'share')
85    self.destShareDir      = os.path.join(self.destDir, 'share')
86    self.rootSrcDir        = os.path.join(self.rootDir, 'src')
87
88    self.ranlib      = self.compilers.RANLIB
89    self.arLibSuffix = self.compilers.AR_LIB_SUFFIX
90    return
91
92  def checkPrefix(self):
93    if not self.installDir:
94      print('********************************************************************')
95      print('PETSc is built without prefix option. So "make install" is not appropriate.')
96      print('If you need a prefix install of PETSc - rerun configure with --prefix option.')
97      print('********************************************************************')
98      sys.exit(1)
99    return
100
101  def checkDestdir(self):
102    if os.path.exists(self.destDir):
103      if os.path.samefile(self.destDir, self.rootDir):
104        print('********************************************************************')
105        print('Incorrect prefix usage. Specified destDir same as current PETSC_DIR')
106        print('********************************************************************')
107        sys.exit(1)
108      if os.path.samefile(self.destDir, os.path.join(self.rootDir,self.arch)):
109        print('********************************************************************')
110        print('Incorrect prefix usage. Specified destDir same as current PETSC_DIR/PETSC_ARCH')
111        print('********************************************************************')
112        sys.exit(1)
113      if not os.path.isdir(os.path.realpath(self.destDir)):
114        print('********************************************************************')
115        print('Specified destDir', self.destDir, 'is not a directory. Cannot proceed!')
116        print('********************************************************************')
117        sys.exit(1)
118      if not os.access(self.destDir, os.W_OK):
119        print('********************************************************************')
120        print('Unable to write to ', self.destDir, 'Perhaps you need to do "sudo make install"')
121        print('********************************************************************')
122        sys.exit(1)
123    return
124
125  def copyfile(self, src, dst, symlinks = False, copyFunc = shutil.copy2):
126    """Copies a single file    """
127    copies = []
128    errors = []
129    if not os.path.exists(dst):
130      os.makedirs(dst)
131    elif not os.path.isdir(dst):
132      raise shutil.Error('Destination is not a directory')
133    srcname = src
134    dstname = os.path.join(dst, os.path.basename(src))
135    try:
136      if symlinks and os.path.islink(srcname):
137        linkto = os.readlink(srcname)
138        os.symlink(linkto, dstname)
139      else:
140        copyFunc(srcname, dstname)
141        copies.append((srcname, dstname))
142    except (IOError, os.error) as why:
143      errors.append((srcname, dstname, str(why)))
144    except shutil.Error as err:
145      errors.extend((srcname,dstname,str(err.args[0])))
146    if errors:
147      raise shutil.Error(errors)
148    return copies
149
150  def fixExamplesMakefile(self, src):
151    '''Change ././${PETSC_ARCH} in makefile in root petsc directory with ${PETSC_DIR}'''
152    lines   = []
153    oldFile = open(src, 'r')
154    alllines=oldFile.read()
155    oldFile.close()
156    newlines=alllines.split('\n')[0]+'\n'  # Firstline
157    # Hardcode PETSC_DIR and PETSC_ARCH to avoid users doing the worng thing
158    newlines+='PETSC_DIR='+self.installDir+'\n'
159    newlines+='PETSC_ARCH=\n'
160    for line in alllines.split('\n')[1:]:
161      if line.startswith('TESTLOGFILE'):
162        newlines+='TESTLOGFILE = $(TESTDIR)/examples-install.log\n'
163      elif line.startswith('CONFIGDIR'):
164        newlines+='CONFIGDIR:=$(PETSC_DIR)/$(PETSC_ARCH)/share/petsc/examples/config\n'
165      elif line.startswith('$(generatedtest)') and 'petscvariables' in line:
166        newlines+='all: test\n\n'+line+'\n'
167      else:
168        newlines+=line+'\n'
169    newFile = open(src, 'w')
170    newFile.write(newlines)
171    newFile.close()
172    return
173
174  def copyConfig(self, src, dst):
175    """Copy configuration/testing files
176    """
177    if not os.path.isdir(dst):
178      raise shutil.Error('Destination is not a directory')
179
180    self.copies.extend(self.copyfile('gmakefile.test',dst))
181    newConfigDir=os.path.join(dst,'config')  # Am not renaming at present
182    if not os.path.isdir(newConfigDir): os.mkdir(newConfigDir)
183    testConfFiles="gmakegentest.py gmakegen.py testparse.py example_template.py".split()
184    testConfFiles+="petsc_harness.sh report_tests.py".split()
185    for tf in testConfFiles:
186      self.copies.extend(self.copyfile(os.path.join('config',tf),newConfigDir))
187    return
188
189  def copyExamples(self, src, dst):
190    """copy the examples directories
191    """
192    for root, dirs, files in os.walk(src, topdown=False):
193      if os.path.basename(root) not in ("tests", "tutorials"): continue
194      self.copies.extend(self.copytree(root, root.replace(src,dst)))
195    return
196
197  def copytree(self, src, dst, symlinks = False, copyFunc = shutil.copy2, exclude = [], exclude_ext= ['.DSYM','.o','.pyc'], recurse = 1):
198    """Recursively copy a directory tree using copyFunc, which defaults to shutil.copy2().
199
200       The copyFunc() you provide is only used on the top level, lower levels always use shutil.copy2
201
202    The destination directory must not already exist.
203    If exception(s) occur, an shutil.Error is raised with a list of reasons.
204
205    If the optional symlinks flag is true, symbolic links in the
206    source tree result in symbolic links in the destination tree; if
207    it is false, the contents of the files pointed to by symbolic
208    links are copied.
209    """
210    copies = []
211    names  = os.listdir(src)
212    if not os.path.exists(dst):
213      os.makedirs(dst)
214    elif not os.path.isdir(dst):
215      raise shutil.Error('Destination is not a directory')
216    errors = []
217    srclinks = []
218    dstlinks = []
219    for name in names:
220      srcname = os.path.join(src, name)
221      dstname = os.path.join(dst, name)
222      try:
223        if symlinks and os.path.islink(srcname):
224          linkto = os.readlink(srcname)
225          os.symlink(linkto, dstname)
226        elif os.path.isdir(srcname) and recurse and not os.path.basename(srcname) in exclude:
227          copies.extend(self.copytree(srcname, dstname, symlinks,exclude = exclude, exclude_ext = exclude_ext))
228        elif os.path.isfile(srcname) and not os.path.basename(srcname) in exclude and os.path.splitext(name)[1] not in exclude_ext :
229          if os.path.islink(srcname):
230            srclinks.append(srcname)
231            dstlinks.append(dstname)
232          else:
233            copyFunc(srcname, dstname)
234            copies.append((srcname, dstname))
235        # XXX What about devices, sockets etc.?
236      except (IOError, os.error) as why:
237        errors.append((srcname, dstname, str(why)))
238      # catch the Error from the recursive copytree so that we can
239      # continue with other files
240      except shutil.Error as err:
241        errors.extend((srcname,dstname,str(err.args[0])))
242    for srcname, dstname in zip(srclinks, dstlinks):
243      try:
244        copyFunc(srcname, dstname)
245        copies.append((srcname, dstname))
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.extend((srcname,dstname,str(err.args[0])))
252    try:
253      shutil.copystat(src, dst)
254    except OSError as e:
255      if WindowsError is not None and isinstance(e, WindowsError):
256        # Copying file access times may fail on Windows
257        pass
258      else:
259        errors.extend((src, dst, str(e)))
260    if errors:
261      raise shutil.Error(errors)
262    return copies
263
264
265  def fixConfFile(self, src):
266    lines   = []
267    oldFile = open(src, 'r')
268    for line in oldFile.readlines():
269      if line.startswith('PETSC_CC_INCLUDES =') or line.startswith('PETSC_FC_INCLUDES ='):
270        continue
271      line = line.replace('PETSC_CC_INCLUDES_INSTALL', 'PETSC_CC_INCLUDES')
272      line = line.replace('PETSC_FC_INCLUDES_INSTALL', 'PETSC_FC_INCLUDES')
273      # remove PETSC_DIR/PETSC_ARCH variables from conf-makefiles. They are no longer necessary
274      line = line.replace('${PETSC_DIR}/${PETSC_ARCH}', self.installDir)
275      line = line.replace('PETSC_ARCH=${PETSC_ARCH}', '')
276      line = line.replace('${PETSC_DIR}', self.installDir)
277      lines.append(line)
278    oldFile.close()
279    newFile = open(src, 'w')
280    newFile.write(''.join(lines))
281    newFile.close()
282    return
283
284  def fixConf(self):
285    import shutil
286    for file in ['rules', 'variables','petscrules', 'petscvariables']:
287      self.fixConfFile(os.path.join(self.destConfDir,file))
288    return
289
290  def createUninstaller(self):
291    uninstallscript = os.path.join(self.destConfDir, 'uninstall.py')
292    f = open(uninstallscript, 'w')
293    # Could use the Python AST to do this
294    f.write('#!'+sys.executable+'\n')
295    f.write('import os\n')
296    f.write('prefixdir = "'+self.installDir+'"\n')
297    files = [dst.replace(self.destDir,self.installDir) for src, dst in self.copies]
298    files.append(uninstallscript.replace(self.destDir,self.installDir))
299    f.write('files = '+repr(files))
300    f.write('''
301for file in files:
302  if os.path.exists(file) or os.path.islink(file):
303    os.remove(file)
304    dir = os.path.dirname(file)
305    while dir not in [os.path.dirname(prefixdir),'/']:
306      try: os.rmdir(dir)
307      except: break
308      dir = os.path.dirname(dir)
309''')
310    f.close()
311    os.chmod(uninstallscript,0o744)
312    return
313
314  def installIncludes(self):
315    exclude = ['makefile']
316    if not hasattr(self.compilers.setCompilers, 'FC'):
317      exclude.append('finclude')
318    if not self.mpi.usingMPIUni:
319      exclude.append('mpiuni')
320    self.copies.extend(self.copytree(self.rootIncludeDir, self.destIncludeDir,exclude = exclude))
321    self.copies.extend(self.copytree(self.archIncludeDir, self.destIncludeDir))
322    return
323
324  def installConf(self):
325    self.copies.extend(self.copytree(self.rootConfDir, self.destConfDir, exclude = ['uncrustify.cfg','bfort-base.txt','bfort-petsc.txt','bfort-mpi.txt','test.log']))
326    self.copies.extend(self.copytree(self.archConfDir, self.destConfDir, exclude = ['sowing', 'configure.log.bkp','configure.log','make.log','gmake.log','test.log','error.log','files','testfiles','RDict.db']))
327    return
328
329  def installBin(self):
330    exclude = ['bfort','bib2html','doc2lt','doctext','mapnames', 'pstogif','pstoxbm','tohtml']
331    self.copies.extend(self.copytree(self.archBinDir, self.destBinDir, exclude = exclude ))
332    exclude = ['maint']
333    if not self.mpi.usingMPIUni:
334      exclude.append('petsc-mpiexec.uni')
335    self.setCompilers.pushLanguage('C')
336    if not self.setCompilers.isWindows(self.setCompilers.getCompiler(),self.log):
337      exclude.append('win32fe')
338    self.setCompilers.popLanguage()
339    self.copies.extend(self.copytree(self.rootBinDir, self.destBinDir, exclude = exclude ))
340    return
341
342  def installShare(self):
343    self.copies.extend(self.copytree(self.rootShareDir, self.destShareDir))
344    examplesdir=os.path.join(self.destShareDir,'petsc','examples')
345    if os.path.exists(examplesdir):
346      shutil.rmtree(examplesdir)
347    os.mkdir(examplesdir)
348    os.mkdir(os.path.join(examplesdir,'src'))
349    self.copyExamples(self.rootSrcDir,os.path.join(examplesdir,'src'))
350    self.copyConfig(self.rootDir,examplesdir)
351    self.fixExamplesMakefile(os.path.join(examplesdir,'gmakefile.test'))
352    return
353
354  def copyLib(self, src, dst):
355    '''Run ranlib on the destination library if it is an archive. Also run install_name_tool on dylib on Mac'''
356    # Symlinks (assumed local) are recreated at dst
357    if os.path.islink(src):
358      linkto = os.readlink(src)
359      try:
360        os.remove(dst)            # In case it already exists
361      except OSError:
362        pass
363      os.symlink(linkto, dst)
364      return
365    shutil.copy2(src, dst)
366    if os.path.splitext(dst)[1] == '.'+self.arLibSuffix:
367      self.executeShellCommand(self.ranlib+' '+dst)
368    if os.path.splitext(dst)[1] == '.dylib' and os.path.isfile('/usr/bin/install_name_tool'):
369      [output,err,flg] = self.executeShellCommand("otool -D "+src)
370      oldname = output[output.find("\n")+1:]
371      installName = oldname.replace(os.path.realpath(self.archDir), self.installDir)
372      self.executeShellCommand('/usr/bin/install_name_tool -id ' + installName + ' ' + dst)
373    # preserve the original timestamps - so that the .a vs .so time order is preserved
374    shutil.copystat(src,dst)
375    return
376
377  def installLib(self):
378    self.copies.extend(self.copytree(self.archLibDir, self.destLibDir, copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
379    self.copies.extend(self.copytree(os.path.join(self.archLibDir,'pkgconfig'), os.path.join(self.destLibDir,'pkgconfig'), copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
380    return
381
382
383  def outputInstallDone(self):
384    from config.packages.make import getMakeUserPath
385    print('''\
386====================================
387Install complete.
388Now to check if the libraries are working do (in current directory):
389%s PETSC_DIR=%s PETSC_ARCH="" check
390====================================\
391''' % (getMakeUserPath(self.arch), self.installDir))
392    return
393
394  def outputDestDirDone(self):
395    print('''\
396====================================
397Copy to DESTDIR %s is now complete.
398Before use - please copy/install over to specified prefix: %s
399====================================\
400''' % (self.destDir,self.installDir))
401    return
402
403  def runsetup(self):
404    self.setup()
405    self.setupDirectories()
406    self.checkPrefix()
407    self.checkDestdir()
408    return
409
410  def runcopy(self):
411    if self.destDir == self.installDir:
412      print('*** Installing PETSc at prefix location:',self.destDir, ' ***')
413    else:
414      print('*** Copying PETSc to DESTDIR location:',self.destDir, ' ***')
415    if not os.path.exists(self.destDir):
416      try:
417        os.makedirs(self.destDir)
418      except:
419        print('********************************************************************')
420        print('Unable to create', self.destDir, 'Perhaps you need to do "sudo make install"')
421        print('********************************************************************')
422        sys.exit(1)
423    self.installIncludes()
424    self.installConf()
425    self.installBin()
426    self.installLib()
427    self.installShare()
428    return
429
430  def runfix(self):
431    self.fixConf()
432    return
433
434  def rundone(self):
435    self.createUninstaller()
436    if self.destDir == self.installDir:
437      self.outputInstallDone()
438    else:
439      self.outputDestDirDone()
440    return
441
442  def run(self):
443    self.runsetup()
444    self.runcopy()
445    self.runfix()
446    self.rundone()
447    return
448
449if __name__ == '__main__':
450  Installer(sys.argv[1:]).run()
451  # temporary hack - delete log files created by BuildSystem - when 'sudo make install' is invoked
452  delfiles=['RDict.db','RDict.log','buildsystem.log','default.log','buildsystem.log.bkp','default.log.bkp']
453  for delfile in delfiles:
454    if os.path.exists(delfile) and (os.stat(delfile).st_uid==0):
455      os.remove(delfile)
456