xref: /petsc/config/install.py (revision e551db170525c6950e3ad11edcf7f326fbf4d6ae)
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    return
56
57  def setup(self):
58    script.Script.setup(self)
59    self.framework = self.loadConfigure()
60    self.setupModules()
61    return
62
63  def setupDirectories(self):
64    self.rootDir    = self.petscdir.dir
65    self.destDir    = os.path.abspath(self.argDB['destDir'])
66    self.installDir = os.path.abspath(os.path.expanduser(self.framework.argDB['prefix']))
67    self.arch       = self.arch.arch
68    self.archDir           = os.path.join(self.rootDir, self.arch)
69    self.rootIncludeDir    = os.path.join(self.rootDir, 'include')
70    self.archIncludeDir    = os.path.join(self.rootDir, self.arch, 'include')
71    self.rootConfDir       = os.path.join(self.rootDir, 'lib','petsc','conf')
72    self.archConfDir       = os.path.join(self.rootDir, self.arch, 'lib','petsc','conf')
73    self.rootBinDir        = os.path.join(self.rootDir, 'bin')
74    self.archBinDir        = os.path.join(self.rootDir, self.arch, 'bin')
75    self.archLibDir        = os.path.join(self.rootDir, self.arch, 'lib')
76    self.destIncludeDir    = os.path.join(self.destDir, 'include')
77    self.destConfDir       = os.path.join(self.destDir, 'lib','petsc','conf')
78    self.destLibDir        = os.path.join(self.destDir, 'lib')
79    self.destBinDir        = os.path.join(self.destDir, 'bin')
80    self.installIncludeDir = os.path.join(self.installDir, 'include')
81    self.installBinDir     = os.path.join(self.installDir, 'bin')
82    self.rootShareDir      = os.path.join(self.rootDir, 'share')
83    self.destShareDir      = os.path.join(self.destDir, 'share')
84
85    self.ranlib      = self.compilers.RANLIB
86    self.arLibSuffix = self.compilers.AR_LIB_SUFFIX
87    return
88
89  def checkPrefix(self):
90    if not self.installDir:
91      print '********************************************************************'
92      print 'PETSc is built without prefix option. So "make install" is not appropriate.'
93      print 'If you need a prefix install of PETSc - rerun configure with --prefix option.'
94      print '********************************************************************'
95      sys.exit(1)
96    return
97
98  def checkDestdir(self):
99    if os.path.exists(self.destDir):
100      if os.path.samefile(self.destDir, self.rootDir):
101        print '********************************************************************'
102        print 'Incorrect prefix usage. Specified destDir same as current PETSC_DIR'
103        print '********************************************************************'
104        sys.exit(1)
105      if os.path.samefile(self.destDir, os.path.join(self.rootDir,self.arch)):
106        print '********************************************************************'
107        print 'Incorrect prefix usage. Specified destDir same as current PETSC_DIR/PETSC_ARCH'
108        print '********************************************************************'
109        sys.exit(1)
110      if not os.path.isdir(os.path.realpath(self.destDir)):
111        print '********************************************************************'
112        print 'Specified destDir', self.destDir, 'is not a directory. Cannot proceed!'
113        print '********************************************************************'
114        sys.exit(1)
115      if not os.access(self.destDir, os.W_OK):
116        print '********************************************************************'
117        print 'Unable to write to ', self.destDir, 'Perhaps you need to do "sudo make install"'
118        print '********************************************************************'
119        sys.exit(1)
120    return
121
122  def copyfile(self, src, dst, symlinks = False, copyFunc = shutil.copy2):
123    """Copies a single file    """
124    copies = []
125    errors = []
126    if not os.path.exists(dst):
127      os.makedirs(dst)
128    elif not os.path.isdir(dst):
129      raise shutil.Error, 'Destination is not a directory'
130    srcname = src
131    dstname = os.path.join(dst, os.path.basename(src))
132    try:
133      if symlinks and os.path.islink(srcname):
134        linkto = os.readlink(srcname)
135        os.symlink(linkto, dstname)
136      else:
137        copyFunc(srcname, dstname)
138        copies.append((srcname, dstname))
139    except (IOError, os.error), why:
140      errors.append((srcname, dstname, str(why)))
141    except shutil.Error, err:
142      errors.extend((srcname,dstname,str(err.args[0])))
143    if errors:
144      raise shutil.Error, errors
145    return copies
146
147  def copyexamplefiles(self, src, dst, copyFunc = shutil.copy2):
148    """Copies all files, but not directories in a single file    """
149    names  = os.listdir(src)
150    for name in names:
151      if not name.endswith('.html'):
152        srcname = os.path.join(src, name)
153        if os.path.isfile(srcname):
154           self.copyfile(srcname,dst)
155
156  def fixExamplesMakefile(self, src):
157    '''Change ././${PETSC_ARCH} in makefile in root petsc directory with ${PETSC_DIR}'''
158    lines   = []
159    oldFile = open(src, 'r')
160    alllines=oldFile.read()
161    oldFile.close()
162    newlines=alllines.split('\n')[0]+'\n'  # Firstline
163    # Hardcode PETSC_DIR and PETSC_ARCH to avoid users doing the worng thing
164    newlines+='PETSC_DIR='+self.installDir+'\n'
165    newlines+='PETSC_ARCH=\n'
166    for line in alllines.split('\n')[1:]:
167      if line.startswith('#'):
168        newlines+=line+'\n'
169      elif line.startswith('CONFIGDIR'):
170          newlines+='CONFIGDIR:=$(PETSC_DIR)/$(PETSC_ARCH)/share/petsc/examples/config\n'
171          newlines+='EXAMPLESDIR:=$(PETSC_DIR)/$(PETSC_ARCH)/share/petsc/examples\n'
172      elif line.startswith('$(TESTDIR)/'):
173          newlines+=re.sub(' %.',' $(EXAMPLESDIR)/%.',line)+'\n'
174      elif line.startswith('include ./lib/petsc/conf/variables'):
175          newlines+=re.sub('include $(PETSC_DIR)/$(PETSC_ARCH)/lib/petsc/conf/variables')
176      else:
177        newlines+=re.sub('PETSC_ARCH','PETSC_DIR)/$(PETSC_ARCH',line)+'\n'
178    newFile = open(src, 'w')
179    newFile.write(newlines)
180    newFile.close()
181    return
182
183  def copyConfig(self, src, dst):
184    """Recursively copy the examples directories
185    """
186    if not os.path.isdir(dst):
187      raise shutil.Error, 'Destination is not a directory'
188
189    self.copyfile('gmakefile.examples',dst)
190    newConfigDir=os.path.join(dst,'config')  # Am not renaming at present
191    if not os.path.isdir(newConfigDir): os.mkdir(newConfigDir)
192    testConfFiles="gmakegentest.py gmakegen.py testparse.py example_template.py".split()
193    #testConfFiles=+"petsc_harness.sh report_tests.py watchtime.sh"
194    testConfFiles+="petsc_harness.sh report_tests.py cmakegen.py".split()
195    for tf in testConfFiles:
196      self.copyfile(os.path.join('config',tf),newConfigDir)
197    return
198
199  def copyExamples(self, src, dst):
200    """Recursively copy the examples directories
201    """
202    if not os.path.isdir(dst):
203      raise shutil.Error, 'Destination is not a directory'
204
205    names  = os.listdir(src)
206    nret2 = 0
207    for name in names:
208      srcname = os.path.join(src, name)
209      dstname = os.path.join(dst, name)
210      if not name.startswith('arch') and os.path.isdir(srcname) and os.path.isfile(os.path.join(srcname,'makefile')):
211        os.mkdir(dstname)
212        nret = self.copyExamples(srcname,dstname)
213        if name == 'tests' or name == 'tutorials':
214          self.copyexamplefiles(srcname,dstname)
215          if os.path.isdir(os.path.join(srcname,'output')):
216            os.mkdir(os.path.join(dstname,'output'))
217            self.copyexamplefiles(os.path.join(srcname,'output'),os.path.join(dstname,'output'))
218          nret = 1
219        if not nret:
220          # prune directory branches that don't have examples under them
221          os.rmdir(dstname)
222        nret2 = nret + nret2
223    return nret2
224
225  def copytree(self, src, dst, symlinks = False, copyFunc = shutil.copy2, exclude = []):
226    """Recursively copy a directory tree using copyFunc, which defaults to shutil.copy2().
227
228       The copyFunc() you provide is only used on the top level, lower levels always use shutil.copy2
229
230    The destination directory must not already exist.
231    If exception(s) occur, an shutil.Error is raised with a list of reasons.
232
233    If the optional symlinks flag is true, symbolic links in the
234    source tree result in symbolic links in the destination tree; if
235    it is false, the contents of the files pointed to by symbolic
236    links are copied.
237    """
238    copies = []
239    names  = os.listdir(src)
240    if not os.path.exists(dst):
241      os.makedirs(dst)
242    elif not os.path.isdir(dst):
243      raise shutil.Error, 'Destination is not a directory'
244    errors = []
245    for name in names:
246      srcname = os.path.join(src, name)
247      dstname = os.path.join(dst, name)
248      try:
249        if symlinks and os.path.islink(srcname):
250          linkto = os.readlink(srcname)
251          os.symlink(linkto, dstname)
252        elif os.path.isdir(srcname):
253          copies.extend(self.copytree(srcname, dstname, symlinks,exclude = exclude))
254        elif not os.path.basename(srcname) in exclude:
255          copyFunc(srcname, dstname)
256          copies.append((srcname, dstname))
257        # XXX What about devices, sockets etc.?
258      except (IOError, os.error), why:
259        errors.append((srcname, dstname, str(why)))
260      # catch the Error from the recursive copytree so that we can
261      # continue with other files
262      except shutil.Error, err:
263        errors.extend((srcname,dstname,str(err.args[0])))
264    try:
265      shutil.copystat(src, dst)
266    except OSError, e:
267      if WindowsError is not None and isinstance(e, WindowsError):
268        # Copying file access times may fail on Windows
269        pass
270      else:
271        errors.extend((src, dst, str(e)))
272    if errors:
273      raise shutil.Error, errors
274    return copies
275
276
277  def fixConfFile(self, src):
278    lines   = []
279    oldFile = open(src, 'r')
280    for line in oldFile.readlines():
281      # paths generated by configure could be different link-path than whats used by user, so fix both
282      line = line.replace(os.path.join(self.rootDir, self.arch), self.installDir)
283      line = line.replace(os.path.realpath(os.path.join(self.rootDir, self.arch)), self.installDir)
284      line = line.replace(os.path.join(self.rootDir, 'bin'), self.installBinDir)
285      line = line.replace(os.path.realpath(os.path.join(self.rootDir, 'bin')), self.installBinDir)
286      line = line.replace(os.path.join(self.rootDir, 'include'), self.installIncludeDir)
287      line = line.replace(os.path.realpath(os.path.join(self.rootDir, 'include')), self.installIncludeDir)
288      # remove PETSC_DIR/PETSC_ARCH variables from conf-makefiles. They are no longer necessary
289      line = line.replace('${PETSC_DIR}/${PETSC_ARCH}', self.installDir)
290      line = line.replace('PETSC_ARCH=${PETSC_ARCH}', '')
291      line = line.replace('${PETSC_DIR}', self.installDir)
292      lines.append(line)
293    oldFile.close()
294    newFile = open(src, 'w')
295    newFile.write(''.join(lines))
296    newFile.close()
297    return
298
299  def fixConf(self):
300    import shutil
301    for file in ['rules', 'variables','petscrules', 'petscvariables']:
302      self.fixConfFile(os.path.join(self.destConfDir,file))
303    self.fixConfFile(os.path.join(self.destLibDir,'pkgconfig','PETSc.pc'))
304    return
305
306  def createUninstaller(self):
307    uninstallscript = os.path.join(self.destConfDir, 'uninstall.py')
308    f = open(uninstallscript, 'w')
309    # Could use the Python AST to do this
310    f.write('#!'+sys.executable+'\n')
311    f.write('import os\n')
312
313    f.write('copies = '+repr(self.copies).replace(self.destDir,self.installDir))
314    f.write('''
315for src, dst in copies:
316  try:
317    os.remove(dst)
318  except:
319    pass
320''')
321    #TODO: need to delete libXXX.YYY.dylib.dSYM directory on Mac
322    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')]
323    newdirs = []
324    for dir in dirs: newdirs.append(os.path.join(self.installDir,dir))
325    f.write('dirs = '+str(newdirs))
326    f.write('''
327for dir in dirs:
328  import shutil
329  try:
330    shutil.rmtree(dir)
331  except:
332    pass
333''')
334    f.close()
335    os.chmod(uninstallscript,0744)
336    return
337
338  def installIncludes(self):
339    # TODO: should exclude petsc-mpi.uni except for uni builds
340    # TODO: should exclude petsc/finclude except for fortran builds
341    self.copies.extend(self.copytree(self.rootIncludeDir, self.destIncludeDir,exclude = ['makefile']))
342    self.copies.extend(self.copytree(self.archIncludeDir, self.destIncludeDir))
343    return
344
345  def installConf(self):
346    self.copies.extend(self.copytree(self.rootConfDir, self.destConfDir))
347    self.copies.extend(self.copytree(self.archConfDir, self.destConfDir, exclude = ['sowing', 'configure.log.bkp']))
348    return
349
350  def installBin(self):
351    self.copies.extend(self.copytree(os.path.join(self.rootBinDir), os.path.join(self.destBinDir)))
352    # TODO: should copy over petsc-mpiexec.uni only for uni builds
353    self.copies.extend(self.copyfile(os.path.join(self.rootBinDir,'petsc-mpiexec.uni'), self.destBinDir))
354    self.copies.extend(self.copytree(self.archBinDir, self.destBinDir, exclude = ['bfort','bib2html','doc2lt','doctext','mapnames', 'pstogif','pstoxbm','tohtml']))
355    return
356
357  def installShare(self):
358    self.copies.extend(self.copytree(self.rootShareDir, self.destShareDir))
359    if os.path.exists(os.path.join(self.destShareDir,'petsc','examples')):
360      shutil.rmtree(os.path.join(self.destShareDir,'petsc','examples'))
361    os.mkdir(os.path.join(self.destShareDir,'petsc','examples'))
362    self.copyExamples(self.rootDir,os.path.join(self.destShareDir,'petsc','examples'))
363    self.copyConfig(self.rootDir,os.path.join(self.destShareDir,'petsc','examples'))
364    self.fixExamplesMakefile(os.path.join(self.destShareDir,'petsc','examples','gmakefile.examples'))
365    return
366
367  def copyLib(self, src, dst):
368    '''Run ranlib on the destination library if it is an archive. Also run install_name_tool on dylib on Mac'''
369    # Symlinks (assumed local) are recreated at dst
370    if os.path.islink(src):
371      linkto = os.readlink(src)
372      try:
373        os.remove(dst)            # In case it already exists
374      except OSError:
375        pass
376      os.symlink(linkto, dst)
377      return
378    # Do not install object files
379    if not os.path.splitext(src)[1] == '.o':
380      shutil.copy2(src, dst)
381    if os.path.splitext(dst)[1] == '.'+self.arLibSuffix:
382      self.executeShellCommand(self.ranlib+' '+dst)
383    if os.path.splitext(dst)[1] == '.dylib' and os.path.isfile('/usr/bin/install_name_tool'):
384      [output,err,flg] = self.executeShellCommand("otool -D "+src)
385      oldname = output[output.find("\n")+1:]
386      installName = oldname.replace(self.archDir, self.installDir)
387      self.executeShellCommand('/usr/bin/install_name_tool -id ' + installName + ' ' + dst)
388    # preserve the original timestamps - so that the .a vs .so time order is preserved
389    shutil.copystat(src,dst)
390    return
391
392  def installLib(self):
393    self.copies.extend(self.copytree(self.archLibDir, self.destLibDir, copyFunc = self.copyLib, exclude = ['.DIR', 'sowing']))
394    return
395
396
397  def outputInstallDone(self):
398    print '''\
399====================================
400Install complete.
401Now to check if the libraries are working do (in current directory):
402make PETSC_DIR=%s PETSC_ARCH="" test
403====================================\
404''' % (self.installDir)
405    return
406
407  def outputDestDirDone(self):
408    print '''\
409====================================
410Copy to DESTDIR %s is now complete.
411Before use - please copy/install over to specified prefix: %s
412====================================\
413''' % (self.destDir,self.installDir)
414    return
415
416  def runsetup(self):
417    self.setup()
418    self.setupDirectories()
419    self.checkPrefix()
420    self.checkDestdir()
421    return
422
423  def runcopy(self):
424    if self.destDir == self.installDir:
425      print '*** Installing PETSc at prefix location:',self.destDir, ' ***'
426    else:
427      print '*** Copying PETSc to DESTDIR location:',self.destDir, ' ***'
428    if not os.path.exists(self.destDir):
429      try:
430        os.makedirs(self.destDir)
431      except:
432        print '********************************************************************'
433        print 'Unable to create', self.destDir, 'Perhaps you need to do "sudo make install"'
434        print '********************************************************************'
435        sys.exit(1)
436    self.installIncludes()
437    self.installConf()
438    self.installBin()
439    self.installLib()
440    self.installShare()
441    return
442
443  def runfix(self):
444    self.fixConf()
445    return
446
447  def rundone(self):
448    self.createUninstaller()
449    if self.destDir == self.installDir:
450      self.outputInstallDone()
451    else:
452      self.outputDestDirDone()
453    return
454
455  def run(self):
456    self.runsetup()
457    self.runcopy()
458    self.runfix()
459    self.rundone()
460    return
461
462if __name__ == '__main__':
463  Installer(sys.argv[1:]).run()
464  # temporary hack - delete log files created by BuildSystem - when 'sudo make install' is invoked
465  delfiles=['RDict.db','RDict.log','buildsystem.log','default.log','buildsystem.log.bkp','default.log.bkp']
466  for delfile in delfiles:
467    if os.path.exists(delfile) and (os.stat(delfile).st_uid==0):
468      os.remove(delfile)
469