OLD | NEW |
| (Empty) |
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 "const size_t kNumResources = %i;\n\n" | |
160 "%s" | |
161 "\n" | |
162 "%s" | |
163 "\n" | |
164 "} // namespace chrome_variations\n") % ( | |
165 len(hashed_tuples), hashes_array, indices_array) | |
166 | |
167 | |
168 def main(resources_file, map_file): | |
169 generated_resources_h = "" | |
170 with open(resources_file, "r") as resources: | |
171 generated_resources_h = resources.read() | |
172 | |
173 if len(generated_resources_h) == 0: | |
174 raise Error("No content loaded for %s." % (resources_file)) | |
175 | |
176 file_content = _GenerateFileContent(generated_resources_h) | |
177 | |
178 with open(map_file, "w") as generated_file: | |
179 generated_file.write(file_content) | |
180 | |
181 | |
182 if __name__ == '__main__': | |
183 sys.exit(main(sys.argv[1], sys.argv[2])) | |
OLD | NEW |