xref: /petsc/config/BuildSystem/script.py (revision 391e379223c8ad233f201b9d10bca97f975e49a9)
1from __future__ import print_function
2from __future__ import absolute_import
3import sys
4if not hasattr(sys, 'version_info'):
5  print('*** Python version 1 is not supported. Please get the latest version from www.python.org ***')
6  sys.exit(4)
7
8import pickle
9
10import subprocess
11
12import nargs
13useThreads = nargs.Arg.findArgument('useThreads', sys.argv[1:])
14if useThreads is None:
15  useThreads = 0 # workaround issue with parallel configure
16elif useThreads == 'no' or useThreads == '0':
17  useThreads = 0
18elif useThreads == 'yes' or useThreads == '1':
19  useThreads = 1
20else:
21  raise RuntimeError('Unknown option value for --useThreads ',useThreads)
22
23useSelect = nargs.Arg.findArgument('useSelect', sys.argv[1:])
24if useSelect is None:
25  useSelect = 1
26elif useSelect == 'no' or useSelect == '0':
27  useSelect = 0
28elif useSelect == 'yes' or useSelect == '1':
29  useSelect = 1
30else:
31  raise RuntimeError('Unknown option value for --useSelect ',useSelect)
32
33import logger
34
35class Script(logger.Logger):
36  def __init__(self, clArgs = None, argDB = None, log = None):
37    self.checkPython()
38    logger.Logger.__init__(self, clArgs, argDB, log)
39    self.shell = '/bin/sh'
40    self.showHelp = 1
41    return
42
43  def hasHelpFlag(self):
44    '''Decide whether to display the help message and exit'''
45    import nargs
46
47    if not self.showHelp:
48      return 0
49    if nargs.Arg.findArgument('help', self.clArgs) is None and nargs.Arg.findArgument('h', self.clArgs) is None:
50      return 0
51    return 1
52
53  def hasListFlag(self):
54    '''Decide whether to display the list of download files and exit'''
55    import nargs
56
57    if not self.showHelp:
58      return 0
59    if nargs.Arg.findArgument('with-packages-download-dir', self.clArgs) is None:
60      return 0
61    return 1
62
63  def setupArguments(self, argDB):
64    '''This method now also creates the help and action logs'''
65    import help
66
67    argDB = logger.Logger.setupArguments(self, argDB)
68
69    self.help = help.Help(argDB)
70    self.help.title = 'Script Help'
71
72    self.actions = help.Info(argDB)
73    self.actions.title = 'Script Actions'
74
75    self.setupHelp(self.help)
76    return argDB
77
78  def setupHelp(self, help):
79    '''This method should be overidden to provide help for arguments'''
80    import nargs
81
82    help.addArgument('Script', '-h',    nargs.ArgBool(None, 0, 'Print this help message', isTemporary = 1), ignoreDuplicates = 1)
83    help.addArgument('Script', '-help', nargs.ArgBool(None, 0, 'Print this help message', isTemporary = 1), ignoreDuplicates = 1)
84    help.addArgument('Script', '-with-packages-download-dir=<dir>', nargs.ArgDir(None,None, 'Skip network download of package tarballs and locate them in specified dir. If not found in dir, print package URL - so it can be obtained manually.', isTemporary = 1), ignoreDuplicates = 1)
85    return help
86
87  def setup(self):
88    ''' This method checks to see whether help was requested'''
89    if hasattr(self, '_setup'):
90      return
91    logger.Logger.setup(self)
92    self._setup = 1
93    if self.hasHelpFlag():
94      self.argDB.readonly = True
95      if self.argDB.target == ['default']:
96        sections = None
97      else:
98        sections = self.argDB.target
99      self.help.output(sections = sections)
100      sys.exit()
101    if self.hasListFlag():
102      self.help.outputDownload()
103    return
104
105  def cleanup(self):
106    '''This method outputs the action log'''
107    self.actions.output(self.log)
108    return
109
110  def checkPython(self):
111    if not hasattr(sys, 'version_info') or sys.version_info < (2,6):
112      raise RuntimeError('BuildSystem requires Python version 2.6 or higher. Get Python at https://www.python.org/')
113    return
114
115  def getModule(root, name):
116    '''Retrieve a specific module from the directory root, bypassing the usual paths'''
117    import imp
118
119    (fp, pathname, description) = imp.find_module(name, [root])
120    try:
121      return imp.load_module(name, fp, pathname, description)
122    finally:
123      if fp: fp.close()
124    return
125  getModule = staticmethod(getModule)
126
127  def importModule(moduleName):
128    '''Import the named module, and return the module object
129       - Works properly for fully qualified names'''
130    module     = __import__(moduleName)
131    components = moduleName.split('.')
132    for comp in components[1:]:
133      module = getattr(module, comp)
134    return module
135  importModule = staticmethod(importModule)
136
137  @staticmethod
138  def runShellCommand(command, log=None, cwd=None):
139    return Script.runShellCommandSeq([command], log=log, cwd=cwd)
140
141  @staticmethod
142  def runShellCommandSeq(commandseq, log=None, cwd=None):
143    Popen = subprocess.Popen
144    PIPE  = subprocess.PIPE
145    output = ''
146    error = ''
147    for command in commandseq:
148      useShell = isinstance(command, str) or isinstance(command, bytes)
149      if log: log.write('Executing: %s\n' % (command,))
150      try:
151        pipe = Popen(command, cwd=cwd, stdin=None, stdout=PIPE, stderr=PIPE,
152                     shell=useShell)
153        (out, err) = pipe.communicate()
154        if sys.version_info >= (3,0):
155          out = out.decode(encoding='UTF-8',errors='replace')
156          err = err.decode(encoding='UTF-8',errors='replace')
157        ret = pipe.returncode
158      except OSError as e:
159        return ('', e.message, e.errno)
160      except FileNotFoundError as e:
161        return ('', e.message, e.errno)
162      output += out
163      error += err
164      if ret:
165        break
166    return (output, error, ret)
167
168  def defaultCheckCommand(command, status, output, error):
169    '''Raise an error if the exit status is nonzero'''
170    if status: raise RuntimeError('Could not execute "%s":\n%s' % (command,output+error))
171  defaultCheckCommand = staticmethod(defaultCheckCommand)
172
173  @staticmethod
174  def executeShellCommand(command, checkCommand = None, timeout = 600.0, log = None, lineLimit = 0, cwd=None, logOutputflg = True):
175    '''Execute a shell command returning the output, and optionally provide a custom error checker
176       - This returns a tuple of the (output, error, statuscode)'''
177    return Script.executeShellCommandSeq([command], checkCommand=checkCommand, timeout=timeout, log=log, lineLimit=lineLimit, cwd=cwd,logOutputflg = logOutputflg)
178
179  @staticmethod
180  def executeShellCommandSeq(commandseq, checkCommand = None, timeout = 600.0, log = None, lineLimit = 0, cwd=None, logOutputflg = True):
181    '''Execute a sequence of shell commands (an && chain) returning the output, and optionally provide a custom error checker
182       - This returns a tuple of the (output, error, statuscode)'''
183    if not checkCommand:
184      checkCommand = Script.defaultCheckCommand
185    if log is None:
186      log = logger.Logger.defaultLog
187    def logOutput(log, output, logOutputflg):
188      import re
189      if not logOutputflg: return output
190      # get rid of multiple blank lines
191      output = re.sub('\n+','\n', output).strip()
192      if output:
193        if lineLimit:
194          output = '\n'.join(output.split('\n')[:lineLimit])
195        if '\n' in output:      # multi-line output
196          log.write('stdout:\n'+output+'\n')
197        else:
198          log.write('stdout: '+output+'\n')
199      return output
200    def runInShell(commandseq, log, cwd):
201      if useThreads:
202        import threading
203        class InShell(threading.Thread):
204          def __init__(self):
205            threading.Thread.__init__(self)
206            self.name = 'Shell Command'
207            self.setDaemon(1)
208          def run(self):
209            (self.output, self.error, self.status) = ('', '', -1) # So these fields exist even if command fails with no output
210            (self.output, self.error, self.status) = Script.runShellCommandSeq(commandseq, log, cwd)
211        thread = InShell()
212        thread.start()
213        thread.join(timeout)
214        if thread.isAlive():
215          error = 'Runaway process exceeded time limit of '+str(timeout)+'s\n'
216          log.write(error)
217          return ('', error, -1)
218        else:
219          return (thread.output, thread.error, thread.status)
220      else:
221        return Script.runShellCommandSeq(commandseq, log, cwd)
222
223    (output, error, status) = runInShell(commandseq, log, cwd)
224    output = logOutput(log, output,logOutputflg)
225    checkCommand(commandseq, status, output, error)
226    return (output, error, status)
227
228  def loadConfigure(self, argDB = None):
229    if argDB is None:
230      argDB = self.argDB
231    if not 'configureCache' in argDB:
232      self.logPrint('No cached configure in RDict at '+str(argDB.saveFilename))
233      return None
234    try:
235      cache = argDB['configureCache']
236      framework = pickle.loads(cache)
237      framework.framework = framework
238      framework.argDB = argDB
239      self.logPrint('Loaded configure to cache: size '+str(len(cache)))
240    except pickle.UnpicklingError as e:
241      framework = None
242      self.logPrint('Invalid cached configure: '+str(e))
243    return framework
244
245import args
246
247class LanguageProcessor(args.ArgumentProcessor):
248  def __init__(self, clArgs = None, argDB = None, framework = None, versionControl = None):
249    self.languageModule      = {}
250    self.preprocessorObject  = {}
251    self.compilerObject      = {}
252    self.linkerObject        = {}
253    self.sharedLinkerObject  = {}
254    self.dynamicLinkerObject = {}
255    self.framework           = framework
256    self.versionControl      = versionControl
257    args.ArgumentProcessor.__init__(self, clArgs, argDB)
258    self.outputFiles         = {}
259    self.modulePath          = 'config.compile'
260    return
261
262  def getCompilers(self):
263    if self.framework is None:
264      return
265    return self.framework.require('config.compilers', None)
266  compilers = property(getCompilers, doc = 'The config.compilers configure object')
267  def getLibraries(self):
268    if self.framework is None:
269      return
270    return self.framework.require('config.libraries', None)
271  libraries = property(getLibraries, doc = 'The config.libraries configure object')
272
273  def __getstate__(self, d = None):
274    '''We only want to pickle the language module names and output files. The other objects are set by configure.'''
275    if d is None:
276      d = args.ArgumentProcessor.__getstate__(self)
277    if 'languageModule' in d:
278      d['languageModule'] = dict([(lang,mod._loadName) for lang,mod in d['languageModule'].items()])
279    for member in ['preprocessorObject', 'compilerObject', 'linkerObject', 'sharedLinkerObject', 'dynamicLinkerObject', 'framework']:
280      if member in d:
281        del d[member]
282    return d
283
284  def __setstate__(self, d):
285    '''We must create the language modules'''
286    args.ArgumentProcessor.__setstate__(self, d)
287    self.__dict__.update(d)
288    [self.getLanguageModule(language, moduleName) for language,moduleName in self.languageModule.items()]
289    self.preprocessorObject  = {}
290    self.compilerObject      = {}
291    self.linkerObject        = {}
292    self.sharedLinkerObject  = {}
293    self.dynamicLinkerObject = {}
294    return
295
296  def setArgDB(self, argDB):
297    args.ArgumentProcessor.setArgDB(self, argDB)
298    for obj in self.preprocessorObject.values():
299      if not hasattr(obj, 'argDB') or not obj.argDB == argDB:
300        obj.argDB = argDB
301    for obj in self.compilerObject.values():
302      if not hasattr(obj, 'argDB') or not obj.argDB == argDB:
303        obj.argDB = argDB
304    for obj in self.linkerObject.values():
305      if not hasattr(obj, 'argDB') or not obj.argDB == argDB:
306        obj.argDB = argDB
307    for obj in self.sharedLinkerObject.values():
308      if not hasattr(obj, 'argDB') or not obj.argDB == argDB:
309        obj.argDB = argDB
310    for obj in self.dynamicLinkerObject.values():
311      if not hasattr(obj, 'argDB') or not obj.argDB == argDB:
312        obj.argDB = argDB
313    if not self.compilers is None:
314      self.compilers.argDB = argDB
315      for obj in self.preprocessorObject.values():
316        if hasattr(obj, 'configCompilers'):
317          obj.configCompilers.argDB = argDB
318      for obj in self.compilerObject.values():
319        if hasattr(obj, 'configCompilers'):
320          obj.configCompilers.argDB = argDB
321      for obj in self.linkerObject.values():
322        if hasattr(obj, 'configCompilers'):
323          obj.configCompilers.argDB = argDB
324      for obj in self.sharedLinkerObject.values():
325        if hasattr(obj, 'configCompilers'):
326          obj.configCompilers.argDB = argDB
327      for obj in self.dynamicLinkerObject.values():
328        if hasattr(obj, 'configCompilers'):
329          obj.configCompilers.argDB = argDB
330    if not self.libraries is None:
331      self.libraries.argDB = argDB
332      for obj in self.linkerObject.values():
333        if hasattr(obj, 'configLibraries'):
334          obj.configLibraries.argDB = argDB
335      for obj in self.sharedLinkerObject.values():
336        if hasattr(obj, 'configLibraries'):
337          obj.configLibraries.argDB = argDB
338      for obj in self.dynamicLinkerObject.values():
339        if hasattr(obj, 'configLibraries'):
340          obj.configLibraries.argDB = argDB
341    return
342  argDB = property(args.ArgumentProcessor.getArgDB, setArgDB, doc = 'The RDict argument database')
343
344  def getLanguageModule(self, language, moduleName = None):
345    '''Return the module associated with operations for a given language
346       - Giving a moduleName explicitly forces a reimport'''
347    if not language in self.languageModule or not moduleName is None:
348      try:
349        if moduleName is None:
350          moduleName = self.modulePath+'.'+language
351        module     = __import__(moduleName)
352      except ImportError as e:
353        if not moduleName is None:
354          self.logPrint('Failure to find language module: '+str(e))
355        try:
356          moduleName = self.modulePath+'.'+language
357          module     = __import__(moduleName)
358        except ImportError as e:
359          self.logPrint('Failure to find language module: '+str(e))
360          moduleName = 'config.compile.'+language
361          module     = __import__(moduleName)
362      components = moduleName.split('.')
363      for component in components[1:]:
364        module   = getattr(module, component)
365      module._loadName = moduleName
366      self.languageModule[language] = module
367    return self.languageModule[language]
368
369  def getPreprocessorObject(self, language):
370    if not language in self.preprocessorObject:
371      self.preprocessorObject[language] = self.getLanguageModule(language).Preprocessor(self.argDB)
372      self.preprocessorObject[language].setup()
373    if not self.compilers is None:
374      self.preprocessorObject[language].configCompilers = self.compilers
375    if not self.versionControl is None:
376      self.preprocessorObject[language].versionControl  = self.versionControl
377    return self.preprocessorObject[language]
378
379  def setPreprocessorObject(self, language, preprocessor):
380    self.preprocessorObject[language] = preprocessor
381    return self.getPreprocessorObject(language)
382
383  def getCompilerObject(self, language):
384    if not language in self.compilerObject:
385      self.compilerObject[language] = self.getLanguageModule(language).Compiler(self.argDB)
386      self.compilerObject[language].setup()
387    if not self.compilers is None:
388      self.compilerObject[language].configCompilers = self.compilers
389    if not self.versionControl is None:
390      self.compilerObject[language].versionControl  = self.versionControl
391    return self.compilerObject[language]
392
393  def setCompilerObject(self, language, compiler):
394    self.compilerObject[language] = compiler
395    return self.getCompilerObject(language)
396
397  def getLinkerObject(self, language):
398    if not language in self.linkerObject:
399      self.linkerObject[language] = self.getLanguageModule(language).Linker(self.argDB)
400      self.linkerObject[language].setup()
401    if not self.compilers is None:
402      self.linkerObject[language].configCompilers = self.compilers
403    if not self.libraries is None:
404      self.linkerObject[language].configLibraries = self.libraries
405    if not self.versionControl is None:
406      self.linkerObject[language].versionControl  = self.versionControl
407    return self.linkerObject[language]
408
409  def setLinkerObject(self, language, linker):
410    self.linkerObject[language] = linker
411    return self.getLinkerObject(language)
412
413  def getSharedLinkerObject(self, language):
414    if not language in self.sharedLinkerObject:
415      self.sharedLinkerObject[language] = self.getLanguageModule(language).SharedLinker(self.argDB)
416      self.sharedLinkerObject[language].setup()
417    if not self.compilers is None:
418      self.sharedLinkerObject[language].configCompilers = self.compilers
419    if not self.libraries is None:
420      self.sharedLinkerObject[language].configLibraries = self.libraries
421    if not self.versionControl is None:
422      self.sharedLinkerObject[language].versionControl  = self.versionControl
423    return self.sharedLinkerObject[language]
424
425  def setSharedLinkerObject(self, language, linker):
426    self.sharedLinkerObject[language] = linker
427    return self.getSharedLinkerObject(language)
428
429  def getDynamicLinkerObject(self, language):
430    if not language in self.dynamicLinkerObject:
431      self.dynamicLinkerObject[language] = self.getLanguageModule(language).DynamicLinker(self.argDB)
432      self.dynamicLinkerObject[language].setup()
433    if not self.compilers is None:
434      self.dynamicLinkerObject[language].configCompilers = self.compilers
435    if not self.libraries is None:
436      self.dynamicLinkerObject[language].configLibraries = self.libraries
437    if not self.versionControl is None:
438      self.dynamicLinkerObject[language].versionControl  = self.versionControl
439    return self.dynamicLinkerObject[language]
440
441  def setDynamicLinkerObject(self, language, linker):
442    self.dynamicLinkerObject[language] = linker
443    return self.getDynamicLinkerObject(language)
444