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 |