Revert "Added incident report for variations seed signature mismatch."
[chromium-blink-merge.git] / chrome / tools / build / win / create_installer_archive.py
blob385b4768c72092b0bbb7dcc2e032cba97f60f17e
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.
6 """Script to create Chrome Installer archive.
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.
13 """
15 import ConfigParser
16 import glob
17 import optparse
18 import os
19 import shutil
20 import subprocess
21 import sys
24 ARCHIVE_DIR = "installer_archive"
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
32 # compressed full archive suffix, will be prefixed by options.output_name
33 COMPRESSED_ARCHIVE_SUFFIX = ".packed.7z"
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"
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)
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)
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)
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.
113 for option in config.options(section):
114 if option.endswith('dir'):
115 continue
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)
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)
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
140 def GetPrevVersion(build_dir, temp_dir, last_chrome_installer, output_name):
141 if not last_chrome_installer:
142 return ''
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]
156 def MakeStagingDirectories(staging_dir):
157 """Creates a staging path for installer archive. If directory exists already,
158 deletes the existing directory.
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)
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)
171 def Readconfig(input_file, current_version):
172 """Reads config information from input file after setting default value of
173 global variabes.
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
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))
190 def CreateArchiveFile(options, staging_dir, current_version, prev_version):
191 """Creates a new installer archive file after deleting any existing old file.
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)
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)
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
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)
241 return compressed_archive_file
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
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.
284 %(setup_file)s %(setup_file_resource_type)s
285 "%(setup_file_path)s"
287 %(archive_file)s B7
288 "%(archive_file_path)s"
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.
296 This method checks the format of setup.exe being used and according sets
297 its resource type.
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"
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("\\","/"),
315 resource_file = _RESOURCE_FILE_TEMPLATE % args
317 with open(resource_file_path, 'w') as f:
318 f.write(resource_file)
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()
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:])
343 with open(os.path.join(output_dir, manifest_name), 'w') as f :
344 f.write(''.join(manifest_lines))
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)
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.")
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")
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"
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"))
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.")
419 for dll in crt_dlls:
420 CopyIfChanged(dll, build_dir)
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)
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)
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)
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)
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>')
490 def main(options):
491 """Main method that reads input file, creates archive file and write
492 resource input file.
494 current_version = BuildVersion(options.build_dir)
496 config = Readconfig(options.input_file, current_version)
498 (staging_dir, temp_dir) = MakeStagingDirectories(options.staging_dir)
500 prev_version = GetPrevVersion(options.build_dir, temp_dir,
501 options.last_chrome_installer,
502 options.output_name)
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)
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)
517 if options.component_build == '1':
518 DoComponentBuildTasks(staging_dir, options.build_dir,
519 options.target_arch, current_version)
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]
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)
533 setup_file = PrepareSetupExec(options,
534 current_build_number, prev_build_number)
536 CreateResourceInputFile(options.output_dir, options.setup_exe_format,
537 archive_file, setup_file, options.resource_file_path)
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}.')
582 options, _ = parser.parse_args()
583 if not options.build_dir:
584 parser.error('You must provide a build dir.')
586 options.build_dir = os.path.normpath(options.build_dir)
588 if not options.staging_dir:
589 parser.error('You must provide a staging dir.')
591 if not options.input_file:
592 parser.error('You must provide an input file')
594 if not options.output_dir:
595 options.output_dir = options.build_dir
597 if not options.resource_file_path:
598 options.resource_file_path = os.path.join(options.build_dir,
599 MINI_INSTALLER_INPUT_FILE)
601 return options
604 if '__main__' == __name__:
605 print sys.argv
606 sys.exit(main(_ParseOptions()))