Home | History | Annotate | Download | only in variations
      1 #!/usr/bin/python
      2 # Copyright 2014 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 import collections
      7 import hashlib
      8 import operator
      9 import os
     10 import re
     11 import sys
     12 
     13 
     14 RESOURCE_EXTRACT_REGEX = re.compile('^#define (\S*) (\d*)$', re.MULTILINE)
     15 
     16 class Error(Exception):
     17   """Base error class for all exceptions in generated_resources_map."""
     18 
     19 
     20 class HashCollisionError(Error):
     21   """Multiple resource names hash to the same value."""
     22 
     23 
     24 Resource = collections.namedtuple("Resource", ['hash', 'name', 'index'])
     25 
     26 
     27 def _HashName(name):
     28   """Returns the hash id for a name.
     29 
     30   Args:
     31     name: The name to hash.
     32 
     33   Returns:
     34     An int that is at most 32 bits.
     35   """
     36   md5hash = hashlib.md5()
     37   md5hash.update(name)
     38   return int(md5hash.hexdigest()[:8], 16)
     39 
     40 
     41 def _GetNameIndexPairsIter(string_to_scan):
     42   """Gets an iterator of the resource name and index pairs of the given string.
     43 
     44   Scans the input string for lines of the form "#define NAME INDEX" and returns
     45   an iterator over all matching (NAME, INDEX) pairs.
     46 
     47   Args:
     48     string_to_scan: The input string to scan.
     49 
     50   Yields:
     51     A tuple of name and index.
     52   """
     53   for match in RESOURCE_EXTRACT_REGEX.finditer(string_to_scan):
     54     yield match.group(1, 2)
     55 
     56 
     57 def _GetResourceListFromString(resources_content):
     58   """Produces a list of |Resource| objects from a string.
     59 
     60   The input string conaints lines of the form "#define NAME INDEX". The returned
     61   list is sorted primarily by hash, then name, and then index.
     62 
     63   Args:
     64     resources_content: The input string to process, contains lines of the form
     65         "#define NAME INDEX".
     66 
     67   Returns:
     68     A sorted list of |Resource| objects.
     69   """
     70   resources = [Resource(_HashName(name), name, index) for name, index in
     71                _GetNameIndexPairsIter(resources_content)]
     72 
     73   # The default |Resource| order makes |resources| sorted by the hash, then
     74   # name, then index.
     75   resources.sort()
     76 
     77   return resources
     78 
     79 
     80 def _CheckForHashCollisions(sorted_resource_list):
     81   """Checks a sorted list of |Resource| objects for hash collisions.
     82 
     83   Args:
     84     sorted_resource_list: A sorted list of |Resource| objects.
     85 
     86   Returns:
     87     A set of all |Resource| objects with collisions.
     88   """
     89   collisions = set()
     90   for i in xrange(len(sorted_resource_list) - 1):
     91     resource = sorted_resource_list[i]
     92     next_resource = sorted_resource_list[i+1]
     93     if resource.hash == next_resource.hash:
     94       collisions.add(resource)
     95       collisions.add(next_resource)
     96 
     97   return collisions
     98 
     99 
    100 def _GenDataArray(
    101     resources, entry_pattern, array_name, array_type, data_getter):
    102   """Generates a C++ statement defining a literal array containing the hashes.
    103 
    104   Args:
    105     resources: A sorted list of |Resource| objects.
    106     entry_pattern: A pattern to be used to generate each entry in the array. The
    107         pattern is expected to have a place for data and one for a comment, in
    108         that order.
    109     array_name: The name of the array being generated.
    110     array_type: The type of the array being generated.
    111     data_getter: A function that gets the array data from a |Resource| object.
    112 
    113   Returns:
    114     A string containing a C++ statement defining the an array.
    115   """
    116   lines = [entry_pattern % (data_getter(r), r.name) for r in resources]
    117   pattern = """const %(type)s %(name)s[] = {
    118 %(content)s
    119 };
    120 """
    121   return pattern % {'type': array_type,
    122                     'name': array_name,
    123                     'content': '\n'.join(lines)}
    124 
    125 
    126 def _GenerateFileContent(resources_content):
    127   """Generates the .cc content from the given generated_resources.h content.
    128 
    129   Args:
    130     resources_content: The input string to process, contains lines of the form
    131         "#define NAME INDEX".
    132 
    133   Returns:
    134     .cc file content defining the kResourceHashes and kResourceIndices arrays.
    135   """
    136   hashed_tuples = _GetResourceListFromString(resources_content)
    137 
    138   collisions = _CheckForHashCollisions(hashed_tuples)
    139   if collisions:
    140     error_message = "\n".join(
    141         ["hash: %i, name: %s" % (i[0], i[1]) for i in sorted(collisions)])
    142     error_message = ("\nThe following names had hash collisions "
    143                      "(sorted by the hash value):\n%s\n" %(error_message))
    144     raise HashCollisionError(error_message)
    145 
    146   hashes_array = _GenDataArray(
    147       hashed_tuples, "    %iU,  // %s", 'kResourceHashes', 'uint32_t',
    148       operator.attrgetter('hash'))
    149   indices_array = _GenDataArray(
    150       hashed_tuples, "    %s,  // %s", 'kResourceIndices', 'int',
    151       operator.attrgetter('index'))
    152 
    153   return (
    154       "// This file was generated by generate_resources_map.py. Do not edit.\n"
    155       "\n\n"
    156       "#include "
    157       "\"chrome/browser/metrics/variations/generated_resources_map.h\"\n\n"
    158       "namespace chrome_variations {\n\n"
    159       "%s"
    160       "\n"
    161       "%s"
    162       "\n"
    163       "}  // namespace chrome_variations\n") % (hashes_array, indices_array)
    164 
    165 
    166 def main(resources_file, map_file):
    167   generated_resources_h = ""
    168   with open(resources_file, "r") as resources:
    169     generated_resources_h = resources.read()
    170 
    171   if len(generated_resources_h) == 0:
    172     raise Error("No content loaded for %s." % (resources_file))
    173 
    174   file_content = _GenerateFileContent(generated_resources_h)
    175 
    176   with open(map_file, "w") as generated_file:
    177     generated_file.write(file_content)
    178 
    179 
    180 if __name__ == '__main__':
    181   sys.exit(main(sys.argv[1], sys.argv[2]))
    182