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