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