diff options
Diffstat (limited to 'debian/bin/buildcheck.py')
-rwxr-xr-x | debian/bin/buildcheck.py | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/debian/bin/buildcheck.py b/debian/bin/buildcheck.py new file mode 100755 index 000000000..3f7ce25a9 --- /dev/null +++ b/debian/bin/buildcheck.py @@ -0,0 +1,285 @@ +#!/usr/bin/python3 + +import sys +import glob +import os +import re + +from debian_linux.abi import Symbols +from debian_linux.config import ConfigCoreDump +from debian_linux.debian import Changelog, VersionLinux + + +class CheckAbi(object): + class SymbolInfo(object): + def __init__(self, symbol, symbol_ref=None): + self.symbol = symbol + self.symbol_ref = symbol_ref or symbol + + @property + def module(self): + return self.symbol.module + + @property + def name(self): + return self.symbol.name + + def write(self, out, ignored): + info = [] + if ignored: + info.append("ignored") + for name in ('module', 'version', 'export'): + data = getattr(self.symbol, name) + data_ref = getattr(self.symbol_ref, name) + if data != data_ref: + info.append("%s: %s -> %s" % (name, data_ref, data)) + else: + info.append("%s: %s" % (name, data)) + out.write("%-48s %s\n" % (self.symbol.name, ", ".join(info))) + + def __init__(self, config, dir, arch, featureset, flavour): + self.config = config + self.arch, self.featureset, self.flavour = arch, featureset, flavour + + self.filename_new = "%s/Module.symvers" % dir + + try: + version_abi = (self.config[('version',)]['abiname_base'] + '-' + + self.config['abi', arch]['abiname']) + except KeyError: + version_abi = self.config[('version',)]['abiname'] + self.filename_ref = ("debian/abi/%s/%s_%s_%s" % + (version_abi, arch, featureset, flavour)) + + def __call__(self, out): + ret = 0 + + new = Symbols(open(self.filename_new)) + unversioned = [name for name in new + if new[name].version == '0x00000000'] + if unversioned: + out.write("ABI is not completely versioned! " + "Refusing to continue.\n") + out.write("\nUnversioned symbols:\n") + for name in sorted(unversioned): + self.SymbolInfo(new[name]).write(out, False) + ret = 1 + + try: + ref = Symbols(open(self.filename_ref)) + except IOError: + out.write("Can't read ABI reference. ABI not checked!\n") + return ret + + symbols, add, change, remove = self._cmp(ref, new) + + ignore = self._ignore(symbols) + + add_effective = add - ignore + change_effective = change - ignore + remove_effective = remove - ignore + + if change_effective or remove_effective: + out.write("ABI has changed! Refusing to continue.\n") + ret = 1 + elif change or remove: + out.write("ABI has changed but all changes have been ignored. " + "Continuing.\n") + elif add_effective: + out.write("New symbols have been added. Continuing.\n") + elif add: + out.write("New symbols have been added but have been ignored. " + "Continuing.\n") + else: + out.write("No ABI changes.\n") + + if add: + out.write("\nAdded symbols:\n") + for name in sorted(add): + symbols[name].write(out, name in ignore) + + if change: + out.write("\nChanged symbols:\n") + for name in sorted(change): + symbols[name].write(out, name in ignore) + + if remove: + out.write("\nRemoved symbols:\n") + for name in sorted(remove): + symbols[name].write(out, name in ignore) + + return ret + + def _cmp(self, ref, new): + ref_names = set(ref.keys()) + new_names = set(new.keys()) + + add = set() + change = set() + remove = set() + + symbols = {} + + for name in new_names - ref_names: + add.add(name) + symbols[name] = self.SymbolInfo(new[name]) + + for name in ref_names.intersection(new_names): + s_ref = ref[name] + s_new = new[name] + + if s_ref != s_new: + change.add(name) + symbols[name] = self.SymbolInfo(s_new, s_ref) + + for name in ref_names - new_names: + remove.add(name) + symbols[name] = self.SymbolInfo(ref[name]) + + return symbols, add, change, remove + + def _ignore_pattern(self, pattern): + ret = [] + for i in re.split(r'(\*\*?)', pattern): + if i == '*': + ret.append(r'[^/]*') + elif i == '**': + ret.append(r'.*') + elif i: + ret.append(re.escape(i)) + return re.compile('^' + ''.join(ret) + '$') + + def _ignore(self, symbols): + # TODO: let config merge this lists + configs = [] + configs.append(self.config.get(('abi', self.arch, self.featureset, + self.flavour), {})) + configs.append(self.config.get(('abi', self.arch, None, self.flavour), + {})) + configs.append(self.config.get(('abi', self.arch, self.featureset), + {})) + configs.append(self.config.get(('abi', self.arch), {})) + configs.append(self.config.get(('abi', None, self.featureset), {})) + configs.append(self.config.get(('abi',), {})) + + ignores = set() + for config in configs: + ignores.update(config.get('ignore-changes', [])) + + filtered = set() + for ignore in ignores: + type = 'name' + if ':' in ignore: + type, ignore = ignore.split(':') + if type in ('name', 'module'): + p = self._ignore_pattern(ignore) + for symbol in symbols.values(): + if p.match(getattr(symbol, type)): + filtered.add(symbol.name) + else: + raise NotImplementedError + + return filtered + + +class CheckImage(object): + def __init__(self, config, dir, arch, featureset, flavour): + self.dir = dir + self.arch, self.featureset, self.flavour = arch, featureset, flavour + + self.changelog = Changelog(version=VersionLinux)[0] + + self.config_entry_base = config.merge('base', arch, featureset, + flavour) + self.config_entry_build = config.merge('build', arch, featureset, + flavour) + self.config_entry_image = config.merge('image', arch, featureset, + flavour) + + def __call__(self, out): + image = self.config_entry_build.get('image-file') + uncompressed_image = self.config_entry_build \ + .get('uncompressed-image-file') + + if not image: + # TODO: Bail out + return 0 + + image = os.path.join(self.dir, image) + if uncompressed_image: + uncompressed_image = os.path.join(self.dir, uncompressed_image) + + fail = 0 + + fail |= self.check_size(out, image, uncompressed_image) + + return fail + + def check_size(self, out, image, uncompressed_image): + value = self.config_entry_image.get('check-size') + + if not value: + return 0 + + dtb_size = 0 + if self.config_entry_image.get('check-size-with-dtb'): + for dtb in glob.glob( + os.path.join(self.dir, 'arch', + self.config_entry_base['kernel-arch'], + 'boot/dts/*.dtb')): + dtb_size = max(dtb_size, os.stat(dtb).st_size) + + size = os.stat(image).st_size + dtb_size + + # 1% overhead is desirable in order to cope with growth + # through the lifetime of a stable release. Warn if this is + # not the case. + usage = (float(size)/value) * 100.0 + out.write('Image size %d/%d, using %.2f%%. ' % (size, value, usage)) + if size > value: + out.write('Too large. Refusing to continue.\n') + return 1 + elif usage >= 99.0: + out.write('Under 1%% space in %s. ' % self.changelog.distribution) + else: + out.write('Image fits. ') + out.write('Continuing.\n') + + # Also check the uncompressed image + if uncompressed_image and \ + self.config_entry_image.get('check-uncompressed-size'): + value = self.config_entry_image.get('check-uncompressed-size') + size = os.stat(uncompressed_image).st_size + usage = (float(size)/value) * 100.0 + out.write('Uncompressed Image size %d/%d, using %.2f%%. ' % + (size, value, usage)) + if size > value: + out.write('Too large. Refusing to continue.\n') + return 1 + elif usage >= 99.0: + out.write('Uncompressed Image Under 1%% space in %s. ' % + self.changelog.distribution) + else: + out.write('Uncompressed Image fits. ') + out.write('Continuing.\n') + + return 0 + + +class Main(object): + def __init__(self, dir, arch, featureset, flavour): + self.args = dir, arch, featureset, flavour + + self.config = ConfigCoreDump(open("debian/config.defines.dump", "rb")) + + def __call__(self): + fail = 0 + + for c in CheckAbi, CheckImage: + fail |= c(self.config, *self.args)(sys.stdout) + + return fail + + +if __name__ == '__main__': + sys.exit(Main(*sys.argv[1:])()) |