xref: /petsc/config/install.py (revision 45ee41800992640f7d9cd245f131a63b64db191d)
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, 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.destDir    = os.path.abspath(self.argDB['destDir'])
67    self.installDir = os.path.abspath(os.path.expanduser(self.framework.argDB['prefix']))
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 copyexamplefiles(self, src, dst, copyFunc = shutil.copy2):
150    """Copies all files, but not directories in a single file    """
151    names  = os.listdir(src)
152    for name in names:
153      if not name.endswith('.html'):
154        srcname = os.path.join(src, name)
155        if os.path.isfile(srcname):
156           self.copyfile(srcname,dst)
157
158  def fixExamplesMakefile(self, src):
159    '''Change ././${PETSC_ARCH} in makefile in root petsc directory with ${PETSC_DIR}'''
160    lines   = []
161    oldFile = open(src, 'r')
162    alllines=oldFile.read()
163    oldFile.close()
164    newlines=alllines.split('\n')[0]+'\n'  # Firstline
165    # Hardcode PETSC_DIR and PETSC_ARCH to avoid users doing the worng thing
166    newlines+='PETSC_DIR='+self.installDir+'\n'
167    newlines+='PETSC_ARCH=\n'
168    for line in alllines.split('\n')[1:]:
169      if line.startswith('TESTLOGFILE'):
170        newlines+='TESTLOGFILE = $(TESTDIR)/examples-install.log\n'
171      elif line.startswith('CONFIGDIR'):
172        newlines+='CONFIGDIR:=$(PETSC_DIR)/$(PETSC_ARCH)/share/petsc/examples/config\n'
173      elif line.startswith('$(generatedtest)') and 'petscvariables' in line:
174        newlines+='all: test\n\n'+line+'\n'
175      else:
176        newlines+=line+'\n'
177    newFile = open(src, 'w')
178    newFile.write(newlines)
179    newFile.close()
180    return
181
182  def copyConfig(self, src, dst):
183    """Recursively copy the examples directories
184    """
185    if not os.path.isdir(dst):
186      raise shutil.Error, 'Destination is not a directory'
187
188    self.copyfile('gmakefile.test',dst)
189    newConfigDir=os.path.join(dst,'config')  # Am not renaming at present
190    if not os.path.isdir(newConfigDir): os.mkdir(newConfigDir)
191    testConfFiles="gmakegentest.py gmakegen.py testparse.py example_template.py".split()
192    testConfFiles+="petsc_harness.sh report_tests.py watchtime.sh".split()
193    testConfFiles+=["cmakegen.py"]
194    for tf in testConfFiles:
195      self.copyfile(os.path.join('config',tf),newConfigDir)
196    return
197
198  def copyExamples(self, src, dst):
199    """copy the examples directories
200    """
201    top=os.path.relpath(src,os.path.abspath(os.curdir))
202    for root, dirs, files in os.walk(top, topdown=False):
203        if not os.path.basename(root) == "examples": continue
204        shutil.copytree(root, os.path.join(dst,root),
205                        ignore=shutil.ignore_patterns('*.dSYM'))
206
207    return
208
209  def copytree(self, src, dst, symlinks = False, copyFunc = shutil.copy2, exclude = [],recurse = 1):
210    """Recursively copy a directory tree using copyFunc, which defaults to shutil.copy2().
211
212       The copyFunc() you provide is only used on the top level, lower levels always use shutil.copy2
213
214    The destination directory must not already exist.
215    If exception(s) occur, an shutil.Error is raised with a list of reasons.
216
217    If the optional symlinks flag is true, symbolic links in the
218    source tree result in symbolic links in the destination tree; if
219    it is false, the contents of the files pointed to by symbolic
220    links are copied.
221    """
222    copies = []
223    names  = os.listdir(src)
224    if not os.path.exists(dst):
225      os.makedirs(dst)
226    elif not os.path.isdir(dst):
227      raise shutil.Error, 'Destination is not a directory'
228    errors = []
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))
238        elif os.path.isfile(srcname) and not os.path.basename(srcname) in exclude:
239          copyFunc(srcname, dstname)
240          copies.append((srcname, dstname))
241        # XXX What about devices, sockets etc.?
242      except (IOError, os.error), why:
243        errors.append((srcname, dstname, str(why)))
244      # catch the Error from the recursive copytree so that we can
245      # continue with other files
246      except shutil.Error, err:
247        errors.extend((srcname,dstname,str(err.args[0])))
248    try:
249      shutil.copystat(src, dst)
250    except OSError, e:
251      if WindowsError is not None and isinstance(e, WindowsError):
252        # Copying file access times may fail on Windows
253        pass
254      else:
255        errors.extend((src, dst, str(e)))
256    if errors:
257      raise shutil.Error, errors
258    return copies
259
260
261  def fixConfFile(self, src):
262    lines   = []
263    oldFile = open(src, 'r')
264    for line in oldFile.readlines():
265      # paths generated by configure could be different link-path than whats used by user, so fix both
266      line = line.replace(os.path.join(self.rootDir, self.arch), self.installDir)
267      line = line.replace(os.path.realpath(os.path.join(self.rootDir, self.arch)), self.installDir)
268      line = line.replace(os.path.join(self.rootDir, 'bin'), self.installBinDir)
269      line = line.replace(os.path.realpath(os.path.join(self.rootDir, 'bin')), self.installBinDir)
270      line = line.replace(os.path.join(self.rootDir, 'include'), self.installIncludeDir)
271      line = line.replace(os.path.realpath(os.path.join(self.rootDir, 'include')), self.installIncludeDir)
272      line = line.replace(self.rootDir, self.installDir)
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    self.fixConfFile(os.path.join(self.destLibDir,'pkgconfig','PETSc.pc'))
289    self.fixConfFile(os.path.join(self.destIncludeDir,'petscconf.h'))
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
299    f.write('copies = '+repr(self.copies).replace(self.destDir,self.installDir))
300    f.write('''
301for src, dst in copies:
302  if os.path.exists(dst):
303    os.remove(dst)
304''')
305    f.close()
306    os.chmod(uninstallscript,0744)
307    return
308
309  def installIncludes(self):
310    exclude = ['makefile']
311    if not hasattr(self.compilers.setCompilers, 'FC'):
312      exclude.append('finclude')
313    if not self.mpi.usingMPIUni:
314      exclude.append('mpiuni')
315    self.copies.extend(self.copytree(self.rootIncludeDir, self.destIncludeDir,exclude = exclude))
316    self.copies.extend(self.copytree(self.archIncludeDir, self.destIncludeDir))
317    return
318
319  def installConf(self):
320    self.copies.extend(self.copytree(self.rootConfDir, self.destConfDir, exclude = ['uncrustify.cfg','bfort-base.txt','bfort-petsc.txt','bfort-mpi.txt','test.log']))
321    self.copies.extend(self.copytree(self.archConfDir, self.destConfDir, exclude = ['sowing', 'configure.log.bkp','configure.log','make.log','gmake.log','test.log','error.log']))
322    return
323
324  def installBin(self):
325    exclude = ['bfort','bib2html','doc2lt','doctext','mapnames', 'pstogif','pstoxbm','tohtml']
326    self.copies.extend(self.copytree(self.archBinDir, self.destBinDir, exclude = exclude ))
327    exclude = ['maint']
328    if not self.mpi.usingMPIUni:
329      exclude.append('petsc-mpiexec.uni')
330    self.setCompilers.pushLanguage('C')
331    if not self.setCompilers.isWindows(self.setCompilers.getCompiler(),self.log):
332      exclude.append('win32fe')
333    self.setCompilers.popLanguage()
334    self.copies.extend(self.copytree(self.rootBinDir, self.destBinDir, exclude = exclude ))
335    return
336
337  def installShare(self):
338    self.copies.extend(self.copytree(self.rootShareDir, self.destShareDir))
339    examplesdir=os.path.join(self.destShareDir,'petsc','examples')
340    if os.path.exists(examplesdir):
341      shutil.rmtree(examplesdir)
342    os.mkdir(examplesdir)
343    os.mkdir(os.path.join(examplesdir,'src'))
344    self.copyExamples(self.rootSrcDir,examplesdir)
345    self.copyConfig(self.rootDir,examplesdir)
346    self.fixExamplesMakefile(os.path.join(examplesdir,'gmakefile.test'))
347    return
348
349  def copyLib(self, src, dst):
350    '''Run ranlib on the destination library if it is an archive. Also run install_name_tool on dylib on Mac'''
351    # Symlinks (assumed local) are recreated at dst
352    if os.path.islink(src):
353      linkto = os.readlink(src)
354      try:
355        os.remove(dst)            # In case it already exists
356      except OSError:
357        pass
358      os.symlink(linkto, dst)
359      return
360    # Do not install object files
361    if not os.path.splitext(src)[1] == '.o':
362      shutil.copy2(src, dst)
363    if os.path.splitext(dst)[1] == '.'+self.arLibSuffix:
364      self.executeShellCommand(self.ranlib+' '+dst)
365    if os.path.splitext(dst)[1] == '.dylib' and os.path.isfile('/usr/bin/install_name_tool'):
366      [output,err,flg] = self.executeShellCommand("otool -D "+src)
367      oldname = output[output.find("\n")+1:]
368      installName = oldname.replace(self.archDir, self.installDir)
369      self.executeShellCommand('/usr/bin/install_name_tool -id ' + installName + ' ' + dst)
370    # preserve the original timestamps - so that the .a vs .so time order is preserved
371    shutil.copystat(src,dst)
372    return
373
374  def installLib(self):
375    self.copies.extend(self.copytree(self.archLibDir, self.destLibDir, copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
376    self.copies.extend(self.copytree(os.path.join(self.archLibDir,'pkgconfig'), os.path.join(self.destLibDir,'pkgconfig'), copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
377    return
378
379
380  def outputInstallDone(self):
381    print '''\
382====================================
383Install complete.
384Now to check if the libraries are working do (in current directory):
385make PETSC_DIR=%s PETSC_ARCH="" test
386====================================\
387''' % (self.installDir)
388    return
389
390  def outputDestDirDone(self):
391    print '''\
392====================================
393Copy to DESTDIR %s is now complete.
394Before use - please copy/install over to specified prefix: %s
395====================================\
396''' % (self.destDir,self.installDir)
397    return
398
399  def runsetup(self):
400    self.setup()
401    self.setupDirectories()
402    self.checkPrefix()
403    self.checkDestdir()
404    return
405
406  def runcopy(self):
407    if self.destDir == self.installDir:
408      print '*** Installing PETSc at prefix location:',self.destDir, ' ***'
409    else:
410      print '*** Copying PETSc to DESTDIR location:',self.destDir, ' ***'
411    if not os.path.exists(self.destDir):
412      try:
413        os.makedirs(self.destDir)
414      except:
415        print '********************************************************************'
416        print 'Unable to create', self.destDir, 'Perhaps you need to do "sudo make install"'
417        print '********************************************************************'
418        sys.exit(1)
419    self.installIncludes()
420    self.installConf()
421    self.installBin()
422    self.installLib()
423    self.installShare()
424    return
425
426  def runfix(self):
427    self.fixConf()
428    return
429
430  def rundone(self):
431    self.createUninstaller()
432    if self.destDir == self.installDir:
433      self.outputInstallDone()
434    else:
435      self.outputDestDirDone()
436    return
437
438  def run(self):
439    self.runsetup()
440    self.runcopy()
441    self.runfix()
442    self.rundone()
443    return
444
445if __name__ == '__main__':
446  Installer(sys.argv[1:]).run()
447  # temporary hack - delete log files created by BuildSystem - when 'sudo make install' is invoked
448  delfiles=['RDict.db','RDict.log','buildsystem.log','default.log','buildsystem.log.bkp','default.log.bkp']
449  for delfile in delfiles:
450    if os.path.exists(delfile) and (os.stat(delfile).st_uid==0):
451      os.remove(delfile)
452