123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- #!/usr/bin/env 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 argparse
- import collections
- import hashlib
- import operator
- import os
- import re
- import sys
- SCRIPT_NAME = "generate_ui_string_overrider.py"
- # Regular expression for parsing the #define macro format. Matches both the
- # version of the macro with allowlist support and the one without. For example,
- # Without generate allowlist flag:
- # #define IDS_FOO_MESSAGE 1234
- # With generate allowlist flag:
- # #define IDS_FOO_MESSAGE (::ui::AllowlistedResource<1234>(), 1234)
- 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.encode('utf-8'))
- 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 contains 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)]
- # Deduplicate resources. Some name-index pairs appear in both chromium_ and
- # google_chrome_ header files. Unless deduplicated here, collisions will be
- # raised in _CheckForHashCollisions.
- resources = list(set(resources))
- # 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 range(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 _GenerateNamespacePrefixAndSuffix(namespace):
- """Generates the namespace prefix and suffix for |namespace|.
- Args:
- namespace: A string corresponding to the namespace name. May be empty.
- Returns:
- A tuple of strings corresponding to the namespace prefix and suffix for
- putting the code in the corresponding namespace in C++. If namespace is
- the empty string, both returned strings are empty too.
- """
- if not namespace:
- return "", ""
- return "namespace %s {\n\n" % namespace, "\n} // namespace %s\n" % namespace
- def _GenerateSourceFileContent(resources_content, namespace, header_filename):
- """Generates the .cc content from the given generated grit headers content.
- Args:
- resources_content: The input string to process, contains lines of the form
- "#define NAME INDEX".
- namespace: The namespace in which the generated code should be scoped. If
- not defined, then the code will be in the global namespace.
- header_filename: Path to the corresponding .h.
- Returns:
- .cc file content implementing the CreateUIStringOverrider() factory.
- """
- hashed_tuples = _GetResourceListFromString(resources_content)
- collisions = _CheckForHashCollisions(hashed_tuples)
- if collisions:
- error_message = "\n".join(
- ["hash: %i, name: %s" % (i.hash, i.name) for i in sorted(collisions)])
- error_message = ("\nThe following names, sorted by hash value, "
- "had hash collisions (One possible cause: strings "
- "appear in different orders for Chrome and Chromium):"
- "\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'))
- namespace_prefix, namespace_suffix = _GenerateNamespacePrefixAndSuffix(
- namespace)
- return (
- "// This file was generated by %(script_name)s. Do not edit.\n"
- "\n"
- "#include \"%(header_filename)s\"\n\n"
- "%(namespace_prefix)s"
- "namespace {\n\n"
- "const size_t kNumResources = %(num_resources)i;\n\n"
- "%(hashes_array)s"
- "\n"
- "%(indices_array)s"
- "\n"
- "} // namespace\n"
- "\n"
- "variations::UIStringOverrider CreateUIStringOverrider() {\n"
- " return variations::UIStringOverrider(\n"
- " kResourceHashes, kResourceIndices, kNumResources);\n"
- "}\n"
- "%(namespace_suffix)s") % {
- 'script_name': SCRIPT_NAME,
- 'header_filename': header_filename,
- 'namespace_prefix': namespace_prefix,
- 'num_resources': len(hashed_tuples),
- 'hashes_array': hashes_array,
- 'indices_array': indices_array,
- 'namespace_suffix': namespace_suffix,
- }
- def _GenerateHeaderFileContent(namespace, header_filename):
- """Generates the .h for to the .cc generated by _GenerateSourceFileContent.
- Args:
- namespace: The namespace in which the generated code should be scoped. If
- not defined, then the code will be in the global namespace.
- header_filename: Path to the corresponding .h. Used to generate the include
- guards.
- Returns:
- .cc file content implementing the CreateUIStringOverrider() factory.
- """
- include_guard = re.sub('[^A-Z]', '_', header_filename.upper()) + '_'
- namespace_prefix, namespace_suffix = _GenerateNamespacePrefixAndSuffix(
- namespace)
- return (
- "// This file was generated by %(script_name)s. Do not edit.\n"
- "\n"
- "#ifndef %(include_guard)s\n"
- "#define %(include_guard)s\n"
- "\n"
- "#include \"components/variations/service/ui_string_overrider.h\"\n\n"
- "%(namespace_prefix)s"
- "// Returns an initialized UIStringOverrider.\n"
- "variations::UIStringOverrider CreateUIStringOverrider();\n"
- "%(namespace_suffix)s"
- "\n"
- "#endif // %(include_guard)s\n"
- ) % {
- 'script_name': SCRIPT_NAME,
- 'include_guard': include_guard,
- 'namespace_prefix': namespace_prefix,
- 'namespace_suffix': namespace_suffix,
- }
- def main():
- arg_parser = argparse.ArgumentParser(
- description="Generate UIStringOverrider factory from resources headers "
- "generated by grit.")
- arg_parser.add_argument(
- "--output_dir", "-o", required=True,
- help="Base directory to for generated files.")
- arg_parser.add_argument(
- "--source_filename", "-S", required=True,
- help="File name of the generated source file.")
- arg_parser.add_argument(
- "--header_filename", "-H", required=True,
- help="File name of the generated header file.")
- arg_parser.add_argument(
- "--namespace", "-N", default="",
- help="Namespace of the generated factory function (code will be in "
- "the global namespace if this is omitted).")
- arg_parser.add_argument(
- "--test_support", "-t", action="store_true", default=False,
- help="Make internal variables accessible for testing.")
- arg_parser.add_argument(
- "inputs", metavar="FILENAME", nargs="+",
- help="Path to resources header file generated by grit.")
- arguments = arg_parser.parse_args()
- generated_resources_h = ""
- for resources_file in arguments.inputs:
- 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))
- source_file_content = _GenerateSourceFileContent(
- generated_resources_h, arguments.namespace, arguments.header_filename)
- header_file_content = _GenerateHeaderFileContent(
- arguments.namespace, arguments.header_filename)
- with open(os.path.join(
- arguments.output_dir, arguments.source_filename), "w") as generated_file:
- generated_file.write(source_file_content)
- with open(os.path.join(
- arguments.output_dir, arguments.header_filename), "w") as generated_file:
- generated_file.write(header_file_content)
- if __name__ == '__main__':
- sys.exit(main())
|