xref: /petsc/config/PETSc/options/arch.py (revision 9fa15c4dcfb14eef1e694c8c4e3db8b9dee7e944)
1import config.base
2import os
3import re
4
5class Configure(config.base.Configure):
6  def __init__(self, framework):
7    config.base.Configure.__init__(self, framework)
8    self.headerPrefix = 'PETSC'
9    self.substPrefix  = 'PETSC'
10    return
11
12  def __str1__(self):
13    if not hasattr(self, 'arch'):
14      return ''
15    return '  PETSC_ARCH: '+str(self.arch)+'\n'
16
17  def setupHelp(self, help):
18    import nargs
19    help.addArgument('PETSc', '-PETSC_ARCH=<string>',     nargs.Arg(None, None, 'The configuration name'))
20    help.addArgument('PETSc', '-with-petsc-arch=<string>',nargs.Arg(None, None, 'The configuration name'))
21    help.addArgument('PETSc', '-force=<bool>',            nargs.ArgBool(None, 0, 'Bypass configure hash caching, and run to completion'))
22    return
23
24  def setupDependencies(self, framework):
25    self.sourceControl = framework.require('config.sourceControl',self)
26    self.petscdir = framework.require('PETSc.options.petscdir', self)
27    return
28
29  def setNativeArchitecture(self):
30    '''Forms the arch as GNU's configure would form it'''
31    import sys
32    arch = 'arch-' + sys.platform.replace('cygwin','mswin')
33    # use opt/debug, c/c++ tags.s
34    arch+= '-'+self.framework.argDB['with-clanguage'].lower().replace('+','x')
35    if self.framework.argDB['with-debugging']:
36      arch += '-debug'
37    else:
38      arch += '-opt'
39    self.nativeArch = arch
40    return
41
42  def configureArchitecture(self):
43    '''Checks if PETSC_ARCH is set and sets it if not set'''
44    # Warn if PETSC_ARCH doesn't match env variable
45    if 'PETSC_ARCH' in self.framework.argDB and 'PETSC_ARCH' in os.environ and self.framework.argDB['PETSC_ARCH'] != os.environ['PETSC_ARCH']:
46      self.logPrintWarning('''\
47PETSC_ARCH from environment does not match command-line or name of script. Using from command-line or name of script: %s, ignoring environment: %s''' % (str(self.framework.argDB['PETSC_ARCH']), str(os.environ['PETSC_ARCH'])))
48      os.environ['PETSC_ARCH'] = self.framework.argDB['PETSC_ARCH']
49    if 'with-petsc-arch' in self.framework.argDB:
50      self.arch = self.framework.argDB['with-petsc-arch']
51      msg = 'option -with-petsc-arch='+str(self.arch)
52    elif 'PETSC_ARCH' in self.framework.argDB:
53      self.arch = self.framework.argDB['PETSC_ARCH']
54      msg = 'option PETSC_ARCH='+str(self.arch)
55    elif 'PETSC_ARCH' in os.environ:
56      self.arch = os.environ['PETSC_ARCH']
57      msg = 'environment variable PETSC_ARCH='+str(self.arch)
58    else:
59      self.arch = self.nativeArch
60    if self.arch.find('/') >= 0 or self.arch.find('\\') >= 0:
61      raise RuntimeError('PETSC_ARCH should not contain path characters, but you have specified with '+msg)
62    if self.arch.startswith('-'):
63      raise RuntimeError('PETSC_ARCH should not start with "-", but you have specified with '+msg)
64    if self.arch.startswith('.'):
65      raise RuntimeError('PETSC_ARCH should not start with ".", but you have specified with '+msg)
66    if not len(self.arch):
67      raise RuntimeError('PETSC_ARCH cannot be empty string. Use a valid string or do not set one. Currently set with '+msg)
68    self.archBase = re.sub(r'^(\w+)[-_]?.*$', r'\1', self.arch)
69    return
70
71  def makeDependency(self,hash,hashfile,hashfilepackages):
72    '''Deletes the current hashfile and saves the hashfile names and its value in framework so that'''
73    '''framework.Configure can create the file upon success of configure'''
74    import os
75    if hash:
76      self.framework.hash = hash
77      self.framework.hashfile = hashfile
78      self.logPrint('Setting hashfile: '+hashfile)
79      if hashfilepackages: self.framework.hashfilepackages = hashfilepackages
80    try:
81      self.logPrint('Deleting configure hash file: '+hashfile)
82      os.remove(hashfile)
83      self.logPrint('Deleted configure hash file: '+hashfile)
84    except:
85      self.logPrint('Unable to delete configure hash file: '+hashfile)
86
87  def checkDependency(self):
88    '''Checks if files in config have changed, the command line options have changed or the PATH has changed'''
89    '''  By default - checks if configure needs to be run'''
90    '''  If --arch-hash it manages the same information but it:'''
91    '''     * computes a short hash for the configuration <hashvalue>'''
92    '''     * sets self.arch and PETSC_ARCH to arch-<hashvalue>'''
93    '''       This results in the downloaded packages being installed once to the arch-<hasvalue> directory'''
94    '''       and a new directory with a different hash is created if the configuration changes.'''
95    '''     This mode is intended mostly for testing to reduce reconfigure and recompile times (not currently used)'''
96    '''  If --package-prefix-hash=directory is provided'''
97    '''     * computes a short hash for the configuration <hashvalue>'''
98    '''     * puts the downloaded external packages into location directory/hash'''
99    '''       This results in the downloaded packages being installed once'''
100    '''       and a new directory with a different hash is created if the configuration changes.'''
101    '''     This mode is intended mostly for testing to reduce time of reinstalling external packages'''
102    import os
103    import sys
104    import hashlib
105    import platform
106    import nargs
107    hash = 'Uname: '+platform.uname().system+' '+platform.uname().processor+'\n'
108    hash += 'PATH=' + os.environ.get('PATH', '') + '\n'
109    args = dict([(nargs.Arg.parseArgument(arg)[0], arg) for arg in self.framework.clArgs])
110    hash += 'args:\n' + '\n'.join('    '+a for a in sorted(args.values())) + '\n'
111    chash=''
112    try:
113      for root, dirs, files in os.walk('config'):
114        if root == 'config':
115          dirs.remove('examples')
116        for f in files:
117          if not f.endswith('.py') or f.startswith('.') or f.startswith('#'):
118            continue
119          fname = os.path.join(root, f)
120          with open(fname,'rb') as f:
121            chash += hashlib.sha256(f.read()).hexdigest() + '  ' + fname + '\n'
122    except:
123      self.logPrint('Error generating file list/hash from config directory for configure hash, forcing new configuration')
124      return
125    hash += '\n'.join(sorted(chash.splitlines()))
126    hashfilepackages = None
127    # Generate short hash to use for the arch so the same arch can be reused if the configuration files don't change
128    if 'arch-hash' in self.argDB:
129      if self.argDB['prefix']:
130        raise RuntimeError('Cannot provide --prefix and --arch-hash')
131      if hasattr(self.argDB,'PETSC_ARCH'):
132        raise RuntimeError('Cannot provide PETSC_ARCH and --arch-hash')
133      if 'package-prefix-hash' in self.argDB:
134        raise RuntimeError('Cannot provide --arch-hash and --package-prefix-hash')
135      if os.getenv('PETSC_ARCH'):
136        raise RuntimeError('Do not set the environmental variable PETSC_ARCH and use --arch-hash')
137    if 'arch-hash' in self.argDB or 'package-prefix-hash' in self.argDB:
138      import hashlib
139      m = hashlib.sha256()
140      m.update(hash.encode('utf-8'))
141      hprefix = m.hexdigest()
142      self.logPrint('Computed hash to be used with --package-prefix-hash option: '+hprefix)
143      if 'arch-hash' in self.argDB:
144        self.argDB['PETSC_ARCH'] = 'arch-'+hprefix[0:6]
145        self.arch = 'arch-'+hprefix[0:6]
146      else:
147        if not os.path.isdir(self.argDB['package-prefix-hash']):
148          self.logPrint('Specified package-prefix-hash location %s not found! Attempting to create this dir!' % self.argDB['package-prefix-hash'])
149          try:
150            os.makedirs(self.argDB['package-prefix-hash'])
151          except Exception as e:
152            self.logPrint('Error creating package-prefix-hash directory '+self.argDB['package-prefix-hash']+': '+str(e))
153            raise RuntimeError('You must have write permission to create prefix directory '+self.argDB['package-prefix-hash'])
154        status = False
155        for idx in range(6,len(hprefix)):
156          hashdirpackages = os.path.join(self.argDB['package-prefix-hash'],hprefix[0:idx])
157          hashfilepackages = os.path.join(hashdirpackages,'configure-hash')
158          if os.path.isdir(hashdirpackages):
159            if os.path.exists(hashfilepackages):
160              self.argDB['package-prefix-hash'] = 'reuse' # indicates prefix libraries already built, no need to rebuild
161              self.logPrint('Found existing '+hashfilepackages+' reusing packages built in '+hashdirpackages)
162              status = True
163              break
164            else:
165              self.logPrint('Found existing '+hashdirpackages+' but it is incomplete so trying a longer directory name based on the hash')
166              continue # perhaps an incomplete build? use a longer hash
167          else:
168            if self.argDB['force']:
169              # since the previous hash associated with --package-prefix-hash
170              # (and hence its directory of built packages) is not available
171              # all the packages associated with that hash cannot be reused
172              raise RuntimeError('You cannot use --force with --package-prefix-hash=directory; you need to delete the $PETSC_ARCH directory and run configure again')
173            self.logPrint('Creating package-prefix-hash subdirectory '+hashdirpackages)
174            try:
175              os.mkdir(hashdirpackages)
176            except Exception as e:
177              raise RuntimeError('You must have write permission on --package-prefix-hash='+self.argDB['package-prefix-hash']+' directory')
178            status = True
179            break
180        if not status:
181          raise RuntimeError('Unable to create package-prefix-hash dir! Suggest cleaning up %s* !' % os.path.join(self.argDB['package-prefix-hash'],hprefix[0:6]) )
182        self.argDB['prefix'] = hashdirpackages
183
184    hashfile = os.path.join(self.arch,'lib','petsc','conf','configure-hash')
185
186    if self.argDB['force']:
187      self.logPrint('Forcing a new configuration requested by use')
188      self.makeDependency(hash,hashfile,hashfilepackages)
189      return
190    a = ''
191    try:
192      with open(hashfile, 'r') as f:
193        a = f.read()
194    except:
195      self.logPrint('No previous hashfile found')
196      self.makeDependency(hash,hashfile,hashfilepackages)
197      return
198    if a == hash:
199      try:
200        self.logPrint('Attempting to save lib/petsc/conf/petscvariables file')
201        with open(os.path.join('lib','petsc','conf','petscvariables'), 'w') as g:
202          g.write('PETSC_ARCH='+self.arch+'\n')
203          g.write('PETSC_DIR='+self.petscdir.dir+'\n')
204          g.write('include $(PETSC_DIR)/$(PETSC_ARCH)/lib/petsc/conf/petscvariables\n')
205          self.logPrint('Saved lib/petsc/conf/petscvariables file')
206      except:
207        self.logPrint('Unable to save lib/petsc/conf/petscvariables file')
208      self.logPrint('configure hash file: '+hashfile+' matches; no need to run configure.')
209      self.logPrintBox('Your configure options and state has not changed; no need to run configure\nHowever you can force a configure run using the option: --force')
210
211      import logger
212      from config.packages.make import getMakeUserPath
213      banner_ends   = 'xxx'
214      banner_middle = '=' * (logger.get_global_divider_length() - 2 * len(banner_ends))
215      banner_line   = banner_middle.join((banner_ends, banner_ends))
216      print(banner_line)
217      print(' Build PETSc libraries with:')
218      print('   %s PETSC_DIR=%s PETSC_ARCH=%s all' % (getMakeUserPath(self.arch), self.petscdir.dir, self.arch))
219      print(banner_line)
220      sys.exit()
221    self.logPrint('configure hash file: '+hashfile+' does not match, need to run configure')
222    self.makeDependency(hash,hashfile,hashfilepackages)
223
224  def configure(self):
225    self.executeTest(self.setNativeArchitecture)
226    self.executeTest(self.configureArchitecture)
227    # required by top-level configure.py
228    self.framework.arch = self.arch
229    self.checkDependency()
230    return
231