blob: 6cf535b857883f1fe51edcaf1b393e2374eaa2cb [file] [log] [blame]
#!/usr/bin/python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import collections
import hashlib
import operator
import os
import re
import sys
RESOURCE_EXTRACT_REGEX = re.compile('^#define (\S*) (\d*)$', re.MULTILINE)
class Error(Exception):
"""Base error class for all exceptions in generated_resources_map."""
class HashCollisionError(Error):
"""Multiple resource names hash to the same value."""
Resource = collections.namedtuple("Resource", ['hash', 'name', 'index'])
def _HashName(name):
"""Returns the hash id for a name.
Args:
name: The name to hash.
Returns:
An int that is at most 32 bits.
"""
md5hash = hashlib.md5()
md5hash.update(name)
return int(md5hash.hexdigest()[:8], 16)
def _GetNameIndexPairsIter(string_to_scan):
"""Gets an iterator of the resource name and index pairs of the given string.
Scans the input string for lines of the form "#define NAME INDEX" and returns
an iterator over all matching (NAME, INDEX) pairs.
Args:
string_to_scan: The input string to scan.
Yields:
A tuple of name and index.
"""
for match in RESOURCE_EXTRACT_REGEX.finditer(string_to_scan):
yield match.group(1, 2)
def _GetResourceListFromString(resources_content):
"""Produces a list of |Resource| objects from a string.
The input string conaints lines of the form "#define NAME INDEX". The returned
list is sorted primarily by hash, then name, and then index.
Args:
resources_content: The input string to process, contains lines of the form
"#define NAME INDEX".
Returns:
A sorted list of |Resource| objects.
"""
resources = [Resource(_HashName(name), name, index) for name, index in
_GetNameIndexPairsIter(resources_content)]
# The default |Resource| order makes |resources| sorted by the hash, then
# name, then index.
resources.sort()
return resources
def _CheckForHashCollisions(sorted_resource_list):
"""Checks a sorted list of |Resource| objects for hash collisions.
Args:
sorted_resource_list: A sorted list of |Resource| objects.
Returns:
A set of all |Resource| objects with collisions.
"""
collisions = set()
for i in xrange(len(sorted_resource_list) - 1):
resource = sorted_resource_list[i]
next_resource = sorted_resource_list[i+1]
if resource.hash == next_resource.hash:
collisions.add(resource)
collisions.add(next_resource)
return collisions
def _GenDataArray(
resources, entry_pattern, array_name, array_type, data_getter):
"""Generates a C++ statement defining a literal array containing the hashes.
Args:
resources: A sorted list of |Resource| objects.
entry_pattern: A pattern to be used to generate each entry in the array. The
pattern is expected to have a place for data and one for a comment, in
that order.
array_name: The name of the array being generated.
array_type: The type of the array being generated.
data_getter: A function that gets the array data from a |Resource| object.
Returns:
A string containing a C++ statement defining the an array.
"""
lines = [entry_pattern % (data_getter(r), r.name) for r in resources]
pattern = """const %(type)s %(name)s[] = {
%(content)s
};
"""
return pattern % {'type': array_type,
'name': array_name,
'content': '\n'.join(lines)}
def _GenerateFileContent(resources_content):
"""Generates the .cc content from the given generated_resources.h content.
Args:
resources_content: The input string to process, contains lines of the form
"#define NAME INDEX".
Returns:
.cc file content defining the kResourceHashes and kResourceIndices arrays.
"""
hashed_tuples = _GetResourceListFromString(resources_content)
collisions = _CheckForHashCollisions(hashed_tuples)
if collisions:
error_message = "\n".join(
["hash: %i, name: %s" % (i[0], i[1]) for i in sorted(collisions)])
error_message = ("\nThe following names had hash collisions "
"(sorted by the hash value):\n%s\n" %(error_message))
raise HashCollisionError(error_message)
hashes_array = _GenDataArray(
hashed_tuples, " %iU, // %s", 'kResourceHashes', 'uint32_t',
operator.attrgetter('hash'))
indices_array = _GenDataArray(
hashed_tuples, " %s, // %s", 'kResourceIndices', 'int',
operator.attrgetter('index'))
return (
"// This file was generated by generate_resources_map.py. Do not edit.\n"
"\n\n"
"#include "
"\"chrome/browser/metrics/variations/generated_resources_map.h\"\n\n"
"namespace chrome_variations {\n\n"
"const size_t kNumResources = %i;\n\n"
"%s"
"\n"
"%s"
"\n"
"} // namespace chrome_variations\n") % (
len(hashed_tuples), hashes_array, indices_array)
def main(resources_file, map_file):
generated_resources_h = ""
with open(resources_file, "r") as resources:
generated_resources_h = resources.read()
if len(generated_resources_h) == 0:
raise Error("No content loaded for %s." % (resources_file))
file_content = _GenerateFileContent(generated_resources_h)
with open(map_file, "w") as generated_file:
generated_file.write(file_content)
if __name__ == '__main__':
sys.exit(main(sys.argv[1], sys.argv[2]))