| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Generates .h and .rc files for installer strings. Run "python | 6 """Generates .h and .rc files for installer strings. Run "python |
| 7 create_string_rc.py" for usage details. | 7 create_string_rc.py" for usage details. |
| 8 | 8 |
| 9 This script generates an rc file and header (NAME.{rc,h}) to be included in | 9 This script generates an rc file and header (NAME.{rc,h}) to be included in |
| 10 setup.exe. The rc file includes translations for strings pulled from the given | 10 setup.exe. The rc file includes translations for strings pulled from the given |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 #define IDS_MY_STRING_AR 1600 | 23 #define IDS_MY_STRING_AR 1600 |
| 24 #define IDS_MY_STRING_BG 1601 | 24 #define IDS_MY_STRING_BG 1601 |
| 25 ... | 25 ... |
| 26 #define IDS_MY_STRING_BASE IDS_MY_STRING_AR | 26 #define IDS_MY_STRING_BASE IDS_MY_STRING_AR |
| 27 | 27 |
| 28 This allows us to lookup an an ID for a string by adding IDS_MY_STRING_BASE and | 28 This allows us to lookup an an ID for a string by adding IDS_MY_STRING_BASE and |
| 29 IDS_L10N_OFFSET_* for the language we are interested in. | 29 IDS_L10N_OFFSET_* for the language we are interested in. |
| 30 """ | 30 """ |
| 31 | 31 |
| 32 import argparse | 32 import argparse |
| 33 import exceptions |
| 33 import glob | 34 import glob |
| 34 import io | 35 import io |
| 35 import os | 36 import os |
| 36 import sys | 37 import sys |
| 37 from xml import sax | 38 from xml import sax |
| 38 | 39 |
| 39 BASEDIR = os.path.dirname(os.path.abspath(__file__)) | 40 BASEDIR = os.path.dirname(os.path.abspath(__file__)) |
| 40 sys.path.append(os.path.join(BASEDIR, '../../../../tools/grit')) | 41 sys.path.append(os.path.join(BASEDIR, '../../../../tools/grit')) |
| 41 sys.path.append(os.path.join(BASEDIR, '../../../../tools/python')) | 42 sys.path.append(os.path.join(BASEDIR, '../../../../tools/python')) |
| 42 | 43 |
| 43 from grit.extern import tclib | 44 from grit.extern import tclib |
| 44 | 45 |
| 45 # The IDs of strings we want to import from the .grd files and include in | 46 # The IDs of strings we want to import from the .grd files and include in |
| 46 # setup.exe's resources. | 47 # setup.exe's resources. |
| 47 STRING_IDS = [ | 48 STRING_IDS = [ |
| 48 'IDS_PRODUCT_NAME', | |
| 49 'IDS_SXS_SHORTCUT_NAME', | |
| 50 'IDS_PRODUCT_DESCRIPTION', | |
| 51 'IDS_ABOUT_VERSION_COMPANY_NAME', | 49 'IDS_ABOUT_VERSION_COMPANY_NAME', |
| 50 'IDS_APP_SHORTCUTS_SUBDIR_NAME', |
| 51 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY', |
| 52 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', |
| 53 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY', |
| 54 'IDS_INBOUND_MDNS_RULE_NAME', |
| 55 'IDS_INBOUND_MDNS_RULE_NAME_CANARY', |
| 56 'IDS_INSTALL_EXISTING_VERSION_LAUNCHED', |
| 57 'IDS_INSTALL_FAILED', |
| 52 'IDS_INSTALL_HIGHER_VERSION', | 58 'IDS_INSTALL_HIGHER_VERSION', |
| 53 'IDS_INSTALL_FAILED', | 59 'IDS_INSTALL_INSUFFICIENT_RIGHTS', |
| 54 'IDS_SAME_VERSION_REPAIR_FAILED', | 60 'IDS_INSTALL_INVALID_ARCHIVE', |
| 55 'IDS_SETUP_PATCH_FAILED', | 61 'IDS_INSTALL_OS_ERROR', |
| 56 'IDS_INSTALL_OS_NOT_SUPPORTED', | 62 'IDS_INSTALL_OS_NOT_SUPPORTED', |
| 57 'IDS_INSTALL_OS_ERROR', | |
| 58 'IDS_INSTALL_SINGLETON_ACQUISITION_FAILED', | 63 'IDS_INSTALL_SINGLETON_ACQUISITION_FAILED', |
| 59 'IDS_INSTALL_TEMP_DIR_FAILED', | 64 'IDS_INSTALL_TEMP_DIR_FAILED', |
| 60 'IDS_INSTALL_UNCOMPRESSION_FAILED', | 65 'IDS_INSTALL_UNCOMPRESSION_FAILED', |
| 61 'IDS_INSTALL_INVALID_ARCHIVE', | 66 'IDS_PRODUCT_DESCRIPTION', |
| 62 'IDS_INSTALL_INSUFFICIENT_RIGHTS', | 67 'IDS_PRODUCT_NAME', |
| 68 'IDS_SAME_VERSION_REPAIR_FAILED', |
| 69 'IDS_SETUP_PATCH_FAILED', |
| 70 'IDS_SHORTCUT_NEW_WINDOW', |
| 63 'IDS_SHORTCUT_TOOLTIP', | 71 'IDS_SHORTCUT_TOOLTIP', |
| 64 'IDS_SHORTCUT_NEW_WINDOW', | 72 'IDS_SXS_SHORTCUT_NAME', |
| 65 'IDS_APP_SHORTCUTS_SUBDIR_NAME', | |
| 66 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY', | |
| 67 'IDS_INBOUND_MDNS_RULE_NAME', | |
| 68 'IDS_INBOUND_MDNS_RULE_NAME_CANARY', | |
| 69 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', | |
| 70 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY', | |
| 71 'IDS_INSTALL_EXISTING_VERSION_LAUNCHED', | |
| 72 ] | 73 ] |
| 73 | 74 |
| 75 # Certain strings are conditional on a brand's install mode (see |
| 76 # chrome/install_static/install_modes.h for details). This allows |
| 77 # installer::GetLocalizedString to return a resource specific to the current |
| 78 # install mode at runtime (e.g., "Google Chrome SxS" as IDS_SHORTCUT_NAME for |
| 79 # the localized shortcut name for Google Chrome's canary channel). |
| 80 # l10n_util::GetStringUTF16 (used within the rest of Chrome) is unaffected, and |
| 81 # will always return the requested string. |
| 82 # |
| 83 # This mapping provides brand- and mode-specific string ids for a given input id |
| 84 # as described here: |
| 85 # { |
| 86 # resource_id_1: { # A resource ID for use with GetLocalizedString. |
| 87 # brand_1: [ # 'google_chrome', for example. |
| 88 # string_id_1, # Strings listed in order of the brand's modes, as |
| 89 # string_id_2, # specified in install_static::InstallConstantIndex. |
| 90 # ... |
| 91 # string_id_N, |
| 92 # ], |
| 93 # brand_2: [ # 'chromium', for example. |
| 94 # ... |
| 95 # ], |
| 96 # }, |
| 97 # resource_id_2: ... |
| 98 # } |
| 99 # 'resource_id_1' names an existing string ID. All calls to |
| 100 # installer::GetLocalizedString with this string ID will map to the |
| 101 # mode-specific string. |
| 102 # |
| 103 # Note: Update the test expectations in GetBaseMessageIdForMode.GoogleStringIds |
| 104 # when adding to/modifying this structure. |
| 105 MODE_SPECIFIC_STRINGS = { |
| 106 'IDS_APP_SHORTCUTS_SUBDIR_NAME': { |
| 107 'google_chrome': [ |
| 108 'IDS_APP_SHORTCUTS_SUBDIR_NAME', |
| 109 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY', |
| 110 ], |
| 111 'chromium': [ |
| 112 'IDS_APP_SHORTCUTS_SUBDIR_NAME', |
| 113 ], |
| 114 }, |
| 115 'IDS_INBOUND_MDNS_RULE_DESCRIPTION': { |
| 116 'google_chrome': [ |
| 117 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', |
| 118 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY', |
| 119 ], |
| 120 'chromium': [ |
| 121 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', |
| 122 ], |
| 123 }, |
| 124 'IDS_INBOUND_MDNS_RULE_NAME': { |
| 125 'google_chrome': [ |
| 126 'IDS_INBOUND_MDNS_RULE_NAME', |
| 127 'IDS_INBOUND_MDNS_RULE_NAME_CANARY', |
| 128 ], |
| 129 'chromium': [ |
| 130 'IDS_INBOUND_MDNS_RULE_NAME', |
| 131 ], |
| 132 }, |
| 133 # In contrast to the strings above, this one (IDS_PRODUCT_NAME) is used |
| 134 # throughout Chrome in mode-independent contexts. Within the installer (the |
| 135 # place where this mapping matters), it is only used for mode-specific strings |
| 136 # such as the name of Chrome's shortcut. |
| 137 'IDS_PRODUCT_NAME': { |
| 138 'google_chrome': [ |
| 139 'IDS_PRODUCT_NAME', |
| 140 'IDS_SXS_SHORTCUT_NAME', |
| 141 ], |
| 142 'chromium': [ |
| 143 'IDS_PRODUCT_NAME', |
| 144 ], |
| 145 }, |
| 146 } |
| 147 # Note: Update the test expectations in GetBaseMessageIdForMode.GoogleStringIds |
| 148 # when adding to/modifying the above structure. |
| 149 |
| 74 # The ID of the first resource string. | 150 # The ID of the first resource string. |
| 75 FIRST_RESOURCE_ID = 1600 | 151 FIRST_RESOURCE_ID = 1600 |
| 76 | 152 |
| 77 | 153 |
| 78 class GrdHandler(sax.handler.ContentHandler): | 154 class GrdHandler(sax.handler.ContentHandler): |
| 79 """Extracts selected strings from a .grd file. | 155 """Extracts selected strings from a .grd file. |
| 80 | 156 |
| 81 Attributes: | 157 Attributes: |
| 82 messages: A dict mapping string identifiers to their corresponding messages. | 158 messages: A dict mapping string identifiers to their corresponding messages. |
| 83 """ | 159 """ |
| (...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 208 translated_string = ''.join(self.__text_scraps).strip() | 284 translated_string = ''.join(self.__text_scraps).strip() |
| 209 for string_id in self.__string_ids: | 285 for string_id in self.__string_ids: |
| 210 self.translations[string_id] = translated_string | 286 self.translations[string_id] = translated_string |
| 211 self.__string_ids = None | 287 self.__string_ids = None |
| 212 self.__text_scraps = [] | 288 self.__text_scraps = [] |
| 213 self.__characters_callback = None | 289 self.__characters_callback = None |
| 214 | 290 |
| 215 | 291 |
| 216 class StringRcMaker(object): | 292 class StringRcMaker(object): |
| 217 """Makes .h and .rc files containing strings and translations.""" | 293 """Makes .h and .rc files containing strings and translations.""" |
| 218 def __init__(self, name, inputs, outdir): | 294 def __init__(self, name, inputs, outdir, brand): |
| 219 """Constructs a maker. | 295 """Constructs a maker. |
| 220 | 296 |
| 221 Args: | 297 Args: |
| 222 name: The base name of the generated files (e.g., | 298 name: The base name of the generated files (e.g., |
| 223 'installer_util_strings'). | 299 'installer_util_strings'). |
| 224 inputs: A list of (grd_file, xtb_dir) pairs containing the source data. | 300 inputs: A list of (grd_file, xtb_dir) pairs containing the source data. |
| 225 outdir: The directory into which the files will be generated. | 301 outdir: The directory into which the files will be generated. |
| 226 """ | 302 """ |
| 227 self.name = name | 303 self.name = name |
| 228 self.inputs = inputs | 304 self.inputs = inputs |
| 229 self.outdir = outdir | 305 self.outdir = outdir |
| 306 self.brand = brand |
| 230 | 307 |
| 231 def MakeFiles(self): | 308 def MakeFiles(self): |
| 232 translated_strings = self.__ReadSourceAndTranslatedStrings() | 309 translated_strings = self.__ReadSourceAndTranslatedStrings() |
| 233 self.__WriteRCFile(translated_strings) | 310 self.__WriteRCFile(translated_strings) |
| 234 self.__WriteHeaderFile(translated_strings) | 311 self.__WriteHeaderFile(translated_strings) |
| 235 | 312 |
| 236 class __TranslationData(object): | 313 class __TranslationData(object): |
| 237 """A container of information about a single translation.""" | 314 """A container of information about a single translation.""" |
| 238 def __init__(self, resource_id_str, language, translation): | 315 def __init__(self, resource_id_str, language, translation): |
| 239 self.resource_id_str = resource_id_str | 316 self.resource_id_str = resource_id_str |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 340 escaped_text)) | 417 escaped_text)) |
| 341 outfile.write(FOOTER_TEXT) | 418 outfile.write(FOOTER_TEXT) |
| 342 | 419 |
| 343 def __WriteHeaderFile(self, translated_strings): | 420 def __WriteHeaderFile(self, translated_strings): |
| 344 """Writes a .h file with resource ids.""" | 421 """Writes a .h file with resource ids.""" |
| 345 # TODO(grt): Stream the lines to the file rather than building this giant | 422 # TODO(grt): Stream the lines to the file rather than building this giant |
| 346 # list of lines first. | 423 # list of lines first. |
| 347 lines = [] | 424 lines = [] |
| 348 do_languages_lines = ['\n#define DO_LANGUAGES'] | 425 do_languages_lines = ['\n#define DO_LANGUAGES'] |
| 349 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING'] | 426 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING'] |
| 427 do_mode_strings_lines = ['\n#define DO_MODE_STRINGS'] |
| 350 | 428 |
| 351 # Write the values for how the languages ids are offset. | 429 # Write the values for how the languages ids are offset. |
| 352 seen_languages = set() | 430 seen_languages = set() |
| 353 offset_id = 0 | 431 offset_id = 0 |
| 354 for translation_data in translated_strings: | 432 for translation_data in translated_strings: |
| 355 lang = translation_data.language | 433 lang = translation_data.language |
| 356 if lang not in seen_languages: | 434 if lang not in seen_languages: |
| 357 seen_languages.add(lang) | 435 seen_languages.add(lang) |
| 358 lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id)) | 436 lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id)) |
| 359 do_languages_lines.append(' HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)' | 437 do_languages_lines.append(' HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)' |
| 360 % (lang.replace('_', '-').lower(), lang)) | 438 % (lang.replace('_', '-').lower(), lang)) |
| 361 offset_id += 1 | 439 offset_id += 1 |
| 362 else: | 440 else: |
| 363 break | 441 break |
| 364 | 442 |
| 365 # Write the resource ids themselves. | 443 # Write the resource ids themselves. |
| 366 resource_id = FIRST_RESOURCE_ID | 444 resource_id = FIRST_RESOURCE_ID |
| 367 for translation_data in translated_strings: | 445 for translation_data in translated_strings: |
| 368 lines.append('#define %s %s' % (translation_data.resource_id_str + '_' + | 446 lines.append('#define %s %s' % (translation_data.resource_id_str + '_' + |
| 369 translation_data.language, | 447 translation_data.language, |
| 370 resource_id)) | 448 resource_id)) |
| 371 resource_id += 1 | 449 resource_id += 1 |
| 372 | 450 |
| 451 # Handle mode-specific strings. |
| 452 for string_id, brands in MODE_SPECIFIC_STRINGS.iteritems(): |
| 453 # Populate the DO_MODE_STRINGS macro. |
| 454 brand_strings = brands.get(self.brand) |
| 455 if not brand_strings: |
| 456 raise exceptions.RuntimeError( |
| 457 'No strings declared for brand \'%s\' in MODE_SPECIFIC_STRINGS for ' |
| 458 'message %s' % (self.brand, string_id)) |
| 459 do_mode_strings_lines.append( |
| 460 ' HANDLE_MODE_STRING(%s_BASE, %s)' |
| 461 % (string_id, ', '.join([ ('%s_BASE' % s) for s in brand_strings]))) |
| 462 |
| 373 # Write out base ID values. | 463 # Write out base ID values. |
| 374 for string_id in STRING_IDS: | 464 for string_id in STRING_IDS: |
| 375 lines.append('#define %s_BASE %s_%s' % (string_id, | 465 lines.append('#define %s_BASE %s_%s' % (string_id, |
| 376 string_id, | 466 string_id, |
| 377 translated_strings[0].language)) | 467 translated_strings[0].language)) |
| 378 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)' | 468 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)' |
| 379 % (string_id, string_id)) | 469 % (string_id, string_id)) |
| 380 | 470 |
| 381 with open(os.path.join(self.outdir, self.name + '.h'), 'wb') as outfile: | 471 with open(os.path.join(self.outdir, self.name + '.h'), 'wb') as outfile: |
| 382 outfile.write('\n'.join(lines)) | 472 outfile.write('\n'.join(lines)) |
| 383 outfile.write('\n#ifndef RC_INVOKED') | 473 outfile.write('\n#ifndef RC_INVOKED') |
| 384 outfile.write(' \\\n'.join(do_languages_lines)) | 474 outfile.write(' \\\n'.join(do_languages_lines)) |
| 385 outfile.write(' \\\n'.join(installer_string_mapping_lines)) | 475 outfile.write(' \\\n'.join(installer_string_mapping_lines)) |
| 476 outfile.write(' \\\n'.join(do_mode_strings_lines)) |
| 386 # .rc files must end in a new line | 477 # .rc files must end in a new line |
| 387 outfile.write('\n#endif // ndef RC_INVOKED\n') | 478 outfile.write('\n#endif // ndef RC_INVOKED\n') |
| 388 | 479 |
| 389 | 480 |
| 390 def ParseCommandLine(): | 481 def ParseCommandLine(): |
| 391 def GrdPathAndXtbDirPair(string): | 482 def GrdPathAndXtbDirPair(string): |
| 392 """Returns (grd_path, xtb_dir) given a colon-separated string of the same. | 483 """Returns (grd_path, xtb_dir) given a colon-separated string of the same. |
| 393 """ | 484 """ |
| 394 parts = string.split(':') | 485 parts = string.split(':') |
| 395 if len(parts) is not 2: | 486 if len(parts) is not 2: |
| 396 raise argparse.ArgumentTypeError('%r is not grd_path:xtb_dir') | 487 raise argparse.ArgumentTypeError('%r is not grd_path:xtb_dir') |
| 397 return (parts[0], parts[1]) | 488 return (parts[0], parts[1]) |
| 398 | 489 |
| 399 parser = argparse.ArgumentParser( | 490 parser = argparse.ArgumentParser( |
| 400 description='Generate .h and .rc files for installer strings.') | 491 description='Generate .h and .rc files for installer strings.') |
| 492 brands = [b for b in MODE_SPECIFIC_STRINGS.itervalues().next().iterkeys()] |
| 493 parser.add_argument('-b', |
| 494 choices=brands, |
| 495 required=True, |
| 496 help='identifier of the browser brand (e.g., chromium).', |
| 497 dest='brand') |
| 401 parser.add_argument('-i', action='append', | 498 parser.add_argument('-i', action='append', |
| 402 type=GrdPathAndXtbDirPair, | 499 type=GrdPathAndXtbDirPair, |
| 403 required=True, | 500 required=True, |
| 404 help='path to .grd file:relative path to .xtb dir', | 501 help='path to .grd file:relative path to .xtb dir', |
| 405 metavar='GRDFILE:XTBDIR', | 502 metavar='GRDFILE:XTBDIR', |
| 406 dest='inputs') | 503 dest='inputs') |
| 407 parser.add_argument('-o', | 504 parser.add_argument('-o', |
| 408 required=True, | 505 required=True, |
| 409 help='output directory for generated .rc and .h files', | 506 help='output directory for generated .rc and .h files', |
| 410 dest='outdir') | 507 dest='outdir') |
| 411 parser.add_argument('-n', | 508 parser.add_argument('-n', |
| 412 required=True, | 509 required=True, |
| 413 help='base name of generated .rc and .h files', | 510 help='base name of generated .rc and .h files', |
| 414 dest='name') | 511 dest='name') |
| 415 return parser.parse_args() | 512 return parser.parse_args() |
| 416 | 513 |
| 417 | 514 |
| 418 def main(): | 515 def main(): |
| 419 args = ParseCommandLine() | 516 args = ParseCommandLine() |
| 420 StringRcMaker(args.name, args.inputs, args.outdir).MakeFiles() | 517 StringRcMaker(args.name, args.inputs, args.outdir, args.brand).MakeFiles() |
| 421 return 0 | 518 return 0 |
| 422 | 519 |
| 423 | 520 |
| 424 if '__main__' == __name__: | 521 if '__main__' == __name__: |
| 425 sys.exit(main()) | 522 sys.exit(main()) |
| OLD | NEW |