xref: /petsc/config/install.py (revision 65f8aed5f7eaa1e2ef2ddeffe666264e0669c876)
1#!/usr/bin/env python
2from __future__ import print_function
3import os, re, shutil, sys
4
5if 'PETSC_DIR' in os.environ:
6  PETSC_DIR = os.environ['PETSC_DIR']
7else:
8  fd = file(os.path.join('lib','petsc','conf','petscvariables'))
9  a = fd.readline()
10  a = fd.readline()
11  PETSC_DIR = a.split('=')[1][0:-1]
12  fd.close()
13
14if 'PETSC_ARCH' in os.environ:
15  PETSC_ARCH = os.environ['PETSC_ARCH']
16else:
17  fd = file(os.path.join('lib','petsc','conf','petscvariables'))
18  a = fd.readline()
19  PETSC_ARCH = a.split('=')[1][0:-1]
20  fd.close()
21
22print('*** Using PETSC_DIR='+PETSC_DIR+' PETSC_ARCH='+PETSC_ARCH+' ***')
23sys.path.insert(0, os.path.join(PETSC_DIR, 'config'))
24sys.path.insert(0, os.path.join(PETSC_DIR, 'config', 'BuildSystem'))
25
26import script
27
28try:
29  WindowsError
30except NameError:
31  WindowsError = None
32
33class Installer(script.Script):
34  def __init__(self, clArgs = None):
35    import RDict
36    argDB = RDict.RDict(None, None, 0, 0, readonly = True)
37    argDB.saveFilename = os.path.join(PETSC_DIR, PETSC_ARCH, 'lib','petsc','conf', 'RDict.db')
38    argDB.load()
39    script.Script.__init__(self, argDB = argDB)
40    if not clArgs is None: self.clArgs = clArgs
41    self.copies = []
42    return
43
44  def setupHelp(self, help):
45    import nargs
46    script.Script.setupHelp(self, help)
47    help.addArgument('Installer', '-destDir=<path>', nargs.Arg(None, '', 'Destination Directory for install'))
48    return
49
50
51  def setupModules(self):
52    self.setCompilers  = self.framework.require('config.setCompilers',         None)
53    self.arch          = self.framework.require('PETSc.options.arch',          None)
54    self.petscdir      = self.framework.require('PETSc.options.petscdir',      None)
55    self.compilers     = self.framework.require('config.compilers',            None)
56    self.mpi           = self.framework.require('config.packages.MPI',         None)
57    return
58
59  def setup(self):
60    script.Script.setup(self)
61    self.framework = self.loadConfigure()
62    self.setupModules()
63    return
64
65  def setupDirectories(self):
66    self.rootDir    = self.petscdir.dir
67    self.installDir = os.path.abspath(os.path.expanduser(self.framework.argDB['prefix']))
68    self.destDir    = os.path.abspath(self.argDB['destDir']+self.installDir)
69    self.arch       = self.arch.arch
70    self.archDir           = os.path.join(self.rootDir, self.arch)
71    self.rootIncludeDir    = os.path.join(self.rootDir, 'include')
72    self.archIncludeDir    = os.path.join(self.rootDir, self.arch, 'include')
73    self.rootConfDir       = os.path.join(self.rootDir, 'lib','petsc','conf')
74    self.archConfDir       = os.path.join(self.rootDir, self.arch, 'lib','petsc','conf')
75    self.rootBinDir        = os.path.join(self.rootDir, 'lib','petsc','bin')
76    self.archBinDir        = os.path.join(self.rootDir, self.arch, 'bin')
77    self.archLibDir        = os.path.join(self.rootDir, self.arch, 'lib')
78    self.destIncludeDir    = os.path.join(self.destDir, 'include')
79    self.destConfDir       = os.path.join(self.destDir, 'lib','petsc','conf')
80    self.destLibDir        = os.path.join(self.destDir, 'lib')
81    self.destBinDir        = os.path.join(self.destDir, 'lib','petsc','bin')
82    self.installIncludeDir = os.path.join(self.installDir, 'include')
83    self.installBinDir     = os.path.join(self.installDir, 'lib','petsc','bin')
84    self.rootShareDir      = os.path.join(self.rootDir, 'share')
85    self.destShareDir      = os.path.join(self.destDir, 'share')
86    self.rootSrcDir        = os.path.join(self.rootDir, 'src')
87
88    self.ranlib      = self.compilers.RANLIB
89    self.arLibSuffix = self.compilers.AR_LIB_SUFFIX
90    return
91
92  def checkPrefix(self):
93    if not self.installDir:
94      print('********************************************************************')
95      print('PETSc is built without prefix option. So "make install" is not appropriate.')
96      print('If you need a prefix install of PETSc - rerun configure with --prefix option.')
97      print('********************************************************************')
98      sys.exit(1)
99    return
100
101  def checkDestdir(self):
102    if os.path.exists(self.destDir):
103      if os.path.samefile(self.destDir, self.rootDir):
104        print('********************************************************************')
105        print('Incorrect prefix usage. Specified destDir same as current PETSC_DIR')
106        print('********************************************************************')
107        sys.exit(1)
108      if os.path.samefile(self.destDir, os.path.join(self.rootDir,self.arch)):
109        print('********************************************************************')
110        print('Incorrect prefix usage. Specified destDir same as current PETSC_DIR/PETSC_ARCH')
111        print('********************************************************************')
112        sys.exit(1)
113      if not os.path.isdir(os.path.realpath(self.destDir)):
114        print('********************************************************************')
115        print('Specified destDir', self.destDir, 'is not a directory. Cannot proceed!')
116        print('********************************************************************')
117        sys.exit(1)
118      if not os.access(self.destDir, os.W_OK):
119        print('********************************************************************')
120        print('Unable to write to ', self.destDir, 'Perhaps you need to do "sudo make install"')
121        print('********************************************************************')
122        sys.exit(1)
123    return
124
125  def copyfile(self, src, dst, symlinks = False, copyFunc = shutil.copy2):
126    """Copies a single file    """
127    copies = []
128    errors = []
129    if not os.path.exists(dst):
130      os.makedirs(dst)
131    elif not os.path.isdir(dst):
132      raise shutil.Error('Destination is not a directory')
133    srcname = src
134    dstname = os.path.join(dst, os.path.basename(src))
135    try:
136      if symlinks and os.path.islink(srcname):
137        linkto = os.readlink(srcname)
138        os.symlink(linkto, dstname)
139      else:
140        copyFunc(srcname, dstname)
141        copies.append((srcname, dstname))
142    except (IOError, os.error) as why:
143      errors.append((srcname, dstname, str(why)))
144    except shutil.Error as err:
145      errors.extend((srcname,dstname,str(err.args[0])))
146    if errors:
147      raise shutil.Error(errors)
148    return copies
149
150  def fixExamplesMakefile(self, src):
151    '''Change ././${PETSC_ARCH} in makefile in root petsc directory with ${PETSC_DIR}'''
152    lines   = []
153    oldFile = open(src, 'r')
154    alllines=oldFile.read()
155    oldFile.close()
156    newlines=alllines.split('\n')[0]+'\n'  # Firstline
157    # Hardcode PETSC_DIR and PETSC_ARCH to avoid users doing the worng thing
158    newlines+='PETSC_DIR='+self.installDir+'\n'
159    newlines+='PETSC_ARCH=\n'
160    for line in alllines.split('\n')[1:]:
161      if line.startswith('TESTLOGFILE'):
162        newlines+='TESTLOGFILE = $(TESTDIR)/examples-install.log\n'
163      elif line.startswith('CONFIGDIR'):
164        newlines+='CONFIGDIR:=$(PETSC_DIR)/$(PETSC_ARCH)/share/petsc/examples/config\n'
165      elif line.startswith('$(generatedtest)') and 'petscvariables' in line:
166        newlines+='all: test\n\n'+line+'\n'
167      else:
168        newlines+=line+'\n'
169    newFile = open(src, 'w')
170    newFile.write(newlines)
171    newFile.close()
172    return
173
174  def copyConfig(self, src, dst):
175    """Recursively copy the examples directories
176    """
177    if not os.path.isdir(dst):
178      raise shutil.Error('Destination is not a directory')
179
180    self.copies.extend(self.copyfile('gmakefile.test',dst))
181    newConfigDir=os.path.join(dst,'config')  # Am not renaming at present
182    if not os.path.isdir(newConfigDir): os.mkdir(newConfigDir)
183    testConfFiles="gmakegentest.py gmakegen.py testparse.py example_template.py".split()
184    testConfFiles+="petsc_harness.sh report_tests.py".split()
185    testConfFiles+=["cmakegen.py"]
186    for tf in testConfFiles:
187      self.copies.extend(self.copyfile(os.path.join('config',tf),newConfigDir))
188    return
189
190  def copyExamples(self, src, dst):
191    """copy the examples directories
192    """
193    for root, dirs, files in os.walk(src, topdown=False):
194      if not os.path.basename(root) == "examples": continue
195      self.copies.extend(self.copytree(root, root.replace(src,dst)))
196    return
197
198  def copytree(self, src, dst, symlinks = False, copyFunc = shutil.copy2, exclude = [], exclude_ext= ['.DSYM','.o','.pyc'], recurse = 1):
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) and recurse and not os.path.basename(srcname) in exclude:
226          copies.extend(self.copytree(srcname, dstname, symlinks,exclude = exclude, exclude_ext = exclude_ext))
227        elif os.path.isfile(srcname) and not os.path.basename(srcname) in exclude and os.path.splitext(name)[1] not in exclude_ext :
228          copyFunc(srcname, dstname)
229          copies.append((srcname, dstname))
230        # XXX What about devices, sockets etc.?
231      except (IOError, os.error) as 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 as err:
236        errors.extend((srcname,dstname,str(err.args[0])))
237    try:
238      shutil.copystat(src, dst)
239    except OSError as 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      if line.startswith('PETSC_CC_INCLUDES =') or line.startswith('PETSC_FC_INCLUDES ='):
255        continue
256      line = line.replace('PETSC_CC_INCLUDES_INSTALL', 'PETSC_CC_INCLUDES')
257      line = line.replace('PETSC_FC_INCLUDES_INSTALL', 'PETSC_FC_INCLUDES')
258      # remove PETSC_DIR/PETSC_ARCH variables from conf-makefiles. They are no longer necessary
259      line = line.replace('${PETSC_DIR}/${PETSC_ARCH}', self.installDir)
260      line = line.replace('PETSC_ARCH=${PETSC_ARCH}', '')
261      line = line.replace('${PETSC_DIR}', self.installDir)
262      lines.append(line)
263    oldFile.close()
264    newFile = open(src, 'w')
265    newFile.write(''.join(lines))
266    newFile.close()
267    return
268
269  def fixConf(self):
270    import shutil
271    for file in ['rules', 'variables','petscrules', 'petscvariables']:
272      self.fixConfFile(os.path.join(self.destConfDir,file))
273    return
274
275  def createUninstaller(self):
276    uninstallscript = os.path.join(self.destConfDir, 'uninstall.py')
277    f = open(uninstallscript, 'w')
278    # Could use the Python AST to do this
279    f.write('#!'+sys.executable+'\n')
280    f.write('import os\n')
281    f.write('prefixdir = "'+self.installDir+'"\n')
282    files = [dst.replace(self.destDir,self.installDir) for src, dst in self.copies]
283    files.append(uninstallscript.replace(self.destDir,self.installDir))
284    f.write('files = '+repr(files))
285    f.write('''
286for file in files:
287  if os.path.exists(file) or os.path.islink(file):
288    os.remove(file)
289    dir = os.path.dirname(file)
290    while dir not in [os.path.dirname(prefixdir),'/']:
291      try: os.rmdir(dir)
292      except: break
293      dir = os.path.dirname(dir)
294''')
295    f.close()
296    os.chmod(uninstallscript,0o744)
297    return
298
299  def installIncludes(self):
300    exclude = ['makefile']
301    if not hasattr(self.compilers.setCompilers, 'FC'):
302      exclude.append('finclude')
303    if not self.mpi.usingMPIUni:
304      exclude.append('mpiuni')
305    self.copies.extend(self.copytree(self.rootIncludeDir, self.destIncludeDir,exclude = exclude))
306    self.copies.extend(self.copytree(self.archIncludeDir, self.destIncludeDir))
307    return
308
309  def installConf(self):
310    self.copies.extend(self.copytree(self.rootConfDir, self.destConfDir, exclude = ['uncrustify.cfg','bfort-base.txt','bfort-petsc.txt','bfort-mpi.txt','test.log']))
311    self.copies.extend(self.copytree(self.archConfDir, self.destConfDir, exclude = ['sowing', 'configure.log.bkp','configure.log','make.log','gmake.log','test.log','error.log','files','testfiles','RDict.db']))
312    return
313
314  def installBin(self):
315    exclude = ['bfort','bib2html','doc2lt','doctext','mapnames', 'pstogif','pstoxbm','tohtml']
316    self.copies.extend(self.copytree(self.archBinDir, self.destBinDir, exclude = exclude ))
317    exclude = ['maint']
318    if not self.mpi.usingMPIUni:
319      exclude.append('petsc-mpiexec.uni')
320    self.setCompilers.pushLanguage('C')
321    if not self.setCompilers.isWindows(self.setCompilers.getCompiler(),self.log):
322      exclude.append('win32fe')
323    self.setCompilers.popLanguage()
324    self.copies.extend(self.copytree(self.rootBinDir, self.destBinDir, exclude = exclude ))
325    return
326
327  def installShare(self):
328    self.copies.extend(self.copytree(self.rootShareDir, self.destShareDir))
329    examplesdir=os.path.join(self.destShareDir,'petsc','examples')
330    if os.path.exists(examplesdir):
331      shutil.rmtree(examplesdir)
332    os.mkdir(examplesdir)
333    os.mkdir(os.path.join(examplesdir,'src'))
334    self.copyExamples(self.rootSrcDir,os.path.join(examplesdir,'src'))
335    self.copyConfig(self.rootDir,examplesdir)
336    self.fixExamplesMakefile(os.path.join(examplesdir,'gmakefile.test'))
337    return
338
339  def copyLib(self, src, dst):
340    '''Run ranlib on the destination library if it is an archive. Also run install_name_tool on dylib on Mac'''
341    # Symlinks (assumed local) are recreated at dst
342    if os.path.islink(src):
343      linkto = os.readlink(src)
344      try:
345        os.remove(dst)            # In case it already exists
346      except OSError:
347        pass
348      os.symlink(linkto, dst)
349      return
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'],recurse = 0))
364    self.copies.extend(self.copytree(os.path.join(self.archLibDir,'pkgconfig'), os.path.join(self.destLibDir,'pkgconfig'), copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
365    return
366
367
368  def outputInstallDone(self):
369    print('''\
370====================================
371Install complete.
372Now to check if the libraries are working do (in current directory):
373make PETSC_DIR=%s PETSC_ARCH="" test
374====================================\
375''' % (self.installDir))
376    return
377
378  def outputDestDirDone(self):
379    print('''\
380====================================
381Copy to DESTDIR %s is now complete.
382Before use - please copy/install over to specified prefix: %s
383====================================\
384''' % (self.destDir,self.installDir))
385    return
386
387  def runsetup(self):
388    self.setup()
389    self.setupDirectories()
390    self.checkPrefix()
391    self.checkDestdir()
392    return
393
394  def runcopy(self):
395    if self.destDir == self.installDir:
396      print('*** Installing PETSc at prefix location:',self.destDir, ' ***')
397    else:
398      print('*** Copying PETSc to DESTDIR location:',self.destDir, ' ***')
399    if not os.path.exists(self.destDir):
400      try:
401        os.makedirs(self.destDir)
402      except:
403        print('********************************************************************')
404        print('Unable to create', self.destDir, 'Perhaps you need to do "sudo make install"')
405        print('********************************************************************')
406        sys.exit(1)
407    self.installIncludes()
408    self.installConf()
409    self.installBin()
410    self.installLib()
411    self.installShare()
412    return
413
414  def runfix(self):
415    self.fixConf()
416    return
417
418  def rundone(self):
419    self.createUninstaller()
420    if self.destDir == self.installDir:
421      self.outputInstallDone()
422    else:
423      self.outputDestDirDone()
424    return
425
426  def run(self):
427    self.runsetup()
428    self.runcopy()
429    self.runfix()
430    self.rundone()
431    return
432
433if __name__ == '__main__':
434  Installer(sys.argv[1:]).run()
435  # temporary hack - delete log files created by BuildSystem - when 'sudo make install' is invoked
436  delfiles=['RDict.db','RDict.log','buildsystem.log','default.log','buildsystem.log.bkp','default.log.bkp']
437  for delfile in delfiles:
438    if os.path.exists(delfile) and (os.stat(delfile).st_uid==0):
439      os.remove(delfile)
440