xref: /petsc/config/install.py (revision 4877389942e948c907dd43b3a2d019cdca14ea44)
1#!/usr/bin/env python3
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.append((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 wrong 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 query_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.append((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.append((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.append((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      # replace PETSC_DIR/lib/petsc/bin with prefix/lib/petsc/bin
278      line = line.replace(self.rootBinDir,self.destBinDir)
279      lines.append(line)
280    oldFile.close()
281    newFile = open(src, 'w')
282    newFile.write(''.join(lines))
283    newFile.close()
284    return
285
286  def fixConf(self):
287    import shutil
288    for file in ['rules', 'variables','petscrules', 'petscvariables']:
289      self.fixConfFile(os.path.join(self.destConfDir,file))
290    return
291
292  def createUninstaller(self):
293    uninstallscript = os.path.join(self.destConfDir, 'uninstall.py')
294    f = open(uninstallscript, 'w')
295    # Could use the Python AST to do this
296    f.write('#!'+sys.executable+'\n')
297    f.write('import os\n')
298    f.write('prefixdir = "'+self.installDir+'"\n')
299    files = [dst.replace(self.destDir,self.installDir) for src, dst in self.copies]
300    files.append(uninstallscript.replace(self.destDir,self.installDir))
301    f.write('files = '+repr(files))
302    f.write('''
303for file in files:
304  if os.path.exists(file) or os.path.islink(file):
305    os.remove(file)
306    dir = os.path.dirname(file)
307    while dir not in [os.path.dirname(prefixdir),'/']:
308      try: os.rmdir(dir)
309      except: break
310      dir = os.path.dirname(dir)
311''')
312    f.close()
313    os.chmod(uninstallscript,0o744)
314    return
315
316  def installIncludes(self):
317    exclude = ['makefile']
318    if not hasattr(self.compilers.setCompilers, 'FC'):
319      exclude.append('finclude')
320    if not self.mpi.usingMPIUni:
321      exclude.append('mpiuni')
322    self.copies.extend(self.copytree(self.rootIncludeDir, self.destIncludeDir,exclude = exclude))
323    self.copies.extend(self.copytree(self.archIncludeDir, self.destIncludeDir))
324    return
325
326  def installConf(self):
327    self.copies.extend(self.copytree(self.rootConfDir, self.destConfDir, exclude = ['uncrustify.cfg','bfort-base.txt','bfort-petsc.txt','bfort-mpi.txt','test.log']))
328    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']))
329    return
330
331  def installBin(self):
332    exclude = ['bfort','bib2html','doc2lt','doctext','mapnames', 'pstogif','pstoxbm','tohtml']
333    self.copies.extend(self.copytree(self.archBinDir, self.destBinDir, exclude = exclude ))
334    exclude = ['maint']
335    if not self.mpi.usingMPIUni:
336      exclude.append('petsc-mpiexec.uni')
337    self.setCompilers.pushLanguage('C')
338    if self.setCompilers.getCompiler().find('win32fe') < 0:
339      exclude.append('win32fe')
340    self.setCompilers.popLanguage()
341    self.copies.extend(self.copytree(self.rootBinDir, self.destBinDir, exclude = exclude ))
342    return
343
344  def installShare(self):
345    self.copies.extend(self.copytree(self.rootShareDir, self.destShareDir))
346    examplesdir=os.path.join(self.destShareDir,'petsc','examples')
347    if os.path.exists(examplesdir):
348      shutil.rmtree(examplesdir)
349    os.mkdir(examplesdir)
350    os.mkdir(os.path.join(examplesdir,'src'))
351    self.copyExamples(self.rootSrcDir,os.path.join(examplesdir,'src'))
352    self.copyConfig(self.rootDir,examplesdir)
353    self.fixExamplesMakefile(os.path.join(examplesdir,'gmakefile.test'))
354    return
355
356  def copyLib(self, src, dst):
357    '''Run ranlib on the destination library if it is an archive. Also run install_name_tool on dylib on Mac'''
358    # Symlinks (assumed local) are recreated at dst
359    if os.path.islink(src):
360      linkto = os.readlink(src)
361      try:
362        os.remove(dst)            # In case it already exists
363      except OSError:
364        pass
365      os.symlink(linkto, dst)
366      return
367    shutil.copy2(src, dst)
368    if self.setCompilers.getCompiler().find('win32fe') < 0 and os.path.splitext(dst)[1] == '.'+self.arLibSuffix:
369      self.executeShellCommand(self.ranlib+' '+dst)
370    if os.path.splitext(dst)[1] == '.dylib' and os.path.isfile('/usr/bin/install_name_tool'):
371      [output,err,flg] = self.executeShellCommand("otool -D "+src)
372      oldname = output[output.find("\n")+1:]
373      installName = oldname.replace(os.path.realpath(self.archDir), self.installDir)
374      self.executeShellCommand('/usr/bin/install_name_tool -id ' + installName + ' ' + dst)
375    # preserve the original timestamps - so that the .a vs .so time order is preserved
376    shutil.copystat(src,dst)
377    return
378
379  def installLib(self):
380    self.copies.extend(self.copytree(self.archLibDir, self.destLibDir, copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
381    self.copies.extend(self.copytree(os.path.join(self.archLibDir,'pkgconfig'), os.path.join(self.destLibDir,'pkgconfig'), copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
382    return
383
384
385  def outputInstallDone(self):
386    from config.packages.make import getMakeUserPath
387    print('''\
388====================================
389Install complete.
390Now to check if the libraries are working do (in current directory):
391%s PETSC_DIR=%s PETSC_ARCH="" check
392====================================\
393''' % (getMakeUserPath(self.arch), self.installDir))
394    return
395
396  def outputDestDirDone(self):
397    print('''\
398====================================
399Copy to DESTDIR %s is now complete.
400Before use - please copy/install over to specified prefix: %s
401====================================\
402''' % (self.destDir,self.installDir))
403    return
404
405  def runsetup(self):
406    self.setup()
407    self.setupDirectories()
408    self.checkPrefix()
409    self.checkDestdir()
410    return
411
412  def runcopy(self):
413    if self.destDir == self.installDir:
414      print('*** Installing PETSc at prefix location:',self.destDir, ' ***')
415    else:
416      print('*** Copying PETSc to DESTDIR location:',self.destDir, ' ***')
417    if not os.path.exists(self.destDir):
418      try:
419        os.makedirs(self.destDir)
420      except:
421        print('********************************************************************')
422        print('Unable to create', self.destDir, 'Perhaps you need to do "sudo make install"')
423        print('********************************************************************')
424        sys.exit(1)
425    self.installIncludes()
426    self.installConf()
427    self.installBin()
428    self.installLib()
429    self.installShare()
430    return
431
432  def runfix(self):
433    self.fixConf()
434    return
435
436  def rundone(self):
437    self.createUninstaller()
438    if self.destDir == self.installDir:
439      self.outputInstallDone()
440    else:
441      self.outputDestDirDone()
442    return
443
444  def run(self):
445    self.runsetup()
446    self.runcopy()
447    self.runfix()
448    self.rundone()
449    return
450
451if __name__ == '__main__':
452  Installer(sys.argv[1:]).run()
453  # temporary hack - delete log files created by BuildSystem - when 'sudo make install' is invoked
454  delfiles=['RDict.db','RDict.log','buildsystem.log','default.log','buildsystem.log.bkp','default.log.bkp']
455  for delfile in delfiles:
456    if os.path.exists(delfile) and (os.stat(delfile).st_uid==0):
457      os.remove(delfile)
458