summaryrefslogtreecommitdiffstats
path: root/debian/bin/buildcheck.py
diff options
context:
space:
mode:
Diffstat (limited to 'debian/bin/buildcheck.py')
-rwxr-xr-xdebian/bin/buildcheck.py285
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:])())