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 |