''' config.base.Configure is the base class for all configure objects. It handles several types of interaction: Framework hooks --------------- The Framework will first instantiate the object and call setupDependencies(). All require() calls should be made in that method. The Framework will then call configure(). If it succeeds, the object will be marked as configured. Generic test execution ---------------------- All configure tests should be run using executeTest() which formats the output and adds metadata for the log. Preprocessing, Compiling, Linking, and Running ---------------------------------------------- Two forms of this check are provided for each operation. The first is an "output" form which is intended to provide the status and complete output of the command. The second, or "check" form will return a success or failure indication based upon the status and output. outputPreprocess(), checkPreprocess(), preprocess() outputCompile(), checkCompile() outputLink(), checkLink() outputRun(), checkRun() The language used for these operation is managed with a stack, similar to autoconf. pushLanguage(), popLanguage() We also provide special forms used to check for valid compiler and linker flags, optionally adding them to the defaults. checkCompilerFlag(), addCompilerFlag() checkLinkerFlag(), addLinkerFlag() Finding Executables ------------------- getExecutable(), getExecutables(), checkExecutable() Output ------ addDefine(), addSubstitution(), addArgumentSubstitution(), addTypedef(), addPrototype() addMakeMacro(), addMakeRule() The object may define a headerPrefix member, which will be appended, followed by an underscore, to every define which is output from it. Similarly, a substPrefix can be defined which applies to every substitution from the object. Typedefs and function prototypes are placed in a separate header in order to accommodate languages such as Fortran whose preprocessor can sometimes fail at these statements. ''' import script import os import time import contextlib class ConfigureSetupError(Exception): pass class Configure(script.Script): def __init__(self, framework, tmpDir = None): script.Script.__init__(self, framework.clArgs, framework.argDB) self.framework = framework self.defines = {} self.makeRules = {} self.makeMacros = {} self.typedefs = {} self.prototypes = {} self.subst = {} self.argSubst = {} self.language = [] if not tmpDir is None: self.tmpDir = tmpDir try: # The __init__ method may be called to reinitialize in the future (e.g., # updateCompilers()) and will need to be re-setup in that case. delattr(self, '_setup') except AttributeError: pass return def setup(self): if hasattr(self, '_setup'): return script.Script.setup(self) self._setup = 1 self.pushLanguage('C') def getTmpDir(self): if not hasattr(self, '_tmpDir'): self._tmpDir = os.path.join(self.framework.tmpDir, self.__module__) if not os.path.isdir(self._tmpDir): os.mkdir(self._tmpDir) return self._tmpDir def setTmpDir(self, temp): if hasattr(self, '_tmpDir'): if os.path.isdir(self._tmpDir): import shutil shutil.rmtree(self._tmpDir) if temp is None: delattr(self, '_tmpDir') if not temp is None: self._tmpDir = temp return tmpDir = property(getTmpDir, setTmpDir, doc = 'Temporary directory for test byproducts') def __str__(self): return '' def logError(self, component, status, output, error): if status: exitstr = ' exit code ' + str(status) else: exitstr = 'exit code 0' self.logWrite('Possible ERROR while running %s:%s\n' % (component, exitstr)) if output: self.logWrite('stdout:\n' + output) if error: self.logWrite('stderr:\n' + error) def executeTest(self, test, args = [], kargs = {}): '''Prints the function and class information for the test and then runs the test''' import time self.logPrintDivider() 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) if test.__doc__: self.logWrite(' '+test.__doc__+'\n') #t = time.time() if not isinstance(args, list): args = [args] ret = test(*args,**kargs) #self.logPrint(' TIME: '+str(time.time() - t)+' sec', debugSection = 'screen', indent = 0) return ret def printTest(self, test): '''Prints the function and class information for a test''' self.logPrintDivider() 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) if test.__doc__: self.logWrite(' '+test.__doc__+'\n') ################################# # Define and Substitution Supported def addMakeRule(self, name, dependencies, rule = []): '''Designate that "name" should be rule in the makefile header (bmake file)''' self.logPrint('Defined make rule "'+name+'" with dependencies "'+str(dependencies)+'" and code '+str(rule)) if not isinstance(rule,list): rule = [rule] self.makeRules[name] = [dependencies,rule] return def addMakeMacro(self, name, value): '''Designate that "name" should be defined to "value" in the makefile header (bmake file)''' self.logPrint('Defined make macro "'+name+'" to "'+str(value)+'"') self.makeMacros[name] = value return def getMakeMacro(self, name): return self.makeMacros.get(name) def delMakeMacro(self, name): '''Designate that "name" should be deleted (never put in) configuration header''' self.logPrint('Deleting "'+name+'"') if name in self.makeMacros: del self.makeMacros[name] return def addDefine(self, name, value): '''Designate that "name" should be defined to "value" in the configuration header''' self.logPrint('Defined "'+name+'" to "'+str(value)+'"') self.defines[name] = value return def delDefine(self, name): '''Designate that "name" should be deleted (never put in) configuration header''' if name in self.defines: self.logPrint('Deleting "'+name+'"') del self.defines[name] return def addTypedef(self, name, value): '''Designate that "name" should be typedefed to "value" in the configuration header''' self.logPrint('Typedefed "'+name+'" to "'+str(value)+'"') self.typedefs[value] = name return def addPrototype(self, prototype, language = 'All'): '''Add a missing function prototype - The language argument defaults to "All" - Other language choices are C, Cxx, extern C''' self.logPrint('Added prototype '+prototype+' to language '+language) language = language.replace('+', 'x') if not language in self.prototypes: self.prototypes[language] = [] self.prototypes[language].append(prototype) return def addSubstitution(self, name, value): '''Designate that "@name@" should be replaced by "value" in all files which experience substitution''' self.logPrint('Substituting "'+name+'" with "'+str(value)+'"') self.subst[name] = value return def addArgumentSubstitution(self, name, arg): '''Designate that "@name@" should be replaced by "arg" in all files which experience substitution''' self.logPrint('Substituting "'+name+'" with '+str(arg)+'('+str(self.argDB[arg])+')') self.argSubst[name] = arg return ################ # Program Checks def checkExecutable(self, dir, name): prog = os.path.join(dir, name) # also strip any \ before spaces, braces, so that we can specify paths the way we want them in makefiles. prog = prog.replace(r'\ ',' ').replace(r'\(','(').replace(r'\)',')') found = 0 self.logWrite(' Checking for program '+prog+'...') if os.path.isfile(prog) and os.access(prog, os.X_OK): found = 1 self.logWrite('found\n') else: self.logWrite('not found\n') return found def getExecutable(self, names, path = [], getFullPath = 0, useDefaultPath = 0, resultName = '', setMakeMacro = 1): '''Search for an executable in the list names - Each name in the list is tried for each entry in the path until a name is located, then it stops - If found, the path is attached to self as an attribute named "name", or "resultName" if given - By default, a make macro "resultName" will hold the path''' found = 0 if isinstance(names,str) and names.startswith('/'): path = os.path.dirname(names) names = os.path.basename(names) if isinstance(names, str): names = [names] if isinstance(path, str): path = path.split(os.path.pathsep) if not len(path): useDefaultPath = 1 def getNames(name, resultName): import re prog = re.match(r'(.*?)(? -1: raise RuntimeError('Runaway process exceeded time limit') if os.path.isfile(self.compilerObj): try: os.remove(self.compilerObj) except RuntimeError as e: self.logWrite('ERROR while removing object file: '+str(e)+'\n') if cleanup and os.path.isfile(self.linkerObj): try: if os.path.exists('/usr/bin/cygcheck.exe'): time.sleep(1) os.remove(self.linkerObj) except RuntimeError as e: self.logWrite('ERROR while removing executable file: '+str(e)+'\n') return (output+error, status) def checkRun(self, includes = '', body = '', cleanup = 1, defaultArg = '', executor = None, linkLanguage=None, timeout = 60, threads = 1): self.logWrite('======== Checking running linked program\n') (output, returnCode) = self.outputRun(includes, body, cleanup, defaultArg, executor,linkLanguage=linkLanguage, timeout = timeout, threads = threads) return not returnCode def splitLibs(self,libArgs): '''Takes a string containing a list of libraries (including potentially -L, -l, -w etc) and generates a list of libraries''' dirs = [] libs = [] for arg in libArgs.split(' '): if not arg: continue if arg.startswith('-L'): dirs.append(arg[2:]) elif arg.startswith('-l'): libs.append(arg[2:]) elif not arg.startswith('-'): libs.append(arg) libArgs = [] for lib in libs: if not os.path.isabs(lib): added = 0 for dir in dirs: if added: break for ext in ['a', 'so','dylib']: filename = os.path.join(dir, 'lib'+lib+'.'+ext) if os.path.isfile(filename): libArgs.append(filename) added = 1 break else: libArgs.append(lib) return libArgs def splitIncludes(self,incArgs): '''Takes a string containing a list of include directories with -I and generates a list of includes''' includes = [] for inc in incArgs.split(' '): if inc.startswith('-I'): # check if directory exists? includes.append(inc[2:]) return includes def setupPackageDependencies(self, framework): '''All calls to the framework addPackageDependency() should be made here''' pass def setupDependencies(self, framework): '''All calls to the framework require() should be made here''' self.framework = framework def configure(self): pass def no_configure(self): pass