Home | History | Annotate | Download | only in win
      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