xref: /petsc/config/install.py (revision 1590dd87ee05ea2a37c0ff7af761f7ebcb1874e6)
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    os.mkdir(os.path.join(self.destShareDir,'petsc','examples'))
333    self.copyExamples(self.rootDir,os.path.join(self.destShareDir,'petsc','examples'))
334    self.fixExamplesMakefile(os.path.join(self.destShareDir,'petsc','examples','makefile'))
335    return
336
337  def copyLib(self, src, dst):
338    '''Run ranlib on the destination library if it is an archive. Also run install_name_tool on dylib on Mac'''
339    # Symlinks (assumed local) are recreated at dst
340    if os.path.islink(src):
341      linkto = os.readlink(src)
342      try:
343        os.remove(dst)            # In case it already exists
344      except OSError:
345        pass
346      os.symlink(linkto, dst)
347      return
348    # Do not install object files
349    if not os.path.splitext(src)[1] == '.o':
350      shutil.copy2(src, dst)
351    if os.path.splitext(dst)[1] == '.'+self.arLibSuffix:
352      self.executeShellCommand(self.ranlib+' '+dst)
353    if os.path.splitext(dst)[1] == '.dylib' and os.path.isfile('/usr/bin/install_name_tool'):
354      [output,err,flg] = self.executeShellCommand("otool -D "+src)
355      oldname = output[output.find("\n")+1:]
356      installName = oldname.replace(self.archDir, self.installDir)
357      self.executeShellCommand('/usr/bin/install_name_tool -id ' + installName + ' ' + dst)
358    # preserve the original timestamps - so that the .a vs .so time order is preserved
359    shutil.copystat(src,dst)
360    return
361
362  def installLib(self):
363    self.copies.extend(self.copytree(self.archLibDir, self.destLibDir, copyFunc = self.copyLib, exclude = ['.DIR', 'sowing']))
364    return
365
366
367  def outputInstallDone(self):
368    print '''\
369====================================
370Install complete.
371Now to check if the libraries are working do (in current directory):
372make PETSC_DIR=%s PETSC_ARCH="" test
373====================================\
374''' % (self.installDir)
375    return
376
377  def outputDestDirDone(self):
378    print '''\
379====================================
380Copy to DESTDIR %s is now complete.
381Before use - please copy/install over to specified prefix: %s
382====================================\
383''' % (self.destDir,self.installDir)
384    return
385
386  def runsetup(self):
387    self.setup()
388    self.setupDirectories()
389    self.checkPrefix()
390    self.checkDestdir()
391    return
392
393  def runcopy(self):
394    if self.destDir == self.installDir:
395      print '*** Installing PETSc at prefix location:',self.destDir, ' ***'
396    else:
397      print '*** Copying PETSc to DESTDIR location:',self.destDir, ' ***'
398    if not os.path.exists(self.destDir):
399      try:
400        os.makedirs(self.destDir)
401      except:
402        print '********************************************************************'
403        print 'Unable to create', self.destDir, 'Perhaps you need to do "sudo make install"'
404        print '********************************************************************'
405        sys.exit(1)
406    self.installIncludes()
407    self.installConf()
408    self.installBin()
409    self.installLib()
410    self.installShare()
411    return
412
413  def runfix(self):
414    self.fixConf()
415    return
416
417  def rundone(self):
418    self.createUninstaller()
419    if self.destDir == self.installDir:
420      self.outputInstallDone()
421    else:
422      self.outputDestDirDone()
423    return
424
425  def run(self):
426    self.runsetup()
427    self.runcopy()
428    self.runfix()
429    self.rundone()
430    return
431
432if __name__ == '__main__':
433  Installer(sys.argv[1:]).run()
434  # temporary hack - delete log files created by BuildSystem - when 'sudo make install' is invoked
435  delfiles=['RDict.db','RDict.log','build.log','default.log','build.log.bkp','default.log.bkp']
436  for delfile in delfiles:
437    if os.path.exists(delfile) and (os.stat(delfile).st_uid==0):
438      os.remove(delfile)
439