| #!/usr/bin/env python3 |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| # |
| """Creates an import library from an import description file.""" |
| import ast |
| import logging |
| import optparse |
| import os |
| import os.path |
| import shutil |
| import subprocess |
| import sys |
| import tempfile |
| |
| _USAGE = """\ |
| Usage: %prog [options] [imports-file] |
| |
| Creates an import library from imports-file. |
| |
| Note: this script uses the microsoft assembler (ml.exe) and the library tool |
| (lib.exe), both of which must be in path. |
| """ |
| |
| _ASM_STUB_HEADER = """\ |
| ; This file is autogenerated by create_importlib_win.py, do not edit. |
| .386 |
| .MODEL FLAT, C |
| .CODE |
| |
| ; Stubs to provide mangled names to lib.exe for the |
| ; correct generation of import libs. |
| """ |
| |
| _DEF_STUB_HEADER = """\ |
| ; This file is autogenerated by create_importlib_win.py, do not edit. |
| |
| ; Export declarations for generating import libs. |
| """ |
| |
| _LOGGER = logging.getLogger() |
| |
| |
| class _Error(Exception): |
| pass |
| |
| |
| class _ImportLibraryGenerator(object): |
| |
| def __init__(self, temp_dir): |
| self._temp_dir = temp_dir |
| |
| def _Shell(self, cmd, **kw): |
| ret = subprocess.call(cmd, **kw) |
| _LOGGER.info('Running "%s" returned %d.', cmd, ret) |
| if ret != 0: |
| raise _Error('Command "%s" returned %d.' % (cmd, ret)) |
| |
| def _ReadImportsFile(self, imports_file): |
| # Slurp the imports file. |
| return ast.literal_eval(open(imports_file).read()) |
| |
| def _WriteStubsFile(self, import_names, output_file): |
| output_file.write(_ASM_STUB_HEADER) |
| |
| for name in import_names: |
| output_file.write('%s PROC\n' % name) |
| output_file.write('%s ENDP\n' % name) |
| |
| output_file.write('END\n') |
| |
| def _WriteDefFile(self, dll_name, import_names, output_file): |
| output_file.write(_DEF_STUB_HEADER) |
| output_file.write('NAME %s\n' % dll_name) |
| output_file.write('EXPORTS\n') |
| for name in import_names: |
| name = name.split('@')[0] |
| output_file.write(' %s\n' % name) |
| |
| def _CreateObj(self, dll_name, imports): |
| """Writes an assembly file containing empty declarations. |
| |
| For each imported function of the form: |
| |
| AddClipboardFormatListener@4 PROC |
| AddClipboardFormatListener@4 ENDP |
| |
| The resulting object file is then supplied to lib.exe with a .def file |
| declaring the corresponding non-adorned exports as they appear on the |
| exporting DLL, e.g. |
| |
| EXPORTS |
| AddClipboardFormatListener |
| |
| In combination, the .def file and the .obj file cause lib.exe to generate |
| an x86 import lib with public symbols named like |
| "__imp__AddClipboardFormatListener@4", binding to exports named like |
| "AddClipboardFormatListener". |
| |
| All of this is perpetrated in a temporary directory, as the intermediate |
| artifacts are quick and easy to produce, and of no interest to anyone |
| after the fact.""" |
| |
| # Create an .asm file to provide stdcall-like stub names to lib.exe. |
| asm_name = dll_name + '.asm' |
| _LOGGER.info('Writing asm file "%s".', asm_name) |
| with open(os.path.join(self._temp_dir, asm_name), 'wb') as stubs_file: |
| self._WriteStubsFile(imports, stubs_file) |
| |
| # Invoke on the assembler to compile it to .obj. |
| obj_name = dll_name + '.obj' |
| cmdline = ['ml.exe', '/nologo', '/c', asm_name, '/Fo', obj_name] |
| self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull)) |
| |
| return obj_name |
| |
| def _CreateImportLib(self, dll_name, imports, architecture, output_file): |
| """Creates an import lib binding imports to dll_name for architecture. |
| |
| On success, writes the import library to output file. |
| """ |
| obj_file = None |
| |
| # For x86 architecture we have to provide an object file for correct |
| # name mangling between the import stubs and the exported functions. |
| if architecture == 'x86': |
| obj_file = self._CreateObj(dll_name, imports) |
| |
| # Create the corresponding .def file. This file has the non stdcall-adorned |
| # names, as exported by the destination DLL. |
| def_name = dll_name + '.def' |
| _LOGGER.info('Writing def file "%s".', def_name) |
| with open(os.path.join(self._temp_dir, def_name), 'wb') as def_file: |
| self._WriteDefFile(dll_name, imports, def_file) |
| |
| # Invoke on lib.exe to create the import library. |
| # We generate everything into the temporary directory, as the .exp export |
| # files will be generated at the same path as the import library, and we |
| # don't want those files potentially gunking the works. |
| dll_base_name, ext = os.path.splitext(dll_name) |
| lib_name = dll_base_name + '.lib' |
| cmdline = [ |
| 'lib.exe', |
| '/machine:%s' % architecture, |
| '/def:%s' % def_name, |
| '/out:%s' % lib_name |
| ] |
| if obj_file: |
| cmdline.append(obj_file) |
| |
| self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull)) |
| |
| # Copy the .lib file to the output directory. |
| shutil.copyfile(os.path.join(self._temp_dir, lib_name), output_file) |
| _LOGGER.info('Created "%s".', output_file) |
| |
| def CreateImportLib(self, imports_file, output_file): |
| # Read the imports file. |
| imports = self._ReadImportsFile(imports_file) |
| |
| # Creates the requested import library in the output directory. |
| self._CreateImportLib(imports['dll_name'], imports['imports'], |
| imports.get('architecture', 'x86'), output_file) |
| |
| |
| def main(): |
| parser = optparse.OptionParser(usage=_USAGE) |
| parser.add_option( |
| '-o', '--output-file', help='Specifies the output file path.') |
| parser.add_option( |
| '-k', |
| '--keep-temp-dir', |
| action='store_true', |
| help='Keep the temporary directory.') |
| parser.add_option( |
| '-v', '--verbose', action='store_true', help='Verbose logging.') |
| |
| options, args = parser.parse_args() |
| |
| if len(args) != 1: |
| parser.error('You must provide an imports file.') |
| |
| if not options.output_file: |
| parser.error('You must provide an output file.') |
| |
| options.output_file = os.path.abspath(options.output_file) |
| |
| if options.verbose: |
| logging.basicConfig(level=logging.INFO) |
| else: |
| logging.basicConfig(level=logging.WARN) |
| |
| temp_dir = tempfile.mkdtemp() |
| _LOGGER.info('Created temporary directory "%s."', temp_dir) |
| try: |
| # Create a generator and create the import lib. |
| generator = _ImportLibraryGenerator(temp_dir) |
| |
| ret = generator.CreateImportLib(args[0], options.output_file) |
| except Exception, e: |
| _LOGGER.exception('Failed to create import lib.') |
| ret = 1 |
| finally: |
| if not options.keep_temp_dir: |
| shutil.rmtree(temp_dir) |
| _LOGGER.info('Deleted temporary directory "%s."', temp_dir) |
| |
| return ret |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |