Chromium Code Reviews| 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 25 matching lines...) Expand all Loading... | |
| 36 import sys | 36 import sys |
| 37 from xml import sax | 37 from xml import sax |
| 38 | 38 |
| 39 BASEDIR = os.path.dirname(os.path.abspath(__file__)) | 39 BASEDIR = os.path.dirname(os.path.abspath(__file__)) |
| 40 sys.path.append(os.path.join(BASEDIR, '../../../../tools/grit')) | 40 sys.path.append(os.path.join(BASEDIR, '../../../../tools/grit')) |
| 41 sys.path.append(os.path.join(BASEDIR, '../../../../tools/python')) | 41 sys.path.append(os.path.join(BASEDIR, '../../../../tools/python')) |
| 42 | 42 |
| 43 from grit.extern import tclib | 43 from grit.extern import tclib |
| 44 | 44 |
| 45 # The IDs of strings we want to import from the .grd files and include in | 45 # The IDs of strings we want to import from the .grd files and include in |
| 46 # setup.exe's resources. | 46 # setup.exe's resources. |
|
manzagop (departed)
2017/03/31 16:19:01
nit: is it common practice to mention the alphabet
grt (UTC plus 2)
2017/04/03 11:59:42
There's no technical requirement, so I don't think
| |
| 47 STRING_IDS = [ | 47 STRING_IDS = [ |
| 48 'IDS_PRODUCT_NAME', | |
| 49 'IDS_SXS_SHORTCUT_NAME', | |
| 50 'IDS_PRODUCT_DESCRIPTION', | |
| 51 'IDS_ABOUT_VERSION_COMPANY_NAME', | 48 'IDS_ABOUT_VERSION_COMPANY_NAME', |
| 49 'IDS_APP_SHORTCUTS_SUBDIR_NAME', | |
| 50 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY', | |
| 51 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', | |
| 52 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY', | |
| 53 'IDS_INBOUND_MDNS_RULE_NAME', | |
| 54 'IDS_INBOUND_MDNS_RULE_NAME_CANARY', | |
| 55 'IDS_INSTALL_EXISTING_VERSION_LAUNCHED', | |
| 56 'IDS_INSTALL_FAILED', | |
| 52 'IDS_INSTALL_HIGHER_VERSION', | 57 'IDS_INSTALL_HIGHER_VERSION', |
| 53 'IDS_INSTALL_FAILED', | 58 'IDS_INSTALL_INSUFFICIENT_RIGHTS', |
| 54 'IDS_SAME_VERSION_REPAIR_FAILED', | 59 'IDS_INSTALL_INVALID_ARCHIVE', |
| 55 'IDS_SETUP_PATCH_FAILED', | 60 'IDS_INSTALL_OS_ERROR', |
| 56 'IDS_INSTALL_OS_NOT_SUPPORTED', | 61 'IDS_INSTALL_OS_NOT_SUPPORTED', |
| 57 'IDS_INSTALL_OS_ERROR', | |
| 58 'IDS_INSTALL_SINGLETON_ACQUISITION_FAILED', | 62 'IDS_INSTALL_SINGLETON_ACQUISITION_FAILED', |
| 59 'IDS_INSTALL_TEMP_DIR_FAILED', | 63 'IDS_INSTALL_TEMP_DIR_FAILED', |
| 60 'IDS_INSTALL_UNCOMPRESSION_FAILED', | 64 'IDS_INSTALL_UNCOMPRESSION_FAILED', |
| 61 'IDS_INSTALL_INVALID_ARCHIVE', | 65 'IDS_PRODUCT_DESCRIPTION', |
| 62 'IDS_INSTALL_INSUFFICIENT_RIGHTS', | 66 'IDS_PRODUCT_NAME', |
| 67 'IDS_SAME_VERSION_REPAIR_FAILED', | |
| 68 'IDS_SETUP_PATCH_FAILED', | |
| 69 'IDS_SHORTCUT_NEW_WINDOW', | |
| 63 'IDS_SHORTCUT_TOOLTIP', | 70 'IDS_SHORTCUT_TOOLTIP', |
| 64 'IDS_SHORTCUT_NEW_WINDOW', | 71 '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 ] | 72 ] |
| 73 | 73 |
| 74 # Certain strings are conditional on a brand's install mode (see | |
| 75 # chrome/install_static/install_modes.h for details). This allows | |
| 76 # installer::GetLocalizedString to return a resource specific to the current | |
| 77 # install mode at runtime (e.g., "Google Chrome SxS" as IDS_SHORTCUT_NAME for | |
| 78 # the localized shortcut name for Google Chrome's canary channel). This mapping | |
| 79 # provides brand- and mode-specific string ids for a given input id as described | |
| 80 # here: | |
| 81 # { | |
| 82 # resource_id_1: { # A resource ID for use with GetLocalizedString. | |
| 83 # brand: [ # 'google_chrome', for example. | |
| 84 # string_id_1, # Strings listed in order of the brand's modes, as | |
|
manzagop (departed)
2017/03/31 16:19:00
Add a comment in the mode file that this needs to
grt (UTC plus 2)
2017/04/03 11:59:42
Done.
| |
| 85 # string_id_2, # specified in install_static::InstallConstantIndex. | |
| 86 # ... | |
| 87 # string_id_N, | |
| 88 # ], | |
| 89 # '': [ # Default brand fallback (Chromium). | |
| 90 # string_id_1, | |
| 91 # ], | |
| 92 # }, | |
| 93 # resource_id_2: ... | |
| 94 # } | |
| 95 # 'resource_id_1' may name an existing string ID or may be distinct. In the | |
| 96 # former case, all calls to installer::GetLocalizedString with the existing | |
| 97 # string ID will map to the mode-specific string. It becomes impossible to get | |
| 98 # the general ("primary") variant of the string when a secondary install mode | |
| 99 # is in use. By using a distinct ID, both the mode-specific and the general can | |
| 100 # be retrieved as needed. | |
|
manzagop (departed)
2017/03/31 16:19:01
(rambling)
IIUC we have string ids, and a layer o
grt (UTC plus 2)
2017/04/03 11:59:42
I introduced the pseudo-id to deal with IDS_PRODUC
grt (UTC plus 2)
2017/04/03 11:59:42
The hybrid was just something I dreamed up while t
| |
| 101 MODE_SPECIFIC_STRINGS = { | |
| 102 'IDS_APP_SHORTCUTS_SUBDIR_NAME': { | |
| 103 'google_chrome': [ | |
|
manzagop (departed)
2017/03/31 16:19:01
nit: use a constant? For the default brand too?
grt (UTC plus 2)
2017/04/03 11:59:42
This matches the -b BRAND value provided on the co
manzagop (departed)
2017/04/03 13:06:33
Oh, I meant introduce constants GOOGLE_BRAND and C
grt (UTC plus 2)
2017/04/03 13:31:30
Okay. The new validation I added should catch any
| |
| 104 'IDS_APP_SHORTCUTS_SUBDIR_NAME', | |
| 105 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY', | |
| 106 ], | |
| 107 '': [ | |
| 108 'IDS_APP_SHORTCUTS_SUBDIR_NAME', | |
| 109 ], | |
| 110 }, | |
| 111 'IDS_INBOUND_MDNS_RULE_DESCRIPTION': { | |
| 112 'google_chrome': [ | |
| 113 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', | |
| 114 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY', | |
| 115 ], | |
| 116 '': [ | |
| 117 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', | |
| 118 ], | |
| 119 }, | |
| 120 'IDS_INBOUND_MDNS_RULE_NAME': { | |
| 121 'google_chrome': [ | |
| 122 'IDS_INBOUND_MDNS_RULE_NAME', | |
| 123 'IDS_INBOUND_MDNS_RULE_NAME_CANARY', | |
| 124 ], | |
| 125 '': [ | |
| 126 'IDS_INBOUND_MDNS_RULE_NAME', | |
| 127 ], | |
| 128 }, | |
| 129 'IDS_SHORTCUT_NAME': { | |
| 130 'google_chrome': [ | |
| 131 'IDS_PRODUCT_NAME', | |
|
manzagop (departed)
2017/03/31 16:19:01
Can you speak to the tradeoffs of introducing a ne
grt (UTC plus 2)
2017/04/03 11:59:42
Does my response above address this question?
manzagop (departed)
2017/04/03 13:06:33
Yep! Thanks.
| |
| 132 'IDS_SXS_SHORTCUT_NAME', | |
| 133 ], | |
| 134 '': [ | |
| 135 'IDS_PRODUCT_NAME', | |
| 136 ], | |
| 137 }, | |
| 138 } | |
| 139 | |
| 74 # The ID of the first resource string. | 140 # The ID of the first resource string. |
| 75 FIRST_RESOURCE_ID = 1600 | 141 FIRST_RESOURCE_ID = 1600 |
| 76 | 142 |
| 77 | 143 |
| 78 class GrdHandler(sax.handler.ContentHandler): | 144 class GrdHandler(sax.handler.ContentHandler): |
| 79 """Extracts selected strings from a .grd file. | 145 """Extracts selected strings from a .grd file. |
| 80 | 146 |
| 81 Attributes: | 147 Attributes: |
| 82 messages: A dict mapping string identifiers to their corresponding messages. | 148 messages: A dict mapping string identifiers to their corresponding messages. |
| 83 """ | 149 """ |
| (...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 208 translated_string = ''.join(self.__text_scraps).strip() | 274 translated_string = ''.join(self.__text_scraps).strip() |
| 209 for string_id in self.__string_ids: | 275 for string_id in self.__string_ids: |
| 210 self.translations[string_id] = translated_string | 276 self.translations[string_id] = translated_string |
| 211 self.__string_ids = None | 277 self.__string_ids = None |
| 212 self.__text_scraps = [] | 278 self.__text_scraps = [] |
| 213 self.__characters_callback = None | 279 self.__characters_callback = None |
| 214 | 280 |
| 215 | 281 |
| 216 class StringRcMaker(object): | 282 class StringRcMaker(object): |
| 217 """Makes .h and .rc files containing strings and translations.""" | 283 """Makes .h and .rc files containing strings and translations.""" |
| 218 def __init__(self, name, inputs, outdir): | 284 def __init__(self, name, inputs, outdir, brand): |
| 219 """Constructs a maker. | 285 """constructs a maker. |
|
manzagop (departed)
2017/03/31 16:19:01
nitty question: no capital letter?
grt (UTC plus 2)
2017/04/03 11:59:42
I wasn't aware I'd changed that. Oops.
| |
| 220 | 286 |
| 221 Args: | 287 Args: |
| 222 name: The base name of the generated files (e.g., | 288 name: The base name of the generated files (e.g., |
| 223 'installer_util_strings'). | 289 'installer_util_strings'). |
| 224 inputs: A list of (grd_file, xtb_dir) pairs containing the source data. | 290 inputs: A list of (grd_file, xtb_dir) pairs containing the source data. |
| 225 outdir: The directory into which the files will be generated. | 291 outdir: The directory into which the files will be generated. |
| 226 """ | 292 """ |
| 227 self.name = name | 293 self.name = name |
| 228 self.inputs = inputs | 294 self.inputs = inputs |
| 229 self.outdir = outdir | 295 self.outdir = outdir |
| 296 self.brand = brand | |
| 230 | 297 |
| 231 def MakeFiles(self): | 298 def MakeFiles(self): |
| 232 translated_strings = self.__ReadSourceAndTranslatedStrings() | 299 translated_strings = self.__ReadSourceAndTranslatedStrings() |
| 233 self.__WriteRCFile(translated_strings) | 300 self.__WriteRCFile(translated_strings) |
| 234 self.__WriteHeaderFile(translated_strings) | 301 self.__WriteHeaderFile(translated_strings) |
| 235 | 302 |
| 236 class __TranslationData(object): | 303 class __TranslationData(object): |
| 237 """A container of information about a single translation.""" | 304 """A container of information about a single translation.""" |
| 238 def __init__(self, resource_id_str, language, translation): | 305 def __init__(self, resource_id_str, language, translation): |
| 239 self.resource_id_str = resource_id_str | 306 self.resource_id_str = resource_id_str |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 340 escaped_text)) | 407 escaped_text)) |
| 341 outfile.write(FOOTER_TEXT) | 408 outfile.write(FOOTER_TEXT) |
| 342 | 409 |
| 343 def __WriteHeaderFile(self, translated_strings): | 410 def __WriteHeaderFile(self, translated_strings): |
| 344 """Writes a .h file with resource ids.""" | 411 """Writes a .h file with resource ids.""" |
| 345 # TODO(grt): Stream the lines to the file rather than building this giant | 412 # TODO(grt): Stream the lines to the file rather than building this giant |
| 346 # list of lines first. | 413 # list of lines first. |
| 347 lines = [] | 414 lines = [] |
| 348 do_languages_lines = ['\n#define DO_LANGUAGES'] | 415 do_languages_lines = ['\n#define DO_LANGUAGES'] |
| 349 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING'] | 416 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING'] |
| 417 do_mode_strings_lines = ['\n#define DO_MODE_STRINGS'] | |
| 350 | 418 |
| 351 # Write the values for how the languages ids are offset. | 419 # Write the values for how the languages ids are offset. |
| 352 seen_languages = set() | 420 seen_languages = set() |
| 353 offset_id = 0 | 421 offset_id = 0 |
| 354 for translation_data in translated_strings: | 422 for translation_data in translated_strings: |
| 355 lang = translation_data.language | 423 lang = translation_data.language |
| 356 if lang not in seen_languages: | 424 if lang not in seen_languages: |
| 357 seen_languages.add(lang) | 425 seen_languages.add(lang) |
| 358 lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id)) | 426 lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id)) |
| 359 do_languages_lines.append(' HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)' | 427 do_languages_lines.append(' HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)' |
| 360 % (lang.replace('_', '-').lower(), lang)) | 428 % (lang.replace('_', '-').lower(), lang)) |
| 361 offset_id += 1 | 429 offset_id += 1 |
| 362 else: | 430 else: |
| 363 break | 431 break |
| 364 | 432 |
| 365 # Write the resource ids themselves. | 433 # Write the resource ids themselves. |
| 366 resource_id = FIRST_RESOURCE_ID | 434 resource_id = FIRST_RESOURCE_ID |
| 367 for translation_data in translated_strings: | 435 for translation_data in translated_strings: |
| 368 lines.append('#define %s %s' % (translation_data.resource_id_str + '_' + | 436 lines.append('#define %s %s' % (translation_data.resource_id_str + '_' + |
| 369 translation_data.language, | 437 translation_data.language, |
| 370 resource_id)) | 438 resource_id)) |
| 371 resource_id += 1 | 439 resource_id += 1 |
| 372 | 440 |
| 441 # Handle mode-specific strings. | |
| 442 for string_id, brands in MODE_SPECIFIC_STRINGS.iteritems(): | |
| 443 # Write the synthetic resource ids for per-mode strings. | |
| 444 if string_id not in STRING_IDS: | |
| 445 lines.append('#define %s_BASE %s' % (string_id, resource_id)) | |
| 446 resource_id += 1 | |
| 447 # Populate the DO_MODE_STRINGS macro. | |
| 448 brand_strings = brands.get(self.brand, brands['']) | |
| 449 do_mode_strings_lines.append( | |
| 450 ' HANDLE_MODE_STRING(%s_BASE, %s)' | |
| 451 % (string_id, ', '.join([ ('%s_BASE' % s) for s in brand_strings]))) | |
|
manzagop (departed)
2017/03/31 16:19:00
Should we validate that no brand string is a pseud
grt (UTC plus 2)
2017/04/03 11:59:42
noop -- i've removed psuedo strings
| |
| 452 | |
| 373 # Write out base ID values. | 453 # Write out base ID values. |
| 374 for string_id in STRING_IDS: | 454 for string_id in STRING_IDS: |
| 375 lines.append('#define %s_BASE %s_%s' % (string_id, | 455 lines.append('#define %s_BASE %s_%s' % (string_id, |
| 376 string_id, | 456 string_id, |
| 377 translated_strings[0].language)) | 457 translated_strings[0].language)) |
| 378 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)' | 458 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)' |
| 379 % (string_id, string_id)) | 459 % (string_id, string_id)) |
| 380 | 460 |
| 381 with open(os.path.join(self.outdir, self.name + '.h'), 'wb') as outfile: | 461 with open(os.path.join(self.outdir, self.name + '.h'), 'wb') as outfile: |
| 382 outfile.write('\n'.join(lines)) | 462 outfile.write('\n'.join(lines)) |
| 383 outfile.write('\n#ifndef RC_INVOKED') | 463 outfile.write('\n#ifndef RC_INVOKED') |
| 384 outfile.write(' \\\n'.join(do_languages_lines)) | 464 outfile.write(' \\\n'.join(do_languages_lines)) |
| 385 outfile.write(' \\\n'.join(installer_string_mapping_lines)) | 465 outfile.write(' \\\n'.join(installer_string_mapping_lines)) |
| 466 outfile.write(' \\\n'.join(do_mode_strings_lines)) | |
| 386 # .rc files must end in a new line | 467 # .rc files must end in a new line |
| 387 outfile.write('\n#endif // ndef RC_INVOKED\n') | 468 outfile.write('\n#endif // ndef RC_INVOKED\n') |
| 388 | 469 |
| 389 | 470 |
| 390 def ParseCommandLine(): | 471 def ParseCommandLine(): |
| 391 def GrdPathAndXtbDirPair(string): | 472 def GrdPathAndXtbDirPair(string): |
| 392 """Returns (grd_path, xtb_dir) given a colon-separated string of the same. | 473 """Returns (grd_path, xtb_dir) given a colon-separated string of the same. |
| 393 """ | 474 """ |
| 394 parts = string.split(':') | 475 parts = string.split(':') |
| 395 if len(parts) is not 2: | 476 if len(parts) is not 2: |
| 396 raise argparse.ArgumentTypeError('%r is not grd_path:xtb_dir') | 477 raise argparse.ArgumentTypeError('%r is not grd_path:xtb_dir') |
| 397 return (parts[0], parts[1]) | 478 return (parts[0], parts[1]) |
| 398 | 479 |
| 399 parser = argparse.ArgumentParser( | 480 parser = argparse.ArgumentParser( |
| 400 description='Generate .h and .rc files for installer strings.') | 481 description='Generate .h and .rc files for installer strings.') |
| 482 parser.add_argument('-b', | |
| 483 required=True, | |
| 484 help='identifier of the browser brand (e.g., chromium).', | |
| 485 dest='brand') | |
| 401 parser.add_argument('-i', action='append', | 486 parser.add_argument('-i', action='append', |
| 402 type=GrdPathAndXtbDirPair, | 487 type=GrdPathAndXtbDirPair, |
| 403 required=True, | 488 required=True, |
| 404 help='path to .grd file:relative path to .xtb dir', | 489 help='path to .grd file:relative path to .xtb dir', |
| 405 metavar='GRDFILE:XTBDIR', | 490 metavar='GRDFILE:XTBDIR', |
| 406 dest='inputs') | 491 dest='inputs') |
| 407 parser.add_argument('-o', | 492 parser.add_argument('-o', |
| 408 required=True, | 493 required=True, |
| 409 help='output directory for generated .rc and .h files', | 494 help='output directory for generated .rc and .h files', |
| 410 dest='outdir') | 495 dest='outdir') |
| 411 parser.add_argument('-n', | 496 parser.add_argument('-n', |
| 412 required=True, | 497 required=True, |
| 413 help='base name of generated .rc and .h files', | 498 help='base name of generated .rc and .h files', |
| 414 dest='name') | 499 dest='name') |
| 415 return parser.parse_args() | 500 return parser.parse_args() |
| 416 | 501 |
| 417 | 502 |
| 418 def main(): | 503 def main(): |
| 419 args = ParseCommandLine() | 504 args = ParseCommandLine() |
| 420 StringRcMaker(args.name, args.inputs, args.outdir).MakeFiles() | 505 StringRcMaker(args.name, args.inputs, args.outdir, args.brand).MakeFiles() |
| 421 return 0 | 506 return 0 |
| 422 | 507 |
| 423 | 508 |
| 424 if '__main__' == __name__: | 509 if '__main__' == __name__: |
| 425 sys.exit(main()) | 510 sys.exit(main()) |
| OLD | NEW |