File: headers-diff-bionic-vs-ndk.py

package info (click to toggle)
android-platform-development 7.0.0%2Br33-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 30,092 kB
  • sloc: ansic: 161,291; java: 15,681; cpp: 7,721; xml: 6,419; python: 5,456; sh: 1,748; lisp: 261; ruby: 183; asm: 132; perl: 88; makefile: 22
file content (249 lines) | stat: -rwxr-xr-x 10,361 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
#!/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 ''