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): 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 107 108 def CopySectionFilesToStagingDir(config, section, staging_dir, src_dir): 109 """Copies installer archive files specified in section from src_dir to 110 staging_dir. This method reads section from config and copies all the 111 files specified from src_dir to staging dir. 112 """ 113 for option in config.options(section): 114 if option.endswith('dir'): 115 continue 116 117 dst_dir = os.path.join(staging_dir, config.get(section, option)) 118 src_paths = glob.glob(os.path.join(src_dir, option)) 119 if src_paths and not os.path.exists(dst_dir): 120 os.makedirs(dst_dir) 121 for src_path in src_paths: 122 dst_path = os.path.join(dst_dir, os.path.basename(src_path)) 123 if not os.path.exists(dst_path): 124 shutil.copy(src_path, dst_dir) 125 126 def GenerateDiffPatch(options, orig_file, new_file, patch_file): 127 if (options.diff_algorithm == "COURGETTE"): 128 exe_file = os.path.join(options.last_chrome_installer, COURGETTE_EXEC) 129 cmd = '%s -gen "%s" "%s" "%s"' % (exe_file, orig_file, new_file, patch_file) 130 else: 131 exe_file = os.path.join(options.build_dir, BSDIFF_EXEC) 132 cmd = [exe_file, orig_file, new_file, patch_file,] 133 RunSystemCommand(cmd) 134 135 def GetLZMAExec(build_dir): 136 lzma_exec = os.path.join(build_dir, "..", "..", "third_party", 137 "lzma_sdk", "Executable", "7za.exe") 138 return lzma_exec 139 140 def GetPrevVersion(build_dir, temp_dir, last_chrome_installer, output_name): 141 if not last_chrome_installer: 142 return '' 143 144 lzma_exec = GetLZMAExec(build_dir) 145 prev_archive_file = os.path.join(last_chrome_installer, 146 output_name + ARCHIVE_SUFFIX) 147 cmd = [lzma_exec, 148 'x', 149 '-o"%s"' % temp_dir, 150 prev_archive_file, 151 'Chrome-bin/*/chrome.dll',] 152 RunSystemCommand(cmd) 153 dll_path = glob.glob(os.path.join(temp_dir, 'Chrome-bin', '*', 'chrome.dll')) 154 return os.path.split(os.path.split(dll_path[0])[0])[1] 155 156 def MakeStagingDirectories(staging_dir): 157 """Creates a staging path for installer archive. If directory exists already, 158 deletes the existing directory. 159 """ 160 file_path = os.path.join(staging_dir, TEMP_ARCHIVE_DIR) 161 if os.path.exists(file_path): 162 shutil.rmtree(file_path) 163 os.makedirs(file_path) 164 165 temp_file_path = os.path.join(staging_dir, TEMP_ARCHIVE_DIR) 166 if os.path.exists(temp_file_path): 167 shutil.rmtree(temp_file_path) 168 os.makedirs(temp_file_path) 169 return (file_path, temp_file_path) 170 171 def Readconfig(input_file, current_version): 172 """Reads config information from input file after setting default value of 173 global variabes. 174 """ 175 variables = {} 176 variables['ChromeDir'] = CHROME_DIR 177 variables['VersionDir'] = os.path.join(variables['ChromeDir'], 178 current_version) 179 config = ConfigParser.SafeConfigParser(variables) 180 config.read(input_file) 181 return config 182 183 def RunSystemCommand(cmd, **kw): 184 print 'Running', cmd 185 exit_code = subprocess.call(cmd, **kw) 186 if (exit_code != 0): 187 raise Exception("Error while running cmd: %s, exit_code: %s" % 188 (cmd, exit_code)) 189 190 def CreateArchiveFile(options, staging_dir, current_version, prev_version): 191 """Creates a new installer archive file after deleting any existing old file. 192 """ 193 # First create an uncompressed archive file for the current build (chrome.7z) 194 lzma_exec = GetLZMAExec(options.build_dir) 195 archive_file = os.path.join(options.output_dir, 196 options.output_name + ARCHIVE_SUFFIX) 197 cmd = [lzma_exec, 198 'a', 199 '-t7z', 200 archive_file, 201 os.path.join(staging_dir, CHROME_DIR), 202 '-mx0',] 203 # There doesnt seem to be any way in 7za.exe to override existing file so 204 # we always delete before creating a new one. 205 if not os.path.exists(archive_file): 206 RunSystemCommand(cmd) 207 elif options.skip_rebuild_archive != "true": 208 os.remove(archive_file) 209 RunSystemCommand(cmd) 210 211 # Do not compress the archive in developer (component) builds. 212 if options.component_build == '1': 213 compressed_file = os.path.join( 214 options.output_dir, options.output_name + COMPRESSED_ARCHIVE_SUFFIX) 215 if os.path.exists(compressed_file): 216 os.remove(compressed_file) 217 return os.path.basename(archive_file) 218 219 # If we are generating a patch, run bsdiff against previous build and 220 # compress the resulting patch file. If this is not a patch just compress the 221 # uncompressed archive file. 222 patch_name_prefix = options.output_name + CHROME_PATCH_FILE_SUFFIX 223 if options.last_chrome_installer: 224 prev_archive_file = os.path.join(options.last_chrome_installer, 225 options.output_name + ARCHIVE_SUFFIX) 226 patch_file = os.path.join(options.build_dir, patch_name_prefix + 227 PATCH_FILE_EXT) 228 GenerateDiffPatch(options, prev_archive_file, archive_file, patch_file) 229 compressed_archive_file = patch_name_prefix + '_' + \ 230 current_version + '_from_' + prev_version + \ 231 COMPRESSED_FILE_EXT 232 orig_file = patch_file 233 else: 234 compressed_archive_file = options.output_name + COMPRESSED_ARCHIVE_SUFFIX 235 orig_file = archive_file 236 237 compressed_archive_file_path = os.path.join(options.output_dir, 238 compressed_archive_file) 239 CompressUsingLZMA(options.build_dir, compressed_archive_file_path, orig_file) 240 241 return compressed_archive_file 242 243 244 def PrepareSetupExec(options, current_version, prev_version): 245 """Prepares setup.exe for bundling in mini_installer based on options.""" 246 if options.setup_exe_format == "FULL": 247 setup_file = SETUP_EXEC 248 elif options.setup_exe_format == "DIFF": 249 if not options.last_chrome_installer: 250 raise Exception( 251 "To use DIFF for setup.exe, --last_chrome_installer is needed.") 252 prev_setup_file = os.path.join(options.last_chrome_installer, SETUP_EXEC) 253 new_setup_file = os.path.join(options.build_dir, SETUP_EXEC) 254 patch_file = os.path.join(options.build_dir, SETUP_PATCH_FILE_PREFIX + 255 PATCH_FILE_EXT) 256 GenerateDiffPatch(options, prev_setup_file, new_setup_file, patch_file) 257 setup_file = SETUP_PATCH_FILE_PREFIX + '_' + current_version + \ 258 '_from_' + prev_version + COMPRESSED_FILE_EXT 259 setup_file_path = os.path.join(options.build_dir, setup_file) 260 CompressUsingLZMA(options.build_dir, setup_file_path, patch_file) 261 else: 262 cmd = ['makecab.exe', 263 '/D', 'CompressionType=LZX', 264 '/V1', 265 '/L', options.output_dir, 266 os.path.join(options.build_dir, SETUP_EXEC),] 267 # Send useless makecab progress on stdout to the bitbucket. 268 RunSystemCommand(cmd, stdout=open(os.devnull, "w")) 269 setup_file = SETUP_EXEC[:-1] + "_" 270 return setup_file 271 272 273 _RESOURCE_FILE_TEMPLATE = """\ 274 // This file is automatically generated by create_installer_archive.py. 275 // It contains the resource entries that are going to be linked inside 276 // mini_installer.exe. For each file to be linked there should be two 277 // lines: 278 // - The first line contains the output filename (without path) and the 279 // type of the resource ('BN' - not compressed , 'BL' - LZ compressed, 280 // 'B7' - LZMA compressed) 281 // - The second line contains the path to the input file. Uses '/' to 282 // separate path components. 283 284 %(setup_file)s %(setup_file_resource_type)s 285 "%(setup_file_path)s" 286 287 %(archive_file)s B7 288 "%(archive_file_path)s" 289 """ 290 291 292 def CreateResourceInputFile( 293 output_dir, setup_format, archive_file, setup_file, resource_file_path): 294 """Creates resource input file (packed_files.txt) for mini_installer project. 295 296 This method checks the format of setup.exe being used and according sets 297 its resource type. 298 """ 299 setup_resource_type = "BL" 300 if (setup_format == "FULL"): 301 setup_resource_type = "BN" 302 elif (setup_format == "DIFF"): 303 setup_resource_type = "B7" 304 305 # Expand the resource file template. 306 args = { 307 'setup_file': setup_file, 308 'setup_file_resource_type': setup_resource_type, 309 'setup_file_path': 310 os.path.join(output_dir, setup_file).replace("\\","/"), 311 'archive_file': archive_file, 312 'archive_file_path': 313 os.path.join(output_dir, archive_file).replace("\\","/"), 314 } 315 resource_file = _RESOURCE_FILE_TEMPLATE % args 316 317 with open(resource_file_path, 'w') as f: 318 f.write(resource_file) 319 320 321 # Reads |manifest_name| from |build_dir| and writes |manifest_name| to 322 # |output_dir| with the same content plus |inserted_string| added just before 323 # |insert_before|. 324 def CopyAndAugmentManifest(build_dir, output_dir, manifest_name, 325 inserted_string, insert_before): 326 with open(os.path.join(build_dir, manifest_name), 'r') as f: 327 manifest_lines = f.readlines() 328 329 insert_line = -1 330 insert_pos = -1 331 for i in xrange(len(manifest_lines)): 332 insert_pos = manifest_lines[i].find(insert_before) 333 if insert_pos != -1: 334 insert_line = i 335 break 336 if insert_line == -1: 337 raise ValueError('Could not find {0} in the manifest:\n{1}'.format( 338 insert_before, ''.join(manifest_lines))) 339 old = manifest_lines[insert_line] 340 manifest_lines[insert_line] = (old[:insert_pos] + '\n' + inserted_string + 341 '\n' + old[insert_pos:]) 342 343 with open(os.path.join(output_dir, manifest_name), 'w') as f : 344 f.write(''.join(manifest_lines)) 345 346 347 def CopyIfChanged(src, target_dir): 348 """Copy specified |src| file to |target_dir|, but only write to target if 349 the file has changed. This avoids a problem during packaging where parts of 350 the build have not completed and have the runtime DLL locked when we try to 351 copy over it. See http://crbug.com/305877 for details.""" 352 assert os.path.isdir(target_dir) 353 dest = os.path.join(target_dir, os.path.basename(src)) 354 if os.path.exists(dest): 355 # We assume the files are OK to buffer fully into memory since we know 356 # they're only 1-2M. 357 with open(src, 'rb') as fsrc: 358 src_data = fsrc.read() 359 with open(dest, 'rb') as fdest: 360 dest_data = fdest.read() 361 if src_data != dest_data: 362 # This may still raise if we get here, but this really should almost 363 # never happen (it would mean that the contents of e.g. msvcr100d.dll 364 # had been changed). 365 shutil.copyfile(src, dest) 366 else: 367 shutil.copyfile(src, dest) 368 369 370 # Copy the relevant CRT DLLs to |build_dir|. We copy DLLs from all versions 371 # of VS installed to make sure we have the correct CRT version, unused DLLs 372 # should not conflict with the others anyways. 373 def CopyVisualStudioRuntimeDLLs(target_arch, build_dir): 374 is_debug = os.path.basename(build_dir).startswith('Debug') 375 if not is_debug and not os.path.basename(build_dir).startswith('Release'): 376 print ("Warning: could not determine build configuration from " 377 "output directory, assuming Release build.") 378 379 crt_dlls = [] 380 sys_dll_dir = None 381 if is_debug: 382 crt_dlls = glob.glob( 383 "C:/Program Files (x86)/Microsoft Visual Studio */VC/redist/" 384 "Debug_NonRedist/" + target_arch + "/Microsoft.*.DebugCRT/*.dll") 385 else: 386 crt_dlls = glob.glob( 387 "C:/Program Files (x86)/Microsoft Visual Studio */VC/redist/" + 388 target_arch + "/Microsoft.*.CRT/*.dll") 389 390 # Also handle the case where someone is building using only winsdk and 391 # doesn't have Visual Studio installed. 392 if not crt_dlls: 393 if target_arch == 'x64': 394 # check we are are on a 64bit system by existence of WOW64 dir 395 if os.access("C:/Windows/SysWOW64", os.F_OK): 396 sys_dll_dir = "C:/Windows/System32" 397 else: 398 # only support packaging of 64bit installer on 64bit system 399 # but this just as bad as not finding DLLs at all so we 400 # don't abort here to mirror behavior below 401 print ("Warning: could not find x64 CRT DLLs on x86 system.") 402 else: 403 # On a 64-bit system, 32-bit dlls are in SysWOW64 (don't ask). 404 if os.access("C:/Windows/SysWOW64", os.F_OK): 405 sys_dll_dir = "C:/Windows/SysWOW64" 406 else: 407 sys_dll_dir = "C:/Windows/System32" 408 409 if sys_dll_dir is not None: 410 if is_debug: 411 crt_dlls = glob.glob(os.path.join(sys_dll_dir, "msvc*0d.dll")) 412 else: 413 crt_dlls = glob.glob(os.path.join(sys_dll_dir, "msvc*0.dll")) 414 415 if not crt_dlls: 416 print ("Warning: could not find CRT DLLs to copy to build dir - target " 417 "may not run on a system that doesn't have those DLLs.") 418 419 for dll in crt_dlls: 420 CopyIfChanged(dll, build_dir) 421 422 423 # Copies component build DLLs and generates required config files and manifests 424 # in order for chrome.exe and setup.exe to be able to find those DLLs at 425 # run-time. 426 # This is meant for developer builds only and should never be used to package 427 # an official build. 428 def DoComponentBuildTasks(staging_dir, build_dir, target_arch, current_version): 429 # Get the required directories for the upcoming operations. 430 chrome_dir = os.path.join(staging_dir, CHROME_DIR) 431 version_dir = os.path.join(chrome_dir, current_version) 432 installer_dir = os.path.join(version_dir, 'Installer') 433 # |installer_dir| is technically only created post-install, but we need it 434 # now to add setup.exe's config and manifest to the archive. 435 if not os.path.exists(installer_dir): 436 os.mkdir(installer_dir) 437 438 # Copy the VS CRT DLLs to |build_dir|. This must be done before the general 439 # copy step below to ensure the CRT DLLs are added to the archive and marked 440 # as a dependency in the exe manifests generated below. 441 CopyVisualStudioRuntimeDLLs(target_arch, build_dir) 442 443 # Explicitly list the component DLLs setup.exe depends on (this list may 444 # contain wildcards). These will be copied to |installer_dir| in the archive. 445 setup_component_dll_globs = [ 'base.dll', 446 'crcrypto.dll', 447 'crnspr.dll', 448 'crnss.dll', 449 'icui18n.dll', 450 'icuuc.dll', 451 'msvc*.dll' ] 452 for setup_component_dll_glob in setup_component_dll_globs: 453 setup_component_dlls = glob.glob(os.path.join(build_dir, 454 setup_component_dll_glob)) 455 for setup_component_dll in setup_component_dlls: 456 shutil.copy(setup_component_dll, installer_dir) 457 458 # Stage all the component DLLs found in |build_dir| to the |version_dir| (for 459 # the version assembly to be able to refer to them below and make sure 460 # chrome.exe can find them at runtime). The component DLLs are considered to 461 # be all the DLLs which have not already been added to the |version_dir| by 462 # virtue of chrome.release. 463 build_dlls = glob.glob(os.path.join(build_dir, '*.dll')) 464 staged_dll_basenames = [os.path.basename(staged_dll) for staged_dll in \ 465 glob.glob(os.path.join(version_dir, '*.dll'))] 466 component_dll_filenames = [] 467 for component_dll in [dll for dll in build_dlls if \ 468 os.path.basename(dll) not in staged_dll_basenames]: 469 component_dll_name = os.path.basename(component_dll) 470 # remoting_*.dll's don't belong in the archive (it doesn't depend on them 471 # in gyp). Trying to copy them causes a build race when creating the 472 # installer archive in component mode. See: crbug.com/180996 473 if component_dll_name.startswith('remoting_'): 474 continue 475 component_dll_filenames.append(component_dll_name) 476 shutil.copy(component_dll, version_dir) 477 478 # Augment {version}.manifest to include all component DLLs as part of the 479 # assembly it constitutes, which will allow dependents of this assembly to 480 # find these DLLs. 481 version_assembly_dll_additions = [] 482 for dll_filename in component_dll_filenames: 483 version_assembly_dll_additions.append(" <file name='%s'/>" % dll_filename) 484 CopyAndAugmentManifest(build_dir, version_dir, 485 '%s.manifest' % current_version, 486 '\n'.join(version_assembly_dll_additions), 487 '</assembly>') 488 489 490 def main(options): 491 """Main method that reads input file, creates archive file and write 492 resource input file. 493 """ 494 current_version = BuildVersion(options.build_dir) 495 496 config = Readconfig(options.input_file, current_version) 497 498 (staging_dir, temp_dir) = MakeStagingDirectories(options.staging_dir) 499 500 prev_version = GetPrevVersion(options.build_dir, temp_dir, 501 options.last_chrome_installer, 502 options.output_name) 503 504 # Preferentially copy the files we can find from the output_dir, as 505 # this is where we'll find the Syzygy-optimized executables when 506 # building the optimized mini_installer. 507 if options.build_dir != options.output_dir: 508 CopyAllFilesToStagingDir(config, options.distribution, 509 staging_dir, options.output_dir, 510 options.enable_hidpi) 511 512 # Now copy the remainder of the files from the build dir. 513 CopyAllFilesToStagingDir(config, options.distribution, 514 staging_dir, options.build_dir, 515 options.enable_hidpi) 516 517 if options.component_build == '1': 518 DoComponentBuildTasks(staging_dir, options.build_dir, 519 options.target_arch, current_version) 520 521 version_numbers = current_version.split('.') 522 current_build_number = version_numbers[2] + '.' + version_numbers[3] 523 prev_build_number = '' 524 if prev_version: 525 version_numbers = prev_version.split('.') 526 prev_build_number = version_numbers[2] + '.' + version_numbers[3] 527 528 # Name of the archive file built (for example - chrome.7z or 529 # patch-<old_version>-<new_version>.7z or patch-<new_version>.7z 530 archive_file = CreateArchiveFile(options, staging_dir, 531 current_build_number, prev_build_number) 532 533 setup_file = PrepareSetupExec(options, 534 current_build_number, prev_build_number) 535 536 CreateResourceInputFile(options.output_dir, options.setup_exe_format, 537 archive_file, setup_file, options.resource_file_path) 538 539 def _ParseOptions(): 540 parser = optparse.OptionParser() 541 parser.add_option('-i', '--input_file', 542 help='Input file describing which files to archive.') 543 parser.add_option('-b', '--build_dir', 544 help='Build directory. The paths in input_file are relative to this.') 545 parser.add_option('--staging_dir', 546 help='Staging directory where intermediate files and directories ' 547 'will be created') 548 parser.add_option('-o', '--output_dir', 549 help='The output directory where the archives will be written. ' 550 'Defaults to the build_dir.') 551 parser.add_option('--resource_file_path', 552 help='The path where the resource file will be output. ' 553 'Defaults to %s in the build directory.' % 554 MINI_INSTALLER_INPUT_FILE) 555 parser.add_option('-d', '--distribution', 556 help='Name of Chromium Distribution. Optional.') 557 parser.add_option('-s', '--skip_rebuild_archive', 558 default="False", help='Skip re-building Chrome.7z archive if it exists.') 559 parser.add_option('-l', '--last_chrome_installer', 560 help='Generate differential installer. The value of this parameter ' 561 'specifies the directory that contains base versions of ' 562 'setup.exe, courgette.exe (if --diff_algorithm is COURGETTE) ' 563 '& chrome.7z.') 564 parser.add_option('-f', '--setup_exe_format', default='COMPRESSED', 565 help='How setup.exe should be included {COMPRESSED|DIFF|FULL}.') 566 parser.add_option('-a', '--diff_algorithm', default='BSDIFF', 567 help='Diff algorithm to use when generating differential patches ' 568 '{BSDIFF|COURGETTE}.') 569 parser.add_option('-n', '--output_name', default='chrome', 570 help='Name used to prefix names of generated archives.') 571 parser.add_option('--enable_hidpi', default='0', 572 help='Whether to include HiDPI resource files.') 573 parser.add_option('--component_build', default='0', 574 help='Whether this archive is packaging a component build. This will ' 575 'also turn off compression of chrome.7z into chrome.packed.7z and ' 576 'helpfully delete any old chrome.packed.7z in |output_dir|.') 577 parser.add_option('--target_arch', default='x86', 578 help='Specify the target architecture for installer - this is used ' 579 'to determine which CRT runtime files to pull and package ' 580 'with the installer archive {x86|x64}.') 581 582 options, _ = parser.parse_args() 583 if not options.build_dir: 584 parser.error('You must provide a build dir.') 585 586 options.build_dir = os.path.normpath(options.build_dir) 587 588 if not options.staging_dir: 589 parser.error('You must provide a staging dir.') 590 591 if not options.input_file: 592 parser.error('You must provide an input file') 593 594 if not options.output_dir: 595 options.output_dir = options.build_dir 596 597 if not options.resource_file_path: 598 options.resource_file_path = os.path.join(options.build_dir, 599 MINI_INSTALLER_INPUT_FILE) 600 601 return options 602 603 604 if '__main__' == __name__: 605 print sys.argv 606 sys.exit(main(_ParseOptions())) 607