OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 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. |
| 5 # |
| 6 """Creates an import library from an import description file.""" |
| 7 import ast |
| 8 import logging |
| 9 import optparse |
| 10 import os |
| 11 import os.path |
| 12 import shutil |
| 13 import subprocess |
| 14 import sys |
| 15 import tempfile |
| 16 |
| 17 |
| 18 _USAGE = """\ |
| 19 Usage: %prog [options] [imports-file] |
| 20 |
| 21 Creates an import library from imports-file. |
| 22 |
| 23 Note: this script uses the microsoft assembler (ml.exe) and the library tool |
| 24 (lib.exe), both of which must be in path. |
| 25 """ |
| 26 |
| 27 |
| 28 _ASM_STUB_HEADER = """\ |
| 29 ; This file is autogenerated by create_importlib_win.py, do not edit. |
| 30 .386 |
| 31 .MODEL FLAT, C |
| 32 .CODE |
| 33 |
| 34 ; Stubs to provide mangled names to lib.exe for the |
| 35 ; correct generation of import libs. |
| 36 """ |
| 37 |
| 38 |
| 39 _DEF_STUB_HEADER = """\ |
| 40 ; This file is autogenerated by create_importlib_win.py, do not edit. |
| 41 |
| 42 ; Export declarations for generating import libs. |
| 43 """ |
| 44 |
| 45 |
| 46 _LOGGER = logging.getLogger() |
| 47 |
| 48 |
| 49 |
| 50 class _Error(Exception): |
| 51 pass |
| 52 |
| 53 |
| 54 class _ImportLibraryGenerator(object): |
| 55 def __init__(self, temp_dir): |
| 56 self._temp_dir = temp_dir |
| 57 |
| 58 def _Shell(self, cmd, **kw): |
| 59 ret = subprocess.call(cmd, **kw) |
| 60 _LOGGER.info('Running "%s" returned %d.', cmd, ret) |
| 61 if ret != 0: |
| 62 raise _Error('Command "%s" returned %d.' % (cmd, ret)) |
| 63 |
| 64 def _ReadImportsFile(self, imports_file): |
| 65 # Slurp the imports file. |
| 66 return ast.literal_eval(open(imports_file).read()) |
| 67 |
| 68 def _WriteStubsFile(self, import_names, output_file): |
| 69 output_file.write(_ASM_STUB_HEADER) |
| 70 |
| 71 for name in import_names: |
| 72 output_file.write('%s PROC\n' % name) |
| 73 output_file.write('%s ENDP\n' % name) |
| 74 |
| 75 output_file.write('END\n') |
| 76 |
| 77 def _WriteDefFile(self, dll_name, import_names, output_file): |
| 78 output_file.write(_DEF_STUB_HEADER) |
| 79 output_file.write('NAME %s\n' % dll_name) |
| 80 output_file.write('EXPORTS\n') |
| 81 for name in import_names: |
| 82 name = name.split('@')[0] |
| 83 output_file.write(' %s\n' % name) |
| 84 |
| 85 def _CreateObj(self, dll_name, imports): |
| 86 """Writes an assembly file containing empty declarations. |
| 87 |
| 88 For each imported function of the form: |
| 89 |
| 90 AddClipboardFormatListener@4 PROC |
| 91 AddClipboardFormatListener@4 ENDP |
| 92 |
| 93 The resulting object file is then supplied to lib.exe with a .def file |
| 94 declaring the corresponding non-adorned exports as they appear on the |
| 95 exporting DLL, e.g. |
| 96 |
| 97 EXPORTS |
| 98 AddClipboardFormatListener |
| 99 |
| 100 In combination, the .def file and the .obj file cause lib.exe to generate |
| 101 an x86 import lib with public symbols named like |
| 102 "__imp__AddClipboardFormatListener@4", binding to exports named like |
| 103 "AddClipboardFormatListener". |
| 104 |
| 105 All of this is perpetrated in a temporary directory, as the intermediate |
| 106 artifacts are quick and easy to produce, and of no interest to anyone |
| 107 after the fact.""" |
| 108 |
| 109 # Create an .asm file to provide stdcall-like stub names to lib.exe. |
| 110 asm_name = dll_name + '.asm' |
| 111 _LOGGER.info('Writing asm file "%s".', asm_name) |
| 112 with open(os.path.join(self._temp_dir, asm_name), 'wb') as stubs_file: |
| 113 self._WriteStubsFile(imports, stubs_file) |
| 114 |
| 115 # Invoke on the assembler to compile it to .obj. |
| 116 obj_name = dll_name + '.obj' |
| 117 cmdline = ['ml.exe', '/nologo', '/c', asm_name, '/Fo', obj_name] |
| 118 self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull)) |
| 119 |
| 120 return obj_name |
| 121 |
| 122 def _CreateImportLib(self, dll_name, imports, architecture, output_file): |
| 123 """Creates an import lib binding imports to dll_name for architecture. |
| 124 |
| 125 On success, writes the import library to output file. |
| 126 """ |
| 127 obj_file = None |
| 128 |
| 129 # For x86 architecture we have to provide an object file for correct |
| 130 # name mangling between the import stubs and the exported functions. |
| 131 if architecture == 'x86': |
| 132 obj_file = self._CreateObj(dll_name, imports) |
| 133 |
| 134 # Create the corresponding .def file. This file has the non stdcall-adorned |
| 135 # names, as exported by the destination DLL. |
| 136 def_name = dll_name + '.def' |
| 137 _LOGGER.info('Writing def file "%s".', def_name) |
| 138 with open(os.path.join(self._temp_dir, def_name), 'wb') as def_file: |
| 139 self._WriteDefFile(dll_name, imports, def_file) |
| 140 |
| 141 # Invoke on lib.exe to create the import library. |
| 142 # We generate everything into the temporary directory, as the .exp export |
| 143 # files will be generated at the same path as the import library, and we |
| 144 # don't want those files potentially gunking the works. |
| 145 dll_base_name, ext = os.path.splitext(dll_name) |
| 146 lib_name = dll_base_name + '.lib' |
| 147 cmdline = ['lib.exe', |
| 148 '/machine:%s' % architecture, |
| 149 '/def:%s' % def_name, |
| 150 '/out:%s' % lib_name] |
| 151 if obj_file: |
| 152 cmdline.append(obj_file) |
| 153 |
| 154 self._Shell(cmdline, cwd=self._temp_dir, stdout=open(os.devnull)) |
| 155 |
| 156 # Copy the .lib file to the output directory. |
| 157 shutil.copyfile(os.path.join(self._temp_dir, lib_name), output_file) |
| 158 _LOGGER.info('Created "%s".', output_file) |
| 159 |
| 160 def CreateImportLib(self, imports_file, output_file): |
| 161 # Read the imports file. |
| 162 imports = self._ReadImportsFile(imports_file) |
| 163 |
| 164 # Creates the requested import library in the output directory. |
| 165 self._CreateImportLib(imports['dll_name'], |
| 166 imports['imports'], |
| 167 imports.get('architecture', 'x86'), |
| 168 output_file) |
| 169 |
| 170 |
| 171 def main(): |
| 172 parser = optparse.OptionParser(usage=_USAGE) |
| 173 parser.add_option('-o', '--output-file', |
| 174 help='Specifies the output file path.') |
| 175 parser.add_option('-k', '--keep-temp-dir', |
| 176 action='store_true', |
| 177 help='Keep the temporary directory.') |
| 178 parser.add_option('-v', '--verbose', |
| 179 action='store_true', |
| 180 help='Verbose logging.') |
| 181 |
| 182 options, args = parser.parse_args() |
| 183 |
| 184 if len(args) != 1: |
| 185 parser.error('You must provide an imports file.') |
| 186 |
| 187 if not options.output_file: |
| 188 parser.error('You must provide an output file.') |
| 189 |
| 190 options.output_file = os.path.abspath(options.output_file) |
| 191 |
| 192 if options.verbose: |
| 193 logging.basicConfig(level=logging.INFO) |
| 194 else: |
| 195 logging.basicConfig(level=logging.WARN) |
| 196 |
| 197 |
| 198 temp_dir = tempfile.mkdtemp() |
| 199 _LOGGER.info('Created temporary directory "%s."', temp_dir) |
| 200 try: |
| 201 # Create a generator and create the import lib. |
| 202 generator = _ImportLibraryGenerator(temp_dir) |
| 203 |
| 204 ret = generator.CreateImportLib(args[0], options.output_file) |
| 205 except Exception, e: |
| 206 _LOGGER.exception('Failed to create import lib.') |
| 207 ret = 1 |
| 208 finally: |
| 209 if not options.keep_temp_dir: |
| 210 shutil.rmtree(temp_dir) |
| 211 _LOGGER.info('Deleted temporary directory "%s."', temp_dir) |
| 212 |
| 213 return ret |
| 214 |
| 215 |
| 216 if __name__ == '__main__': |
| 217 sys.exit(main()) |
OLD | NEW |