Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(226)

Side by Side Diff: chrome/installer/util/prebuild/create_string_rc.py

Issue 2791593002: Allow installer::GetLocalizedString to return mode-specific strings. (Closed)
Patch Set: Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698