diff options
Diffstat (limited to 'build/win/importlibs/create_importlib_win.py')
-rwxr-xr-x | build/win/importlibs/create_importlib_win.py | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/build/win/importlibs/create_importlib_win.py b/build/win/importlibs/create_importlib_win.py new file mode 100755 index 0000000..e71bfe4 --- /dev/null +++ b/build/win/importlibs/create_importlib_win.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python +# 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) + + 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) + + # 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() + + print 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 imports.') + 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()) |