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
This commit is contained in:
Chris Johns 2020-09-15 11:20:44 +10:00
parent c38f93b0c6
commit 7f7363f5f4
5 changed files with 143 additions and 104 deletions

View File

@ -42,6 +42,11 @@ import os
import re import re
import sys import sys
try:
import configparser
except ImportError:
import ConfigParser as configparser
# #
# Global controls. # Global controls.
# #
@ -63,6 +68,9 @@ verboseDetail = 2
verboseMoreDetail = 3 verboseMoreDetail = 3
verboseDebug = 4 verboseDebug = 4
BUILDSET_DIR = "buildset"
BUILDSET_DEFAULT = "buildset/default.ini"
def verbose(level=verboseInfo): def verbose(level=verboseInfo):
return verboseLevel >= level return verboseLevel >= level
@ -761,8 +769,10 @@ class File(object):
state = state and (self.pathComposer == self.pathComposer) state = state and (self.pathComposer == self.pathComposer)
state = state and (self.originPath == self.originPath) state = state and (self.originPath == self.originPath)
state = state and (self.forwardConverter == self.forwardConverter) state = state and (self.forwardConverter == self.forwardConverter)
state = state and (self.self.reverseConverter == self.self.reverseConverter) state = state and (self.self.reverseConverter
state = state and (self.buildSystemComposer == self.buildSystemComposer) == self.self.reverseConverter)
state = state and (self.buildSystemComposer
== self.buildSystemComposer)
return state return state
def processSource(self, forward): def processSource(self, forward):
@ -794,13 +804,12 @@ class Module(object):
def __init__(self, manager, name, enabled=True): def __init__(self, manager, name, enabled=True):
self.manager = manager self.manager = manager
self.name = name self.name = name
self.conditionalOn = "none"
self.files = [] self.files = []
self.cpuDependentSourceFiles = {} self.cpuDependentSourceFiles = {}
self.dependencies = [] self.dependencies = []
def __str__(self): def __str__(self):
out = [self.name + ': conditional-on=' + self.conditionalOn] out = [self.name + ':']
if len(self.dependencies) > 0: if len(self.dependencies) > 0:
out += [' Deps: ' + str(len(self.dependencies))] out += [' Deps: ' + str(len(self.dependencies))]
out += [' ' + type(d).__name__ for d in self.dependencies] out += [' ' + type(d).__name__ for d in self.dependencies]
@ -981,13 +990,16 @@ class Module(object):
NoConverter(), assertSourceFile, NoConverter(), assertSourceFile,
sourceFileBuildComposer) sourceFileBuildComposer)
def addTest(self, testFragementComposer): def addTest(self, testFragementComposer, dependencies=[]):
self.files += [ self.files += [
File('user', testFragementComposer.testName, PathComposer(), File('user', testFragementComposer.testName, PathComposer(),
NoConverter(), NoConverter(), testFragementComposer) NoConverter(), NoConverter(), testFragementComposer)
] ]
self.dependencies += dependencies
def addDependency(self, dep): def addDependency(self, dep):
if not isinstance(dep, str):
raise TypeError('dependencies are a string: %s' % (self.name))
self.dependencies += [dep] self.dependencies += [dep]
@ -1010,18 +1022,75 @@ class ModuleManager(object):
out += [str(self.modules[m]), ''] out += [str(self.modules[m]), '']
return os.linesep.join(out) 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): def getAllModules(self):
if 'modules' in self.configuration: if 'modules' in self.configuration:
return self.configuration['modules'] return sorted(self.configuration['modules'])
return [] return []
def getEnabledModules(self): def getEnabledModules(self):
if 'modules-enabled' in self.configuration: if 'modules-enabled' in self.configuration:
return self.configuration['modules-enabled'] return sorted(self.configuration['modules-enabled'])
return [] return []
def addModule(self, module): 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): def processSource(self, direction):
if verbose(verboseDetail): if verbose(verboseDetail):
@ -1035,15 +1104,6 @@ class ModuleManager(object):
def getConfiguration(self): def getConfiguration(self):
return copy.deepcopy(self.configuration) 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): def generateBuild(self, only_enabled=True):
modules_to_process = self.getEnabledModules() modules_to_process = self.getEnabledModules()
# Used for copy between FreeBSD and RTEMS # Used for copy between FreeBSD and RTEMS
@ -1053,6 +1113,7 @@ class ModuleManager(object):
if m not in self.modules: if m not in self.modules:
raise KeyError('enabled module not registered: %s' % (m)) raise KeyError('enabled module not registered: %s' % (m))
self.modules[m].generate() self.modules[m].generate()
self._checkDependencies()
def duplicateCheck(self): def duplicateCheck(self):
dups = [] dups = []
@ -1068,6 +1129,25 @@ class ModuleManager(object):
dups += [(m, mod, fm.getPath(), fm.getSpace())] dups += [(m, mod, fm.getPath(), fm.getSpace())]
return dups 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): def setGenerators(self):
self.generator['convert'] = Converter self.generator['convert'] = Converter
self.generator['no-convert'] = NoConverter self.generator['no-convert'] = NoConverter

