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