xref: /petsc/config/BuildSystem/config/base.py (revision f77274b79407ec88a30444acc61c64bd80a1ddb7)
1'''
2config.base.Configure is the base class for all configure objects. It handles several types of interaction:
3
4Framework hooks
5---------------
6
7  The Framework will first instantiate the object and call setupDependencies(). All require()
8  calls should be made in that method.
9
10  The Framework will then call configure(). If it succeeds, the object will be marked as configured.
11
12Generic test execution
13----------------------
14
15  All configure tests should be run using
16
17  executeTest()
18
19which formats the output and adds metadata for the log.
20
21Preprocessing, Compiling, Linking, and Running
22----------------------------------------------
23
24  Two forms of this check are provided for each operation. The first is an "output" form which is
25intended to provide the status and complete output of the command. The second, or "check" form will
26return a success or failure indication based upon the status and output.
27
28  outputPreprocess(), checkPreprocess(), preprocess()
29  outputCompile(),    checkCompile()
30  outputLink(),       checkLink()
31  outputRun(),        checkRun()
32
33  The language used for these operation is managed with a stack, similar to autoconf.
34
35  pushLanguage(), popLanguage()
36
37  We also provide special forms used to check for valid compiler and linker flags, optionally adding
38them to the defaults.
39
40  checkCompilerFlag(), addCompilerFlag()
41  checkLinkerFlag(),   addLinkerFlag()
42
43Finding Executables
44-------------------
45
46  getExecutable(), getExecutables(), checkExecutable()
47
48Output
49------
50
51  addDefine(), addSubstitution(), addArgumentSubstitution(), addTypedef(), addPrototype()
52  addMakeMacro(), addMakeRule()
53
54  The object may define a headerPrefix member, which will be appended, followed
55by an underscore, to every define which is output from it. Similarly, a substPrefix
56can be defined which applies to every substitution from the object. Typedefs and
57function prototypes are placed in a separate header in order to accommodate languages
58such as Fortran whose preprocessor can sometimes fail at these statements.
59'''
60import script
61
62import os
63import time
64import contextlib
65
66class ConfigureSetupError(Exception):
67  pass
68
69class Configure(script.Script):
70  def __init__(self, framework, tmpDir = None):
71    script.Script.__init__(self, framework.clArgs, framework.argDB)
72    self.framework       = framework
73    self.defines         = {}
74    self.makeRules       = {}
75    self.makeMacros      = {}
76    self.typedefs        = {}
77    self.prototypes      = {}
78    self.subst           = {}
79    self.argSubst        = {}
80    self.language        = []
81    if not tmpDir is None:
82      self.tmpDir        = tmpDir
83    try:
84      # The __init__ method may be called to reinitialize in the future (e.g.,
85      # updateCompilers()) and will need to be re-setup in that case.
86      delattr(self, '_setup')
87    except AttributeError:
88      pass
89    return
90
91  def setup(self):
92    if hasattr(self, '_setup'):
93      return
94    script.Script.setup(self)
95    self._setup = 1
96    self.pushLanguage('C')
97
98  def getTmpDir(self):
99    if not hasattr(self, '_tmpDir'):
100      self._tmpDir = os.path.join(self.framework.tmpDir, self.__module__)
101      if not os.path.isdir(self._tmpDir): os.mkdir(self._tmpDir)
102    return self._tmpDir
103  def setTmpDir(self, temp):
104    if hasattr(self, '_tmpDir'):
105      if os.path.isdir(self._tmpDir):
106        import shutil
107        shutil.rmtree(self._tmpDir)
108      if temp is None:
109        delattr(self, '_tmpDir')
110    if not temp is None:
111      self._tmpDir = temp
112    return
113  tmpDir = property(getTmpDir, setTmpDir, doc = 'Temporary directory for test byproducts')
114
115  def __str__(self):
116    return ''
117
118  def logError(self, component, status, output, error):
119    if status:
120      exitstr = ' exit code ' + str(status)
121    else:
122      exitstr = 'exit code 0'
123    self.logWrite('Possible ERROR while running %s:%s\n' % (component, exitstr))
124    if output:
125      self.logWrite('stdout:\n' + output)
126    if error:
127      self.logWrite('stderr:\n' + error)
128
129  def executeTest(self, test, args = [], kargs = {}):
130    '''Prints the function and class information for the test and then runs the test'''
131    import time
132
133    self.logPrintDivider()
134    self.logPrint('TESTING: '+str(test.__func__.__name__)+' from '+str(test.__self__.__class__.__module__)+'('+str(test.__func__.__code__.co_filename)+':'+str(test.__func__.__code__.co_firstlineno)+')', debugSection = 'screen', indent = 0)
135    if test.__doc__: self.logWrite('  '+test.__doc__+'\n')
136    #t = time.time()
137    if not isinstance(args, list): args = [args]
138    ret = test(*args,**kargs)
139    #self.logPrint('  TIME: '+str(time.time() - t)+' sec', debugSection = 'screen', indent = 0)
140    return ret
141
142  def printTest(self, test):
143    '''Prints the function and class information for a test'''
144    self.logPrintDivider()
145    self.logPrint('TESTING: '+str(test.__func__.__name__)+' from '+str(test.__self__.__class__.__module__)+'('+str(test.__func__.__code__.co_filename)+':'+str(test.__func__.__code__.co_firstlineno)+')', debugSection = 'screen', indent = 0)
146    if test.__doc__: self.logWrite('  '+test.__doc__+'\n')
147
148  #################################
149  # Define and Substitution Supported
150  def addMakeRule(self, name, dependencies, rule = []):
151    '''Designate that "name" should be rule in the makefile header (bmake file)'''
152    self.logPrint('Defined make rule "'+name+'" with dependencies "'+str(dependencies)+'" and code '+str(rule))
153    if not isinstance(rule,list): rule = [rule]
154    self.makeRules[name] = [dependencies,rule]
155    return
156
157  def addMakeMacro(self, name, value):
158    '''Designate that "name" should be defined to "value" in the makefile header (bmake file)'''
159    self.logPrint('Defined make macro "'+name+'" to "'+str(value)+'"')
160    self.makeMacros[name] = value
161    return
162
163  def getMakeMacro(self, name):
164    return self.makeMacros.get(name)
165
166  def delMakeMacro(self, name):
167    '''Designate that "name" should be deleted (never put in) configuration header'''
168    self.logPrint('Deleting "'+name+'"')
169    if name in self.makeMacros: del self.makeMacros[name]
170    return
171
172  def addDefine(self, name, value):
173    '''Designate that "name" should be defined to "value" in the configuration header'''
174    self.logPrint('Defined "'+name+'" to "'+str(value)+'"')
175    self.defines[name] = value
176    return
177
178  def delDefine(self, name):
179    '''Designate that "name" should be deleted (never put in)  configuration header'''
180    if name in self.defines:
181      self.logPrint('Deleting "'+name+'"')
182      del self.defines[name]
183    return
184
185  def addTypedef(self, name, value):
186    '''Designate that "name" should be typedefed to "value" in the configuration header'''
187    self.logPrint('Typedefed "'+name+'" to "'+str(value)+'"')
188    self.typedefs[value] = name
189    return
190
191  def addPrototype(self, prototype, language = 'All'):
192    '''Add a missing function prototype
193       - The language argument defaults to "All"
194       - Other language choices are C, Cxx, extern C'''
195    self.logPrint('Added prototype '+prototype+' to language '+language)
196    language = language.replace('+', 'x')
197    if not language in self.prototypes:
198      self.prototypes[language] = []
199    self.prototypes[language].append(prototype)
200    return
201
202  def addSubstitution(self, name, value):
203    '''Designate that "@name@" should be replaced by "value" in all files which experience substitution'''
204    self.logPrint('Substituting "'+name+'" with "'+str(value)+'"')
205    self.subst[name] = value
206    return
207
208  def addArgumentSubstitution(self, name, arg):
209    '''Designate that "@name@" should be replaced by "arg" in all files which experience substitution'''
210    self.logPrint('Substituting "'+name+'" with '+str(arg)+'('+str(self.argDB[arg])+')')
211    self.argSubst[name] = arg
212    return
213
214  ################
215  # Program Checks
216  def checkExecutable(self, dir, name):
217    prog  = os.path.join(dir, name)
218    # also strip any \ before spaces, braces, so that we can specify paths the way we want them in makefiles.
219    prog  = prog.replace(r'\ ',' ').replace(r'\(','(').replace(r'\)',')')
220    found = 0
221    self.logWrite('    Checking for program '+prog+'...')
222    if os.path.isfile(prog) and os.access(prog, os.X_OK):
223      found = 1
224      self.logWrite('found\n')
225    else:
226      self.logWrite('not found\n')
227    return found
228
229  def getExecutable(self, names, path = [], getFullPath = 0, useDefaultPath = 0, resultName = '', setMakeMacro = 1):
230    '''Search for an executable in the list names
231       - Each name in the list is tried for each entry in the path until a name is located, then it stops
232       - If found, the path is attached to self as an attribute named "name", or "resultName" if given
233       - By default, a make macro "resultName" will hold the path'''
234    found = 0
235    if isinstance(names,str) and names.startswith('/'):
236      path = os.path.dirname(names)
237      names = os.path.basename(names)
238
239    if isinstance(names, str):
240      names = [names]
241    if isinstance(path, str):
242      path = path.split(os.path.pathsep)
243    if not len(path):
244      useDefaultPath = 1
245
246    def getNames(name, resultName):
247      import re
248      prog = re.match(r'(.*?)(?<!\\)(\s.*)',name)
249      if prog:
250        name = prog.group(1)
251        options = prog.group(2)
252      else:
253        options = ''
254      if not resultName:
255        varName = name
256      else:
257        varName = resultName
258      return name, options, varName
259
260    varName = names[0]
261    varPath = ''
262    for d in path:
263      for name in names:
264        name, options, varName = getNames(name, resultName)
265        if self.checkExecutable(d, name):
266          found = 1
267          getFullPath = 1
268          varPath = d
269          break
270      if found: break
271    if useDefaultPath and not found:
272      for d in os.environ['PATH'].split(os.path.pathsep):
273        for name in names:
274          name, options, varName = getNames(name, resultName)
275          if self.checkExecutable(d, name):
276            found = 1
277            varPath = d
278            break
279        if found: break
280    if not found:
281      dirs = self.argDB['with-executables-search-path']
282      if not isinstance(dirs, list): dirs = dirs.split(os.path.pathsep)
283      for d in dirs:
284        for name in names:
285          name, options, varName = getNames(name, resultName)
286          if self.checkExecutable(d, name):
287            found = 1
288            getFullPath = 1
289            varPath = d
290            break
291        if found: break
292
293    if found:
294      if getFullPath:
295        setattr(self, varName, os.path.abspath(os.path.join(varPath, name))+options)
296      else:
297        setattr(self, varName, name+options)
298      if setMakeMacro:
299        self.addMakeMacro(varName.upper(), getattr(self, varName))
300    else:
301      def logPrintFilesInPath(path):
302        for d in path:
303          try:
304            self.logWrite('      '+d+': '+' '.join(os.listdir(d))+'\n')
305          except Exception as e:
306            self.logWrite('      Warning accessing '+d+' gives errors: '+str(e)+'\n')
307        return
308      if path:
309        self.logWrite('  Unable to find programs: %s in listing of the specific search path: %s\n' % (names, path))
310        logPrintFilesInPath(path)
311    return found
312
313  def getExecutables(self, names, path = '', getFullPath = 0, useDefaultPath = 0, resultName = ''):
314    '''Search for an executable in the list names
315       - The full path given is searched for each name in turn
316       - If found, the path is stored in the variable "name", or "resultName" if given'''
317    for name in names:
318      if self.getExecutable(name, path = path, getFullPath = getFullPath, useDefaultPath = useDefaultPath, resultName = resultName):
319        return name
320    return None
321
322  ###############################################
323  # Preprocessor, Compiler, and Linker Operations
324  def pushLanguage(self, language):
325    if language == 'C++': language = 'Cxx'
326    self.language.append(language)
327    return self.language[-1]
328
329  def popLanguage(self):
330    self.language.pop()
331    return self.language[-1]
332
333  @contextlib.contextmanager
334  def Language(self, lang):
335    if lang is None:
336      yield
337    else:
338      try:
339        yield self.pushLanguage(lang)
340      finally:
341        self.popLanguage()
342
343  def getHeaders(self):
344    self.compilerDefines = os.path.join(self.tmpDir, 'confdefs.h')
345    self.compilerFixes   = os.path.join(self.tmpDir, 'conffix.h')
346    return
347
348  def getPreprocessor(self):
349    self.getHeaders()
350    preprocessor       = self.framework.getPreprocessorObject(self.language[-1])
351    preprocessor.checkSetup()
352    return preprocessor.getProcessor()
353
354  def getCompiler(self, lang=None):
355    with self.Language(lang):
356      self.getHeaders()
357      compiler            = self.framework.getCompilerObject(self.language[-1])
358      compiler.checkSetup()
359      self.compilerSource = os.path.join(self.tmpDir, 'conftest'+compiler.sourceExtension)
360      self.compilerObj    = os.path.join(self.tmpDir, compiler.getTarget(self.compilerSource))
361      return compiler.getProcessor()
362
363  def getCompilerFlags(self):
364    return self.framework.getCompilerObject(self.language[-1]).getFlags()
365
366  def getLinker(self):
367    self.getHeaders()
368    linker            = self.framework.getLinkerObject(self.language[-1])
369    linker.checkSetup()
370    self.linkerSource = os.path.join(self.tmpDir, 'conftest'+linker.sourceExtension)
371    self.linkerObj    = linker.getTarget(self.linkerSource, 0)
372    return linker.getProcessor()
373
374  def getLinkerFlags(self):
375    return self.framework.getLinkerObject(self.language[-1]).getFlags()
376
377  def getSharedLinker(self):
378    self.getHeaders()
379    linker            = self.framework.getSharedLinkerObject(self.language[-1])
380    linker.checkSetup()
381    self.linkerSource = os.path.join(self.tmpDir, 'conftest'+linker.sourceExtension)
382    self.linkerObj    = linker.getTarget(self.linkerSource, 1)
383    return linker.getProcessor()
384
385  def getSharedLinkerFlags(self):
386    return self.framework.getSharedLinkerObject(self.language[-1]).getFlags()
387
388  def getDynamicLinker(self):
389    self.getHeaders()
390    linker            = self.framework.getDynamicLinkerObject(self.language[-1])
391    linker.checkSetup()
392    self.linkerSource = os.path.join(self.tmpDir, 'conftest'+linker.sourceExtension)
393    self.linkerObj    = linker.getTarget(self.linkerSource, 1)
394    return linker.getProcessor()
395
396  def getDynamicLinkerFlags(self):
397    return self.framework.getDynamicLinkerObject(self.language[-1]).getFlags()
398
399  def getPreprocessorCmd(self):
400    self.getCompiler()
401    preprocessor = self.framework.getPreprocessorObject(self.language[-1])
402    preprocessor.checkSetup()
403    preprocessor.includeDirectories.add(self.tmpDir)
404    return preprocessor.getCommand(self.compilerSource)
405
406  def getCompilerCmd(self):
407    self.getCompiler()
408    compiler = self.framework.getCompilerObject(self.language[-1])
409    compiler.checkSetup()
410    compiler.includeDirectories.add(self.tmpDir)
411    return compiler.getCommand(self.compilerSource, self.compilerObj)
412
413  def getLinkerCmd(self):
414    self.getLinker()
415    linker = self.framework.getLinkerObject(self.language[-1])
416    linker.checkSetup()
417    return linker.getCommand(self.linkerSource, self.linkerObj)
418
419  def getFullLinkerCmd(self, objects, executable):
420    self.getLinker()
421    linker = self.framework.getLinkerObject(self.language[-1])
422    linker.checkSetup()
423    return linker.getCommand(objects, executable)
424
425  def getSharedLinkerCmd(self):
426    self.getSharedLinker()
427    linker = self.framework.getSharedLinkerObject(self.language[-1])
428    linker.checkSetup()
429    return linker.getCommand(self.linkerSource, self.linkerObj)
430
431  def getDynamicLinkerCmd(self):
432    self.getDynamicLinker()
433    linker = self.framework.getDynamicLinkerObject(self.language[-1])
434    linker.checkSetup()
435    return linker.getCommand(self.linkerSource, self.linkerObj)
436
437  def getCode(self, includes, body = None, codeBegin = None, codeEnd = None):
438    language = self.language[-1]
439    if includes and not includes[-1] == '\n':
440      includes += '\n'
441    if language in ['C', 'CUDA', 'Cxx', 'HIP', 'SYCL']:
442      codeStr = ''
443      if self.compilerDefines: codeStr = '#include "'+os.path.basename(self.compilerDefines)+'"\n'
444      codeStr += '#include "conffix.h"\n'+includes
445      if not body is None:
446        if codeBegin is None:
447          codeBegin = '\nint main(void) {\n'
448        if codeEnd is None:
449          if len(body) == 0:
450            codeEnd = '  return 0;\n}\n'
451          elif body.strip().endswith(';') or body.strip().endswith('}') or body.strip().endswith('\n#endif'):
452            codeEnd = '\n  return 0;\n}\n'
453          else:
454            codeEnd = ';\n  return 0;\n}\n'
455        codeStr += codeBegin+body+codeEnd
456    elif language == 'FC':
457      if not includes is None and body is None:
458        codeStr = includes
459      else:
460        codeStr = ''
461      if not body is None:
462        if codeBegin is None:
463          codeBegin = '      program main\n'
464          if not includes is None:
465            codeBegin = codeBegin+includes
466        if codeEnd is None:
467          codeEnd   = '\n      end\n'
468        codeStr += codeBegin+body+codeEnd
469    else:
470      raise RuntimeError('Cannot determine code body for language: '+language)
471    codeStr += '\n'
472    return codeStr
473
474  def preprocess(self, codeStr, timeout = 600.0):
475    def report(command, status, output, error):
476      if error or status:
477        self.logError('preprocessor', status, output, error)
478        self.logWrite('Source:\n'+self.getCode(codeStr))
479
480    command = self.getPreprocessorCmd()
481    if self.compilerDefines: self.framework.outputHeader(self.compilerDefines)
482    self.framework.outputCHeader(self.compilerFixes)
483    self.logWrite('Preprocessing source:\n'+self.getCode(codeStr))
484    f = open(self.compilerSource, 'w')
485    f.write(self.getCode(codeStr))
486    f.close()
487    (out, err, ret) = Configure.executeShellCommand(command, checkCommand = report, timeout = timeout, log = self.log, logOutputflg = False, lineLimit = 100000)
488    if self.cleanup:
489      for filename in [self.compilerDefines, self.compilerFixes, self.compilerSource]:
490        if os.path.isfile(filename): os.remove(filename)
491    return (out, err, ret)
492
493  def outputPreprocess(self, codeStr):
494    '''Return the contents of stdout when preprocessing "codeStr"'''
495    return self.preprocess(codeStr)[0]
496
497  def checkPreprocess(self, codeStr, timeout = 600.0):
498    '''Return True if no error occurred
499       - An error is signaled by a nonzero return code, or output on stderr'''
500    (out, err, ret) = self.preprocess(codeStr, timeout = timeout)
501    err = self.framework.filterPreprocessOutput(err, self.log)
502    return not ret and not len(err)
503
504  # Should be static
505  def getPreprocessorFlagsName(self, language):
506    if language == 'C':
507      flagsArg = 'CPPFLAGS'
508    elif language == 'CUDA':
509      flagsArg = 'CUDAPPFLAGS'
510    elif language == 'Cxx':
511      flagsArg = 'CXXPPFLAGS'
512    elif language == 'FC':
513      flagsArg = 'FPPFLAGS'
514    elif language == 'HIP':
515      flagsArg = 'HIPPPFLAGS'
516    elif language == 'SYCL':
517      flagsArg = 'SYCLPPFLAGS'
518    else:
519      raise RuntimeError('Unknown language: '+language)
520    return flagsArg
521
522  def getPreprocessorFlagsArg(self):
523    '''Return the name of the argument which holds the preprocessor flags for the current language'''
524    return self.getPreprocessorFlagsName(self.language[-1])
525
526  def filterCompileOutput(self, output, flag = '', filterAlways = 0):
527    return self.framework.filterCompileOutput(output, flag = flag, filterAlways = filterAlways)
528
529  def outputCompile(self, includes = '', body = '', cleanup = 1, codeBegin = None, codeEnd = None):
530    '''Return the error output from this compile and the return code'''
531    def report(command, status, output, error):
532      if error or status:
533        self.logError('compiler', status, output, error)
534      else:
535        self.logWrite('Successful compile:\n')
536      self.logWrite('Source:\n'+self.getCode(includes, body, codeBegin, codeEnd))
537
538    cleanup = cleanup and self.framework.doCleanup
539    command = self.getCompilerCmd()
540    if self.compilerDefines: self.framework.outputHeader(self.compilerDefines)
541    self.framework.outputCHeader(self.compilerFixes)
542    with open(self.compilerSource, 'w') as f:
543      f.write(self.getCode(includes, body, codeBegin, codeEnd))
544    (out, err, ret) = Configure.executeShellCommand(command, checkCommand = report, log = self.log)
545    if not os.path.isfile(self.compilerObj):
546      err += '\nPETSc Error: No output file produced'
547    if cleanup:
548      for filename in [self.compilerDefines, self.compilerFixes, self.compilerSource, self.compilerObj]:
549        if os.path.isfile(filename): os.remove(filename)
550    return (out, err, ret)
551
552  def checkCompile(self, includes = '', body = '', cleanup = 1, codeBegin = None, codeEnd = None, flag = ''):
553    '''Returns True if the compile was successful'''
554    (output, error, returnCode) = self.outputCompile(includes, body, cleanup, codeBegin, codeEnd)
555    output = self.filterCompileOutput(output+'\n'+error,flag=flag)
556    return not (returnCode or len(output))
557
558  def getCompilerFlagsName(language, compilerOnly = 0):
559    if language == 'C':
560      flagsArg = 'CFLAGS'
561    elif language == 'CUDA':
562      flagsArg = 'CUDAFLAGS'
563    elif language == 'Cxx':
564      if compilerOnly:
565        flagsArg = 'CXX_CXXFLAGS'
566      else:
567        flagsArg = 'CXXFLAGS'
568    elif language == 'HIP':
569      flagsArg = 'HIPFLAGS'
570    elif language == 'SYCL':
571      flagsArg = 'SYCLFLAGS'
572    elif language == 'FC':
573      flagsArg = 'FFLAGS'
574    else:
575      raise RuntimeError('Unknown language: '+language)
576    return flagsArg
577  getCompilerFlagsName = staticmethod(getCompilerFlagsName)
578
579  def getCompilerFlagsArg(self, compilerOnly = 0):
580    '''Return the name of the argument which holds the compiler flags for the current language'''
581    return self.getCompilerFlagsName(self.language[-1], compilerOnly)
582
583  def filterLinkOutput(self, output, filterAlways = 0):
584    return self.framework.filterLinkOutput(output, filterAlways = filterAlways)
585
586  def outputLink(self, includes, body, cleanup = 1, codeBegin = None, codeEnd = None, shared = 0, linkLanguage=None, examineOutput=lambda ret,out,err:None,flag=''):
587    import sys
588
589    (out, err, ret) = self.outputCompile(includes, body, cleanup = 0, codeBegin = codeBegin, codeEnd = codeEnd)
590    examineOutput(ret, out, err)
591    out = self.filterCompileOutput(out+'\n'+err,flag=flag)
592    if ret or len(out):
593      self.logPrint('Compile failed inside link\n'+out)
594      self.linkerObj = ''
595      return (out, ret)
596
597    cleanup = cleanup and self.framework.doCleanup
598
599    langPushed = 0
600    if linkLanguage is not None and linkLanguage != self.language[-1]:
601      self.pushLanguage(linkLanguage)
602      langPushed = 1
603    if shared == 'dynamic':
604      cmd = self.getDynamicLinkerCmd()
605    elif shared:
606      cmd = self.getSharedLinkerCmd()
607    else:
608      cmd = self.getLinkerCmd()
609    if langPushed:
610      self.popLanguage()
611
612    linkerObj = self.linkerObj
613    def report(command, status, output, error):
614      if error or status:
615        self.logError('linker', status, output, error)
616        examineOutput(status, output, error)
617      return
618    (out, err, ret) = Configure.executeShellCommand(cmd, checkCommand = report, log = self.log)
619    self.linkerObj = linkerObj
620    if os.path.isfile(self.compilerObj): os.remove(self.compilerObj)
621    if cleanup:
622      if os.path.isfile(self.linkerObj):os.remove(self.linkerObj)
623      pdbfile = os.path.splitext(self.linkerObj)[0]+'.pdb'
624      if os.path.isfile(pdbfile): os.remove(pdbfile)
625    return (out+'\n'+err, ret)
626
627  def checkLink(self, includes = '', body = '', cleanup = 1, codeBegin = None, codeEnd = None, shared = 0, linkLanguage=None, examineOutput=lambda ret,out,err:None):
628    (output, returnCode) = self.outputLink(includes, body, cleanup, codeBegin, codeEnd, shared, linkLanguage, examineOutput)
629    output = self.filterLinkOutput(output)
630    return not (returnCode or len(output))
631
632  def getLinkerFlagsName(language):
633    if language in ['C', 'CUDA', 'Cxx', 'FC', 'HIP']:
634      flagsArg = 'LDFLAGS'
635    elif language == 'SYCL':
636      flagsArg = 'SYCLC_LINKER_FLAGS' # refer to SYCL.py. I need standalone sycl linker flags in make macros, so I don't use LDFLAGS
637    else:
638      raise RuntimeError('Unknown language: '+language)
639    return flagsArg
640  getLinkerFlagsName = staticmethod(getLinkerFlagsName)
641
642  def getLinkerFlagsArg(self):
643    '''Return the name of the argument which holds the linker flags for the current language'''
644    return self.getLinkerFlagsName(self.language[-1])
645
646  def outputRun(self, includes, body, cleanup = 1, defaultOutputArg = '', executor = None,linkLanguage=None, timeout = 60, threads = 1):
647    if not self.checkLink(includes, body, cleanup = 0, linkLanguage=linkLanguage): return ('', 1)
648    self.logWrite('Testing executable '+self.linkerObj+' to see if it can be run\n')
649    if not os.path.isfile(self.linkerObj):
650      self.logWrite('ERROR executable '+self.linkerObj+' does not exist\n')
651      return ('', 1)
652    if not os.access(self.linkerObj, os.X_OK):
653      self.logWrite('ERROR while running executable: '+self.linkerObj+' is not executable\n')
654      return ('', 1)
655    if self.argDB['with-batch']:
656      if defaultOutputArg:
657        if defaultOutputArg in self.argDB:
658          return (self.argDB[defaultOutputArg], 0)
659        else:
660          raise ConfigureSetupError('Must give a default value for '+defaultOutputArg+' since generated executables cannot be run with the --with-batch option')
661      else:
662        raise ConfigureSetupError('Generated executables cannot be run with the --with-batch option')
663    cleanup = cleanup and self.framework.doCleanup
664    if executor:
665      command = executor+' '+self.linkerObj
666    else:
667      command = self.linkerObj
668    output  = ''
669    error   = ''
670    status  = 1
671    self.logWrite('Executing: '+command+'\n')
672    try:
673      (output, error, status) = Configure.executeShellCommand(command, log = self.log, timeout = timeout, threads = threads)
674    except RuntimeError as e:
675      self.logWrite('ERROR while running executable: '+str(e)+'\n')
676      if str(e).find('Runaway process exceeded time limit') > -1:
677        raise RuntimeError('Runaway process exceeded time limit')
678    if os.path.isfile(self.compilerObj):
679      try:
680        os.remove(self.compilerObj)
681      except RuntimeError as e:
682        self.logWrite('ERROR while removing object file: '+str(e)+'\n')
683    if cleanup and os.path.isfile(self.linkerObj):
684      try:
685        if os.path.exists('/usr/bin/cygcheck.exe'): time.sleep(1)
686        os.remove(self.linkerObj)
687      except RuntimeError as e:
688        self.logWrite('ERROR while removing executable file: '+str(e)+'\n')
689    return (output+error, status)
690
691  def checkRun(self, includes = '', body = '', cleanup = 1, defaultArg = '', executor = None, linkLanguage=None, timeout = 60, threads = 1):
692    self.logWrite('======== Checking running linked program\n')
693    (output, returnCode) = self.outputRun(includes, body, cleanup, defaultArg, executor,linkLanguage=linkLanguage, timeout = timeout, threads = threads)
694    return not returnCode
695
696  def splitLibs(self,libArgs):
697    '''Takes a string containing a list of libraries (including potentially -L, -l, -w etc) and generates a list of libraries'''
698    dirs = []
699    libs = []
700    for arg in libArgs.split(' '):
701      if not arg: continue
702      if arg.startswith('-L'):
703        dirs.append(arg[2:])
704      elif arg.startswith('-l'):
705        libs.append(arg[2:])
706      elif not arg.startswith('-'):
707        libs.append(arg)
708    libArgs = []
709    for lib in libs:
710      if not os.path.isabs(lib):
711        added = 0
712        for dir in dirs:
713          if added:
714            break
715          for ext in ['a', 'so','dylib']:
716            filename = os.path.join(dir, 'lib'+lib+'.'+ext)
717            if os.path.isfile(filename):
718              libArgs.append(filename)
719              added = 1
720              break
721      else:
722        libArgs.append(lib)
723    return libArgs
724
725  def splitIncludes(self,incArgs):
726    '''Takes a string containing a list of include directories with -I and generates a list of includes'''
727    includes = []
728    for inc in incArgs.split(' '):
729      if inc.startswith('-I'):
730        # check if directory exists?
731        includes.append(inc[2:])
732    return includes
733
734  def setupPackageDependencies(self, framework):
735    '''All calls to the framework addPackageDependency() should be made here'''
736    pass
737
738  def setupDependencies(self, framework):
739    '''All calls to the framework require() should be made here'''
740    self.framework = framework
741
742  def configure(self):
743    pass
744
745  def no_configure(self):
746    pass
747