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, 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