diff --git a/ndk/tools/headers-diff-bionic-vs-ndk.py b/ndk/tools/headers-diff-bionic-vs-ndk.py new file mode 100755 index 000000000..1f8a9e3fb --- /dev/null +++ b/ndk/tools/headers-diff-bionic-vs-ndk.py @@ -0,0 +1,248 @@ +#!/usr/bin/python +# +# This tool is used to compare headers between Bionic and NDK +# script should be in development/ndk/tools for correct roots autodetection +# + +import sys, os, os.path +import subprocess +import argparse, textwrap + +class FileCollector: + """Collect headers from Bionic and sysroot + + sysincludes data format: + sysincludes -- dict with arch as key + sysincludes[arch] -- dict with includes root as key + sysincludes[arch][root] -- dict with header name as key + sysincludes[arch][root][header] -- list [last_platform, ..., first_platform] + """ + + def __init__(self, platforms_root, archs): + """Init platform roots and structures before collecting""" + self.platforms = [] + self.archs = archs + self.sysincludes = {} + for arch in self.archs: + self.sysincludes[arch] = {} + + ## scaning available platforms ## + for dirname in os.listdir(platforms_root): + path = os.path.join(platforms_root, dirname) + if os.path.isdir(path) and ('android' in dirname): + self.platforms.append(dirname) + try: + self.platforms.sort(key = lambda s: int(s.split('-')[1])) + self.root = platforms_root + except Exception: + print 'Wrong platforms list \n{0}'.format(str(self.platforms)) + + def scan_dir(self, root): + """Non-recursive file scan in directory""" + files = [] + for filename in os.listdir(root): + if os.path.isfile(os.path.join(root, filename)): + files.append(filename) + return files + + def scan_includes(self, root): + """Recursive includes scan in given root""" + includes = [] + includes_root = os.path.join(root, 'include') + if not os.path.isdir(includes_root): + return includes + + ## recursive scanning ## + includes.append(('', self.scan_dir(includes_root))) + for dirname, dirnames, filenames in os.walk(includes_root): + for subdirname in dirnames: + path = os.path.join(dirname, subdirname) + relpath = os.path.relpath(path, includes_root) + includes.append((relpath, self.scan_dir(path))) + + return includes + + def scan_archs_includes(self, root): + """Scan includes for all defined archs in given root""" + includes = {} + includes['common'] = self.scan_includes(root) + + for arch in [a for a in self.archs if a != 'common']: + arch_root = os.path.join(root, arch) + includes[arch] = self.scan_includes(arch_root) + + return includes + + def scan_platform_includes(self, platform): + """Scan all platform includes of one layer""" + platform_root = os.path.join(self.root, platform) + return self.scan_archs_includes(platform_root) + + def scan_bionic_includes(self, bionic_root): + """Scan Bionic's libc includes""" + self.bionic_root = bionic_root + self.bionic_includes = self.scan_archs_includes(bionic_root) + + def append_sysincludes(self, arch, root, headers, platform): + """Merge new platform includes layer with current sysincludes""" + if not (root in self.sysincludes[arch]): + self.sysincludes[arch][root] = {} + + for include in headers: + if include in self.sysincludes[arch][root]: + last_platform = self.sysincludes[arch][root][include][0] + if platform != last_platform: + self.sysincludes[arch][root][include].insert(0, platform) + else: + self.sysincludes[arch][root][include] = [platform] + + def update_to_platform(self, platform): + """Update sysincludes state by applying new platform layer""" + new_includes = self.scan_platform_includes(platform) + for arch in self.archs: + for pack in new_includes[arch]: + self.append_sysincludes(arch, pack[0], pack[1], platform) + + def scan_sysincludes(self, target_platform): + """Fully automated sysincludes collector upto specified platform""" + version = int(target_platform.split('-')[1]) + layers = filter(lambda s: int(s.split('-')[1]) <= version, self.platforms) + for platform in layers: + self.update_to_platform(platform) + + +class BionicSysincludes: + def set_roots(self): + """Automated roots initialization (AOSP oriented)""" + script_root = os.path.dirname(os.path.realpath(__file__)) + self.aosp_root = os.path.normpath(os.path.join(script_root, '../../..')) + self.platforms_root = os.path.join(self.aosp_root, 'development/ndk/platforms') + self.bionic_root = os.path.join(self.aosp_root, 'bionic/libc') + + def scan_includes(self): + """Scan all required includes""" + self.collector = FileCollector(self.platforms_root, self.archs) + ## detecting latest platform ## + self.platforms = self.collector.platforms + latest_platform = self.platforms[-1:][0] + ## scanning both includes repositories ## + self.collector.scan_sysincludes(latest_platform) + self.collector.scan_bionic_includes(self.bionic_root) + ## scan results ## + self.sysincludes = self.collector.sysincludes + self.bionic_includes = self.collector.bionic_includes + + def git_diff(self, file_origin, file_probe): + """Difference routine based on git diff""" + try: + subprocess.check_output(['git', 'diff', '--no-index', file_origin, file_probe]) + except subprocess.CalledProcessError as error: + return error.output + return None + + def match_with_bionic_includes(self): + """Compare headers between Bionic and sysroot""" + self.diffs = {} + ## for every arch ## + for arch in self.archs: + arch_root = (lambda s: s if s != 'common' else '')(arch) + ## for every includes directory ## + for pack in self.bionic_includes[arch]: + root = pack[0] + path_bionic = os.path.join(self.bionic_root, arch_root, 'include', root) + ## for every header that both in Bionic and sysroot ## + for include in pack[1]: + if include in self.sysincludes[arch][root]: + ## completing paths ## + platform = self.sysincludes[arch][root][include][0] + file_origin = os.path.join(path_bionic, include) + file_probe = os.path.join(self.platforms_root, platform, arch_root, 'include', root, include) + ## comparison by git diff ## + output = self.git_diff(file_origin, file_probe) + if output is not None: + if arch not in self.diffs: + self.diffs[arch] = {} + if root not in self.diffs[arch]: + self.diffs[arch][root] = {} + ## storing git diff ## + self.diffs[arch][root][include] = output + + def print_history(self, arch, root, header): + """Print human-readable list header updates across platforms""" + history = self.sysincludes[arch][root][header] + for platform in self.platforms: + entry = (lambda s: s.split('-')[1] if s in history else '-')(platform) + print '{0:3}'.format(entry), + print '' + + def show_and_store_results(self): + """Print summary list of headers and write diff-report to file""" + try: + diff_fd = open(self.diff_file, 'w') + for arch in self.archs: + if arch not in self.diffs: + continue + print '{0}/'.format(arch) + roots = self.diffs[arch].keys() + roots.sort() + for root in roots: + print ' {0}/'.format((lambda s: s if s != '' else '../include')(root)) + includes = self.diffs[arch][root].keys() + includes.sort() + for include in includes: + print ' {0:32}'.format(include), + self.print_history(arch, root, include) + diff = self.diffs[arch][root][include] + diff_fd.write(diff) + diff_fd.write('\n\n') + print '' + print '' + + finally: + diff_fd.close() + + def main(self): + self.set_roots() + self.scan_includes() + self.match_with_bionic_includes() + self.show_and_store_results() + +if __name__ == '__main__': + ## configuring command line parser ## + parser = argparse.ArgumentParser(formatter_class = argparse.RawTextHelpFormatter, + description = 'Headers comparison tool between bionic and NDK platforms') + parser.epilog = textwrap.dedent(''' + output format: + {architecture}/ + {directory}/ + {header name}.h {platforms history} + + platforms history format: + number X means header has been changed in android-X + `-\' means it is the same + + diff-report format: + git diff output for all headers + use --diff option to specify filename + ''') + + parser.add_argument('--archs', metavar = 'A', nargs = '+', + default = ['common', 'arm', 'x86', 'mips'], + help = 'list of architectures\n(default: common arm x86 mips)') + parser.add_argument('--diff', metavar = 'FILE', nargs = 1, + default = ['headers-diff-bionic-vs-ndk.diff'], + help = 'diff-report filename\n(default: `bionic-vs-sysincludes_report.diff\')') + + ## parsing arguments ## + args = parser.parse_args() + + ## doing work ## + app = BionicSysincludes() + app.archs = map((lambda s: 'arch-{0}'.format(s) if s != 'common' else s), args.archs) + app.diff_file = args.diff[0] + app.main() + + print 'Headers listed above are DIFFERENT in Bionic and NDK platforms' + print 'See `{0}\' for details'.format(app.diff_file) + print 'See --help for format description.' + print ''