2 from __future__ import absolute_import, division, print_function
4 from ctypes import ArgumentError
13 k_header_magic_LE = b'pamh'
14 k_header_magic_BE = b'hmap'
19 Apply the "well-known" headermap hash function.
22 return sum((ord(c.lower()) * 13
25 class HeaderMap(object):
28 with open(path, 'rb') as f:
30 if magic == k_header_magic_LE:
32 elif magic == k_header_magic_BE:
35 raise SystemExit("error: %s: not a headermap" % (
38 # Read the header information.
39 header_fmt = endian_code + 'HHIIII'
40 header_size = struct.calcsize(header_fmt)
41 data = f.read(header_size)
42 if len(data) != header_size:
43 raise SystemExit("error: %s: truncated headermap header" % (
46 (version, reserved, strtable_offset, num_entries,
47 num_buckets, _) = struct.unpack(header_fmt, data)
50 raise SystemExit("error: %s: unknown headermap version: %r" % (
53 raise SystemExit("error: %s: invalid reserved value in header" % (
56 # The number of buckets must be a power of two.
57 if num_buckets == 0 or (num_buckets & num_buckets - 1) != 0:
58 raise SystemExit("error: %s: invalid number of buckets" % (
61 # Read all of the buckets.
62 bucket_fmt = endian_code + 'III'
63 bucket_size = struct.calcsize(bucket_fmt)
64 buckets_data = f.read(num_buckets * bucket_size)
65 if len(buckets_data) != num_buckets * bucket_size:
66 raise SystemExit("error: %s: truncated headermap buckets" % (
68 buckets = [struct.unpack(bucket_fmt,
69 buckets_data[i*bucket_size:(i+1)*bucket_size])
70 for i in range(num_buckets)]
72 # Read the string table; the format doesn't explicitly communicate the
73 # size of the string table (which is dumb), so assume it is the rest of
76 strtable_size = f.tell() - strtable_offset
77 f.seek(strtable_offset)
79 if strtable_size == 0:
80 raise SystemExit("error: %s: unable to read zero-sized string table"%(
82 strtable = f.read(strtable_size)
84 if len(strtable) != strtable_size:
85 raise SystemExit("error: %s: unable to read complete string table"%(
88 raise SystemExit("error: %s: invalid string table in headermap" % (
91 return HeaderMap(num_entries, buckets, strtable)
93 def __init__(self, num_entries, buckets, strtable):
94 self.num_entries = num_entries
95 self.buckets = buckets
96 self.strtable = strtable
98 def get_string(self, idx):
99 if idx >= len(self.strtable):
100 raise SystemExit("error: %s: invalid string index" % (
102 end_idx = self.strtable.index(0, idx)
103 return self.strtable[idx:end_idx].decode()
107 for key_idx,prefix_idx,suffix_idx in self.buckets:
110 yield (self.get_string(key_idx),
111 self.get_string(prefix_idx) + self.get_string(suffix_idx))
115 def action_dump(name, args):
116 "dump a headermap file"
118 parser = optparse.OptionParser("%%prog %s [options] <headermap path>" % (
120 parser.add_option("-v", "--verbose", dest="verbose",
121 help="show more verbose output [%default]",
122 action="store_true", default=False)
123 (opts, args) = parser.parse_args(args)
126 parser.error("invalid number of arguments")
130 hmap = HeaderMap.frompath(path)
132 # Dump all of the buckets.
133 print ('Header Map: %s' % (path,))
135 print ('headermap: %r' % (path,))
136 print (' num entries: %d' % (hmap.num_entries,))
137 print (' num buckets: %d' % (len(hmap.buckets),))
138 print (' string table size: %d' % (len(hmap.strtable),))
139 for i,bucket in enumerate(hmap.buckets):
140 key_idx,prefix_idx,suffix_idx = bucket
146 key = hmap.get_string(key_idx)
147 prefix = hmap.get_string(prefix_idx)
148 suffix = hmap.get_string(suffix_idx)
150 print (" bucket[%d]: %r -> (%r, %r) -- %d" % (
151 i, key, prefix, suffix, (hmap_hash(key) & (len(hmap.buckets) - 1))))
153 mappings = sorted(hmap.mappings)
154 for key,value in mappings:
155 print ("%s -> %s" % (key, value))
158 def next_power_of_two(value):
161 return 1 if value == 0 else 2**(value - 1).bit_length()
163 def action_write(name, args):
164 "write a headermap file from a JSON definition"
166 parser = optparse.OptionParser("%%prog %s [options] <input path> <output path>" % (
168 (opts, args) = parser.parse_args(args)
171 parser.error("invalid number of arguments")
173 input_path,output_path = args
175 with open(input_path, "r") as f:
176 input_data = json.load(f)
178 # Compute the headermap contents, we make a table that is 1/3 full.
179 mappings = input_data['mappings']
180 num_buckets = next_power_of_two(len(mappings) * 3)
183 for i in range(num_buckets)]
186 for key,value in mappings.items():
187 if not isinstance(key, str):
188 key = key.decode('utf-8')
189 if not isinstance(value, str):
190 value = value.decode('utf-8')
191 max_value_len = max(max_value_len, len(value))
193 key_idx = len(strtable)
194 strtable += key + '\0'
195 prefix = os.path.dirname(value) + '/'
196 suffix = os.path.basename(value)
197 prefix_idx = len(strtable)
198 strtable += prefix + '\0'
199 suffix_idx = len(strtable)
200 strtable += suffix + '\0'
202 hash = hmap_hash(key)
203 for i in range(num_buckets):
204 idx = (hash + i) % num_buckets
205 if table[idx][0] == 0:
206 table[idx] = (key_idx, prefix_idx, suffix_idx)
212 magic = k_header_magic_LE
214 header_fmt = endian_code + 'HHIIII'
215 header_size = struct.calcsize(header_fmt)
216 bucket_fmt = endian_code + 'III'
217 bucket_size = struct.calcsize(bucket_fmt)
218 strtable_offset = magic_size + header_size + num_buckets * bucket_size
219 header = (1, 0, strtable_offset, len(mappings),
220 num_buckets, max_value_len)
222 # Write out the headermap.
223 with open(output_path, 'wb') as f:
225 f.write(struct.pack(header_fmt, *header))
227 f.write(struct.pack(bucket_fmt, *bucket))
228 f.write(strtable.encode())
230 def action_tovfs(name, args):
231 "convert a headermap to a VFS layout"
233 parser = optparse.OptionParser("%%prog %s [options] <headermap path>" % (
235 parser.add_option("", "--build-path", dest="build_path",
236 help="build path prefix",
237 action="store", type=str)
238 (opts, args) = parser.parse_args(args)
241 parser.error("invalid number of arguments")
242 if opts.build_path is None:
243 parser.error("--build-path is required")
245 input_path,output_path = args
247 hmap = HeaderMap.frompath(input_path)
249 # Create the table for all the objects.
252 build_dir_contents = []
254 'name' : opts.build_path,
255 'type' : 'directory',
256 'contents' : build_dir_contents }]
258 # We assume we are mapping framework paths, so a key of "Foo/Bar.h" maps to
259 # "<build path>/Foo.framework/Headers/Bar.h".
260 for key,value in hmap.mappings:
261 # If this isn't a framework style mapping, ignore it.
262 components = key.split('/')
263 if len(components) != 2:
265 framework_name,header_name = components
266 build_dir_contents.append({
267 'name' : '%s.framework/Headers/%s' % (framework_name,
270 'external-contents' : value })
272 with open(output_path, 'w') as f:
273 json.dump(vfs, f, indent=2)
275 commands = dict((name[7:].replace("_","-"), f)
276 for name,f in locals().items()
277 if name.startswith('action_'))
280 print ("Usage: %s command [options]" % (
281 os.path.basename(sys.argv[0])), file=sys.stderr)
282 print (file=sys.stderr)
283 print ("Available commands:", file=sys.stderr)
284 cmds_width = max(map(len, commands))
285 for name,func in sorted(commands.items()):
286 print (" %-*s - %s" % (cmds_width, name, func.__doc__), file=sys.stderr)
290 if len(sys.argv) < 2 or sys.argv[1] not in commands:
294 commands[cmd](cmd, sys.argv[2:])
296 if __name__ == '__main__':