xref: /petsc/config/install.py (revision b41ce5d507ea9a58bfa83cf403107a702e77a67d)
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  try:
290    os.remove(dst)
291  except:
292    pass
293''')
294    #TODO: need to delete libXXX.YYY.dylib.dSYM directory on Mac
295    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')]
296    newdirs = []
297    for dir in dirs: newdirs.append(os.path.join(self.installDir,dir))
298    f.write('dirs = '+str(newdirs))
299    f.write('''
300for dir in dirs:
301  import shutil
302  try:
303    shutil.rmtree(dir)
304  except:
305    pass
306''')
307    f.close()
308    os.chmod(uninstallscript,0744)
309    return
310
311  def installIncludes(self):
312    # TODO: should exclude petsc-mpi.uni except for uni builds
313    # TODO: should exclude petsc/finclude except for fortran builds
314    self.copies.extend(self.copytree(self.rootIncludeDir, self.destIncludeDir,exclude = ['makefile']))
315    self.copies.extend(self.copytree(self.archIncludeDir, self.destIncludeDir))
316    return
317
318  def installConf(self):
319    self.copies.extend(self.copytree(self.rootConfDir, self.destConfDir))
320    self.copies.extend(self.copytree(self.archConfDir, self.destConfDir, exclude = ['sowing', 'configure.log.bkp']))
321    return
322
323  def installBin(self):
324    self.copies.extend(self.copytree(os.path.join(self.rootBinDir), os.path.join(self.destBinDir)))
325    # TODO: should copy over petsc-mpiexec.uni only for uni builds
326    self.copies.extend(self.copyfile(os.path.join(self.rootBinDir,'petsc-mpiexec.uni'), self.destBinDir))
327    self.copies.extend(self.copytree(self.archBinDir, self.destBinDir, exclude = ['bfort','bib2html','doc2lt','doctext','mapnames', 'pstogif','pstoxbm','tohtml']))
328    return
329
330  def installShare(self):
331    self.copies.extend(self.copytree(self.rootShareDir, self.destShareDir))
332    if os.path.exists(os.path.join(self.destShareDir,'petsc','examples')):
333      shutil.rmtree(os.path.join(self.destShareDir,'petsc','examples'))
334    os.mkdir(os.path.join(self.destShareDir,'petsc','examples'))
335    self.copyExamples(self.rootDir,os.path.join(self.destShareDir,'petsc','examples'))
336    self.fixExamplesMakefile(os.path.join(self.destShareDir,'petsc','examples','makefile'))
337    return
338
339  def copyLib(self, src, dst):
340    '''Run ranlib on the destination library if it is an archive. Also run install_name_tool on dylib on Mac'''
341    # Symlinks (assumed local) are recreated at dst
342    if os.path.islink(src):
343      linkto = os.readlink(src)
344      try:
345        os.remove(dst)            # In case it already exists
346      except OSError:
347        pass
348      os.symlink(linkto, dst)
349      return
350    # Do not install object files
351    if not os.path.splitext(src)[1] == '.o':
352      shutil.copy2(src, dst)
353    if os.path.splitext(dst)[1] == '.'+self.arLibSuffix:
354      self.executeShellCommand(self.ranlib+' '+dst)
355    if os.path.splitext(dst)[1] == '.dylib' and os.path.isfile('/usr/bin/install_name_tool'):
356      [output,err,flg] = self.executeShellCommand("otool -D "+src)
357      oldname = output[output.find("\n")+1:]
358      installName = oldname.replace(self.archDir, self.installDir)
359      self.executeShellCommand('/usr/bin/install_name_tool -id ' + installName + ' ' + dst)
360    # preserve the original timestamps - so that the .a vs .so time order is preserved
361    shutil.copystat(src,dst)
362    return
363
364  def installLib(self):
365    self.copies.extend(self.copytree(self.archLibDir, self.destLibDir, copyFunc = self.copyLib, exclude = ['.DIR', 'sowing']))
366    return
367
368
369  def outputInstallDone(self):
370    print '''\
371====================================
372Install complete.
373Now to check if the libraries are working do (in current directory):
374make PETSC_DIR=%s PETSC_ARCH="" test
375====================================\
376''' % (self.installDir)
377    return
378
379  def outputDestDirDone(self):
380    print '''\
381====================================
382Copy to DESTDIR %s is now complete.
383Before use - please copy/install over to specified prefix: %s
384====================================\
385''' % (self.destDir,self.installDir)
386    return
387
388  def runsetup(self):
389    self.setup()
390    self.setupDirectories()
391    self.checkPrefix()
392    self.checkDestdir()
393    return
394
395  def runcopy(self):
396    if self.destDir == self.installDir:
397      print '*** Installing PETSc at prefix location:',self.destDir, ' ***'
398    else:
399      print '*** Copying PETSc to DESTDIR location:',self.destDir, ' ***'
400    if not os.path.exists(self.destDir):
401      try:
402        os.makedirs(self.destDir)
403      except:
404        print '********************************************************************'
405        print 'Unable to create', self.destDir, 'Perhaps you need to do "sudo make install"'
406        print '********************************************************************'
407        sys.exit(1)
408    self.installIncludes()
409    self.installConf()
410    self.installBin()
411    self.installLib()
412    self.installShare()
413    return
414
415  def runfix(self):
416    self.fixConf()
417    return
418
419  def rundone(self):
420    self.createUninstaller()
421    if self.destDir == self.installDir:
422      self.outputInstallDone()
423    else:
424      self.outputDestDirDone()
425    return
426
427  def run(self):
428    self.runsetup()
429    self.runcopy()
430    self.runfix()
431    self.rundone()
432    return
433
434if __name__ == '__main__':
435  Installer(sys.argv[1:]).run()
436  # temporary hack - delete log files created by BuildSystem - when 'sudo make install' is invoked
437  delfiles=['RDict.db','RDict.log','buildsystem.log','default.log','buildsystem.log.bkp','default.log.bkp']
438  for delfile in delfiles:
439    if os.path.exists(delfile) and (os.stat(delfile).st_uid==0):
440      os.remove(delfile)
441