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