Grigoriy Kraynov | a67bdae | 2013-06-21 14:25:10 +0400 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | # |
| 3 | # This tool is used to compare headers between Bionic and NDK |
| 4 | # script should be in development/ndk/tools for correct roots autodetection |
| 5 | # |
| 6 | |
| 7 | import sys, os, os.path |
| 8 | import subprocess |
| 9 | import argparse, textwrap |
| 10 | |
| 11 | class FileCollector: |
| 12 | """Collect headers from Bionic and sysroot |
| 13 | |
| 14 | sysincludes data format: |
| 15 | sysincludes -- dict with arch as key |
| 16 | sysincludes[arch] -- dict with includes root as key |
| 17 | sysincludes[arch][root] -- dict with header name as key |
| 18 | sysincludes[arch][root][header] -- list [last_platform, ..., first_platform] |
| 19 | """ |
| 20 | |
| 21 | def __init__(self, platforms_root, archs): |
| 22 | """Init platform roots and structures before collecting""" |
| 23 | self.platforms = [] |
| 24 | self.archs = archs |
| 25 | self.sysincludes = {} |
| 26 | for arch in self.archs: |
| 27 | self.sysincludes[arch] = {} |
| 28 | |
| 29 | ## scaning available platforms ## |
| 30 | for dirname in os.listdir(platforms_root): |
| 31 | path = os.path.join(platforms_root, dirname) |
| 32 | if os.path.isdir(path) and ('android' in dirname): |
| 33 | self.platforms.append(dirname) |
| 34 | try: |
| 35 | self.platforms.sort(key = lambda s: int(s.split('-')[1])) |
| 36 | self.root = platforms_root |
| 37 | except Exception: |
| 38 | print 'Wrong platforms list \n{0}'.format(str(self.platforms)) |
| 39 | |
| 40 | def scan_dir(self, root): |
| 41 | """Non-recursive file scan in directory""" |
| 42 | files = [] |
| 43 | for filename in os.listdir(root): |
| 44 | if os.path.isfile(os.path.join(root, filename)): |
| 45 | files.append(filename) |
| 46 | return files |
| 47 | |
| 48 | def scan_includes(self, root): |
| 49 | """Recursive includes scan in given root""" |
| 50 | includes = [] |
| 51 | includes_root = os.path.join(root, 'include') |
| 52 | if not os.path.isdir(includes_root): |
| 53 | return includes |
| 54 | |
| 55 | ## recursive scanning ## |
| 56 | includes.append(('', self.scan_dir(includes_root))) |
| 57 | for dirname, dirnames, filenames in os.walk(includes_root): |
| 58 | for subdirname in dirnames: |
| 59 | path = os.path.join(dirname, subdirname) |
| 60 | relpath = os.path.relpath(path, includes_root) |
| 61 | includes.append((relpath, self.scan_dir(path))) |
| 62 | |
| 63 | return includes |
| 64 | |
| 65 | def scan_archs_includes(self, root): |
| 66 | """Scan includes for all defined archs in given root""" |
| 67 | includes = {} |
| 68 | includes['common'] = self.scan_includes(root) |
| 69 | |
| 70 | for arch in [a for a in self.archs if a != 'common']: |
| 71 | arch_root = os.path.join(root, arch) |
| 72 | includes[arch] = self.scan_includes(arch_root) |
| 73 | |
| 74 | return includes |
| 75 | |
| 76 | def scan_platform_includes(self, platform): |
| 77 | """Scan all platform includes of one layer""" |
| 78 | platform_root = os.path.join(self.root, platform) |
| 79 | return self.scan_archs_includes(platform_root) |
| 80 | |
| 81 | def scan_bionic_includes(self, bionic_root): |
| 82 | """Scan Bionic's libc includes""" |
| 83 | self.bionic_root = bionic_root |
| 84 | self.bionic_includes = self.scan_archs_includes(bionic_root) |
| 85 | |
| 86 | def append_sysincludes(self, arch, root, headers, platform): |
| 87 | """Merge new platform includes layer with current sysincludes""" |
| 88 | if not (root in self.sysincludes[arch]): |
| 89 | self.sysincludes[arch][root] = {} |
| 90 | |
| 91 | for include in headers: |
| 92 | if include in self.sysincludes[arch][root]: |
| 93 | last_platform = self.sysincludes[arch][root][include][0] |
| 94 | if platform != last_platform: |
| 95 | self.sysincludes[arch][root][include].insert(0, platform) |
| 96 | else: |
| 97 | self.sysincludes[arch][root][include] = [platform] |
| 98 | |
| 99 | def update_to_platform(self, platform): |
| 100 | """Update sysincludes state by applying new platform layer""" |
| 101 | new_includes = self.scan_platform_includes(platform) |
| 102 | for arch in self.archs: |
| 103 | for pack in new_includes[arch]: |
| 104 | self.append_sysincludes(arch, pack[0], pack[1], platform) |
| 105 | |
| 106 | def scan_sysincludes(self, target_platform): |
| 107 | """Fully automated sysincludes collector upto specified platform""" |
| 108 | version = int(target_platform.split('-')[1]) |
| 109 | layers = filter(lambda s: int(s.split('-')[1]) <= version, self.platforms) |
| 110 | for platform in layers: |
| 111 | self.update_to_platform(platform) |
| 112 | |
| 113 | |
| 114 | class BionicSysincludes: |
| 115 | def set_roots(self): |
| 116 | """Automated roots initialization (AOSP oriented)""" |
| 117 | script_root = os.path.dirname(os.path.realpath(__file__)) |
| 118 | self.aosp_root = os.path.normpath(os.path.join(script_root, '../../..')) |
| 119 | self.platforms_root = os.path.join(self.aosp_root, 'development/ndk/platforms') |
| 120 | self.bionic_root = os.path.join(self.aosp_root, 'bionic/libc') |
| 121 | |
| 122 | def scan_includes(self): |
| 123 | """Scan all required includes""" |
| 124 | self.collector = FileCollector(self.platforms_root, self.archs) |
| 125 | ## detecting latest platform ## |
| 126 | self.platforms = self.collector.platforms |
| 127 | latest_platform = self.platforms[-1:][0] |
| 128 | ## scanning both includes repositories ## |
| 129 | self.collector.scan_sysincludes(latest_platform) |
| 130 | self.collector.scan_bionic_includes(self.bionic_root) |
| 131 | ## scan results ## |
| 132 | self.sysincludes = self.collector.sysincludes |
| 133 | self.bionic_includes = self.collector.bionic_includes |
| 134 | |
| 135 | def git_diff(self, file_origin, file_probe): |
| 136 | """Difference routine based on git diff""" |
| 137 | try: |
| 138 | subprocess.check_output(['git', 'diff', '--no-index', file_origin, file_probe]) |
| 139 | except subprocess.CalledProcessError as error: |
| 140 | return error.output |
| 141 | return None |
| 142 | |
| 143 | def match_with_bionic_includes(self): |
| 144 | """Compare headers between Bionic and sysroot""" |
| 145 | self.diffs = {} |
| 146 | ## for every arch ## |
| 147 | for arch in self.archs: |
| 148 | arch_root = (lambda s: s if s != 'common' else '')(arch) |
| 149 | ## for every includes directory ## |
| 150 | for pack in self.bionic_includes[arch]: |
| 151 | root = pack[0] |
| 152 | path_bionic = os.path.join(self.bionic_root, arch_root, 'include', root) |
| 153 | ## for every header that both in Bionic and sysroot ## |
| 154 | for include in pack[1]: |
Grigoriy Kraynov | fc36418 | 2013-10-07 17:30:08 +0400 | [diff] [blame] | 155 | if (root in self.sysincludes[arch]) and \ |
| 156 | (include in self.sysincludes[arch][root]): |
Grigoriy Kraynov | a67bdae | 2013-06-21 14:25:10 +0400 | [diff] [blame] | 157 | ## completing paths ## |
| 158 | platform = self.sysincludes[arch][root][include][0] |
| 159 | file_origin = os.path.join(path_bionic, include) |
| 160 | file_probe = os.path.join(self.platforms_root, platform, arch_root, 'include', root, include) |
| 161 | ## comparison by git diff ## |
| 162 | output = self.git_diff(file_origin, file_probe) |
| 163 | if output is not None: |
| 164 | if arch not in self.diffs: |
| 165 | self.diffs[arch] = {} |
| 166 | if root not in self.diffs[arch]: |
| 167 | self.diffs[arch][root] = {} |
| 168 | ## storing git diff ## |
| 169 | self.diffs[arch][root][include] = output |
| 170 | |
| 171 | def print_history(self, arch, root, header): |
| 172 | """Print human-readable list header updates across platforms""" |
| 173 | history = self.sysincludes[arch][root][header] |
| 174 | for platform in self.platforms: |
| 175 | entry = (lambda s: s.split('-')[1] if s in history else '-')(platform) |
| 176 | print '{0:3}'.format(entry), |
| 177 | print '' |
| 178 | |
| 179 | def show_and_store_results(self): |
| 180 | """Print summary list of headers and write diff-report to file""" |
| 181 | try: |
| 182 | diff_fd = open(self.diff_file, 'w') |
| 183 | for arch in self.archs: |
| 184 | if arch not in self.diffs: |
| 185 | continue |
| 186 | print '{0}/'.format(arch) |
| 187 | roots = self.diffs[arch].keys() |
| 188 | roots.sort() |
| 189 | for root in roots: |
| 190 | print ' {0}/'.format((lambda s: s if s != '' else '../include')(root)) |
| 191 | includes = self.diffs[arch][root].keys() |
| 192 | includes.sort() |
| 193 | for include in includes: |
| 194 | print ' {0:32}'.format(include), |
| 195 | self.print_history(arch, root, include) |
| 196 | diff = self.diffs[arch][root][include] |
| 197 | diff_fd.write(diff) |
| 198 | diff_fd.write('\n\n') |
| 199 | print '' |
| 200 | print '' |
| 201 | |
| 202 | finally: |
| 203 | diff_fd.close() |
| 204 | |
| 205 | def main(self): |
| 206 | self.set_roots() |
| 207 | self.scan_includes() |
| 208 | self.match_with_bionic_includes() |
| 209 | self.show_and_store_results() |
| 210 | |
| 211 | if __name__ == '__main__': |
| 212 | ## configuring command line parser ## |
| 213 | parser = argparse.ArgumentParser(formatter_class = argparse.RawTextHelpFormatter, |
| 214 | description = 'Headers comparison tool between bionic and NDK platforms') |
| 215 | parser.epilog = textwrap.dedent(''' |
| 216 | output format: |
| 217 | {architecture}/ |
| 218 | {directory}/ |
| 219 | {header name}.h {platforms history} |
| 220 | |
| 221 | platforms history format: |
| 222 | number X means header has been changed in android-X |
| 223 | `-\' means it is the same |
| 224 | |
| 225 | diff-report format: |
| 226 | git diff output for all headers |
| 227 | use --diff option to specify filename |
| 228 | ''') |
| 229 | |
| 230 | parser.add_argument('--archs', metavar = 'A', nargs = '+', |
| 231 | default = ['common', 'arm', 'x86', 'mips'], |
| 232 | help = 'list of architectures\n(default: common arm x86 mips)') |
| 233 | parser.add_argument('--diff', metavar = 'FILE', nargs = 1, |
| 234 | default = ['headers-diff-bionic-vs-ndk.diff'], |
| 235 | help = 'diff-report filename\n(default: `bionic-vs-sysincludes_report.diff\')') |
| 236 | |
| 237 | ## parsing arguments ## |
| 238 | args = parser.parse_args() |
| 239 | |
| 240 | ## doing work ## |
| 241 | app = BionicSysincludes() |
| 242 | app.archs = map((lambda s: 'arch-{0}'.format(s) if s != 'common' else s), args.archs) |
| 243 | app.diff_file = args.diff[0] |
| 244 | app.main() |
| 245 | |
| 246 | print 'Headers listed above are DIFFERENT in Bionic and NDK platforms' |
| 247 | print 'See `{0}\' for details'.format(app.diff_file) |
| 248 | print 'See --help for format description.' |
| 249 | print '' |