View File

@ -150,6 +150,7 @@ try:
build = builder.ModuleManager() build = builder.ModuleManager()
libbsd.load(build) libbsd.load(build)
build.loadConfig()
build.generateBuild(only_enabled=False) build.generateBuild(only_enabled=False)
dups = build.duplicateCheck() dups = build.duplicateCheck()

View File

@ -960,7 +960,7 @@ class dev_usb_controller(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['dev_usb']) self.addDependency('dev_usb')
self.addKernelSpaceHeaderFiles( self.addKernelSpaceHeaderFiles(
[ [
'sys/dev/usb/controller/ohci.h', 'sys/dev/usb/controller/ohci.h',
@ -999,7 +999,7 @@ class dev_usb_input(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['dev_usb']) self.addDependency('dev_usb')
self.addKernelSpaceHeaderFiles( self.addKernelSpaceHeaderFiles(
[ [
'sys/dev/usb/input/usb_rdesc.h', 'sys/dev/usb/input/usb_rdesc.h',
@ -1027,7 +1027,7 @@ class dev_usb_net(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['dev_usb']) self.addDependency('dev_usb')
self.addKernelSpaceHeaderFiles( self.addKernelSpaceHeaderFiles(
[ [
'sys/dev/usb/net/if_auereg.h', 'sys/dev/usb/net/if_auereg.h',
@ -1077,7 +1077,7 @@ class dev_usb_quirk(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['dev_usb']) self.addDependency('dev_usb')
self.addKernelSpaceHeaderFiles( self.addKernelSpaceHeaderFiles(
[ [
'sys/dev/usb/quirk/usb_quirk.h', 'sys/dev/usb/quirk/usb_quirk.h',
@ -1100,7 +1100,7 @@ class dev_usb_serial(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['dev_usb']) self.addDependency('dev_usb')
self.addKernelSpaceHeaderFiles( self.addKernelSpaceHeaderFiles(
[ [
'sys/dev/usb/serial/uftdi_reg.h', 'sys/dev/usb/serial/uftdi_reg.h',
@ -1145,7 +1145,7 @@ class dev_usb_storage(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['dev_usb']) self.addDependency('dev_usb')
self.addKernelSpaceSourceFiles( self.addKernelSpaceSourceFiles(
[ [
'sys/dev/usb/storage/umass.c', 'sys/dev/usb/storage/umass.c',
@ -1163,7 +1163,7 @@ class dev_usb_controller_bbb(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['dev_usb']) self.addDependency('dev_usb')
self.addKernelSpaceHeaderFiles( self.addKernelSpaceHeaderFiles(
[ [
'sys/arm/ti/ti_cpuid.h', 'sys/arm/ti/ti_cpuid.h',
@ -1200,7 +1200,7 @@ class dev_usb_wlan(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['dev_usb']) self.addDependency('dev_usb')
self.addKernelSpaceHeaderFiles( self.addKernelSpaceHeaderFiles(
[ [
'sys/dev/usb/wlan/if_rsureg.h', 'sys/dev/usb/wlan/if_rsureg.h',
@ -1250,7 +1250,7 @@ class dev_wlan_rtwn(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['dev_usb']) self.addDependency('dev_usb')
self.addKernelSpaceHeaderFiles( self.addKernelSpaceHeaderFiles(
[ [
'sys/dev/rtwn/if_rtwn_beacon.h', 'sys/dev/rtwn/if_rtwn_beacon.h',
@ -2875,7 +2875,7 @@ class nfsv2(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['rpc_user']) self.addDependency('rpc_user')
self.addRTEMSUserSourceFiles( self.addRTEMSUserSourceFiles(
[ [
'nfsclient/mount_prot_xdr.c', 'nfsclient/mount_prot_xdr.c',
@ -3276,7 +3276,7 @@ class crypto_openssl(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['user_space']) self.addDependency('user_space')
self.addUserSpaceHeaderFiles( self.addUserSpaceHeaderFiles(
[ [
'crypto/openssl/crypto/aes/aes_locl.h', 'crypto/openssl/crypto/aes/aes_locl.h',
@ -4258,7 +4258,7 @@ class usr_bin_openssl(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addDependency(mm['crypto_openssl']) self.addDependency('crypto_openssl')
self.addUserSpaceHeaderFiles( self.addUserSpaceHeaderFiles(
[ [
'crypto/openssl/apps/apps.h', 'crypto/openssl/apps/apps.h',
@ -5328,7 +5328,8 @@ class tests(builder.Module):
def generate(self): def generate(self):
mm = self.manager mm = self.manager
self.addTest(mm.generator['test']('epoch01', ['test_main'], extraLibs = ['rtemstest'])) self.addTest(mm.generator['test']('epoch01', ['test_main'], extraLibs = ['rtemstest']))
self.addTest(mm.generator['test']('nfs01', ['test_main'], netTest = True)) self.addTest(mm.generator['test']('nfs01', ['test_main'], netTest = True),
['nfsv2'])
self.addTest(mm.generator['test']('foobarclient', ['test_main'], self.addTest(mm.generator['test']('foobarclient', ['test_main'],
runTest = False, netTest = True)) runTest = False, netTest = True))
self.addTest(mm.generator['test']('foobarserver', ['test_main'], self.addTest(mm.generator['test']('foobarserver', ['test_main'],
@ -5498,7 +5499,5 @@ def load(mm):
mm.addModule(tests(mm)) mm.addModule(tests(mm))
mm.setModuleConfigiuration()
# XXX TODO Check that no file is also listed in empty # XXX TODO Check that no file is also listed in empty
# XXX TODO Check that no file in in two modules # XXX TODO Check that no file in in two modules

View File

@ -46,6 +46,10 @@ import builder
import rtems_waf.rtems as rtems import rtems_waf.rtems as rtems
BUILDSET_DIR = builder.BUILDSET_DIR
BUILDSET_DEFAULT = builder.BUILDSET_DEFAULT
windows = os.name == 'nt' windows = os.name == 'nt'
if windows: if windows:
@ -138,9 +142,15 @@ class Builder(builder.ModuleManager):
self.data = {} self.data = {}
for mn in self.getEnabledModules(): enabled_modules = self.getEnabledModules()
for mn in enabled_modules:
m = self[mn] m = self[mn]
if m.conditionalOn == "none": enabled = True
for dep in m.dependencies:
if dep not in enabled_modules:
enabled = False
break
if enabled:
for f in m.files: for f in m.files:
_dataInsert(self.data, 'all', f.getSpace(), _dataInsert(self.data, 'all', f.getSpace(),
f.getFragment()) f.getFragment())

75
wscript
View File

@ -45,76 +45,24 @@ except:
import sys import sys
sys.exit(1) sys.exit(1)
import libbsd
import waf_libbsd
import os.path import os.path
import runpy import runpy
import sys import sys
try:
import configparser
except ImportError:
import ConfigParser as configparser
import waflib.Options import waflib.Options
import libbsd
import waf_libbsd
builders = {} builders = {}
BUILDSET_DIR = "buildset"
BUILDSET_DEFAULT = "buildset/default.ini"
def load_ini(conf, f):
ini = configparser.ConfigParser()
ini.read(f)
if not ini.has_section('general'):
conf.fatal("'{}' is missing a general section.".format(f))
if not ini.has_option('general', 'name'):
conf.fatal("'{}' is missing a general/name.".format(f))
if ini.has_option('general', 'extends'):
extends = ini.get('general', 'extends')
extendfile = None
basepath = os.path.dirname(f)
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:
conf.fatal(
"'{}': Invalid file given for general/extends:'{}'".format(
f, extends))
base = load_ini(conf, 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 load_config(conf, f):
ini = load_ini(conf, f)
config = {}
config['name'] = ini.get('general', 'name')
config['modules-enabled'] = []
mods = []
if ini.has_section('modules'):
mods = ini.options('modules')
for mod in mods:
if ini.getboolean('modules', mod):
config['modules-enabled'].append(mod)
return config
def update_builders(ctx, buildset_opt): def update_builders(ctx, buildset_opt):
global builders global builders
builders = {} builders = {}
buildsets = [] buildsets = []
if buildset_opt == []: if buildset_opt == []:
buildset_opt.append(BUILDSET_DEFAULT) buildset_opt.append(waf_libbsd.BUILDSET_DEFAULT)
for bs in buildset_opt: for bs in buildset_opt:
if os.path.isdir(bs): if os.path.isdir(bs):
for f in os.listdir(bs): for f in os.listdir(bs):
@ -123,15 +71,16 @@ def update_builders(ctx, buildset_opt):
else: else:
for f in bs.split(','): for f in bs.split(','):
buildsets += [f] buildsets += [f]
for bs in buildsets: for bs in buildsets:
try:
builder = waf_libbsd.Builder() builder = waf_libbsd.Builder()
libbsd.load(builder) libbsd.load(builder)
bsconfig = load_config(ctx, bs) builder.loadConfig(bs)
bsname = bsconfig['name']
builder.updateConfiguration(bsconfig)
builder.generate(rtems_version) builder.generate(rtems_version)
builders[bsname] = builder except Exception as exc:
raise
ctx.fatal(str(exc))
builders[builder.getName()] = builder
def bsp_init(ctx, env, contexts): def bsp_init(ctx, env, contexts):
@ -250,7 +199,7 @@ def configure(conf):
conf.env.OPTIMIZATION = conf.options.optimization conf.env.OPTIMIZATION = conf.options.optimization
conf.env.BUILDSET = conf.options.buildset conf.env.BUILDSET = conf.options.buildset
if len(conf.env.BUILDSET) == 0: if len(conf.env.BUILDSET) == 0:
conf.env.BUILDSET += [BUILDSET_DEFAULT] conf.env.BUILDSET += [waf_libbsd.BUILDSET_DEFAULT]
update_builders(conf, conf.env.BUILDSET) update_builders(conf, conf.env.BUILDSET)
rtems.configure(conf, bsp_configure) rtems.configure(conf, bsp_configure)