| #!/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])) |