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