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