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

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

Issue 2802713002: Enable side-by-side beta and dev channels. (Closed)
Patch Set: add missing test stanza for reland 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 26 matching lines...) Expand all
37 import sys 37 import sys
38 from xml import sax 38 from xml import sax
39 39
40 BASEDIR = os.path.dirname(os.path.abspath(__file__)) 40 BASEDIR = os.path.dirname(os.path.abspath(__file__))
41 sys.path.append(os.path.join(BASEDIR, '../../../../tools/grit')) 41 sys.path.append(os.path.join(BASEDIR, '../../../../tools/grit'))
42 sys.path.append(os.path.join(BASEDIR, '../../../../tools/python')) 42 sys.path.append(os.path.join(BASEDIR, '../../../../tools/python'))
43 43
44 from grit.extern import tclib 44 from grit.extern import tclib
45 45
46 # 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
47 # setup.exe's resources. 47 # setup.exe's resources. These strings are universal for all brands.
48 STRING_IDS = [ 48 STRING_IDS = [
49 'IDS_ABOUT_VERSION_COMPANY_NAME', 49 'IDS_ABOUT_VERSION_COMPANY_NAME',
50 'IDS_APP_SHORTCUTS_SUBDIR_NAME', 50 'IDS_APP_SHORTCUTS_SUBDIR_NAME',
51 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY',
52 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', 51 'IDS_INBOUND_MDNS_RULE_DESCRIPTION',
53 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY',
54 'IDS_INBOUND_MDNS_RULE_NAME', 52 'IDS_INBOUND_MDNS_RULE_NAME',
55 'IDS_INBOUND_MDNS_RULE_NAME_CANARY',
56 'IDS_INSTALL_EXISTING_VERSION_LAUNCHED', 53 'IDS_INSTALL_EXISTING_VERSION_LAUNCHED',
57 'IDS_INSTALL_FAILED', 54 'IDS_INSTALL_FAILED',
58 'IDS_INSTALL_HIGHER_VERSION', 55 'IDS_INSTALL_HIGHER_VERSION',
59 'IDS_INSTALL_INSUFFICIENT_RIGHTS', 56 'IDS_INSTALL_INSUFFICIENT_RIGHTS',
60 'IDS_INSTALL_INVALID_ARCHIVE', 57 'IDS_INSTALL_INVALID_ARCHIVE',
61 'IDS_INSTALL_OS_ERROR', 58 'IDS_INSTALL_OS_ERROR',
62 'IDS_INSTALL_OS_NOT_SUPPORTED', 59 'IDS_INSTALL_OS_NOT_SUPPORTED',
63 'IDS_INSTALL_SINGLETON_ACQUISITION_FAILED', 60 'IDS_INSTALL_SINGLETON_ACQUISITION_FAILED',
64 'IDS_INSTALL_TEMP_DIR_FAILED', 61 'IDS_INSTALL_TEMP_DIR_FAILED',
65 'IDS_INSTALL_UNCOMPRESSION_FAILED', 62 'IDS_INSTALL_UNCOMPRESSION_FAILED',
66 'IDS_PRODUCT_DESCRIPTION', 63 'IDS_PRODUCT_DESCRIPTION',
67 'IDS_PRODUCT_NAME', 64 'IDS_PRODUCT_NAME',
68 'IDS_SAME_VERSION_REPAIR_FAILED', 65 'IDS_SAME_VERSION_REPAIR_FAILED',
69 'IDS_SETUP_PATCH_FAILED', 66 'IDS_SETUP_PATCH_FAILED',
70 'IDS_SHORTCUT_NEW_WINDOW', 67 'IDS_SHORTCUT_NEW_WINDOW',
71 'IDS_SHORTCUT_TOOLTIP', 68 'IDS_SHORTCUT_TOOLTIP',
72 'IDS_SXS_SHORTCUT_NAME',
73 ] 69 ]
74 70
75 # Certain strings are conditional on a brand's install mode (see 71 # Certain strings are conditional on a brand's install mode (see
76 # chrome/install_static/install_modes.h for details). This allows 72 # chrome/install_static/install_modes.h for details). This allows
77 # installer::GetLocalizedString to return a resource specific to the current 73 # 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 74 # 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). 75 # the localized shortcut name for Google Chrome's canary channel).
80 # l10n_util::GetStringUTF16 (used within the rest of Chrome) is unaffected, and 76 # l10n_util::GetStringUTF16 (used within the rest of Chrome) is unaffected, and
81 # will always return the requested string. 77 # will always return the requested string.
82 # 78 #
(...skipping 16 matching lines...) Expand all
99 # 'resource_id_1' names an existing string ID. All calls to 95 # 'resource_id_1' names an existing string ID. All calls to
100 # installer::GetLocalizedString with this string ID will map to the 96 # installer::GetLocalizedString with this string ID will map to the
101 # mode-specific string. 97 # mode-specific string.
102 # 98 #
103 # Note: Update the test expectations in GetBaseMessageIdForMode.GoogleStringIds 99 # Note: Update the test expectations in GetBaseMessageIdForMode.GoogleStringIds
104 # when adding to/modifying this structure. 100 # when adding to/modifying this structure.
105 MODE_SPECIFIC_STRINGS = { 101 MODE_SPECIFIC_STRINGS = {
106 'IDS_APP_SHORTCUTS_SUBDIR_NAME': { 102 'IDS_APP_SHORTCUTS_SUBDIR_NAME': {
107 'google_chrome': [ 103 'google_chrome': [
108 'IDS_APP_SHORTCUTS_SUBDIR_NAME', 104 'IDS_APP_SHORTCUTS_SUBDIR_NAME',
105 'IDS_APP_SHORTCUTS_SUBDIR_NAME_BETA',
106 'IDS_APP_SHORTCUTS_SUBDIR_NAME_DEV',
109 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY', 107 'IDS_APP_SHORTCUTS_SUBDIR_NAME_CANARY',
110 ], 108 ],
111 'chromium': [ 109 'chromium': [
112 'IDS_APP_SHORTCUTS_SUBDIR_NAME', 110 'IDS_APP_SHORTCUTS_SUBDIR_NAME',
113 ], 111 ],
114 }, 112 },
115 'IDS_INBOUND_MDNS_RULE_DESCRIPTION': { 113 'IDS_INBOUND_MDNS_RULE_DESCRIPTION': {
116 'google_chrome': [ 114 'google_chrome': [
117 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', 115 'IDS_INBOUND_MDNS_RULE_DESCRIPTION',
116 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_BETA',
117 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_DEV',
118 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY', 118 'IDS_INBOUND_MDNS_RULE_DESCRIPTION_CANARY',
119 ], 119 ],
120 'chromium': [ 120 'chromium': [
121 'IDS_INBOUND_MDNS_RULE_DESCRIPTION', 121 'IDS_INBOUND_MDNS_RULE_DESCRIPTION',
122 ], 122 ],
123 }, 123 },
124 'IDS_INBOUND_MDNS_RULE_NAME': { 124 'IDS_INBOUND_MDNS_RULE_NAME': {
125 'google_chrome': [ 125 'google_chrome': [
126 'IDS_INBOUND_MDNS_RULE_NAME', 126 'IDS_INBOUND_MDNS_RULE_NAME',
127 'IDS_INBOUND_MDNS_RULE_NAME_BETA',
128 'IDS_INBOUND_MDNS_RULE_NAME_DEV',
127 'IDS_INBOUND_MDNS_RULE_NAME_CANARY', 129 'IDS_INBOUND_MDNS_RULE_NAME_CANARY',
128 ], 130 ],
129 'chromium': [ 131 'chromium': [
130 'IDS_INBOUND_MDNS_RULE_NAME', 132 'IDS_INBOUND_MDNS_RULE_NAME',
131 ], 133 ],
132 }, 134 },
133 # In contrast to the strings above, this one (IDS_PRODUCT_NAME) is used 135 # In contrast to the strings above, this one (IDS_PRODUCT_NAME) is used
134 # throughout Chrome in mode-independent contexts. Within the installer (the 136 # throughout Chrome in mode-independent contexts. Within the installer (the
135 # place where this mapping matters), it is only used for mode-specific strings 137 # place where this mapping matters), it is only used for mode-specific strings
136 # such as the name of Chrome's shortcut. 138 # such as the name of Chrome's shortcut.
137 'IDS_PRODUCT_NAME': { 139 'IDS_PRODUCT_NAME': {
138 'google_chrome': [ 140 'google_chrome': [
139 'IDS_PRODUCT_NAME', 141 'IDS_PRODUCT_NAME',
142 'IDS_SHORTCUT_NAME_BETA',
143 'IDS_SHORTCUT_NAME_DEV',
140 'IDS_SXS_SHORTCUT_NAME', 144 'IDS_SXS_SHORTCUT_NAME',
141 ], 145 ],
142 'chromium': [ 146 'chromium': [
143 'IDS_PRODUCT_NAME', 147 'IDS_PRODUCT_NAME',
144 ], 148 ],
145 }, 149 },
146 } 150 }
147 # Note: Update the test expectations in GetBaseMessageIdForMode.GoogleStringIds 151 # Note: Update the test expectations in GetBaseMessageIdForMode.GoogleStringIds
148 # when adding to/modifying the above structure. 152 # when adding to/modifying the above structure.
149 153
150 # The ID of the first resource string. 154 # The ID of the first resource string.
151 FIRST_RESOURCE_ID = 1600 155 FIRST_RESOURCE_ID = 1600
152 156
153 157
154 class GrdHandler(sax.handler.ContentHandler): 158 class GrdHandler(sax.handler.ContentHandler):
155 """Extracts selected strings from a .grd file. 159 """Extracts selected strings from a .grd file.
156 160
157 Attributes: 161 Attributes:
158 messages: A dict mapping string identifiers to their corresponding messages. 162 messages: A dict mapping string identifiers to their corresponding messages.
159 """ 163 """
160 def __init__(self, string_ids): 164 def __init__(self, string_id_set):
161 """Constructs a handler that reads selected strings from a .grd file. 165 """Constructs a handler that reads selected strings from a .grd file.
162 166
163 The dict attribute |messages| is populated with the strings that are read. 167 The dict attribute |messages| is populated with the strings that are read.
164 168
165 Args: 169 Args:
166 string_ids: A list of message identifiers to extract. 170 string_id_set: A set of message identifiers to extract.
167 """ 171 """
168 sax.handler.ContentHandler.__init__(self) 172 sax.handler.ContentHandler.__init__(self)
169 self.messages = {} 173 self.messages = {}
170 self.__id_set = set(string_ids) 174 self.__id_set = string_id_set
171 self.__message_name = None 175 self.__message_name = None
172 self.__element_stack = [] 176 self.__element_stack = []
173 self.__text_scraps = [] 177 self.__text_scraps = []
174 self.__characters_callback = None 178 self.__characters_callback = None
175 179
176 def startElement(self, name, attrs): 180 def startElement(self, name, attrs):
177 self.__element_stack.append(name) 181 self.__element_stack.append(name)
178 if name == 'message': 182 if name == 'message':
179 self.__OnOpenMessage(attrs.getValue('name')) 183 self.__OnOpenMessage(attrs.getValue('name'))
180 184
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
299 'installer_util_strings'). 303 'installer_util_strings').
300 inputs: A list of (grd_file, xtb_dir) pairs containing the source data. 304 inputs: A list of (grd_file, xtb_dir) pairs containing the source data.
301 outdir: The directory into which the files will be generated. 305 outdir: The directory into which the files will be generated.
302 """ 306 """
303 self.name = name 307 self.name = name
304 self.inputs = inputs 308 self.inputs = inputs
305 self.outdir = outdir 309 self.outdir = outdir
306 self.brand = brand 310 self.brand = brand
307 311
308 def MakeFiles(self): 312 def MakeFiles(self):
309 translated_strings = self.__ReadSourceAndTranslatedStrings() 313 string_id_set = self.__BuildStringIds()
314 translated_strings = self.__ReadSourceAndTranslatedStrings(string_id_set)
310 self.__WriteRCFile(translated_strings) 315 self.__WriteRCFile(translated_strings)
311 self.__WriteHeaderFile(translated_strings) 316 self.__WriteHeaderFile(string_id_set, translated_strings)
312 317
313 class __TranslationData(object): 318 class __TranslationData(object):
314 """A container of information about a single translation.""" 319 """A container of information about a single translation."""
315 def __init__(self, resource_id_str, language, translation): 320 def __init__(self, resource_id_str, language, translation):
316 self.resource_id_str = resource_id_str 321 self.resource_id_str = resource_id_str
317 self.language = language 322 self.language = language
318 self.translation = translation 323 self.translation = translation
319 324
320 def __cmp__(self, other): 325 def __cmp__(self, other):
321 """Allow __TranslationDatas to be sorted by id then by language.""" 326 """Allow __TranslationDatas to be sorted by id then by language."""
322 id_result = cmp(self.resource_id_str, other.resource_id_str) 327 id_result = cmp(self.resource_id_str, other.resource_id_str)
323 return cmp(self.language, other.language) if id_result == 0 else id_result 328 return cmp(self.language, other.language) if id_result == 0 else id_result
324 329
325 def __ReadSourceAndTranslatedStrings(self): 330 def __BuildStringIds(self):
331 """Returns the set of string IDs to extract from the grd and xtb files."""
332 # Start with the strings that apply to all brands.
333 string_id_set = set(STRING_IDS)
334 # Add in the strings for the current brand.
335 for string_id, brands in MODE_SPECIFIC_STRINGS.iteritems():
336 brand_strings = brands.get(self.brand)
337 if not brand_strings:
338 raise exceptions.RuntimeError(
339 'No strings declared for brand \'%s\' in MODE_SPECIFIC_STRINGS for '
340 'message %s' % (self.brand, string_id))
341 string_id_set.update(brand_strings)
342 return string_id_set
343
344 def __ReadSourceAndTranslatedStrings(self, string_id_set):
326 """Reads the source strings and translations from all inputs.""" 345 """Reads the source strings and translations from all inputs."""
327 translated_strings = [] 346 translated_strings = []
328 for grd_file, xtb_dir in self.inputs: 347 for grd_file, xtb_dir in self.inputs:
329 # Get the name of the grd file sans extension. 348 # Get the name of the grd file sans extension.
330 source_name = os.path.splitext(os.path.basename(grd_file))[0] 349 source_name = os.path.splitext(os.path.basename(grd_file))[0]
331 # Compute a glob for the translation files. 350 # Compute a glob for the translation files.
332 xtb_pattern = os.path.join(os.path.dirname(grd_file), xtb_dir, 351 xtb_pattern = os.path.join(os.path.dirname(grd_file), xtb_dir,
333 '%s*.xtb' % source_name) 352 '%s*.xtb' % source_name)
334 translated_strings.extend( 353 translated_strings.extend(
335 self.__ReadSourceAndTranslationsFrom(grd_file, glob.glob(xtb_pattern))) 354 self.__ReadSourceAndTranslationsFrom(string_id_set, grd_file,
355 glob.glob(xtb_pattern)))
336 translated_strings.sort() 356 translated_strings.sort()
337 return translated_strings 357 return translated_strings
338 358
339 def __ReadSourceAndTranslationsFrom(self, grd_file, xtb_files): 359 def __ReadSourceAndTranslationsFrom(self, string_id_set, grd_file, xtb_files):
340 """Reads source strings and translations for a .grd file. 360 """Reads source strings and translations for a .grd file.
341 361
342 Reads the source strings and all available translations for the messages 362 Reads the source strings and all available translations for the messages
343 identified by STRING_IDS. The source string is used where translations are 363 identified by string_id_set. The source string is used where translations
344 missing. 364 are missing.
345 365
346 Args: 366 Args:
367 string_id_set: The identifiers of the strings to read.
347 grd_file: Path to a .grd file. 368 grd_file: Path to a .grd file.
348 xtb_files: List of paths to .xtb files. 369 xtb_files: List of paths to .xtb files.
349 370
350 Returns: 371 Returns:
351 An unsorted list of __TranslationData instances. 372 An unsorted list of __TranslationData instances.
352 """ 373 """
353 sax_parser = sax.make_parser() 374 sax_parser = sax.make_parser()
354 375
355 # Read the source (en-US) string from the .grd file. 376 # Read the source (en-US) string from the .grd file.
356 grd_handler = GrdHandler(STRING_IDS) 377 grd_handler = GrdHandler(string_id_set)
357 sax_parser.setContentHandler(grd_handler) 378 sax_parser.setContentHandler(grd_handler)
358 sax_parser.parse(grd_file) 379 sax_parser.parse(grd_file)
359 source_strings = grd_handler.messages 380 source_strings = grd_handler.messages
360 381
361 # Manually put the source strings as en-US in the list of translated 382 # Manually put the source strings as en-US in the list of translated
362 # strings. 383 # strings.
363 translated_strings = [] 384 translated_strings = []
364 for string_id, message_text in source_strings.iteritems(): 385 for string_id, message_text in source_strings.iteritems():
365 translated_strings.append(self.__TranslationData(string_id, 386 translated_strings.append(self.__TranslationData(string_id,
366 'EN_US', 387 'EN_US',
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
410 for translation in translated_strings: 431 for translation in translated_strings:
411 # Escape special characters for the rc file. 432 # Escape special characters for the rc file.
412 escaped_text = (translation.translation.replace('"', '""') 433 escaped_text = (translation.translation.replace('"', '""')
413 .replace('\t', '\\t') 434 .replace('\t', '\\t')
414 .replace('\n', '\\n')) 435 .replace('\n', '\\n'))
415 outfile.write(u' %s "%s"\n' % 436 outfile.write(u' %s "%s"\n' %
416 (translation.resource_id_str + '_' + translation.language, 437 (translation.resource_id_str + '_' + translation.language,
417 escaped_text)) 438 escaped_text))
418 outfile.write(FOOTER_TEXT) 439 outfile.write(FOOTER_TEXT)
419 440
420 def __WriteHeaderFile(self, translated_strings): 441 def __WriteHeaderFile(self, string_id_set, translated_strings):
421 """Writes a .h file with resource ids.""" 442 """Writes a .h file with resource ids."""
422 # TODO(grt): Stream the lines to the file rather than building this giant 443 # TODO(grt): Stream the lines to the file rather than building this giant
423 # list of lines first. 444 # list of lines first.
424 lines = [] 445 lines = []
425 do_languages_lines = ['\n#define DO_LANGUAGES'] 446 do_languages_lines = ['\n#define DO_LANGUAGES']
426 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING'] 447 installer_string_mapping_lines = ['\n#define DO_INSTALLER_STRING_MAPPING']
427 do_mode_strings_lines = ['\n#define DO_MODE_STRINGS'] 448 do_mode_strings_lines = ['\n#define DO_MODE_STRINGS']
428 449
429 # Write the values for how the languages ids are offset. 450 # Write the values for how the languages ids are offset.
430 seen_languages = set() 451 seen_languages = set()
(...skipping 23 matching lines...) Expand all
454 brand_strings = brands.get(self.brand) 475 brand_strings = brands.get(self.brand)
455 if not brand_strings: 476 if not brand_strings:
456 raise exceptions.RuntimeError( 477 raise exceptions.RuntimeError(
457 'No strings declared for brand \'%s\' in MODE_SPECIFIC_STRINGS for ' 478 'No strings declared for brand \'%s\' in MODE_SPECIFIC_STRINGS for '
458 'message %s' % (self.brand, string_id)) 479 'message %s' % (self.brand, string_id))
459 do_mode_strings_lines.append( 480 do_mode_strings_lines.append(
460 ' HANDLE_MODE_STRING(%s_BASE, %s)' 481 ' HANDLE_MODE_STRING(%s_BASE, %s)'
461 % (string_id, ', '.join([ ('%s_BASE' % s) for s in brand_strings]))) 482 % (string_id, ', '.join([ ('%s_BASE' % s) for s in brand_strings])))
462 483
463 # Write out base ID values. 484 # Write out base ID values.
464 for string_id in STRING_IDS: 485 for string_id in sorted(string_id_set):
465 lines.append('#define %s_BASE %s_%s' % (string_id, 486 lines.append('#define %s_BASE %s_%s' % (string_id,
466 string_id, 487 string_id,
467 translated_strings[0].language)) 488 translated_strings[0].language))
468 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)' 489 installer_string_mapping_lines.append(' HANDLE_STRING(%s_BASE, %s)'
469 % (string_id, string_id)) 490 % (string_id, string_id))
470 491
471 with open(os.path.join(self.outdir, self.name + '.h'), 'wb') as outfile: 492 with open(os.path.join(self.outdir, self.name + '.h'), 'wb') as outfile:
472 outfile.write('\n'.join(lines)) 493 outfile.write('\n'.join(lines))
473 outfile.write('\n#ifndef RC_INVOKED') 494 outfile.write('\n#ifndef RC_INVOKED')
474 outfile.write(' \\\n'.join(do_languages_lines)) 495 outfile.write(' \\\n'.join(do_languages_lines))
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
513 534
514 535
515 def main(): 536 def main():
516 args = ParseCommandLine() 537 args = ParseCommandLine()
517 StringRcMaker(args.name, args.inputs, args.outdir, args.brand).MakeFiles() 538 StringRcMaker(args.name, args.inputs, args.outdir, args.brand).MakeFiles()
518 return 0 539 return 0
519 540
520 541
521 if '__main__' == __name__: 542 if '__main__' == __name__:
522 sys.exit(main()) 543 sys.exit(main())
OLDNEW
« no previous file with comments | « chrome/installer/util/l10n_string_util_unittest.cc ('k') | chrome/test/mini_installer/config/chrome_beta_installed.prop » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698