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