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 argparse |
| 7 import collections |
| 8 import hashlib |
| 9 import operator |
| 10 import os |
| 11 import re |
| 12 import sys |
| 13 |
| 14 SCRIPT_NAME = "generate_ui_string_overrider.py" |
| 15 RESOURCE_EXTRACT_REGEX = re.compile('^#define (\S*) (\d*)$', re.MULTILINE) |
| 16 |
| 17 class Error(Exception): |
| 18 """Base error class for all exceptions in generated_resources_map.""" |
| 19 |
| 20 |
| 21 class HashCollisionError(Error): |
| 22 """Multiple resource names hash to the same value.""" |
| 23 |
| 24 |
| 25 Resource = collections.namedtuple("Resource", ['hash', 'name', 'index']) |
| 26 |
| 27 |
| 28 def _HashName(name): |
| 29 """Returns the hash id for a name. |
| 30 |
| 31 Args: |
| 32 name: The name to hash. |
| 33 |
| 34 Returns: |
| 35 An int that is at most 32 bits. |
| 36 """ |
| 37 md5hash = hashlib.md5() |
| 38 md5hash.update(name) |
| 39 return int(md5hash.hexdigest()[:8], 16) |
| 40 |
| 41 |
| 42 def _GetNameIndexPairsIter(string_to_scan): |
| 43 """Gets an iterator of the resource name and index pairs of the given string. |
| 44 |
| 45 Scans the input string for lines of the form "#define NAME INDEX" and returns |
| 46 an iterator over all matching (NAME, INDEX) pairs. |
| 47 |
| 48 Args: |
| 49 string_to_scan: The input string to scan. |
| 50 |
| 51 Yields: |
| 52 A tuple of name and index. |
| 53 """ |
| 54 for match in RESOURCE_EXTRACT_REGEX.finditer(string_to_scan): |
| 55 yield match.group(1, 2) |
| 56 |
| 57 |
| 58 def _GetResourceListFromString(resources_content): |
| 59 """Produces a list of |Resource| objects from a string. |
| 60 |
| 61 The input string contains lines of the form "#define NAME INDEX". The returned |
| 62 list is sorted primarily by hash, then name, and then index. |
| 63 |
| 64 Args: |
| 65 resources_content: The input string to process, contains lines of the form |
| 66 "#define NAME INDEX". |
| 67 |
| 68 Returns: |
| 69 A sorted list of |Resource| objects. |
| 70 """ |
| 71 resources = [Resource(_HashName(name), name, index) for name, index in |
| 72 _GetNameIndexPairsIter(resources_content)] |
| 73 |
| 74 # The default |Resource| order makes |resources| sorted by the hash, then |
| 75 # name, then index. |
| 76 resources.sort() |
| 77 |
| 78 return resources |
| 79 |
| 80 |
| 81 def _CheckForHashCollisions(sorted_resource_list): |
| 82 """Checks a sorted list of |Resource| objects for hash collisions. |
| 83 |
| 84 Args: |
| 85 sorted_resource_list: A sorted list of |Resource| objects. |
| 86 |
| 87 Returns: |
| 88 A set of all |Resource| objects with collisions. |
| 89 """ |
| 90 collisions = set() |
| 91 for i in xrange(len(sorted_resource_list) - 1): |
| 92 resource = sorted_resource_list[i] |
| 93 next_resource = sorted_resource_list[i+1] |
| 94 if resource.hash == next_resource.hash: |
| 95 collisions.add(resource) |
| 96 collisions.add(next_resource) |
| 97 |
| 98 return collisions |
| 99 |
| 100 |
| 101 def _GenDataArray( |
| 102 resources, entry_pattern, array_name, array_type, data_getter): |
| 103 """Generates a C++ statement defining a literal array containing the hashes. |
| 104 |
| 105 Args: |
| 106 resources: A sorted list of |Resource| objects. |
| 107 entry_pattern: A pattern to be used to generate each entry in the array. The |
| 108 pattern is expected to have a place for data and one for a comment, in |
| 109 that order. |
| 110 array_name: The name of the array being generated. |
| 111 array_type: The type of the array being generated. |
| 112 data_getter: A function that gets the array data from a |Resource| object. |
| 113 |
| 114 Returns: |
| 115 A string containing a C++ statement defining the an array. |
| 116 """ |
| 117 lines = [entry_pattern % (data_getter(r), r.name) for r in resources] |
| 118 pattern = """const %(type)s %(name)s[] = { |
| 119 %(content)s |
| 120 }; |
| 121 """ |
| 122 return pattern % {'type': array_type, |
| 123 'name': array_name, |
| 124 'content': '\n'.join(lines)} |
| 125 |
| 126 |
| 127 def _GenerateNamespacePrefixAndSuffix(namespace): |
| 128 """Generates the namespace prefix and suffix for |namespace|. |
| 129 |
| 130 Args: |
| 131 namespace: A string corresponding to the namespace name. May be empty. |
| 132 |
| 133 Returns: |
| 134 A tuple of strings corresponding to the namespace prefix and suffix for |
| 135 putting the code in the corresponding namespace in C++. If namespace is |
| 136 the empty string, both returned strings are empty too. |
| 137 """ |
| 138 if not namespace: |
| 139 return "", "" |
| 140 return "namespace %s {\n\n" % namespace, "\n} // namespace %s\n" % namespace |
| 141 |
| 142 |
| 143 def _GenerateSourceFileContent(resources_content, namespace, header_filename): |
| 144 """Generates the .cc content from the given generated grit headers content. |
| 145 |
| 146 Args: |
| 147 resources_content: The input string to process, contains lines of the form |
| 148 "#define NAME INDEX". |
| 149 |
| 150 namespace: The namespace in which the generated code should be scoped. If |
| 151 not defined, then the code will be in the global namespace. |
| 152 |
| 153 header_filename: Path to the corresponding .h. |
| 154 |
| 155 Returns: |
| 156 .cc file content implementing the CreateUIStringOverrider() factory. |
| 157 """ |
| 158 hashed_tuples = _GetResourceListFromString(resources_content) |
| 159 |
| 160 collisions = _CheckForHashCollisions(hashed_tuples) |
| 161 if collisions: |
| 162 error_message = "\n".join( |
| 163 ["hash: %i, name: %s" % (i[0], i[1]) for i in sorted(collisions)]) |
| 164 error_message = ("\nThe following names had hash collisions " |
| 165 "(sorted by the hash value):\n%s\n" %(error_message)) |
| 166 raise HashCollisionError(error_message) |
| 167 |
| 168 hashes_array = _GenDataArray( |
| 169 hashed_tuples, " %iU, // %s", 'kResourceHashes', 'uint32_t', |
| 170 operator.attrgetter('hash')) |
| 171 indices_array = _GenDataArray( |
| 172 hashed_tuples, " %s, // %s", 'kResourceIndices', 'int', |
| 173 operator.attrgetter('index')) |
| 174 |
| 175 namespace_prefix, namespace_suffix = _GenerateNamespacePrefixAndSuffix( |
| 176 namespace) |
| 177 |
| 178 return ( |
| 179 "// This file was generated by %(script_name)s. Do not edit.\n" |
| 180 "\n" |
| 181 "#include \"%(header_filename)s\"\n\n" |
| 182 "%(namespace_prefix)s" |
| 183 "namespace {\n\n" |
| 184 "const size_t kNumResources = %(num_resources)i;\n\n" |
| 185 "%(hashes_array)s" |
| 186 "\n" |
| 187 "%(indices_array)s" |
| 188 "\n" |
| 189 "} // namespace\n" |
| 190 "\n" |
| 191 "variations::UIStringOverrider CreateUIStringOverrider() {\n" |
| 192 " return variations::UIStringOverrider(\n" |
| 193 " kResourceHashes, kResourceIndices, kNumResources);\n" |
| 194 "}\n" |
| 195 "%(namespace_suffix)s") % { |
| 196 'script_name': SCRIPT_NAME, |
| 197 'header_filename': header_filename, |
| 198 'namespace_prefix': namespace_prefix, |
| 199 'num_resources': len(hashed_tuples), |
| 200 'hashes_array': hashes_array, |
| 201 'indices_array': indices_array, |
| 202 'namespace_suffix': namespace_suffix, |
| 203 } |
| 204 |
| 205 |
| 206 def _GenerateHeaderFileContent(namespace, header_filename): |
| 207 """Generates the .h for to the .cc generated by _GenerateSourceFileContent. |
| 208 |
| 209 Args: |
| 210 namespace: The namespace in which the generated code should be scoped. If |
| 211 not defined, then the code will be in the global namespace. |
| 212 |
| 213 header_filename: Path to the corresponding .h. Used to generate the include |
| 214 guards. |
| 215 |
| 216 Returns: |
| 217 .cc file content implementing the CreateUIStringOverrider() factory. |
| 218 """ |
| 219 |
| 220 include_guard = re.sub('[^A-Z]', '_', header_filename.upper()) + '_' |
| 221 namespace_prefix, namespace_suffix = _GenerateNamespacePrefixAndSuffix( |
| 222 namespace) |
| 223 |
| 224 return ( |
| 225 "// This file was generated by %(script_name)s. Do not edit.\n" |
| 226 "\n" |
| 227 "#ifndef %(include_guard)s\n" |
| 228 "#define %(include_guard)s\n" |
| 229 "\n" |
| 230 "#include \"components/variations/service/ui_string_overrider.h\"\n\n" |
| 231 "%(namespace_prefix)s" |
| 232 "// Returns an initialized UIStringOverrider.\n" |
| 233 "variations::UIStringOverrider CreateUIStringOverrider();\n" |
| 234 "%(namespace_suffix)s" |
| 235 "\n" |
| 236 "#endif // %(include_guard)s\n" |
| 237 ) % { |
| 238 'script_name': SCRIPT_NAME, |
| 239 'include_guard': include_guard, |
| 240 'namespace_prefix': namespace_prefix, |
| 241 'namespace_suffix': namespace_suffix, |
| 242 } |
| 243 |
| 244 |
| 245 def main(): |
| 246 arg_parser = argparse.ArgumentParser( |
| 247 description="Generate UIStringOverrider factory from resources headers " |
| 248 "generated by grit.") |
| 249 arg_parser.add_argument( |
| 250 "--output_dir", "-o", required=True, |
| 251 help="Base directory to for generated files.") |
| 252 arg_parser.add_argument( |
| 253 "--source_filename", "-S", required=True, |
| 254 help="File name of the generated source file.") |
| 255 arg_parser.add_argument( |
| 256 "--header_filename", "-H", required=True, |
| 257 help="File name of the generated header file.") |
| 258 arg_parser.add_argument( |
| 259 "--namespace", "-N", default="", |
| 260 help="Namespace of the generated factory function (code will be in " |
| 261 "the global namespace if this is omitted).") |
| 262 arg_parser.add_argument( |
| 263 "--test_support", "-t", action="store_true", default=False, |
| 264 help="Make internal variables accessible for testing.") |
| 265 arg_parser.add_argument( |
| 266 "inputs", metavar="FILENAME", nargs="+", |
| 267 help="Path to resources header file generated by grit.") |
| 268 arguments = arg_parser.parse_args() |
| 269 |
| 270 generated_resources_h = "" |
| 271 for resources_file in arguments.inputs: |
| 272 with open(resources_file, "r") as resources: |
| 273 generated_resources_h += resources.read() |
| 274 |
| 275 if len(generated_resources_h) == 0: |
| 276 raise Error("No content loaded for %s." % (resources_file)) |
| 277 |
| 278 source_file_content = _GenerateSourceFileContent( |
| 279 generated_resources_h, arguments.namespace, arguments.header_filename) |
| 280 header_file_content = _GenerateHeaderFileContent( |
| 281 arguments.namespace, arguments.header_filename) |
| 282 |
| 283 with open(os.path.join( |
| 284 arguments.output_dir, arguments.source_filename), "w") as generated_file: |
| 285 generated_file.write(source_file_content) |
| 286 with open(os.path.join( |
| 287 arguments.output_dir, arguments.header_filename), "w") as generated_file: |
| 288 generated_file.write(header_file_content) |
| 289 |
| 290 |
| 291 if __name__ == '__main__': |
| 292 sys.exit(main()) |
OLD | NEW |