xref: /petsc/config/install.py (revision dc9df7f6ce99c8d1a01a2ef5bdf66dbbcd7eefc9)
1#!/usr/bin/env python
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 = file(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 = file(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    """Recursively copy the examples directories
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    testConfFiles+=["cmakegen.py"]
186    for tf in testConfFiles:
187      self.copies.extend(self.copyfile(os.path.join('config',tf),newConfigDir))
188    return
189
190  def copyExamples(self, src, dst):
191    """copy the examples directories
192    """
193    for root, dirs, files in os.walk(src, topdown=False):
194      if not os.path.basename(root) == "examples": continue
195      self.copies.extend(self.copytree(root, root.replace(src,dst)))
196    return
197
198  def copytree(self, src, dst, symlinks = False, copyFunc = shutil.copy2, exclude = [], exclude_ext= ['.DSYM','.o','.pyc'], recurse = 1):
199    """Recursively copy a directory tree using copyFunc, which defaults to shutil.copy2().
200
201       The copyFunc() you provide is only used on the top level, lower levels always use shutil.copy2
202
203    The destination directory must not already exist.
204    If exception(s) occur, an shutil.Error is raised with a list of reasons.
205
206    If the optional symlinks flag is true, symbolic links in the
207    source tree result in symbolic links in the destination tree; if
208    it is false, the contents of the files pointed to by symbolic
209    links are copied.
210    """
211    copies = []
212    names  = os.listdir(src)
213    if not os.path.exists(dst):
214      os.makedirs(dst)
215    elif not os.path.isdir(dst):
216      raise shutil.Error('Destination is not a directory')
217    errors = []
218    for name in names:
219      srcname = os.path.join(src, name)
220      dstname = os.path.join(dst, name)
221      try:
222        if symlinks and os.path.islink(srcname):
223          linkto = os.readlink(srcname)
224          os.symlink(linkto, dstname)
225        elif os.path.isdir(srcname) and recurse and not os.path.basename(srcname) in exclude:
226          copies.extend(self.copytree(srcname, dstname, symlinks,exclude = exclude, exclude_ext = exclude_ext))
227        elif os.path.isfile(srcname) and not os.path.basename(srcname) in exclude and os.path.splitext(name)[1] not in exclude_ext :
228          copyFunc(srcname, dstname)
229          copies.append((srcname, dstname))
230        # XXX What about devices, sockets etc.?
231      except (IOError, os.error) as why:
232        errors.append((srcname, dstname, str(why)))
233      # catch the Error from the recursive copytree so that we can
234      # continue with other files
235      except shutil.Error as err:
236        errors.extend((srcname,dstname,str(err.args[0])))
237    try:
238      shutil.copystat(src, dst)
239    except OSError as e:
240      if WindowsError is not None and isinstance(e, WindowsError):
241        # Copying file access times may fail on Windows
242        pass
243      else:
244        errors.extend((src, dst, str(e)))
245    if errors:
246      raise shutil.Error(errors)
247    return copies
248
249
250  def fixConfFile(self, src):
251    lines   = []
252    oldFile = open(src, 'r')
253    for line in oldFile.readlines():
254      # paths generated by configure could be different link-path than whats used by user, so fix both
255      line = line.replace(os.path.join(self.rootDir, self.arch), self.installDir)
256      line = line.replace(os.path.realpath(os.path.join(self.rootDir, self.arch)), self.installDir)
257      line = line.replace(os.path.join(self.rootDir, 'bin'), self.installBinDir)
258      line = line.replace(os.path.realpath(os.path.join(self.rootDir, 'bin')), self.installBinDir)
259      line = line.replace(os.path.join(self.rootDir, 'include'), self.installIncludeDir)
260      line = line.replace(os.path.realpath(os.path.join(self.rootDir, 'include')), self.installIncludeDir)
261      # remove PETSC_DIR/PETSC_ARCH variables from conf-makefiles. They are no longer necessary
262      line = line.replace('${PETSC_DIR}/${PETSC_ARCH}', self.installDir)
263      line = line.replace('PETSC_ARCH=${PETSC_ARCH}', '')
264      line = line.replace('${PETSC_DIR}', self.installDir)
265      lines.append(line)
266    oldFile.close()
267    newFile = open(src, 'w')
268    newFile.write(''.join(lines))
269    newFile.close()
270    return
271
272  def fixConf(self):
273    import shutil
274    for file in ['rules', 'variables','petscrules', 'petscvariables']:
275      self.fixConfFile(os.path.join(self.destConfDir,file))
276    self.fixConfFile(os.path.join(self.destLibDir,'pkgconfig','PETSc.pc'))
277    return
278
279  def createUninstaller(self):
280    uninstallscript = os.path.join(self.destConfDir, 'uninstall.py')
281    f = open(uninstallscript, 'w')
282    # Could use the Python AST to do this
283    f.write('#!'+sys.executable+'\n')
284    f.write('import os\n')
285    f.write('prefixdir = "'+self.installDir+'"\n')
286    files = [dst.replace(self.destDir,self.installDir) for src, dst in self.copies]
287    files.append(uninstallscript.replace(self.destDir,self.installDir))
288    f.write('files = '+repr(files))
289    f.write('''
290for file in files:
291  if os.path.exists(file) or os.path.islink(file):
292    os.remove(file)
293    dir = os.path.dirname(file)
294    while dir not in [os.path.dirname(prefixdir),'/']:
295      try: os.rmdir(dir)
296      except: break
297      dir = os.path.dirname(dir)
298''')
299    f.close()
300    os.chmod(uninstallscript,0o744)
301    return
302
303  def installIncludes(self):
304    exclude = ['makefile']
305    if not hasattr(self.compilers.setCompilers, 'FC'):
306      exclude.append('finclude')
307    if not self.mpi.usingMPIUni:
308      exclude.append('mpiuni')
309    self.copies.extend(self.copytree(self.rootIncludeDir, self.destIncludeDir,exclude = exclude))
310    self.copies.extend(self.copytree(self.archIncludeDir, self.destIncludeDir))
311    return
312
313  def installConf(self):
314    self.copies.extend(self.copytree(self.rootConfDir, self.destConfDir, exclude = ['uncrustify.cfg','bfort-base.txt','bfort-petsc.txt','bfort-mpi.txt','test.log']))
315    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']))
316    return
317
318  def installBin(self):
319    exclude = ['bfort','bib2html','doc2lt','doctext','mapnames', 'pstogif','pstoxbm','tohtml']
320    self.copies.extend(self.copytree(self.archBinDir, self.destBinDir, exclude = exclude ))
321    exclude = ['maint']
322    if not self.mpi.usingMPIUni:
323      exclude.append('petsc-mpiexec.uni')
324    self.setCompilers.pushLanguage('C')
325    if not self.setCompilers.isWindows(self.setCompilers.getCompiler(),self.log):
326      exclude.append('win32fe')
327    self.setCompilers.popLanguage()
328    self.copies.extend(self.copytree(self.rootBinDir, self.destBinDir, exclude = exclude ))
329    return
330
331  def installShare(self):
332    self.copies.extend(self.copytree(self.rootShareDir, self.destShareDir))
333    examplesdir=os.path.join(self.destShareDir,'petsc','examples')
334    if os.path.exists(examplesdir):
335      shutil.rmtree(examplesdir)
336    os.mkdir(examplesdir)
337    os.mkdir(os.path.join(examplesdir,'src'))
338    self.copyExamples(self.rootSrcDir,examplesdir)
339    self.copyConfig(self.rootDir,examplesdir)
340    self.fixExamplesMakefile(os.path.join(examplesdir,'gmakefile.test'))
341    return
342
343  def copyLib(self, src, dst):
344    '''Run ranlib on the destination library if it is an archive. Also run install_name_tool on dylib on Mac'''
345    # Symlinks (assumed local) are recreated at dst
346    if os.path.islink(src):
347      linkto = os.readlink(src)
348      try:
349        os.remove(dst)            # In case it already exists
350      except OSError:
351        pass
352      os.symlink(linkto, dst)
353      return
354    shutil.copy2(src, dst)
355    if os.path.splitext(dst)[1] == '.'+self.arLibSuffix:
356      self.executeShellCommand(self.ranlib+' '+dst)
357    if os.path.splitext(dst)[1] == '.dylib' and os.path.isfile('/usr/bin/install_name_tool'):
358      [output,err,flg] = self.executeShellCommand("otool -D "+src)
359      oldname = output[output.find("\n")+1:]
360      installName = oldname.replace(self.archDir, self.installDir)
361      self.executeShellCommand('/usr/bin/install_name_tool -id ' + installName + ' ' + dst)
362    # preserve the original timestamps - so that the .a vs .so time order is preserved
363    shutil.copystat(src,dst)
364    return
365
366  def installLib(self):
367    self.copies.extend(self.copytree(self.archLibDir, self.destLibDir, copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
368    self.copies.extend(self.copytree(os.path.join(self.archLibDir,'pkgconfig'), os.path.join(self.destLibDir,'pkgconfig'), copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
369    return
370
371
372  def outputInstallDone(self):
373    print('''\
374====================================
375Install complete.
376Now to check if the libraries are working do (in current directory):
377make PETSC_DIR=%s PETSC_ARCH="" test
378====================================\
379''' % (self.installDir))
380    return
381
382  def outputDestDirDone(self):
383    print('''\
384====================================
385Copy to DESTDIR %s is now complete.
386Before use - please copy/install over to specified prefix: %s
387====================================\
388''' % (self.destDir,self.installDir))
389    return
390
391  def runsetup(self):
392    self.setup()
393    self.setupDirectories()
394    self.checkPrefix()
395    self.checkDestdir()
396    return
397
398  def runcopy(self):
399    if self.destDir == self.installDir:
400      print('*** Installing PETSc at prefix location:',self.destDir, ' ***')
401    else:
402      print('*** Copying PETSc to DESTDIR location:',self.destDir, ' ***')
403    if not os.path.exists(self.destDir):
404      try:
405        os.makedirs(self.destDir)
406      except:
407        print('********************************************************************')
408        print('Unable to create', self.destDir, 'Perhaps you need to do "sudo make install"')
409        print('********************************************************************')
410        sys.exit(1)
411    self.installIncludes()
412    self.installConf()
413    self.installBin()
414    self.installLib()
415    self.installShare()
416    return
417
418  def runfix(self):
419    self.fixConf()
420    return
421
422  def rundone(self):
423    self.createUninstaller()
424    if self.destDir == self.installDir:
425      self.outputInstallDone()
426    else:
427      self.outputDestDirDone()
428    return
429
430  def run(self):
431    self.runsetup()
432    self.runcopy()
433    self.runfix()
434    self.rundone()
435    return
436
437if __name__ == '__main__':
438  Installer(sys.argv[1:]).run()
439  # temporary hack - delete log files created by BuildSystem - when 'sudo make install' is invoked
440  delfiles=['RDict.db','RDict.log','buildsystem.log','default.log','buildsystem.log.bkp','default.log.bkp']
441  for delfile in delfiles:
442    if os.path.exists(delfile) and (os.stat(delfile).st_uid==0):
443      os.remove(delfile)
444