|  | #!/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 '' |