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