xref: /petsc/config/install.py (revision 9b2db2227e056d74e10acbb4c7c75cbd904ecd7e)
1#!/usr/bin/env python
2import os, re, shutil, sys
3
4if os.environ.has_key('PETSC_DIR'):
5  PETSC_DIR = os.environ['PETSC_DIR']
6else:
7  fd = file(os.path.join('lib','petsc','conf','petscvariables'))
8  a = fd.readline()
9  a = fd.readline()
10  PETSC_DIR = a.split('=')[1][0:-1]
11  fd.close()
12
13if os.environ.has_key('PETSC_ARCH'):
14  PETSC_ARCH = os.environ['PETSC_ARCH']
15else:
16  fd = file(os.path.join('lib','petsc','conf','petscvariables'))
17  a = fd.readline()
18  PETSC_ARCH = a.split('=')[1][0:-1]
19  fd.close()
20
21print '*** Using PETSC_DIR='+PETSC_DIR+' PETSC_ARCH='+PETSC_ARCH+' ***'
22sys.path.insert(0, os.path.join(PETSC_DIR, 'config'))
23sys.path.insert(0, os.path.join(PETSC_DIR, 'config', 'BuildSystem'))
24
25import script
26
27try:
28  WindowsError
29except NameError:
30  WindowsError = None
31
32class Installer(script.Script):
33  def __init__(self, clArgs = None):
34    import RDict
35    argDB = RDict.RDict(None, None, 0, 0, readonly = True)
36    argDB.saveFilename = os.path.join(PETSC_DIR, PETSC_ARCH, 'lib','petsc','conf', 'RDict.db')
37    argDB.load()
38    script.Script.__init__(self, argDB = argDB)
39    if not clArgs is None: self.clArgs = clArgs
40    self.copies = []
41    return
42
43  def setupHelp(self, help):
44    import nargs
45    script.Script.setupHelp(self, help)
46    help.addArgument('Installer', '-destDir=<path>', nargs.Arg(None, '', 'Destination Directory for install'))
47    return
48
49
50  def setupModules(self):
51    self.setCompilers  = self.framework.require('config.setCompilers',         None)
52    self.arch          = self.framework.require('PETSc.options.arch',          None)
53    self.petscdir      = self.framework.require('PETSc.options.petscdir',      None)
54    self.compilers     = self.framework.require('config.compilers',            None)
55    self.mpi           = self.framework.require('config.packages.MPI',         None)
56    return
57
58  def setup(self):
59    script.Script.setup(self)
60    self.framework = self.loadConfigure()
61    self.setupModules()
62    return
63
64  def setupDirectories(self):
65    self.rootDir    = self.petscdir.dir
66    self.installDir = os.path.abspath(os.path.expanduser(self.framework.argDB['prefix']))
67    self.destDir    = os.path.abspath(self.argDB['destDir']+self.installDir)
68    self.arch       = self.arch.arch
69    self.archDir           = os.path.join(self.rootDir, self.arch)
70    self.rootIncludeDir    = os.path.join(self.rootDir, 'include')
71    self.archIncludeDir    = os.path.join(self.rootDir, self.arch, 'include')
72    self.rootConfDir       = os.path.join(self.rootDir, 'lib','petsc','conf')
73    self.archConfDir       = os.path.join(self.rootDir, self.arch, 'lib','petsc','conf')
74    self.rootBinDir        = os.path.join(self.rootDir, 'lib','petsc','bin')
75    self.archBinDir        = os.path.join(self.rootDir, self.arch, 'bin')
76    self.archLibDir        = os.path.join(self.rootDir, self.arch, 'lib')
77    self.destIncludeDir    = os.path.join(self.destDir, 'include')
78    self.destConfDir       = os.path.join(self.destDir, 'lib','petsc','conf')
79    self.destLibDir        = os.path.join(self.destDir, 'lib')
80    self.destBinDir        = os.path.join(self.destDir, 'lib','petsc','bin')
81    self.installIncludeDir = os.path.join(self.installDir, 'include')
82    self.installBinDir     = os.path.join(self.installDir, 'lib','petsc','bin')
83    self.rootShareDir      = os.path.join(self.rootDir, 'share')
84    self.destShareDir      = os.path.join(self.destDir, 'share')
85    self.rootSrcDir        = os.path.join(self.rootDir, 'src')
86
87    self.ranlib      = self.compilers.RANLIB
88    self.arLibSuffix = self.compilers.AR_LIB_SUFFIX
89    return
90
91  def checkPrefix(self):
92    if not self.installDir:
93      print '********************************************************************'
94      print 'PETSc is built without prefix option. So "make install" is not appropriate.'
95      print 'If you need a prefix install of PETSc - rerun configure with --prefix option.'
96      print '********************************************************************'
97      sys.exit(1)
98    return
99
100  def checkDestdir(self):
101    if os.path.exists(self.destDir):
102      if os.path.samefile(self.destDir, self.rootDir):
103        print '********************************************************************'
104        print 'Incorrect prefix usage. Specified destDir same as current PETSC_DIR'
105        print '********************************************************************'
106        sys.exit(1)
107      if os.path.samefile(self.destDir, os.path.join(self.rootDir,self.arch)):
108        print '********************************************************************'
109        print 'Incorrect prefix usage. Specified destDir same as current PETSC_DIR/PETSC_ARCH'
110        print '********************************************************************'
111        sys.exit(1)
112      if not os.path.isdir(os.path.realpath(self.destDir)):
113        print '********************************************************************'
114        print 'Specified destDir', self.destDir, 'is not a directory. Cannot proceed!'
115        print '********************************************************************'
116        sys.exit(1)
117      if not os.access(self.destDir, os.W_OK):
118        print '********************************************************************'
119        print 'Unable to write to ', self.destDir, 'Perhaps you need to do "sudo make install"'
120        print '********************************************************************'
121        sys.exit(1)
122    return
123
124  def copyfile(self, src, dst, symlinks = False, copyFunc = shutil.copy2):
125    """Copies a single file    """
126    copies = []
127    errors = []
128    if not os.path.exists(dst):
129      os.makedirs(dst)
130    elif not os.path.isdir(dst):
131      raise shutil.Error, 'Destination is not a directory'
132    srcname = src
133    dstname = os.path.join(dst, os.path.basename(src))
134    try:
135      if symlinks and os.path.islink(srcname):
136        linkto = os.readlink(srcname)
137        os.symlink(linkto, dstname)
138      else:
139        copyFunc(srcname, dstname)
140        copies.append((srcname, dstname))
141    except (IOError, os.error), why:
142      errors.append((srcname, dstname, str(why)))
143    except shutil.Error, err:
144      errors.extend((srcname,dstname,str(err.args[0])))
145    if errors:
146      raise shutil.Error, errors
147    return copies
148
149  def fixExamplesMakefile(self, src):
150    '''Change ././${PETSC_ARCH} in makefile in root petsc directory with ${PETSC_DIR}'''
151    lines   = []
152    oldFile = open(src, 'r')
153    alllines=oldFile.read()
154    oldFile.close()
155    newlines=alllines.split('\n')[0]+'\n'  # Firstline
156    # Hardcode PETSC_DIR and PETSC_ARCH to avoid users doing the worng thing
157    newlines+='PETSC_DIR='+self.installDir+'\n'
158    newlines+='PETSC_ARCH=\n'
159    for line in alllines.split('\n')[1:]:
160      if line.startswith('TESTLOGFILE'):
161        newlines+='TESTLOGFILE = $(TESTDIR)/examples-install.log\n'
162      elif line.startswith('CONFIGDIR'):
163        newlines+='CONFIGDIR:=$(PETSC_DIR)/$(PETSC_ARCH)/share/petsc/examples/config\n'
164      elif line.startswith('$(generatedtest)') and 'petscvariables' in line:
165        newlines+='all: test\n\n'+line+'\n'
166      else:
167        newlines+=line+'\n'
168    newFile = open(src, 'w')
169    newFile.write(newlines)
170    newFile.close()
171    return
172
173  def copyConfig(self, src, dst):
174    """Recursively copy the examples directories
175    """
176    if not os.path.isdir(dst):
177      raise shutil.Error, 'Destination is not a directory'
178
179    self.copies.extend(self.copyfile('gmakefile.test',dst))
180    newConfigDir=os.path.join(dst,'config')  # Am not renaming at present
181    if not os.path.isdir(newConfigDir): os.mkdir(newConfigDir)
182    testConfFiles="gmakegentest.py gmakegen.py testparse.py example_template.py".split()
183    testConfFiles+="petsc_harness.sh report_tests.py".split()
184    testConfFiles+=["cmakegen.py"]
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    top=os.path.relpath(src,os.path.abspath(os.curdir))
193    for root, dirs, files in os.walk(top, topdown=False):
194        if not os.path.basename(root) == "examples": continue
195        self.copies.extend(self.copytree(root, os.path.join(dst,root)))
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), 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, err:
236        errors.extend((srcname,dstname,str(err.args[0])))
237    try:
238      shutil.copystat(src, dst)
239    except OSError, 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,0744)
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