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