Clang Project

clang_source_code/utils/hmaptool/hmaptool
1#!/usr/bin/env python
2from __future__ import absolute_import, division, print_function
3
4import json
5import optparse
6import os
7import struct
8import sys
9
10###
11
12k_header_magic_LE = 'pamh'
13k_header_magic_BE = 'hmap'
14
15def hmap_hash(str):
16    """hash(str) -> int
17
18    Apply the "well-known" headermap hash function.
19    """
20
21    return sum((ord(c.lower()) * 13
22                for c in str), 0)
23
24class HeaderMap(object):
25    @staticmethod
26    def frompath(path):
27        with open(path, 'rb') as f:
28            magic = f.read(4)
29            if magic == k_header_magic_LE:
30                endian_code = '<'
31            elif magic == k_header_magic_BE:
32                endian_code = '>'
33            else:
34                raise SystemExit("error: %s: not a headermap" % (
35                        path,))
36
37            # Read the header information.
38            header_fmt = endian_code + 'HHIIII'
39            header_size = struct.calcsize(header_fmt)
40            data = f.read(header_size)
41            if len(data) != header_size:
42                raise SystemExit("error: %s: truncated headermap header" % (
43                        path,))
44
45            (version, reserved, strtable_offset, num_entries,
46             num_buckets, max_value_len) = struct.unpack(header_fmt, data)
47
48            if version != 1:
49                raise SystemExit("error: %s: unknown headermap version: %r" % (
50                        path, version))
51            if reserved != 0:
52                raise SystemExit("error: %s: invalid reserved value in header" % (
53                        path,))
54
55            # The number of buckets must be a power of two.
56            if num_buckets == 0 or (num_buckets & num_buckets - 1) != 0:
57                raise SystemExit("error: %s: invalid number of buckets" % (
58                        path,))
59
60            # Read all of the buckets.
61            bucket_fmt = endian_code + 'III'
62            bucket_size = struct.calcsize(bucket_fmt)
63            buckets_data = f.read(num_buckets * bucket_size)
64            if len(buckets_data) != num_buckets * bucket_size:
65                raise SystemExit("error: %s: truncated headermap buckets" % (
66                        path,))
67            buckets = [struct.unpack(bucket_fmt,
68                                     buckets_data[i*bucket_size:(i+1)*bucket_size])
69                       for i in range(num_buckets)]
70
71            # Read the string table; the format doesn't explicitly communicate the
72            # size of the string table (which is dumb), so assume it is the rest of
73            # the file.
74            f.seek(0, 2)
75            strtable_size = f.tell() - strtable_offset
76            f.seek(strtable_offset)
77
78            if strtable_size == 0:
79                raise SystemExit("error: %s: unable to read zero-sized string table"%(
80                        path,))
81            strtable = f.read(strtable_size)
82
83            if len(strtable) != strtable_size:
84                raise SystemExit("error: %s: unable to read complete string table"%(
85                        path,))
86            if strtable[-1] != '\0':
87                raise SystemExit("error: %s: invalid string table in headermap" % (
88                        path,))
89
90            return HeaderMap(num_entries, buckets, strtable)
91
92    def __init__(self, num_entries, buckets, strtable):
93        self.num_entries = num_entries
94        self.buckets = buckets
95        self.strtable = strtable
96
97    def get_string(self, idx):
98        if idx >= len(self.strtable):
99            raise SystemExit("error: %s: invalid string index" % (
100                    path,))
101        end_idx = self.strtable.index('\0', idx)
102        return self.strtable[idx:end_idx]
103
104    @property
105    def mappings(self):
106        for key_idx,prefix_idx,suffix_idx in self.buckets:
107            if key_idx == 0:
108                continue
109            yield (self.get_string(key_idx),
110                   self.get_string(prefix_idx) + self.get_string(suffix_idx))
111
112###
113
114def action_dump(name, args):
115    "dump a headermap file"
116
117    parser = optparse.OptionParser("%%prog %s [options] <headermap path>" % (
118            name,))
119    parser.add_option("-v", "--verbose", dest="verbose",
120                      help="show more verbose output [%default]",
121                      action="store_true", default=False)
122    (opts, args) = parser.parse_args(args)
123
124    if len(args) != 1:
125        parser.error("invalid number of arguments")
126
127    path, = args
128
129    hmap = HeaderMap.frompath(path)
130
131    # Dump all of the buckets.
132    print ('Header Map: %s' % (path,))
133    if opts.verbose:
134        print ('headermap: %r' % (path,))
135        print ('  num entries: %d' % (hmap.num_entries,))
136        print ('  num buckets: %d' % (len(hmap.buckets),))
137        print ('  string table size: %d' % (len(hmap.strtable),))
138        for i,bucket in enumerate(hmap.buckets):
139            key_idx,prefix_idx,suffix_idx = bucket
140
141            if key_idx == 0:
142                continue
143
144            # Get the strings.
145            key = hmap.get_string(key_idx)
146            prefix = hmap.get_string(prefix_idx)
147            suffix = hmap.get_string(suffix_idx)
148
149            print ("  bucket[%d]: %r -> (%r, %r) -- %d" % (
150                i, key, prefix, suffix, (hmap_hash(key) & (num_buckets - 1))))
151    else:
152        mappings = sorted(hmap.mappings)
153        for key,value in mappings:
154            print ("%s -> %s" % (key, value))
155    print ()
156
157def next_power_of_two(value):
158    if value < 0:
159        raise ArgumentError
160    return 1 if value == 0 else 2**(value - 1).bit_length()
161
162def action_write(name, args):
163    "write a headermap file from a JSON definition"
164
165    parser = optparse.OptionParser("%%prog %s [options] <input path> <output path>" % (
166            name,))
167    (opts, args) = parser.parse_args(args)
168
169    if len(args) != 2:
170        parser.error("invalid number of arguments")
171
172    input_path,output_path = args
173
174    with open(input_path, "r") as f:
175        input_data = json.load(f)
176
177    # Compute the headermap contents, we make a table that is 1/3 full.
178    mappings = input_data['mappings']
179    num_buckets = next_power_of_two(len(mappings) * 3)
180
181    table = [(0, 0, 0)
182             for i in range(num_buckets)]
183    max_value_len = 0
184    strtable = "\0"
185    for key,value in mappings.items():
186        if not isinstance(key, str):
187            key = key.decode('utf-8')
188        if not isinstance(value, str):
189            value = value.decode('utf-8')
190        max_value_len = max(max_value_len, len(value))
191
192        key_idx = len(strtable)
193        strtable += key + '\0'
194        prefix = os.path.dirname(value) + '/'
195        suffix = os.path.basename(value)
196        prefix_idx = len(strtable)
197        strtable += prefix + '\0'
198        suffix_idx = len(strtable)
199        strtable += suffix + '\0'
200
201        hash = hmap_hash(key)
202        for i in range(num_buckets):
203            idx = (hash + i) % num_buckets
204            if table[idx][0] == 0:
205                table[idx] = (key_idx, prefix_idx, suffix_idx)
206                break
207        else:
208            raise RuntimeError
209
210    endian_code = '<'
211    magic = k_header_magic_LE
212    magic_size = 4
213    header_fmt = endian_code + 'HHIIII'
214    header_size = struct.calcsize(header_fmt)
215    bucket_fmt = endian_code + 'III'
216    bucket_size = struct.calcsize(bucket_fmt)
217    strtable_offset = magic_size + header_size + num_buckets * bucket_size
218    header = (1, 0, strtable_offset, len(mappings),
219              num_buckets, max_value_len)
220
221    # Write out the headermap.
222    with open(output_path, 'wb') as f:
223        f.write(magic.encode())
224        f.write(struct.pack(header_fmt, *header))
225        for bucket in table:
226            f.write(struct.pack(bucket_fmt, *bucket))
227        f.write(strtable.encode())
228
229def action_tovfs(name, args):
230    "convert a headermap to a VFS layout"
231
232    parser = optparse.OptionParser("%%prog %s [options] <headermap path>" % (
233            name,))
234    parser.add_option("", "--build-path", dest="build_path",
235                      help="build path prefix",
236                      action="store", type=str)
237    (opts, args) = parser.parse_args(args)
238
239    if len(args) != 2:
240        parser.error("invalid number of arguments")
241    if opts.build_path is None:
242        parser.error("--build-path is required")
243
244    input_path,output_path = args
245
246    hmap = HeaderMap.frompath(input_path)
247
248    # Create the table for all the objects.
249    vfs = {}
250    vfs['version'] = 0
251    build_dir_contents = []
252    vfs['roots'] = [{
253            'name' : opts.build_path,
254            'type' : 'directory',
255            'contents' : build_dir_contents }]
256
257    # We assume we are mapping framework paths, so a key of "Foo/Bar.h" maps to
258    # "<build path>/Foo.framework/Headers/Bar.h".
259    for key,value in hmap.mappings:
260        # If this isn't a framework style mapping, ignore it.
261        components = key.split('/')
262        if len(components) != 2:
263            continue
264        framework_name,header_name = components
265        build_dir_contents.append({
266                'name' : '%s.framework/Headers/%s' % (framework_name,
267                                                      header_name),
268                'type' : 'file',
269                'external-contents' : value })
270
271    with open(output_path, 'w') as f:
272        json.dump(vfs, f, indent=2)
273
274commands = dict((name[7:].replace("_","-"), f)
275                for name,f in locals().items()
276                if name.startswith('action_'))
277
278def usage():
279    print ("Usage: %s command [options]" % (
280        os.path.basename(sys.argv[0])), file=sys.stderr)
281    print (file=sys.stderr)
282    print ("Available commands:", file=sys.stderr)
283    cmds_width = max(map(len, commands))
284    for name,func in sorted(commands.items()):
285        print ("  %-*s - %s" % (cmds_width, name, func.__doc__), file=sys.stderr)
286    sys.exit(1)
287
288def main():
289    if len(sys.argv) < 2 or sys.argv[1] not in commands:
290        usage()
291
292    cmd = sys.argv[1]
293    commands[cmd](cmd, sys.argv[2:])
294
295if __name__ == '__main__':
296    main()
297