blob: cb4157da7f91ca7d4f480d767c28aeff593b8035 [file] [log] [blame]
Grigoriy Kraynova67bdae2013-06-21 14:25:10 +04001#!/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
7import sys, os, os.path
8import subprocess
9import argparse, textwrap
10
11class 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
114class 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 Kraynovfc364182013-10-07 17:30:08 +0400155 if (root in self.sysincludes[arch]) and \
156 (include in self.sysincludes[arch][root]):
Grigoriy Kraynova67bdae2013-06-21 14:25:10 +0400157 ## 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
211if __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 ''