1 #!/usr/bin/env python 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 4 # found in the LICENSE file. 5 6 """Script to create Chrome Installer archive. 7 8 This script is used to create an archive of all the files required for a 9 Chrome install in appropriate directory structure. It reads chrome.release 10 file as input, creates chrome.7z archive, compresses setup.exe and 11 generates packed_files.txt for mini_installer project. 12 13 """ 14 15 import ConfigParser 16 import glob 17 import optparse 18 import os 19 import shutil 20 import subprocess 21 import sys 22 23 24 ARCHIVE_DIR = "installer_archive" 25 26 # suffix to uncompresed full archive file, appended to options.output_name 27 ARCHIVE_SUFFIX = ".7z" 28 BSDIFF_EXEC = "bsdiff.exe" 29 CHROME_DIR = "Chrome-bin" 30 CHROME_PATCH_FILE_SUFFIX = "_patch" # prefixed by options.output_name 31 32 # compressed full archive suffix, will be prefixed by options.output_name 33 COMPRESSED_ARCHIVE_SUFFIX = ".packed.7z" 34 35 COMPRESSED_FILE_EXT = ".packed.7z" # extension of patch archive file 36 COURGETTE_EXEC = "courgette.exe" 37 MINI_INSTALLER_INPUT_FILE = "packed_files.txt" 38 PATCH_FILE_EXT = '.diff' 39 SETUP_EXEC = "setup.exe" 40 SETUP_PATCH_FILE_PREFIX = "setup_patch" 41 TEMP_ARCHIVE_DIR = "temp_installer_archive" 42 VERSION_FILE = "VERSION" 43 44 45 def BuildVersion(build_dir): 46 """Returns the full build version string constructed from information in 47 VERSION_FILE. Any segment not found in that file will default to '0'. 48 """ 49 major = 0 50 minor = 0 51 build = 0 52 patch = 0 53 for line in open(os.path.join(build_dir, '../../chrome', VERSION_FILE), 'r'): 54 line = line.rstrip() 55 if line.startswith('MAJOR='): 56 major = line[6:] 57 elif line.startswith('MINOR='): 58 minor = line[6:] 59 elif line.startswith('BUILD='): 60 build = line[6:] 61 elif line.startswith('PATCH='): 62 patch = line[6:] 63 return '%s.%s.%s.%s' % (major, minor, build, patch) 64 65 66 def CompressUsingLZMA(build_dir, compressed_file, input_file): 67 lzma_exec = GetLZMAExec(build_dir) 68 cmd = [lzma_exec, 69 'a', '-t7z', 70 # Flags equivalent to -mx9 (ultra) but with the bcj2 turned on (exe 71 # pre-filter). This results in a ~2.3MB decrease in installer size on 72 # a 24MB installer. 73 # Additionally, these settings reflect a 7zip 4.42 and up change in 74 # the definition of -mx9, increasting the dicionary size moving to 75 # 26bit = 64MB. This results in an additional ~3.5MB decrease. 76 # Older 7zip versions can support these settings, as these changes 77 # rely on existing functionality in the lzma format. 78 '-m0=BCJ2', 79 '-m1=LZMA:d27:fb128', 80 '-m2=LZMA:d22:fb128:mf=bt2', 81 '-m3=LZMA:d22:fb128:mf=bt2', 82 '-mb0:1', 83 '-mb0s1:2', 84 '-mb0s2:3', 85 compressed_file, 86 input_file,] 87 if os.path.exists(compressed_file): 88 os.remove(compressed_file) 89 RunSystemCommand(cmd) 90 91 92 def CopyAllFilesToStagingDir(config, distribution, staging_dir, build_dir, 93 enable_hidpi, enable_touch_ui): 94 """Copies the files required for installer archive. 95 Copies all common files required for various distributions of Chromium and 96 also files for the specific Chromium build specified by distribution. 97 """ 98 CopySectionFilesToStagingDir(config, 'GENERAL', staging_dir, build_dir) 99 if distribution: 100 if len(distribution) > 1 and distribution[0] == '_': 101 distribution = distribution[1:] 102 CopySectionFilesToStagingDir(config, distribution.upper(), 103 staging_dir, build_dir) 104 if enable_hidpi == '1': 105 CopySectionFilesToStagingDir(config, 'HIDPI', staging_dir, build_dir) 106 if enable_touch_ui == '1': 107 CopySectionFilesToStagingDir(config, 'TOUCH', staging_dir, build_dir) 108 109 110 def CopySectionFilesToStagingDir(config, section, staging_dir, src_dir): 111 """Copies installer archive files specified in section from src_dir to 112 staging_dir. This method reads section from config and copies all the 113 files specified from src_dir to staging dir. 114 """ 115 for option in config.options(section): 116 if option.endswith('dir'): 117 continue 118 119 dst_dir = os.path.join(staging_dir, config.get(section, option)) 120 src_paths = glob.glob(os.path.join(src_dir, option)) 121 if src_paths and not os.path.exists(dst_dir): 122 os.makedirs(dst_dir) 123 for src_path in src_paths: 124 dst_path = os.path.join(dst_dir, os.path.basename(src_path)) 125 if not os.path.exists(dst_path): 126 shutil.copy(src_path, dst_dir) 127 128 def GenerateDiffPatch(options, orig_file, new_file, patch_file): 129 if (options.diff_algorithm == "COURGETTE"): 130 exe_file = os.path.join(options.last_chrome_installer, COURGETTE_EXEC) 131 cmd = '%s -gen "%s" "%s" "%s"' % (exe_file, orig_file, new_file, patch_file) 132 else: 133 exe_file = os.path.join(options.build_dir, BSDIFF_EXEC) 134 cmd = [exe_file, orig_file, new_file, patch_file,] 135 RunSystemCommand(cmd) 136 137 def GetLZMAExec(build_dir): 138 lzma_exec = os.path.join(build_dir, "..", "..", "third_party", 139 "lzma_sdk", "Executable", "7za.exe") 140 return lzma_exec 141 142 def GetPrevVersion(build_dir, temp_dir, last_chrome_installer, output_name): 143 if not last_chrome_installer: 144 return '' 145 146 lzma_exec = GetLZMAExec(build_dir) 147 prev_archive_file = os.path.join(last_chrome_installer, 148 output_name + ARCHIVE_SUFFIX) 149 cmd = [lzma_exec, 150 'x', 151 '-o"%s"' % temp_dir, 152 prev_archive_file, 153 'Chrome-bin/*/chrome.dll',] 154 RunSystemCommand(cmd) 155 dll_path = glob.glob(os.path.join(temp_dir, 'Chrome-bin', '*', 'chrome.dll')) 156 return os.path.split(os.path.split(dll_path[0])[0])[1] 157 158 def MakeStagingDirectories(staging_dir): 159 """Creates a staging path for installer archive. If directory exists already, 160 deletes the existing directory. 161 """ 162 file_path = os.path.join(staging_dir, TEMP_ARCHIVE_DIR) 163 if os.path.exists(file_path): 164 shutil.rmtree(file_path) 165 os.makedirs(file_path) 166 167 temp_file_path = os.path.join(staging_dir, TEMP_ARCHIVE_DIR) 168 if os.path.exists(temp_file_path): 169 shutil.rmtree(temp_file_path) 170 os.makedirs(temp_file_path) 171 return (file_path, temp_file_path) 172 173 def Readconfig(input_file, current_version): 174 """Reads config information from input file after setting default value of 175 global variabes. 176 """ 177 variables = {} 178 variables['ChromeDir'] = CHROME_DIR 179 variables['VersionDir'] = os.path.join(variables['ChromeDir'], 180 current_version) 181 config = ConfigParser.SafeConfigParser(variables) 182 config.read(input_file) 183 return config 184 185 def RunSystemCommand(cmd, **kw): 186 print 'Running', cmd 187 exit_code = subprocess.call(cmd, **kw) 188 if (exit_code != 0): 189 raise Exception("Error while running cmd: %s, exit_code: %s" % 190 (cmd, exit_code)) 191 192 def CreateArchiveFile(options, staging_dir, current_version, prev_version): 193 """Creates a new installer archive file after deleting any existing old file. 194 """ 195 # First create an uncompressed archive file for the current build (chrome.7z) 196 lzma_exec = GetLZMAExec(options.build_dir) 197 archive_file = os.path.join(options.output_dir, 198 options.output_name + ARCHIVE_SUFFIX) 199 cmd = [lzma_exec, 200 'a', 201 '-t7z', 202 archive_file, 203 os.path.join(staging_dir, CHROME_DIR), 204 '-mx0',] 205 # There doesnt seem to be any way in 7za.exe to override existing file so 206 # we always delete before creating a new one. 207 if not os.path.exists(archive_file): 208 RunSystemCommand(cmd) 209 elif options.skip_rebuild_archive != "true": 210 os.remove(archive_file) 211 RunSystemCommand(cmd) 212 213 # Do not compress the archive in developer (component) builds. 214 if options.component_build == '1': 215 compressed_file = os.path.join( 216 options.output_dir, options.output_name + COMPRESSED_ARCHIVE_SUFFIX) 217 if os.path.exists(compressed_file): 218 os.remove(compressed_file) 219 return os.path.basename(archive_file) 220 221 # If we are generating a patch, run bsdiff against previous build and 222 # compress the resulting patch file. If this is not a patch just compress the 223 # uncompressed archive file. 224 patch_name_prefix = options.output_name + CHROME_PATCH_FILE_SUFFIX 225 if options.last_chrome_installer: 226 prev_archive_file = os.path.join(options.last_chrome_installer, 227 options.output_name + ARCHIVE_SUFFIX) 228 patch_file = os.path.join(options.build_dir, patch_name_prefix + 229 PATCH_FILE_EXT) 230 GenerateDiffPatch(options, prev_archive_file, archive_file, patch_file) 231 compressed_archive_file = patch_name_prefix + '_' + \ 232 current_version + '_from_' + prev_version + \ 233 COMPRESSED_FILE_EXT 234 orig_file = patch_file 235 else: 236 compressed_archive_file = options.output_name + COMPRESSED_ARCHIVE_SUFFIX 237 orig_file = archive_file 238 239 compressed_archive_file_path = os.path.join(options.output_dir, 240 compressed_archive_file) 241 CompressUsingLZMA(options.build_dir, compressed_archive_file_path, orig_file) 242 243 return compressed_archive_file 244 245 246 def PrepareSetupExec(options, current_version, prev_version): 247 """Prepares setup.exe for bundling in mini_installer based on options.""" 248 if options.setup_exe_format == "FULL": 249 setup_file = SETUP_EXEC 250 elif options.setup_exe_format == "DIFF": 251 if not options.last_chrome_installer: 252 raise Exception( 253 "To use DIFF for setup.exe, --last_chrome_installer is needed.") 254 prev_setup_file = os.path.join(options.last_chrome_installer, SETUP_EXEC) 255 new_setup_file = os.path.join(options.build_dir, SETUP_EXEC) 256 patch_file = os.path.join(options.build_dir, SETUP_PATCH_FILE_PREFIX + 257 PATCH_FILE_EXT) 258 GenerateDiffPatch(options, prev_setup_file, new_setup_file, patch_file) 259 setup_file = SETUP_PATCH_FILE_PREFIX + '_' + current_version + \ 260 '_from_' + prev_version + COMPRESSED_FILE_EXT 261 setup_file_path = os.path.join(options.build_dir, setup_file) 262 CompressUsingLZMA(options.build_dir, setup_file_path, patch_file) 263 else: 264 cmd = ['makecab.exe', 265 '/D', 'CompressionType=LZX', 266 '/V1', 267 '/L', options.output_dir, 268 os.path.join(options.build_dir, SETUP_EXEC),] 269 # Send useless makecab progress on stdout to the bitbucket. 270 RunSystemCommand(cmd, stdout=open(os.devnull, "w")) 271 setup_file = SETUP_EXEC[:-1] + "_" 272 return setup_file 273 274 275 _RESOURCE_FILE_TEMPLATE = """\ 276 // This file is automatically generated by create_installer_archive.py. 277 // It contains the resource entries that are going to be linked inside 278 // mini_installer.exe. For each file to be linked there should be two 279 // lines: 280 // - The first line contains the output filename (without path) and the 281 // type of the resource ('BN' - not compressed , 'BL' - LZ compressed, 282 // 'B7' - LZMA compressed) 283 // - The second line contains the path to the input file. Uses '/' to 284 // separate path components. 285 286 %(setup_file)s %(setup_file_resource_type)s 287 "%(setup_file_path)s" 288 289 %(archive_file)s B7 290 "%(archive_file_path)s" 291 """ 292 293 294 def CreateResourceInputFile( 295 output_dir, setup_format, archive_file, setup_file, resource_file_path): 296 """Creates resource input file (packed_files.txt) for mini_installer project. 297 298 This method checks the format of setup.exe being used and according sets 299 its resource type. 300 """ 301 setup_resource_type = "BL" 302 if (setup_format == "FULL"): 303 setup_resource_type = "BN" 304 elif (setup_format == "DIFF"): 305 setup_resource_type = "B7" 306 307 # Expand the resource file template. 308 args = { 309 'setup_file': setup_file, 310 'setup_file_resource_type': setup_resource_type, 311 'setup_file_path': 312 os.path.join(output_dir, setup_file).replace("\\","/"), 313 'archive_file': archive_file, 314 'archive_file_path': 315 os.path.join(output_dir, archive_file).replace("\\","/"), 316 } 317 resource_file = _RESOURCE_FILE_TEMPLATE % args 318 319 with open(resource_file_path, 'w') as f: 320 f.write(resource_file) 321 322 323 # Reads |manifest_name| from |build_dir| and writes |manifest_name| to 324 # |output_dir| with the same content plus |inserted_string| added just before 325 # |insert_before|. 326 def CopyAndAugmentManifest(build_dir, output_dir, manifest_name, 327 inserted_string, insert_before): 328 manifest_file = open(os.path.join(build_dir, manifest_name), 'r') 329 manifest_lines = manifest_file.readlines() 330 manifest_file.close() 331 332 insert_line = -1 333 insert_pos = -1 334 for i in xrange(len(manifest_lines)): 335 insert_pos = manifest_lines[i].find(insert_before) 336 if insert_pos != -1: 337 insert_line = i 338 break 339 if insert_line == -1: 340 raise ValueError('Could not find {0} in the manifest:\n{1}'.format( 341 insert_before, ''.join(manifest_lines))) 342 old = manifest_lines[insert_line] 343 manifest_lines[insert_line] = (old[:insert_pos] + inserted_string + 344 old[insert_pos:]) 345 346 modified_manifest_file = open( 347 os.path.join(output_dir, manifest_name), 'w') 348 modified_manifest_file.write(''.join(manifest_lines)) 349 modified_manifest_file.close() 350 351 352 # Copy the relevant CRT DLLs to |build_dir|. We copy DLLs from all versions 353 # of VS installed to make sure we have the correct CRT version, unused DLLs 354 # should not conflict with the others anyways. 355 def CopyVisualStudioRuntimeDLLs(build_dir, target_arch): 356 is_debug = os.path.basename(build_dir).startswith('Debug') 357 if not is_debug and not os.path.basename(build_dir).startswith('Release'): 358 print ("Warning: could not determine build configuration from " 359 "output directory, assuming Release build.") 360 361 crt_dlls = [] 362 sys_dll_dir = None 363 if is_debug: 364 crt_dlls = glob.glob( 365 "C:/Program Files (x86)/Microsoft Visual Studio */VC/redist/" 366 "Debug_NonRedist/" + target_arch + "/Microsoft.*.DebugCRT/*.dll") 367 else: 368 crt_dlls = glob.glob( 369 "C:/Program Files (x86)/Microsoft Visual Studio */VC/redist/" + 370 target_arch + "/Microsoft.*.CRT/*.dll") 371 372 # Also handle the case where someone is building using only winsdk and 373 # doesn't have Visual Studio installed. 374 if not crt_dlls: 375 if target_arch == 'x64': 376 # check we are are on a 64bit system by existence of WOW64 dir 377 if os.access("C:/Windows/SysWOW64", os.F_OK): 378 sys_dll_dir = "C:/Windows/System32" 379 else: 380 # only support packaging of 64bit installer on 64bit system 381 # but this just as bad as not finding DLLs at all so we 382 # don't abort here to mirror behavior below 383 print ("Warning: could not find x64 CRT DLLs on x86 system.") 384 else: 385 # On a 64-bit system, 32-bit dlls are in SysWOW64 (don't ask). 386 if os.access("C:/Windows/SysWOW64", os.F_OK): 387 sys_dll_dir = "C:/Windows/SysWOW64" 388 else: 389 sys_dll_dir = "C:/Windows/System32" 390 391 if sys_dll_dir is not None: 392 if is_debug: 393 crt_dlls = glob.glob(os.path.join(sys_dll_dir, "msvc*0d.dll")) 394 else: 395 crt_dlls = glob.glob(os.path.join(sys_dll_dir, "msvc*0.dll")) 396 397 if not crt_dlls: 398 print ("Warning: could not find CRT DLLs to copy to build dir - target " 399 "may not run on a system that doesn't have those DLLs.") 400 401 for dll in crt_dlls: 402 shutil.copy(dll, build_dir) 403 404 405 # Copies component build DLLs and generates required config files and manifests 406 # in order for chrome.exe and setup.exe to be able to find those DLLs at 407 # run-time. 408 # This is meant for developer builds only and should never be used to package 409 # an official build. 410 def DoComponentBuildTasks(staging_dir, build_dir, target_arch, current_version): 411 # Get the required directories for the upcoming operations. 412 chrome_dir = os.path.join(staging_dir, CHROME_DIR) 413 version_dir = os.path.join(chrome_dir, current_version) 414 installer_dir = os.path.join(version_dir, 'Installer') 415 # |installer_dir| is technically only created post-install, but we need it 416 # now to add setup.exe's config and manifest to the archive. 417 if not os.path.exists(installer_dir): 418 os.mkdir(installer_dir) 419 420 # Copy the VS CRT DLLs to |build_dir|. This must be done before the general 421 # copy step below to ensure the CRT DLLs are added to the archive and marked 422 # as a dependency in the exe manifests generated below. 423 CopyVisualStudioRuntimeDLLs(build_dir, target_arch) 424 425 # Copy all the DLLs in |build_dir| to the version directory. Simultaneously 426 # build a list of their names to mark them as dependencies of chrome.exe and 427 # setup.exe later. 428 dlls = glob.glob(os.path.join(build_dir, '*.dll')) 429 dll_names = [] 430 for dll in dlls: 431 # remoting_*.dll's don't belong in the archive (it doesn't depend on them 432 # in gyp). Trying to copy them causes a build race when creating the 433 # installer archive in component mode. See: crbug.com/180996 434 if os.path.basename(dll).startswith('remoting_'): 435 continue 436 shutil.copy(dll, version_dir) 437 dll_names.append(os.path.splitext(os.path.basename(dll))[0]) 438 439 exe_config = ( 440 "<configuration>\n" 441 " <windows>\n" 442 " <assemblyBinding xmlns='urn:schemas-microsoft-com:asm.v1'>\n" 443 " <probing privatePath='{rel_path}'/>\n" 444 " </assemblyBinding>\n" 445 " </windows>\n" 446 "</configuration>") 447 448 # Write chrome.exe.config to point to the version directory. 449 chrome_exe_config_file = open( 450 os.path.join(chrome_dir, 'chrome.exe.config'), 'w') 451 chrome_exe_config_file.write(exe_config.format(rel_path=current_version)) 452 chrome_exe_config_file.close() 453 454 # Write setup.exe.config to point to the version directory (which is one 455 # level up from setup.exe post-install). 456 setup_exe_config_file = open( 457 os.path.join(installer_dir, 'setup.exe.config'), 'w') 458 setup_exe_config_file.write(exe_config.format(rel_path='..')) 459 setup_exe_config_file.close() 460 461 # Add a dependency for each DLL in |dlls| to the existing manifests for 462 # chrome.exe and setup.exe. Some of these DLLs are not actually used by 463 # either process, but listing them all as dependencies doesn't hurt as it 464 # only makes them visible to the exes, just like they already are in the 465 # build output directory. 466 exe_manifest_dependencies_list = [] 467 for name in dll_names: 468 exe_manifest_dependencies_list.append( 469 "<dependency>" 470 "<dependentAssembly>" 471 "<assemblyIdentity type='win32' name='chrome.{dll_name}' " 472 "version='0.0.0.0' language='*'/>" 473 "</dependentAssembly>" 474 "</dependency>".format(dll_name=name)) 475 476 exe_manifest_dependencies = ''.join(exe_manifest_dependencies_list) 477 478 # Write a modified chrome.exe.manifest beside chrome.exe. 479 CopyAndAugmentManifest(build_dir, chrome_dir, 'chrome.exe.manifest', 480 exe_manifest_dependencies, '</assembly>') 481 482 # Write a modified setup.exe.manifest beside setup.exe in 483 # |version_dir|/Installer. 484 CopyAndAugmentManifest(build_dir, installer_dir, 'setup.exe.manifest', 485 exe_manifest_dependencies, '</assembly>') 486 487 # Generate assembly manifests for each DLL in |dlls|. These do not interfere 488 # with the private manifests potentially embedded in each DLL. They simply 489 # allow chrome.exe and setup.exe to see those DLLs although they are in a 490 # separate directory post-install. 491 for name in dll_names: 492 dll_manifest = ( 493 "<assembly\n" 494 " xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>\n" 495 " <assemblyIdentity name='chrome.{dll_name}' version='0.0.0.0'\n" 496 " type='win32'/>\n" 497 " <file name='{dll_name}.dll'/>\n" 498 "</assembly>".format(dll_name=name)) 499 500 dll_manifest_file = open(os.path.join( 501 version_dir, "chrome.{dll_name}.manifest".format(dll_name=name)), 'w') 502 dll_manifest_file.write(dll_manifest) 503 dll_manifest_file.close() 504 505 506 def main(options): 507 """Main method that reads input file, creates archive file and write 508 resource input file. 509 """ 510 current_version = BuildVersion(options.build_dir) 511 512 config = Readconfig(options.input_file, current_version) 513 514 (staging_dir, temp_dir) = MakeStagingDirectories(options.staging_dir) 515 516 prev_version = GetPrevVersion(options.build_dir, temp_dir, 517 options.last_chrome_installer, 518 options.output_name) 519 520 # Preferentially copy the files we can find from the output_dir, as 521 # this is where we'll find the Syzygy-optimized executables when 522 # building the optimized mini_installer. 523 if options.build_dir != options.output_dir: 524 CopyAllFilesToStagingDir(config, options.distribution, 525 staging_dir, options.output_dir, 526 options.enable_hidpi, options.enable_touch_ui) 527 528 # Now copy the remainder of the files from the build dir. 529 CopyAllFilesToStagingDir(config, options.distribution, 530 staging_dir, options.build_dir, 531 options.enable_hidpi, options.enable_touch_ui) 532 533 if options.component_build == '1': 534 DoComponentBuildTasks(staging_dir, options.build_dir, 535 options.target_arch, current_version) 536 537 version_numbers = current_version.split('.') 538 current_build_number = version_numbers[2] + '.' + version_numbers[3] 539 prev_build_number = '' 540 if prev_version: 541 version_numbers = prev_version.split('.') 542 prev_build_number = version_numbers[2] + '.' + version_numbers[3] 543 544 # Name of the archive file built (for example - chrome.7z or 545 # patch-<old_version>-<new_version>.7z or patch-<new_version>.7z 546 archive_file = CreateArchiveFile(options, staging_dir, 547 current_build_number, prev_build_number) 548 549 setup_file = PrepareSetupExec(options, 550 current_build_number, prev_build_number) 551 552 CreateResourceInputFile(options.output_dir, options.setup_exe_format, 553 archive_file, setup_file, options.resource_file_path) 554 555 def _ParseOptions(): 556 parser = optparse.OptionParser() 557 parser.add_option('-i', '--input_file', 558 help='Input file describing which files to archive.') 559 parser.add_option('-b', '--build_dir', 560 help='Build directory. The paths in input_file are relative to this.') 561 parser.add_option('--staging_dir', 562 help='Staging directory where intermediate files and directories ' 563 'will be created') 564 parser.add_option('-o', '--output_dir', 565 help='The output directory where the archives will be written. ' 566 'Defaults to the build_dir.') 567 parser.add_option('--resource_file_path', 568 help='The path where the resource file will be output. ' 569 'Defaults to %s in the build directory.' % 570 MINI_INSTALLER_INPUT_FILE) 571 parser.add_option('-d', '--distribution', 572 help='Name of Chromium Distribution. Optional.') 573 parser.add_option('-s', '--skip_rebuild_archive', 574 default="False", help='Skip re-building Chrome.7z archive if it exists.') 575 parser.add_option('-l', '--last_chrome_installer', 576 help='Generate differential installer. The value of this parameter ' 577 'specifies the directory that contains base versions of ' 578 'setup.exe, courgette.exe (if --diff_algorithm is COURGETTE) ' 579 '& chrome.7z.') 580 parser.add_option('-f', '--setup_exe_format', default='COMPRESSED', 581 help='How setup.exe should be included {COMPRESSED|DIFF|FULL}.') 582 parser.add_option('-a', '--diff_algorithm', default='BSDIFF', 583 help='Diff algorithm to use when generating differential patches ' 584 '{BSDIFF|COURGETTE}.') 585 parser.add_option('-n', '--output_name', default='chrome', 586 help='Name used to prefix names of generated archives.') 587 parser.add_option('--enable_hidpi', default='0', 588 help='Whether to include HiDPI resource files.') 589 parser.add_option('--enable_touch_ui', default='0', 590 help='Whether to include resource files from the "TOUCH" section of the ' 591 'input file.') 592 parser.add_option('--component_build', default='0', 593 help='Whether this archive is packaging a component build. This will ' 594 'also turn off compression of chrome.7z into chrome.packed.7z and ' 595 'helpfully delete any old chrome.packed.7z in |output_dir|.') 596 parser.add_option('--target_arch', default='x86', 597 help='Specify the target architecture for installer - this is used ' 598 'to determine which CRT runtime files to pull and package ' 599 'with the installer archive {x86|x64}.') 600 601 options, _ = parser.parse_args() 602 if not options.build_dir: 603 parser.error('You must provide a build dir.') 604 605 options.build_dir = os.path.normpath(options.build_dir) 606 607 if not options.staging_dir: 608 parser.error('You must provide a staging dir.') 609 610 if not options.input_file: 611 parser.error('You must provide an input file') 612 613 if not options.output_dir: 614 options.output_dir = options.build_dir 615 616 if not options.resource_file_path: 617 options.resource_file_path = os.path.join(options.build_dir, 618 MINI_INSTALLER_INPUT_FILE) 619 620 return options 621 622 623 if '__main__' == __name__: 624 print sys.argv 625 sys.exit(main(_ParseOptions())) 626