xref: /petsc/config/install.py (revision 65f00f635ea1cc29e370e3dd3c95024910fc8f14)
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      # paths generated by configure could be different link-path than whats used by user, so fix both
255      line = line.replace(os.path.join(self.rootDir, self.arch), self.installDir)
256      line = line.replace(os.path.realpath(os.path.join(self.rootDir, self.arch)), self.installDir)
257      line = line.replace(os.path.join(self.rootDir, 'bin'), self.installBinDir)
258      line = line.replace(os.path.realpath(os.path.join(self.rootDir, 'bin')), self.installBinDir)
259      line = line.replace(os.path.join(self.rootDir, 'include'), self.installIncludeDir)
260      line = line.replace(os.path.realpath(os.path.join(self.rootDir, 'include')), self.installIncludeDir)
261      # remove PETSC_DIR/PETSC_ARCH variables from conf-makefiles. They are no longer necessary
262      line = line.replace('${PETSC_DIR}/${PETSC_ARCH}', self.installDir)
263      line = line.replace('PETSC_ARCH=${PETSC_ARCH}', '')
264      line = line.replace('${PETSC_DIR}', self.installDir)
265      lines.append(line)
266    oldFile.close()
267    newFile = open(src, 'w')
268    newFile.write(''.join(lines))
269    newFile.close()
270    return
271
272  def fixConf(self):
273    import shutil
274    for file in ['rules', 'variables','petscrules', 'petscvariables']:
275      self.fixConfFile(os.path.join(self.destConfDir,file))
276    return
277
278  def createUninstaller(self):
279    uninstallscript = os.path.join(self.destConfDir, 'uninstall.py')
280    f = open(uninstallscript, 'w')
281    # Could use the Python AST to do this
282    f.write('#!'+sys.executable+'\n')
283    f.write('import os\n')
284    f.write('prefixdir = "'+self.installDir+'"\n')
285    files = [dst.replace(self.destDir,self.installDir) for src, dst in self.copies]
286    files.append(uninstallscript.replace(self.destDir,self.installDir))
287    f.write('files = '+repr(files))
288    f.write('''
289for file in files:
290  if os.path.exists(file) or os.path.islink(file):
291    os.remove(file)
292    dir = os.path.dirname(file)
293    while dir not in [os.path.dirname(prefixdir),'/']:
294      try: os.rmdir(dir)
295      except: break
296      dir = os.path.dirname(dir)
297''')
298    f.close()
299    os.chmod(uninstallscript,0o744)
300    return
301
302  def installIncludes(self):
303    exclude = ['makefile']
304    if not hasattr(self.compilers.setCompilers, 'FC'):
305      exclude.append('finclude')
306    if not self.mpi.usingMPIUni:
307      exclude.append('mpiuni')
308    self.copies.extend(self.copytree(self.rootIncludeDir, self.destIncludeDir,exclude = exclude))
309    self.copies.extend(self.copytree(self.archIncludeDir, self.destIncludeDir))
310    return
311
312  def installConf(self):
313    self.copies.extend(self.copytree(self.rootConfDir, self.destConfDir, exclude = ['uncrustify.cfg','bfort-base.txt','bfort-petsc.txt','bfort-mpi.txt','test.log']))
314    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']))
315    return
316
317  def installBin(self):
318    exclude = ['bfort','bib2html','doc2lt','doctext','mapnames', 'pstogif','pstoxbm','tohtml']
319    self.copies.extend(self.copytree(self.archBinDir, self.destBinDir, exclude = exclude ))
320    exclude = ['maint']
321    if not self.mpi.usingMPIUni:
322      exclude.append('petsc-mpiexec.uni')
323    self.setCompilers.pushLanguage('C')
324    if not self.setCompilers.isWindows(self.setCompilers.getCompiler(),self.log):
325      exclude.append('win32fe')
326    self.setCompilers.popLanguage()
327    self.copies.extend(self.copytree(self.rootBinDir, self.destBinDir, exclude = exclude ))
328    return
329
330  def installShare(self):
331    self.copies.extend(self.copytree(self.rootShareDir, self.destShareDir))
332    examplesdir=os.path.join(self.destShareDir,'petsc','examples')
333    if os.path.exists(examplesdir):
334      shutil.rmtree(examplesdir)
335    os.mkdir(examplesdir)
336    os.mkdir(os.path.join(examplesdir,'src'))
337    self.copyExamples(self.rootSrcDir,examplesdir)
338    self.copyConfig(self.rootDir,examplesdir)
339    self.fixExamplesMakefile(os.path.join(examplesdir,'gmakefile.test'))
340    return
341
342  def copyLib(self, src, dst):
343    '''Run ranlib on the destination library if it is an archive. Also run install_name_tool on dylib on Mac'''
344    # Symlinks (assumed local) are recreated at dst
345    if os.path.islink(src):
346      linkto = os.readlink(src)
347      try:
348        os.remove(dst)            # In case it already exists
349      except OSError:
350        pass
351      os.symlink(linkto, dst)
352      return
353    shutil.copy2(src, dst)
354    if os.path.splitext(dst)[1] == '.'+self.arLibSuffix:
355      self.executeShellCommand(self.ranlib+' '+dst)
356    if os.path.splitext(dst)[1] == '.dylib' and os.path.isfile('/usr/bin/install_name_tool'):
357      [output,err,flg] = self.executeShellCommand("otool -D "+src)
358      oldname = output[output.find("\n")+1:]
359      installName = oldname.replace(self.archDir, self.installDir)
360      self.executeShellCommand('/usr/bin/install_name_tool -id ' + installName + ' ' + dst)
361    # preserve the original timestamps - so that the .a vs .so time order is preserved
362    shutil.copystat(src,dst)
363    return
364
365  def installLib(self):
366    self.copies.extend(self.copytree(self.archLibDir, self.destLibDir, copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
367    self.copies.extend(self.copytree(os.path.join(self.archLibDir,'pkgconfig'), os.path.join(self.destLibDir,'pkgconfig'), copyFunc = self.copyLib, exclude = ['.DIR'],recurse = 0))
368    return
369
370
371  def outputInstallDone(self):
372    print('''\
373====================================
374Install complete.
375Now to check if the libraries are working do (in current directory):
376make PETSC_DIR=%s PETSC_ARCH="" test
377====================================\
378''' % (self.installDir))
379    return
380
381  def outputDestDirDone(self):
382    print('''\
383====================================
384Copy to DESTDIR %s is now complete.
385Before use - please copy/install over to specified prefix: %s
386====================================\
387''' % (self.destDir,self.installDir))
388    return
389
390  def runsetup(self):
391    self.setup()
392    self.setupDirectories()
393    self.checkPrefix()
394    self.checkDestdir()
395    return
396
397  def runcopy(self):
398    if self.destDir == self.installDir:
399      print('*** Installing PETSc at prefix location:',self.destDir, ' ***')
400    else:
401      print('*** Copying PETSc to DESTDIR location:',self.destDir, ' ***')
402    if not os.path.exists(self.destDir):
403      try:
404        os.makedirs(self.destDir)
405      except:
406        print('********************************************************************')
407        print('Unable to create', self.destDir, 'Perhaps you need to do "sudo make install"')
408        print('********************************************************************')
409        sys.exit(1)
410    self.installIncludes()
411    self.installConf()
412    self.installBin()
413    self.installLib()
414    self.installShare()
415    return
416
417  def runfix(self):
418    self.fixConf()
419    return
420
421  def rundone(self):
422    self.createUninstaller()
423    if self.destDir == self.installDir:
424      self.outputInstallDone()
425    else:
426      self.outputDestDirDone()
427    return
428
429  def run(self):
430    self.runsetup()
431    self.runcopy()
432    self.runfix()
433    self.rundone()
434    return
435
436if __name__ == '__main__':
437  Installer(sys.argv[1:]).run()
438  # temporary hack - delete log files created by BuildSystem - when 'sudo make install' is invoked
439  delfiles=['RDict.db','RDict.log','buildsystem.log','default.log','buildsystem.log.bkp','default.log.bkp']
440  for delfile in delfiles:
441    if os.path.exists(delfile) and (os.stat(delfile).st_uid==0):
442      os.remove(delfile)
443