| #!/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 (root in self.sysincludes[arch]) and \ |
| (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 '' |