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