18d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt#!/usr/bin/env python
28d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt# Copyright (c) 2012 The Chromium Authors. All rights reserved.
361d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt# Use of this source code is governed by a BSD-style license that can be
48d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt# found in the LICENSE file.
5c5ec7f57ead87efa365800228aa0b09a12d9e6c4Dmitry Shmidt#
6c5ec7f57ead87efa365800228aa0b09a12d9e6c4Dmitry Shmidt"""Creates an import library from an import description file."""
78d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtimport ast
88d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtimport logging
98d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtimport optparse
108d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtimport os
118d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtimport os.path
128d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtimport shutil
138d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtimport subprocess
148d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtimport sys
158d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtimport tempfile
168d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
178d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
181f69aa52ea2e0a73ac502565df8c666ee49cab6aDmitry Shmidt_USAGE = """\
198d520ff1dc2da35cdca849e982051b86468016d8Dmitry ShmidtUsage: %prog [options] [imports-file]
208d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
218d520ff1dc2da35cdca849e982051b86468016d8Dmitry ShmidtCreates an import library from imports-file.
228d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
238d520ff1dc2da35cdca849e982051b86468016d8Dmitry ShmidtNote: this script uses the microsoft assembler (ml.exe) and the library tool
248d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    (lib.exe), both of which must be in path.
2561d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt"""
268d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
278d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
288d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt_ASM_STUB_HEADER = """\
298d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt; This file is autogenerated by create_importlib_win.py, do not edit.
308d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt.386
318d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt.MODEL FLAT, C
3204f534e89ed127da4077485376f24debc50d80d5Dmitry Shmidt.CODE
338d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
348d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt; Stubs to provide mangled names to lib.exe for the
358d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt; correct generation of import libs.
368d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt"""
378d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
388d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
39d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt_DEF_STUB_HEADER = """\
4061d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt; This file is autogenerated by create_importlib_win.py, do not edit.
4161d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt
428d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt; Export declarations for generating import libs.
438d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt"""
448d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
458d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
4604f534e89ed127da4077485376f24debc50d80d5Dmitry Shmidt_LOGGER = logging.getLogger()
478d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
488d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
498d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
508d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtclass _Error(Exception):
518d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  pass
528d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
538d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
548d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtclass _ImportLibraryGenerator(object):
558d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  def __init__(self, temp_dir):
5661d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    self._temp_dir = temp_dir
5761d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt
5861d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt  def _Shell(self, cmd, **kw):
5961d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    ret = subprocess.call(cmd, **kw)
60d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    _LOGGER.info('Running "%s" returned %d.', cmd, ret)
6161d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    if ret != 0:
6261d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt      raise _Error('Command "%s" returned %d.' % (cmd, ret))
6361d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt
6461d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt  def _ReadImportsFile(self, imports_file):
658d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    # Slurp the imports file.
668d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    return ast.literal_eval(open(imports_file).read())
678d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
688d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  def _WriteStubsFile(self, import_names, output_file):
698d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    output_file.write(_ASM_STUB_HEADER)
708d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
718d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    for name in import_names:
7261d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt      output_file.write('%s PROC\n' % name)
738d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt      output_file.write('%s ENDP\n' % name)
748d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
758d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    output_file.write('END\n')
768d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
77d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt  def _WriteDefFile(self, dll_name, import_names, output_file):
78d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    output_file.write(_DEF_STUB_HEADER)
79d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    output_file.write('NAME %s\n' % dll_name)
80d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    output_file.write('EXPORTS\n')
81d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    for name in import_names:
82d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt      name = name.split('@')[0]
83d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt      output_file.write('  %s\n' % name)
84d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt
85d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt  def _CreateObj(self, dll_name, imports):
86d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    """Writes an assembly file containing empty declarations.
87d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt
88d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    For each imported function of the form:
89d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt
90d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    AddClipboardFormatListener@4 PROC
91d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    AddClipboardFormatListener@4 ENDP
92d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt
93d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    The resulting object file is then supplied to lib.exe with a .def file
94d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    declaring the corresponding non-adorned exports as they appear on the
95d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    exporting DLL, e.g.
96d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt
97d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    EXPORTS
98d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt      AddClipboardFormatListener
99d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt
1008d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    In combination, the .def file and the .obj file cause lib.exe to generate
1018d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    an x86 import lib with public symbols named like
1021f69aa52ea2e0a73ac502565df8c666ee49cab6aDmitry Shmidt    "__imp__AddClipboardFormatListener@4", binding to exports named like
103d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    "AddClipboardFormatListener".
104d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt
1058d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    All of this is perpetrated in a temporary directory, as the intermediate
1068d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    artifacts are quick and easy to produce, and of no interest to anyone
10704f534e89ed127da4077485376f24debc50d80d5Dmitry Shmidt    after the fact."""
1088d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
10904f534e89ed127da4077485376f24debc50d80d5Dmitry Shmidt    # Create an .asm file to provide stdcall-like stub names to lib.exe.
1108d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    asm_name = dll_name + '.asm'
11161d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    _LOGGER.info('Writing asm file "%s".', asm_name)
11261d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    with open(os.path.join(self._temp_dir, asm_name), 'wb') as stubs_file:
11361d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt      self._WriteStubsFile(imports, stubs_file)
1148d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
11504f534e89ed127da4077485376f24debc50d80d5Dmitry Shmidt    # Invoke on the assembler to compile it to .obj.
11604f534e89ed127da4077485376f24debc50d80d5Dmitry Shmidt    obj_name = dll_name + '.obj'
11761d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    cmdline = ['ml.exe', '/nologo', '/c', asm_name, '/Fo', obj_name]
11861d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull))
11961d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt
12061d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    return obj_name
12161d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt
12261d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt  def _CreateImportLib(self, dll_name, imports, architecture, output_file):
12361d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    """Creates an import lib binding imports to dll_name for architecture.
12461d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt
12561d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    On success, writes the import library to output file.
126d5e4923d04122f81300fa68fb07d64ede28fd44dDmitry Shmidt    """
12761d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    obj_file = None
12861d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt
12961d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    # For x86 architecture we have to provide an object file for correct
13061d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    # name mangling between the import stubs and the exported functions.
13161d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    if architecture == 'x86':
13261d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt      obj_file = self._CreateObj(dll_name, imports)
13361d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt
13461d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    # Create the corresponding .def file. This file has the non stdcall-adorned
13561d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    # names, as exported by the destination DLL.
13661d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    def_name = dll_name + '.def'
13761d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    _LOGGER.info('Writing def file "%s".', def_name)
13861d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    with open(os.path.join(self._temp_dir, def_name), 'wb') as def_file:
13961d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt      self._WriteDefFile(dll_name, imports, def_file)
1408d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1418d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    # Invoke on lib.exe to create the import library.
1428d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    # We generate everything into the temporary directory, as the .exp export
1438d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    # files will be generated at the same path as the import library, and we
1448d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    # don't want those files potentially gunking the works.
1458d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    dll_base_name, ext = os.path.splitext(dll_name)
1468d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    lib_name = dll_base_name + '.lib'
1478d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    cmdline = ['lib.exe',
1488d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt               '/machine:%s' % architecture,
1498d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt               '/def:%s' % def_name,
1508d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt               '/out:%s' % lib_name]
1518d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    if obj_file:
1528d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt      cmdline.append(obj_file)
1538d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1548d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull))
1558d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1568d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    # Copy the .lib file to the output directory.
1578d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    shutil.copyfile(os.path.join(self._temp_dir, lib_name), output_file)
1588d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    _LOGGER.info('Created "%s".', output_file)
1598d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1608d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  def CreateImportLib(self, imports_file, output_file):
1618d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    # Read the imports file.
1628d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    imports = self._ReadImportsFile(imports_file)
1638d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1648d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    # Creates the requested import library in the output directory.
1658d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    self._CreateImportLib(imports['dll_name'],
1668d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt                          imports['imports'],
1678d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt                          imports.get('architecture', 'x86'),
1688d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt                          output_file)
1698d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1708d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1718d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtdef main():
1728d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  parser = optparse.OptionParser(usage=_USAGE)
1738d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  parser.add_option('-o', '--output-file',
1748d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt                    help='Specifies the output file path.')
1758d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  parser.add_option('-k', '--keep-temp-dir',
1768d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt                    action='store_true',
1778d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt                    help='Keep the temporary directory.')
1788d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  parser.add_option('-v', '--verbose',
1798d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt                    action='store_true',
1808d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt                    help='Verbose logging.')
1818d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1828d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  options, args = parser.parse_args()
1838d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1848d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  if len(args) != 1:
18561d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt    parser.error('You must provide an imports file.')
18661d9df3e62aaa0e87ad05452fcb95142159a17b6Dmitry Shmidt
1878d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  if not options.output_file:
1888d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    parser.error('You must provide an output file.')
1898d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1908d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  options.output_file = os.path.abspath(options.output_file)
1918d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1928d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  if options.verbose:
1938d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    logging.basicConfig(level=logging.INFO)
1948d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  else:
1958d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    logging.basicConfig(level=logging.WARN)
1968d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1978d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
1988d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  temp_dir = tempfile.mkdtemp()
1998d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  _LOGGER.info('Created temporary directory "%s."', temp_dir)
2008d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  try:
2018d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    # Create a generator and create the import lib.
2028d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    generator = _ImportLibraryGenerator(temp_dir)
2038d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
20404949598a23f501be6eec21697465fd46a28840aDmitry Shmidt    ret = generator.CreateImportLib(args[0], options.output_file)
20504949598a23f501be6eec21697465fd46a28840aDmitry Shmidt  except Exception, e:
2068d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    _LOGGER.exception('Failed to create import lib.')
2078d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    ret = 1
2088d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  finally:
2098d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt    if not options.keep_temp_dir:
2108d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt      shutil.rmtree(temp_dir)
2118d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt      _LOGGER.info('Deleted temporary directory "%s."', temp_dir)
2128d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
2138d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  return ret
2148d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
2158d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt
2168d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidtif __name__ == '__main__':
2178d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt  sys.exit(main())
2188d520ff1dc2da35cdca849e982051b86468016d8Dmitry Shmidt