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.
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'.
53 for line
in open(os
.path
.join(build_dir
, '../../chrome', VERSION_FILE
), 'r'):
55 if line
.startswith('MAJOR='):
57 elif line
.startswith('MINOR='):
59 elif line
.startswith('BUILD='):
61 elif line
.startswith('PATCH='):
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
)
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
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.
80 '-m2=LZMA:d22:fb128:mf=bt2',
81 '-m3=LZMA:d22:fb128:mf=bt2',
87 if os
.path
.exists(compressed_file
):
88 os
.remove(compressed_file
)
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.
98 CopySectionFilesToStagingDir(config
, 'GENERAL', staging_dir
, build_dir
)
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
)
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.
115 for option
in config
.options(section
):
116 if option
.endswith('dir'):
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
):
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
)
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
)
133 exe_file
= os
.path
.join(options
.build_dir
, BSDIFF_EXEC
)
134 cmd
= [exe_file
, orig_file
, new_file
, patch_file
,]
135 RunSystemCommand(cmd
)
137 def GetLZMAExec(build_dir
):
138 lzma_exec
= os
.path
.join(build_dir
, "..", "..", "third_party",
139 "lzma_sdk", "Executable", "7za.exe")
142 def GetPrevVersion(build_dir
, temp_dir
, last_chrome_installer
, output_name
):
143 if not last_chrome_installer
:
146 lzma_exec
= GetLZMAExec(build_dir
)
147 prev_archive_file
= os
.path
.join(last_chrome_installer
,
148 output_name
+ ARCHIVE_SUFFIX
)
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]
158 def MakeStagingDirectories(staging_dir
):
159 """Creates a staging path for installer archive. If directory exists already,
160 deletes the existing directory.
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
)
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
)
173 def Readconfig(input_file
, current_version
):
174 """Reads config information from input file after setting default value of
178 variables
['ChromeDir'] = CHROME_DIR
179 variables
['VersionDir'] = os
.path
.join(variables
['ChromeDir'],
181 config
= ConfigParser
.SafeConfigParser(variables
)
182 config
.read(input_file
)
185 def RunSystemCommand(cmd
, **kw
):
187 exit_code
= subprocess
.call(cmd
, **kw
)
189 raise Exception("Error while running cmd: %s, exit_code: %s" %
192 def CreateArchiveFile(options
, staging_dir
, current_version
, prev_version
):
193 """Creates a new installer archive file after deleting any existing old file.
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
)
203 os
.path
.join(staging_dir
, CHROME_DIR
),
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
)
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
)
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
+
230 GenerateDiffPatch(options
, prev_archive_file
, archive_file
, patch_file
)
231 compressed_archive_file
= patch_name_prefix
+ '_' + \
232 current_version
+ '_from_' + prev_version
+ \
234 orig_file
= patch_file
236 compressed_archive_file
= options
.output_name
+ COMPRESSED_ARCHIVE_SUFFIX
237 orig_file
= archive_file
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
)
243 return compressed_archive_file
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
:
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
+
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
)
264 cmd
= ['makecab.exe',
265 '/D', 'CompressionType=LZX',
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] + "_"
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
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.
286 %(setup_file)s %(setup_file_resource_type)s
287 "%(setup_file_path)s"
290 "%(archive_file_path)s"
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.
298 This method checks the format of setup.exe being used and according sets
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"
307 # Expand the resource file template.
309 'setup_file': setup_file
,
310 'setup_file_resource_type': setup_resource_type
,
312 os
.path
.join(output_dir
, setup_file
).replace("\\","/"),
313 'archive_file': archive_file
,
315 os
.path
.join(output_dir
, archive_file
).replace("\\","/"),
317 resource_file
= _RESOURCE_FILE_TEMPLATE
% args
319 with
open(resource_file_path
, 'w') as f
:
320 f
.write(resource_file
)
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
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()
334 for i
in xrange(len(manifest_lines
)):
335 insert_pos
= manifest_lines
[i
].find(insert_before
)
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
+
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()
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.")
364 crt_dlls
= glob
.glob(
365 "C:/Program Files (x86)/Microsoft Visual Studio */VC/redist/"
366 "Debug_NonRedist/" + target_arch
+ "/Microsoft.*.DebugCRT/*.dll")
368 crt_dlls
= glob
.glob(
369 "C:/Program Files (x86)/Microsoft Visual Studio */VC/redist/" +
370 target_arch
+ "/Microsoft.*.CRT/*.dll")
372 # Also handle the case where someone is building using only winsdk and
373 # doesn't have Visual Studio installed.
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"
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.")
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"
389 sys_dll_dir
= "C:/Windows/System32"
391 if sys_dll_dir
is not None:
393 crt_dlls
= glob
.glob(os
.path
.join(sys_dll_dir
, "msvc*0d.dll"))
395 crt_dlls
= glob
.glob(os
.path
.join(sys_dll_dir
, "msvc*0.dll"))
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.")
402 shutil
.copy(dll
, build_dir
)
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
408 # This is meant for developer builds only and should never be used to package
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
)
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
)
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
428 dlls
= glob
.glob(os
.path
.join(build_dir
, '*.dll'))
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_'):
436 shutil
.copy(dll
, version_dir
)
437 dll_names
.append(os
.path
.splitext(os
.path
.basename(dll
))[0])
442 " <assemblyBinding xmlns='urn:schemas-microsoft-com:asm.v1'>\n"
443 " <probing privatePath='{rel_path}'/>\n"
444 " </assemblyBinding>\n"
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()
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()
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(
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
))
476 exe_manifest_dependencies
= ''.join(exe_manifest_dependencies_list
)
478 # Write a modified chrome.exe.manifest beside chrome.exe.
479 CopyAndAugmentManifest(build_dir
, chrome_dir
, 'chrome.exe.manifest',
480 exe_manifest_dependencies
, '</assembly>')
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>')
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
:
494 " xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>\n"
495 " <assemblyIdentity name='chrome.{dll_name}' version='0.0.0.0'\n"
497 " <file name='{dll_name}.dll'/>\n"
498 "</assembly>".format(dll_name
=name
))
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()
507 """Main method that reads input file, creates archive file and write
510 current_version
= BuildVersion(options
.build_dir
)
512 config
= Readconfig(options
.input_file
, current_version
)
514 (staging_dir
, temp_dir
) = MakeStagingDirectories(options
.staging_dir
)
516 prev_version
= GetPrevVersion(options
.build_dir
, temp_dir
,
517 options
.last_chrome_installer
,
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
)
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
)
533 if options
.component_build
== '1':
534 DoComponentBuildTasks(staging_dir
, options
.build_dir
,
535 options
.target_arch
, current_version
)
537 version_numbers
= current_version
.split('.')
538 current_build_number
= version_numbers
[2] + '.' + version_numbers
[3]
539 prev_build_number
= ''
541 version_numbers
= prev_version
.split('.')
542 prev_build_number
= version_numbers
[2] + '.' + version_numbers
[3]
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
)
549 setup_file
= PrepareSetupExec(options
,
550 current_build_number
, prev_build_number
)
552 CreateResourceInputFile(options
.output_dir
, options
.setup_exe_format
,
553 archive_file
, setup_file
, options
.resource_file_path
)
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 '
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) '
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 '
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}.')
601 options
, _
= parser
.parse_args()
602 if not options
.build_dir
:
603 parser
.error('You must provide a build dir.')
605 options
.build_dir
= os
.path
.normpath(options
.build_dir
)
607 if not options
.staging_dir
:
608 parser
.error('You must provide a staging dir.')
610 if not options
.input_file
:
611 parser
.error('You must provide an input file')
613 if not options
.output_dir
:
614 options
.output_dir
= options
.build_dir
616 if not options
.resource_file_path
:
617 options
.resource_file_path
= os
.path
.join(options
.build_dir
,
618 MINI_INSTALLER_INPUT_FILE
)
623 if '__main__' == __name__
:
625 sys
.exit(main(_ParseOptions()))