Blame | Last modification | View Log | Download | RSS feed
#!/usr/bin/env python3from __future__ import absolute_import, division, print_functionfrom ctypes import ArgumentErrorimport jsonimport optparseimport osimport structimport sys###k_header_magic_LE = b'pamh'k_header_magic_BE = b'hmap'def hmap_hash(str):"""hash(str) -> intApply the "well-known" headermap hash function."""return sum((ord(c.lower()) * 13for c in str), 0)class HeaderMap(object):@staticmethoddef frompath(path):with open(path, 'rb') as f:magic = f.read(4)if magic == k_header_magic_LE:endian_code = '<'elif magic == k_header_magic_BE:endian_code = '>'else:raise SystemExit("error: %s: not a headermap" % (path,))# Read the header information.header_fmt = endian_code + 'HHIIII'header_size = struct.calcsize(header_fmt)data = f.read(header_size)if len(data) != header_size:raise SystemExit("error: %s: truncated headermap header" % (path,))(version, reserved, strtable_offset, num_entries,num_buckets, _) = struct.unpack(header_fmt, data)if version != 1:raise SystemExit("error: %s: unknown headermap version: %r" % (path, version))if reserved != 0:raise SystemExit("error: %s: invalid reserved value in header" % (path,))# The number of buckets must be a power of two.if num_buckets == 0 or (num_buckets & num_buckets - 1) != 0:raise SystemExit("error: %s: invalid number of buckets" % (path,))# Read all of the buckets.bucket_fmt = endian_code + 'III'bucket_size = struct.calcsize(bucket_fmt)buckets_data = f.read(num_buckets * bucket_size)if len(buckets_data) != num_buckets * bucket_size:raise SystemExit("error: %s: truncated headermap buckets" % (path,))buckets = [struct.unpack(bucket_fmt,buckets_data[i*bucket_size:(i+1)*bucket_size])for i in range(num_buckets)]# Read the string table; the format doesn't explicitly communicate the# size of the string table (which is dumb), so assume it is the rest of# the file.f.seek(0, 2)strtable_size = f.tell() - strtable_offsetf.seek(strtable_offset)if strtable_size == 0:raise SystemExit("error: %s: unable to read zero-sized string table"%(path,))strtable = f.read(strtable_size)if len(strtable) != strtable_size:raise SystemExit("error: %s: unable to read complete string table"%(path,))if strtable[-1] != 0:raise SystemExit("error: %s: invalid string table in headermap" % (path,))return HeaderMap(num_entries, buckets, strtable)def __init__(self, num_entries, buckets, strtable):self.num_entries = num_entriesself.buckets = bucketsself.strtable = strtabledef get_string(self, idx):if idx >= len(self.strtable):raise SystemExit("error: %s: invalid string index" % (idx,))end_idx = self.strtable.index(0, idx)return self.strtable[idx:end_idx].decode()@propertydef mappings(self):for key_idx,prefix_idx,suffix_idx in self.buckets:if key_idx == 0:continueyield (self.get_string(key_idx),self.get_string(prefix_idx) + self.get_string(suffix_idx))###def action_dump(name, args):"dump a headermap file"parser = optparse.OptionParser("%%prog %s [options] <headermap path>" % (name,))parser.add_option("-v", "--verbose", dest="verbose",help="show more verbose output [%default]",action="store_true", default=False)(opts, args) = parser.parse_args(args)if len(args) != 1:parser.error("invalid number of arguments")path, = argshmap = HeaderMap.frompath(path)# Dump all of the buckets.print ('Header Map: %s' % (path,))if opts.verbose:print ('headermap: %r' % (path,))print (' num entries: %d' % (hmap.num_entries,))print (' num buckets: %d' % (len(hmap.buckets),))print (' string table size: %d' % (len(hmap.strtable),))for i,bucket in enumerate(hmap.buckets):key_idx,prefix_idx,suffix_idx = bucketif key_idx == 0:continue# Get the strings.key = hmap.get_string(key_idx)prefix = hmap.get_string(prefix_idx)suffix = hmap.get_string(suffix_idx)print (" bucket[%d]: %r -> (%r, %r) -- %d" % (i, key, prefix, suffix, (hmap_hash(key) & (len(hmap.buckets) - 1))))else:mappings = sorted(hmap.mappings)for key,value in mappings:print ("%s -> %s" % (key, value))print ()def next_power_of_two(value):if value < 0:raise ArgumentErrorreturn 1 if value == 0 else 2**(value - 1).bit_length()def action_write(name, args):"write a headermap file from a JSON definition"parser = optparse.OptionParser("%%prog %s [options] <input path> <output path>" % (name,))(opts, args) = parser.parse_args(args)if len(args) != 2:parser.error("invalid number of arguments")input_path,output_path = argswith open(input_path, "r") as f:input_data = json.load(f)# Compute the headermap contents, we make a table that is 1/3 full.mappings = input_data['mappings']num_buckets = next_power_of_two(len(mappings) * 3)table = [(0, 0, 0)for i in range(num_buckets)]max_value_len = 0strtable = "\0"for key,value in mappings.items():if not isinstance(key, str):key = key.decode('utf-8')if not isinstance(value, str):value = value.decode('utf-8')max_value_len = max(max_value_len, len(value))key_idx = len(strtable)strtable += key + '\0'prefix = os.path.dirname(value) + '/'suffix = os.path.basename(value)prefix_idx = len(strtable)strtable += prefix + '\0'suffix_idx = len(strtable)strtable += suffix + '\0'hash = hmap_hash(key)for i in range(num_buckets):idx = (hash + i) % num_bucketsif table[idx][0] == 0:table[idx] = (key_idx, prefix_idx, suffix_idx)breakelse:raise RuntimeErrorendian_code = '<'magic = k_header_magic_LEmagic_size = 4header_fmt = endian_code + 'HHIIII'header_size = struct.calcsize(header_fmt)bucket_fmt = endian_code + 'III'bucket_size = struct.calcsize(bucket_fmt)strtable_offset = magic_size + header_size + num_buckets * bucket_sizeheader = (1, 0, strtable_offset, len(mappings),num_buckets, max_value_len)# Write out the headermap.with open(output_path, 'wb') as f:f.write(magic)f.write(struct.pack(header_fmt, *header))for bucket in table:f.write(struct.pack(bucket_fmt, *bucket))f.write(strtable.encode())def action_tovfs(name, args):"convert a headermap to a VFS layout"parser = optparse.OptionParser("%%prog %s [options] <headermap path>" % (name,))parser.add_option("", "--build-path", dest="build_path",help="build path prefix",action="store", type=str)(opts, args) = parser.parse_args(args)if len(args) != 2:parser.error("invalid number of arguments")if opts.build_path is None:parser.error("--build-path is required")input_path,output_path = argshmap = HeaderMap.frompath(input_path)# Create the table for all the objects.vfs = {}vfs['version'] = 0build_dir_contents = []vfs['roots'] = [{'name' : opts.build_path,'type' : 'directory','contents' : build_dir_contents }]# We assume we are mapping framework paths, so a key of "Foo/Bar.h" maps to# "<build path>/Foo.framework/Headers/Bar.h".for key,value in hmap.mappings:# If this isn't a framework style mapping, ignore it.components = key.split('/')if len(components) != 2:continueframework_name,header_name = componentsbuild_dir_contents.append({'name' : '%s.framework/Headers/%s' % (framework_name,header_name),'type' : 'file','external-contents' : value })with open(output_path, 'w') as f:json.dump(vfs, f, indent=2)commands = dict((name[7:].replace("_","-"), f)for name,f in locals().items()if name.startswith('action_'))def usage():print ("Usage: %s command [options]" % (os.path.basename(sys.argv[0])), file=sys.stderr)print (file=sys.stderr)print ("Available commands:", file=sys.stderr)cmds_width = max(map(len, commands))for name,func in sorted(commands.items()):print (" %-*s - %s" % (cmds_width, name, func.__doc__), file=sys.stderr)sys.exit(1)def main():if len(sys.argv) < 2 or sys.argv[1] not in commands:usage()cmd = sys.argv[1]commands[cmd](cmd, sys.argv[2:])if __name__ == '__main__':main()