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