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