From 7f7363f5f46f0e762bbaee089db9904324ce11f9 Mon Sep 17 00:00:00 2001 From: Chris Johns Date: Tue, 15 Sep 2020 11:20:44 +1000 Subject: waf: Implement module dependency checking in the build system - Do not build a test if a dependency is not enabled. - Perform a dependency check and generate an error if an enabled module depends on a disabled module. Closes #4077 --- builder.py | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 97 insertions(+), 17 deletions(-) (limited to 'builder.py') diff --git a/builder.py b/builder.py index fe2158a9..7e79d5f9 100755 --- a/builder.py +++ b/builder.py @@ -42,6 +42,11 @@ import os import re import sys +try: + import configparser +except ImportError: + import ConfigParser as configparser + # # Global controls. # @@ -63,6 +68,9 @@ verboseDetail = 2 verboseMoreDetail = 3 verboseDebug = 4 +BUILDSET_DIR = "buildset" +BUILDSET_DEFAULT = "buildset/default.ini" + def verbose(level=verboseInfo): return verboseLevel >= level @@ -761,8 +769,10 @@ class File(object): state = state and (self.pathComposer == self.pathComposer) state = state and (self.originPath == self.originPath) state = state and (self.forwardConverter == self.forwardConverter) - state = state and (self.self.reverseConverter == self.self.reverseConverter) - state = state and (self.buildSystemComposer == self.buildSystemComposer) + state = state and (self.self.reverseConverter + == self.self.reverseConverter) + state = state and (self.buildSystemComposer + == self.buildSystemComposer) return state def processSource(self, forward): @@ -794,13 +804,12 @@ class Module(object): def __init__(self, manager, name, enabled=True): self.manager = manager self.name = name - self.conditionalOn = "none" self.files = [] self.cpuDependentSourceFiles = {} self.dependencies = [] def __str__(self): - out = [self.name + ': conditional-on=' + self.conditionalOn] + out = [self.name + ':'] if len(self.dependencies) > 0: out += [' Deps: ' + str(len(self.dependencies))] out += [' ' + type(d).__name__ for d in self.dependencies] @@ -981,13 +990,16 @@ class Module(object): NoConverter(), assertSourceFile, sourceFileBuildComposer) - def addTest(self, testFragementComposer): + def addTest(self, testFragementComposer, dependencies=[]): self.files += [ File('user', testFragementComposer.testName, PathComposer(), NoConverter(), NoConverter(), testFragementComposer) ] + self.dependencies += dependencies def addDependency(self, dep): + if not isinstance(dep, str): + raise TypeError('dependencies are a string: %s' % (self.name)) self.dependencies += [dep] @@ -1010,18 +1022,75 @@ class ModuleManager(object): out += [str(self.modules[m]), ''] return os.linesep.join(out) + def _loadIni(self, ini_file): + if not os.path.exists(ini_file): + raise FileNotFoundError('file not found: %s' % (ini_file)) + ini = configparser.ConfigParser() + ini.read(ini_file) + if not ini.has_section('general'): + raise Exception( + "'{}' is missing a general section.".format(ini_file)) + if not ini.has_option('general', 'name'): + raise Exception("'{}' is missing a general/name.".format(ini_file)) + if ini.has_option('general', 'extends'): + extends = ini.get('general', 'extends') + extendfile = None + basepath = os.path.dirname(ini_file) + if os.path.isfile(os.path.join(basepath, extends)): + extendfile = os.path.join(basepath, extends) + elif os.path.isfile(os.path.join(BUILDSET_DIR, extends)): + extendfile = os.path.join(BUILDSET_DIR, extends) + else: + raise Exception( + "'{}': Invalid file given for general/extends:'{}'".format( + ini_file, extends)) + base = self._loadIni(extendfile) + for s in ini.sections(): + if not base.has_section(s): + base.add_section(s) + for o in ini.options(s): + val = ini.get(s, o) + base.set(s, o, val) + ini = base + return ini + + def _checkDependencies(self): + enabled_modules = self.getEnabledModules() + enabled_modules.remove('tests') + for mod in enabled_modules: + if mod not in self.modules: + raise KeyError('enabled module not found: %s' % (mod)) + for dep in self.modules[mod].dependencies: + if dep not in self.modules: + print(type(dep)) + raise KeyError('dependent module not found: %s' % (dep)) + if dep not in enabled_modules: + raise Exception('module "%s" dependency "%s" not enabled' % + (mod, dep)) + def getAllModules(self): if 'modules' in self.configuration: - return self.configuration['modules'] + return sorted(self.configuration['modules']) return [] def getEnabledModules(self): if 'modules-enabled' in self.configuration: - return self.configuration['modules-enabled'] + return sorted(self.configuration['modules-enabled']) return [] def addModule(self, module): - self.modules[module.name] = module + name = module.name + if name in self.modules: + raise KeyError('module already added: %' % (name)) + self.modules[name] = module + if 'modules' not in self.configuration: + self.configuration['modules'] = [] + if 'modules-enabled' not in self.configuration: + self.configuration['modules-enabled'] = [] + self.configuration['modules'] += [name] + self.configuration['modules-enabled'] += [name] + self.configuration['modules'].sort() + self.configuration['modules-enabled'].sort def processSource(self, direction): if verbose(verboseDetail): @@ -1035,15 +1104,6 @@ class ModuleManager(object): def getConfiguration(self): return copy.deepcopy(self.configuration) - def updateConfiguration(self, config): - self.configuration.update(config) - - def setModuleConfigiuration(self): - mods = sorted(self.modules.keys()) - self.configuration['modules'] = mods - # Enabled modules are overwritten by config file. Default to all. - self.configuration['modules-enabled'] = mods - def generateBuild(self, only_enabled=True): modules_to_process = self.getEnabledModules() # Used for copy between FreeBSD and RTEMS @@ -1053,6 +1113,7 @@ class ModuleManager(object): if m not in self.modules: raise KeyError('enabled module not registered: %s' % (m)) self.modules[m].generate() + self._checkDependencies() def duplicateCheck(self): dups = [] @@ -1068,6 +1129,25 @@ class ModuleManager(object): dups += [(m, mod, fm.getPath(), fm.getSpace())] return dups + def loadConfig(self, config=BUILDSET_DEFAULT): + if 'name' in self.configuration: + raise KeyError('configuration already loaded: %s (%s)' % \ + (self.configuration['name'], config)) + ini = self._loadIni(config) + self.configuration['name'] = ini.get('general', 'name') + self.configuration['modules-enabled'] = [] + mods = [] + if ini.has_section('modules'): + mods = ini.options('modules') + for mod in mods: + if ini.getboolean('modules', mod): + self.configuration['modules-enabled'].append(mod) + + def getName(self): + if 'name' not in self.configuration: + raise KeyError('configuration not loaded') + return self.configuration['name'] + def setGenerators(self): self.generator['convert'] = Converter self.generator['no-convert'] = NoConverter -- cgit v1.2.3