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
,
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
)
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'):
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
):
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
)
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")
140 def GetPrevVersion(build_dir
, temp_dir
, last_chrome_installer
, output_name
):
141 if not last_chrome_installer
:
144 lzma_exec
= GetLZMAExec(build_dir
)
145 prev_archive_file
= os
.path
.join(last_chrome_installer
,
146 output_name
+ ARCHIVE_SUFFIX
)
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
176 variables
['ChromeDir'] = CHROME_DIR
177 variables
['VersionDir'] = os
.path
.join(variables
['ChromeDir'],
179 config
= ConfigParser
.SafeConfigParser(variables
)
180 config
.read(input_file
)
183 def RunSystemCommand(cmd
, **kw
):
185 exit_code
= subprocess
.call(cmd
, **kw
)
187 raise Exception("Error while running cmd: %s, exit_code: %s" %
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
)
201 os
.path
.join(staging_dir
, CHROME_DIR
),
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
+
228 GenerateDiffPatch(options
, prev_archive_file
, archive_file
, patch_file
)
229 compressed_archive_file
= patch_name_prefix
+ '_' + \
230 current_version
+ '_from_' + prev_version
+ \
232 orig_file
= patch_file
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
:
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
+
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
)
262 cmd
= ['makecab.exe',
263 '/D', 'CompressionType=LZX',
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] + "_"
273 _RESOURCE_FILE_HEADER
= """\
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
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.
286 def CreateResourceInputFile(
287 output_dir
, setup_format
, archive_file
, setup_file
, resource_file_path
,
288 component_build
, staging_dir
, current_version
):
289 """Creates resource input file (packed_files.txt) for mini_installer project.
291 This method checks the format of setup.exe being used and according sets
294 setup_resource_type
= "BL"
295 if (setup_format
== "FULL"):
296 setup_resource_type
= "BN"
297 elif (setup_format
== "DIFF"):
298 setup_resource_type
= "B7"
300 # An array of (file, type, path) tuples of the files to be included.
302 resources
.append((setup_file
, setup_resource_type
,
303 os
.path
.join(output_dir
, setup_file
)))
304 resources
.append((archive_file
, 'B7',
305 os
.path
.join(output_dir
, archive_file
)))
306 # Include all files needed to run setup.exe (these are copied into the
307 # 'Installer' dir by DoComponentBuildTasks).
309 installer_dir
= os
.path
.join(staging_dir
, CHROME_DIR
, current_version
,
311 for file in os
.listdir(installer_dir
):
312 resources
.append((file, 'BN', os
.path
.join(installer_dir
, file)))
314 with
open(resource_file_path
, 'w') as f
:
315 f
.write(_RESOURCE_FILE_HEADER
)
316 for (file, type, path
) in resources
:
317 f
.write('\n%s %s\n "%s"\n' % (file, type, path
.replace("\\","/")))
320 # Reads |manifest_name| from |build_dir| and writes |manifest_name| to
321 # |output_dir| with the same content plus |inserted_string| added just before
323 def CopyAndAugmentManifest(build_dir
, output_dir
, manifest_name
,
324 inserted_string
, insert_before
):
325 with
open(os
.path
.join(build_dir
, manifest_name
), 'r') as f
:
326 manifest_lines
= f
.readlines()
330 for i
in xrange(len(manifest_lines
)):
331 insert_pos
= manifest_lines
[i
].find(insert_before
)
335 if insert_line
== -1:
336 raise ValueError('Could not find {0} in the manifest:\n{1}'.format(
337 insert_before
, ''.join(manifest_lines
)))
338 old
= manifest_lines
[insert_line
]
339 manifest_lines
[insert_line
] = (old
[:insert_pos
] + '\n' + inserted_string
+
340 '\n' + old
[insert_pos
:])
342 with
open(os
.path
.join(output_dir
, manifest_name
), 'w') as f
:
343 f
.write(''.join(manifest_lines
))
346 def CopyIfChanged(src
, target_dir
):
347 """Copy specified |src| file to |target_dir|, but only write to target if
348 the file has changed. This avoids a problem during packaging where parts of
349 the build have not completed and have the runtime DLL locked when we try to
350 copy over it. See http://crbug.com/305877 for details."""
351 assert os
.path
.isdir(target_dir
)
352 dest
= os
.path
.join(target_dir
, os
.path
.basename(src
))
353 if os
.path
.exists(dest
):
354 # We assume the files are OK to buffer fully into memory since we know
356 with
open(src
, 'rb') as fsrc
:
357 src_data
= fsrc
.read()
358 with
open(dest
, 'rb') as fdest
:
359 dest_data
= fdest
.read()
360 if src_data
!= dest_data
:
361 # This may still raise if we get here, but this really should almost
362 # never happen (it would mean that the contents of e.g. msvcr100d.dll
364 shutil
.copyfile(src
, dest
)
366 shutil
.copyfile(src
, dest
)
369 # Copy the relevant CRT DLLs to |build_dir|. We copy DLLs from all versions
370 # of VS installed to make sure we have the correct CRT version, unused DLLs
371 # should not conflict with the others anyways.
372 def CopyVisualStudioRuntimeDLLs(target_arch
, build_dir
):
373 is_debug
= os
.path
.basename(build_dir
).startswith('Debug')
374 if not is_debug
and not os
.path
.basename(build_dir
).startswith('Release'):
375 print ("Warning: could not determine build configuration from "
376 "output directory, assuming Release build.")
381 crt_dlls
= glob
.glob(
382 "C:/Program Files (x86)/Microsoft Visual Studio */VC/redist/"
383 "Debug_NonRedist/" + target_arch
+ "/Microsoft.*.DebugCRT/*.dll")
385 crt_dlls
= glob
.glob(
386 "C:/Program Files (x86)/Microsoft Visual Studio */VC/redist/" +
387 target_arch
+ "/Microsoft.*.CRT/*.dll")
389 # Also handle the case where someone is building using only winsdk and
390 # doesn't have Visual Studio installed.
392 if target_arch
== 'x64':
393 # check we are are on a 64bit system by existence of WOW64 dir
394 if os
.access("C:/Windows/SysWOW64", os
.F_OK
):
395 sys_dll_dir
= "C:/Windows/System32"
397 # only support packaging of 64bit installer on 64bit system
398 # but this just as bad as not finding DLLs at all so we
399 # don't abort here to mirror behavior below
400 print ("Warning: could not find x64 CRT DLLs on x86 system.")
402 # On a 64-bit system, 32-bit dlls are in SysWOW64 (don't ask).
403 if os
.access("C:/Windows/SysWOW64", os
.F_OK
):
404 sys_dll_dir
= "C:/Windows/SysWOW64"
406 sys_dll_dir
= "C:/Windows/System32"
408 if sys_dll_dir
is not None:
410 crt_dlls
= glob
.glob(os
.path
.join(sys_dll_dir
, "msvc*0d.dll"))
412 crt_dlls
= glob
.glob(os
.path
.join(sys_dll_dir
, "msvc*0.dll"))
415 print ("Warning: could not find CRT DLLs to copy to build dir - target "
416 "may not run on a system that doesn't have those DLLs.")
419 CopyIfChanged(dll
, build_dir
)
422 # Copies component build DLLs and generates required config files and manifests
423 # in order for chrome.exe and setup.exe to be able to find those DLLs at
425 # This is meant for developer builds only and should never be used to package
427 def DoComponentBuildTasks(staging_dir
, build_dir
, target_arch
, current_version
):
428 # Get the required directories for the upcoming operations.
429 chrome_dir
= os
.path
.join(staging_dir
, CHROME_DIR
)
430 version_dir
= os
.path
.join(chrome_dir
, current_version
)
431 installer_dir
= os
.path
.join(version_dir
, 'Installer')
432 # |installer_dir| is technically only created post-install, but we need it
433 # now to add setup.exe's config and manifest to the archive.
434 if not os
.path
.exists(installer_dir
):
435 os
.mkdir(installer_dir
)
437 # Copy the VS CRT DLLs to |build_dir|. This must be done before the general
438 # copy step below to ensure the CRT DLLs are added to the archive and marked
439 # as a dependency in the exe manifests generated below.
440 CopyVisualStudioRuntimeDLLs(target_arch
, build_dir
)
442 # Explicitly list the component DLLs setup.exe depends on (this list may
443 # contain wildcards). These will be copied to |installer_dir| in the archive.
444 setup_component_dll_globs
= [ 'base.dll',
450 for setup_component_dll_glob
in setup_component_dll_globs
:
451 setup_component_dlls
= glob
.glob(os
.path
.join(build_dir
,
452 setup_component_dll_glob
))
453 for setup_component_dll
in setup_component_dlls
:
454 shutil
.copy(setup_component_dll
, installer_dir
)
456 # Stage all the component DLLs found in |build_dir| to the |version_dir| (for
457 # the version assembly to be able to refer to them below and make sure
458 # chrome.exe can find them at runtime). The component DLLs are considered to
459 # be all the DLLs which have not already been added to the |version_dir| by
460 # virtue of chrome.release.
461 build_dlls
= glob
.glob(os
.path
.join(build_dir
, '*.dll'))
462 staged_dll_basenames
= [os
.path
.basename(staged_dll
) for staged_dll
in \
463 glob
.glob(os
.path
.join(version_dir
, '*.dll'))]
464 component_dll_filenames
= []
465 for component_dll
in [dll
for dll
in build_dlls
if \
466 os
.path
.basename(dll
) not in staged_dll_basenames
]:
467 component_dll_name
= os
.path
.basename(component_dll
)
468 # remoting_*.dll's don't belong in the archive (it doesn't depend on them
469 # in gyp). Trying to copy them causes a build race when creating the
470 # installer archive in component mode. See: crbug.com/180996
471 if component_dll_name
.startswith('remoting_'):
473 component_dll_filenames
.append(component_dll_name
)
474 shutil
.copy(component_dll
, version_dir
)
476 # Augment {version}.manifest to include all component DLLs as part of the
477 # assembly it constitutes, which will allow dependents of this assembly to
479 version_assembly_dll_additions
= []
480 for dll_filename
in component_dll_filenames
:
481 version_assembly_dll_additions
.append(" <file name='%s'/>" % dll_filename
)
482 CopyAndAugmentManifest(build_dir
, version_dir
,
483 '%s.manifest' % current_version
,
484 '\n'.join(version_assembly_dll_additions
),
489 """Main method that reads input file, creates archive file and write
492 current_version
= BuildVersion(options
.build_dir
)
494 config
= Readconfig(options
.input_file
, current_version
)
496 (staging_dir
, temp_dir
) = MakeStagingDirectories(options
.staging_dir
)
498 prev_version
= GetPrevVersion(options
.build_dir
, temp_dir
,
499 options
.last_chrome_installer
,
502 # Preferentially copy the files we can find from the output_dir, as
503 # this is where we'll find the Syzygy-optimized executables when
504 # building the optimized mini_installer.
505 if options
.build_dir
!= options
.output_dir
:
506 CopyAllFilesToStagingDir(config
, options
.distribution
,
507 staging_dir
, options
.output_dir
,
508 options
.enable_hidpi
)
510 # Now copy the remainder of the files from the build dir.
511 CopyAllFilesToStagingDir(config
, options
.distribution
,
512 staging_dir
, options
.build_dir
,
513 options
.enable_hidpi
)
515 if options
.component_build
== '1':
516 DoComponentBuildTasks(staging_dir
, options
.build_dir
,
517 options
.target_arch
, current_version
)
519 version_numbers
= current_version
.split('.')
520 current_build_number
= version_numbers
[2] + '.' + version_numbers
[3]
521 prev_build_number
= ''
523 version_numbers
= prev_version
.split('.')
524 prev_build_number
= version_numbers
[2] + '.' + version_numbers
[3]
526 # Name of the archive file built (for example - chrome.7z or
527 # patch-<old_version>-<new_version>.7z or patch-<new_version>.7z
528 archive_file
= CreateArchiveFile(options
, staging_dir
,
529 current_build_number
, prev_build_number
)
531 setup_file
= PrepareSetupExec(options
,
532 current_build_number
, prev_build_number
)
534 CreateResourceInputFile(options
.output_dir
, options
.setup_exe_format
,
535 archive_file
, setup_file
, options
.resource_file_path
,
536 options
.component_build
== '1', staging_dir
,
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 '
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) '
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
)
604 if '__main__' == __name__
:
606 sys
.exit(main(_ParseOptions()))