xref: /petsc/config/install.py (revision fbf9dbe564678ed6eff1806adbc4c4f01b9743f4)
1#!/usr/bin/env python3
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 = open(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 = open(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    help.addArgument('Installer', '-no-examples', nargs.Arg(None, '', 'Skip installing examples'))
49    return
50
51
52  def setupModules(self):
53    self.setCompilers  = self.framework.require('config.setCompilers',         None)
54    self.arch          = self.framework.require('PETSc.options.arch',          None)
55    self.petscdir      = self.framework.require('PETSc.options.petscdir',      None)
56    self.compilers     = self.framework.require('config.compilers',            None)
57    self.mpi           = self.framework.require('config.packages.MPI',         None)
58    return
59
60  def setup(self):
61    script.Script.setup(self)
62    self.framework = self.loadConfigure()
63    self.setupModules()
64    return
65
66  def setupDirectories(self):
67    self.rootDir    = self.petscdir.dir
68    self.installDir = os.path.abspath(os.path.expanduser(self.framework.argDB['prefix']))
69    self.destDir    = os.path.abspath(self.argDB['destDir']+self.installDir)
70    self.arch       = self.arch.arch
71    self.archDir           = os.path.join(self.rootDir, self.arch)
72    self.rootIncludeDir    = os.path.join(self.rootDir, 'include')
73    self.archIncludeDir    = os.path.join(self.rootDir, self.arch, 'include')
74    self.rootConfDir       = os.path.join(self.rootDir, 'lib','petsc','conf')
75    self.archConfDir       = os.path.join(self.rootDir, self.arch, 'lib','petsc','conf')
76    self.rootBinDir        = os.path.join(self.rootDir, 'lib','petsc','bin')
77    self.archBinDir        = os.path.join(self.rootDir, self.arch, 'bin')
78    self.archLibDir        = os.path.join(self.rootDir, self.arch, 'lib')
79    self.destIncludeDir    = os.path.join(self.destDir, 'include')
80    self.destConfDir       = os.path.join(self.destDir, 'lib','petsc','conf')
81    self.destLibDir        = os.path.join(self.destDir, 'lib')
82    self.destBinDir        = os.path.join(self.destDir, 'lib','petsc','bin')
83    self.installIncludeDir = os.path.join(self.installDir, 'include')
84    self.installBinDir     = os.path.join(self.installDir, 'lib','petsc','bin')
85    self.rootShareDir      = os.path.join(self.rootDir, 'share')
86    self.destShareDir      = os.path.join(self.destDir, 'share')
87    self.rootSrcDir        = os.path.join(self.rootDir, 'src')
88
89    self.ranlib      = self.compilers.RANLIB
90    self.arLibSuffix = self.compilers.AR_LIB_SUFFIX
91    return
92
93  def checkPrefix(self):
94    if not self.installDir:
95      print('********************************************************************')
96      print('PETSc is built without prefix option. So "make install" is not appropriate.')
97      print('If you need a prefix install of PETSc - rerun configure with --prefix option.')
98      print('********************************************************************')
99      sys.exit(1)
100    return
101
102  def checkDestdir(self):
103    if os.path.exists(self.destDir):
104      if os.path.samefile(self.destDir, self.rootDir):
105        print('********************************************************************')
106        print('Incorrect prefix usage. Specified destDir same as current PETSC_DIR')
107        print('********************************************************************')
108        sys.exit(1)
109      if os.path.samefile(self.destDir, os.path.join(self.rootDir,self.arch)):
110        print('********************************************************************')
111        print('Incorrect prefix usage. Specified destDir same as current PETSC_DIR/PETSC_ARCH')
112        print('********************************************************************')
113        sys.exit(1)
114      if not os.path.isdir(os.path.realpath(self.destDir)):
115        print('********************************************************************')
116        print('Specified destDir', self.destDir, 'is not a directory. Cannot proceed!')
117        print('********************************************************************')
118        sys.exit(1)
119      if not os.access(self.destDir, os.W_OK):
120        print('********************************************************************')
121        print('Unable to write to ', self.destDir, 'Perhaps you need to do "sudo make install"')
122        print('********************************************************************')
123        sys.exit(1)
124    return
125
126  def copyfile(self, src, dst, symlinks = False, copyFunc = shutil.copy2):
127    """Copies a single file    """
128    copies = []
129    errors = []
130    if not os.path.exists(dst):
131      os.makedirs(dst)
132    elif not os.path.isdir(dst):
133      raise shutil.Error('Destination is not a directory')
134    srcname = src
135    dstname = os.path.join(dst, os.path.basename(src))
136    try:
137      if symlinks and os.path.islink(srcname):
138        linkto = os.readlink(srcname)
139        os.symlink(linkto, dstname)
140      else:
141        copyFunc(srcname, dstname)
142        copies.append((srcname, dstname))
143    except (IOError, os.error) as why:
144      errors.append((srcname, dstname, str(why)))
145    except shutil.Error as err:
146      errors.append((srcname,dstname,str(err.args[0])))
147    if errors:
148      raise shutil.Error(errors)
149    return copies
150
151  def fixExamplesMakefile(self, src):
152    '''Change ././${PETSC_ARCH} in makefile in root petsc directory with ${PETSC_DIR}'''
153    lines   = []
154    oldFile = open(src, 'r')
155    alllines=oldFile.read()
156    oldFile.close()
157    newlines=alllines.split('\n')[0]+'\n'  # Firstline
158    # Hardcode PETSC_DIR and PETSC_ARCH to avoid users doing the wrong thing
159    newlines+='PETSC_DIR='+self.installDir+'\n'
160    newlines+='PETSC_ARCH=\n'
161    for line in alllines.split('\n')[1:]:
162      if line.startswith('TESTLOGFILE'):
163        newlines+='TESTLOGFILE = $(TESTDIR)/examples-install.log\n'
164      elif line.startswith('CONFIGDIR'):
165        newlines+='CONFIGDIR:=$(PETSC_DIR)/$(PETSC_ARCH)/share/petsc/examples/config\n'
166      elif line.startswith('$(generatedtest)') and 'petscvariables' in line:
167        newlines+='all: test\n\n'+line+'\n'
168      else:
169        newlines+=line+'\n'
170    newFile = open(src, 'w')
171    newFile.write(newlines)
172    newFile.close()
173    return
174
175  def copyConfig(self, src, dst):
176    """Copy configuration/testing files
177    """
178    if not os.path.isdir(dst):
179      raise shutil.Error('Destination is not a directory')
180
181    self.copies.extend(self.copyfile('gmakefile.test',dst))
182    newConfigDir=os.path.join(dst,'config')  # Am not renaming at present
183    if not os.path.isdir(newConfigDir): os.mkdir(newConfigDir)
184    testConfFiles="gmakegentest.py gmakegen.py testparse.py example_template.py".split()
185    testConfFiles+="petsc_harness.sh report_tests.py query_tests.py".split()
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 os.path.basename(root) not in ("tests", "tutorials"): 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    srclinks = []
219    dstlinks = []
220    for name in names:
221      srcname = os.path.join(src, name)
222      dstname = os.path.join(dst, name)
223      try:
224        if symlinks and os.path.islink(srcname):
225          linkto = os.readlink(srcname)
226          os.symlink(linkto, dstname)
227        elif os.path.isdir(srcname) and recurse and not os.path.basename(srcname) in exclude:
228          copies.extend(self.copytree(srcname, dstname, symlinks,exclude = exclude, exclude_ext = exclude_ext))
229        elif os.path.isfile(srcname) and not os.path.basename(srcname) in exclude and os.path.splitext(name)[1] not in exclude_ext :
230          if os.path.islink(srcname):
231            srclinks.append(srcname)
232            dstlinks.append(dstname)
233          else:
234            copyFunc(srcname, dstname)
235            copies.append((srcname, dstname))
236        # XXX What about devices, sockets etc.?
237      except (IOError, os.error) as why:
238        errors.append((srcname, dstname, str(why)))
239      # catch the Error from the recursive copytree so that we can
240      # continue with other files
241      except shutil.Error as err:
242        errors.append((srcname,dstname,str(err.args[0])))
243    for srcname, dstname in zip(srclinks, dstlinks):
244      try:
245        copyFunc(srcname, dstname)
246        copies.append((srcname, dstname))
247      except (IOError, os.error) as why:
248        errors.append((srcname, dstname, str(why)))
249      # catch the Error from the recursive copytree so that we can
250      # continue with other files
251      except shutil.Error as err:
252        errors.append((srcname,dstname,str(err.args[0])))
253    try:
254      shutil.copystat(src, dst)
255    except OSError as e:
256      if WindowsError is not None and isinstance(e, WindowsError):
257        # Copying file access times may fail on Windows
258        pass
259      else:
260        errors.append((src, dst, str(e)))
261    if errors:
262      raise shutil.Error(errors)
263    return copies
264
265
266  def fixConfFile(self, src):
267    lines   = []
268    oldFile = open(src, 'r')
269    for line in oldFile.readlines():
270      if line.startswith('PETSC_CC_INCLUDES =') or line.startswith('PETSC_FC_INCLUDES ='):
271        continue
272      line = line.replace('PETSC_CC_INCLUDES_INSTALL', 'PETSC_CC_INCLUDES')
273      line = line.replace('PETSC_FC_INCLUDES_INSTALL', 'PETSC_FC_INCLUDES')
274      # remove PETSC_DIR/PETSC_ARCH variables from conf-makefiles. They are no longer necessary
275      line = line.replace('${PETSC_DIR}/${PETSC_ARCH}', self.installDir)
276      line = line.replace('PETSC_ARCH=${PETSC_ARCH}', '')
277      line = line.replace('${PETSC_DIR}', self.installDir)
278      # replace PETSC_DIR/lib/petsc/bin with prefix/lib/petsc/bin
279      line = line.replace(self.rootBinDir,self.destBinDir)
280      lines.append(line)
281    oldFile.close()
282    newFile = open(src, 'w')
283    newFile.write(''.join(lines))
284    newFile.close()
285    return
286
287  def fixConf(self):
288    import shutil
289    for file in ['rules', 'rules.doc', 'rules.utils', 'variables', 'petscrules', 'petscvariables']:
290      self.fixConfFile(os.path.join(self.destConfDir,file))
291    return
292
293  def createUninstaller(self):
294    uninstallscript = os.path.join(self.destConfDir, 'uninstall.py')
295    f = open(uninstallscript, 'w')
296    # Could use the Python AST to do this
297    f.write('#!'+sys.executable+'\n')
298    f.write('import os\n')
299    f.write('prefixdir = "'+self.installDir+'"\n')
300    files = [dst.replace(self.destDir,self.installDir) for src, dst in self.copies]
301    files.append(uninstallscript.replace(self.destDir,self.installDir))
302    f.write('files = '+repr(files))
303    f.write('''
304for file in files:
305  if os.path.exists(file) or os.path.islink(file):
306    os.remove(file)
307    dir = os.path.dirname(file)
308    while dir not in [os.path.dirname(prefixdir),'/']:
309      try: os.rmdir(dir)
310      except: break
311      dir = os.path.dirname(dir)
312''')
313    f.close()
314    os.chmod(uninstallscript,0o744)
315    return
316
317  def installIncludes(self):
318    exclude = ['makefile']
319    if not hasattr(self.compilers.setCompilers, 'FC'):
320      exclude.append('finclude')
321    if not self.mpi.usingMPIUni:
322      exclude.append('mpiuni')
323    self.copies.extend(self.copytree(self.rootIncludeDir, self.destIncludeDir,exclude = exclude))
324    self.copies.extend(self.copytree(self.archIncludeDir, self.destIncludeDir))
325    return
326
327  def installConf(self):
328    self.copies.extend(self.copytree(self.rootConfDir, self.destConfDir, exclude = ['uncrustify.cfg','bfort-base.txt','bfort-petsc.txt','bfort-mpi.txt','test.log']))
329    self.copies.extend(self.copytree(self.archConfDir, self.destConfDir, exclude = ['sowing', 'configure.log.bkp','configure.log','make.log','gmake.log','test.log','error.log','memoryerror.log','files','testfiles','RDict.db']))
330    return
331
332  def installBin(self):
333    exclude = ['bfort','bib2html','doc2lt','doctext','mapnames', 'pstogif','pstoxbm','tohtml']
334    self.copies.extend(self.copytree(self.archBinDir, self.destBinDir, exclude = exclude ))
335    exclude = ['maint']
336    if not self.mpi.usingMPIUni:
337      exclude.append('petsc-mpiexec.uni')
338    self.setCompilers.pushLanguage('C')
339    if self.setCompilers.getCompiler().find('win32fe') < 0:
340      exclude.append('win32fe')
341    self.setCompilers.popLanguage()
342    self.copies.extend(self.copytree(self.rootBinDir, self.destBinDir, exclude = exclude ))
343    return
344
345  def installShare(self):
346    self.copies.extend(self.copytree(self.rootShareDir, self.destShareDir))
347    examplesdir=os.path.join(self.destShareDir,'petsc','examples')
348    if os.path.exists(examplesdir):
349      shutil.rmtree(examplesdir)
350    os.mkdir(examplesdir)
351    os.mkdir(os.path.join(examplesdir,'src'))
352    self.copyConfig(self.rootDir,examplesdir)
353    if not self.argDB['no-examples']:
354        self.copyExamples(self.rootSrcDir,os.path.join(examplesdir,'src'))
355        self.fixExamplesMakefile(os.path.join(examplesdir,'gmakefile.test'))
356    return
357
358  def copyLib(self, src, dst):
359    '''Run ranlib on the destination library if it is an archive. Also run install_name_tool on dylib on Mac'''
360    # Symlinks (assumed local) are recreated at dst
361    if os.path.islink(src):
362      linkto = os.readlink(src)
363      try:
364        os.remove(dst)            # In case it already exists
365      except OSError:
366        pass
367      os.symlink(linkto, dst)
368      return
369    shutil.copy2(src, dst)
370    if self.setCompilers.getCompiler().find('win32fe') < 0 and os.path.splitext(dst)[1] == '.'+self.arLibSuffix:
371      self.executeShellCommand(self.ranlib+' '+dst)
372    if os.path.splitext(dst)[1] == '.dylib' and os.path.isfile('/usr/bin/install_name_tool'):
373      [output,err,flg] = self.executeShellCommand("otool -D "+src)
374      oldname = output[output.find("\n")+1:]
375      installName = oldname.replace(os.path.realpath(self.archDir), self.installDir)
376      self.executeShellCommand('/usr/bin/install_name_tool -id ' + installName + ' ' + dst)
377    # preserve the original timestamps - so that the .a vs .so time order is preserved
378    shutil.copystat(src,dst)
379    return
380
381  def installLib(self):
382    self.copies.extend(self.copytree(self.archLibDir, self.destLibDir, copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
383    self.copies.extend(self.copytree(os.path.join(self.archLibDir,'pkgconfig'), os.path.join(self.destLibDir,'pkgconfig'), copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
384    return
385
386
387  def outputInstallDone(self):
388    from config.packages.make import getMakeUserPath
389    print('''\
390====================================
391Install complete.
392Now to check if the libraries are working do (in current directory):
393%s PETSC_DIR=%s PETSC_ARCH="" check
394====================================\
395''' % (getMakeUserPath(self.arch), self.installDir))
396    return
397
398  def outputDestDirDone(self):
399    print('''\
400====================================
401Copy to DESTDIR %s is now complete.
402Before use - please copy/install over to specified prefix: %s
403====================================\
404''' % (self.destDir,self.installDir))
405    return
406
407  def runsetup(self):
408    self.setup()
409    self.setupDirectories()
410    self.checkPrefix()
411    self.checkDestdir()
412    return
413
414  def runcopy(self):
415    if self.destDir == self.installDir:
416      print('*** Installing PETSc at prefix location:',self.destDir, ' ***')
417    else:
418      print('*** Copying PETSc to DESTDIR location:',self.destDir, ' ***')
419    if not os.path.exists(self.destDir):
420      try:
421        os.makedirs(self.destDir)
422      except:
423        print('********************************************************************')
424        print('Unable to create', self.destDir, 'Perhaps you need to do "sudo make install"')
425        print('********************************************************************')
426        sys.exit(1)
427    self.installIncludes()
428    self.installConf()
429    self.installBin()
430    self.installLib()
431    self.installShare()
432    return
433
434  def runfix(self):
435    self.fixConf()
436    return
437
438  def rundone(self):
439    self.createUninstaller()
440    if self.destDir == self.installDir:
441      self.outputInstallDone()
442    else:
443      self.outputDestDirDone()
444    return
445
446  def run(self):
447    self.runsetup()
448    self.runcopy()
449    self.runfix()
450    self.rundone()
451    return
452
453if __name__ == '__main__':
454  Installer(sys.argv[1:]).run()
455  # temporary hack - delete log files created by BuildSystem - when 'sudo make install' is invoked
456  delfiles=['RDict.db','RDict.log','buildsystem.log','default.log','buildsystem.log.bkp','default.log.bkp']
457  for delfile in delfiles:
458    if os.path.exists(delfile) and (os.stat(delfile).st_uid==0):
459      os.remove(delfile)
460