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, do not edit. | |
M-A Ruel
2013/03/26 12:59:14
; This file is autogenerated by create_importlib_w
Sigurður Ásgeirsson
2013/03/26 15:35:35
Done.
| |
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, do not edit. | |
M-A Ruel
2013/03/26 12:59:14
same
Sigurður Ásgeirsson
2013/03/26 15:35:35
Done.
| |
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 | |
M-A Ruel
2013/03/26 12:59:14
remove one line
Sigurður Ásgeirsson
2013/03/26 15:35:35
Done.
| |
86 def _CreateObj(self, dll_name, imports): | |
87 """Writes an assembly file containing empty declarations. | |
88 | |
89 For each imported function of the form: | |
90 | |
91 AddClipboardFormatListener@4 PROC | |
92 AddClipboardFormatListener@4 ENDP | |
93 | |
94 The resulting object file is then supplied to lib.exe with a .def file | |
95 declaring the corresponding non-adorned exports as they appear on the | |
96 exporting DLL, e.g. | |
97 | |
98 EXPORTS | |
99 AddClipboardFormatListener | |
100 | |
101 In combination, the .def file and the .obj file cause lib.exe to generate | |
102 an x86 import lib with public symbols named like | |
103 "__imp__AddClipboardFormatListener@4", binding to exports named like | |
104 "AddClipboardFormatListener". | |
105 | |
106 All of this is perpetrated in a temporary directory, as the intermediate | |
107 artifacts are quick and easy to produce, and of no interest to anyone | |
108 after the fact.""" | |
109 | |
110 # Create an .asm file to provide stdcall-like stub names to lib.exe. | |
111 asm_name = dll_name + '.asm' | |
112 _LOGGER.info('Writing asm file "%s".', asm_name) | |
113 with open(os.path.join(self._temp_dir, asm_name), 'w') as stubs_file: | |
M-A Ruel
2013/03/26 12:59:14
We always use 'wb' for consistency. Unless you act
Sigurður Ásgeirsson
2013/03/26 15:35:35
Done.
| |
114 self._WriteStubsFile(imports, stubs_file) | |
115 | |
116 # Invoke on the assembler to compile it to .obj. | |
117 obj_name = dll_name + '.obj' | |
118 cmdline = ['ml.exe', '/nologo', '/c', asm_name, '/Fo', obj_name] | |
119 self._Shell(cmdline, cwd=self._temp_dir) | |
120 | |
121 return obj_name | |
122 | |
123 def _CreateImportLib(self, dll_name, imports, architecture, output_file): | |
124 """Creates an import lib binding imports to dll_name for architecture. | |
125 | |
126 On success, writes the import library to output file. | |
127 """ | |
128 obj_file = None | |
129 | |
130 # For x86 architecture we have to provide an object file for correct | |
131 # name mangling between the import stubs and the exported functions. | |
132 if architecture == 'x86': | |
133 obj_file = self._CreateObj(dll_name, imports) | |
134 | |
135 print "architecture", architecture | |
136 print "Obj file", obj_file # DO NOT SUBMIT | |
M-A Ruel
2013/03/26 12:59:14
I guess you won't submit this :)
Sigurður Ásgeirsson
2013/03/26 15:35:35
Ooops - I'm used to the presubmit check (in Syzygy
M-A Ruel
2013/03/26 15:44:37
Yes but only on commit.
| |
137 | |
138 # Create the corresponding .def file. This file has the non stdcall-adorned | |
139 # names, as exported by the destination DLL. | |
140 def_name = dll_name + '.def' | |
141 _LOGGER.info('Writing def file "%s".', def_name) | |
142 with open(os.path.join(self._temp_dir, def_name), 'w') as def_file: | |
143 self._WriteDefFile(dll_name, imports, def_file) | |
144 | |
145 # Invoke on lib.exe to create the import library. | |
146 # We generate everything into the temporary directory, as the .exp export | |
147 # files will be generated at the same path as the import library, and we | |
148 # don't want those files potentially gunking the works. | |
149 dll_base_name, ext = os.path.splitext(dll_name) | |
150 lib_name = dll_base_name + '.lib' | |
151 cmdline = ['lib.exe', | |
152 '/machine:%s' % architecture, | |
153 '/def:%s' % def_name, | |
154 '/out:%s' % lib_name] | |
155 if obj_file: | |
156 cmdline.append(obj_file) | |
157 | |
158 self._Shell(cmdline, cwd=self._temp_dir) | |
159 | |
160 # Copy the .lib file to the output directory. | |
161 shutil.copyfile(os.path.join(self._temp_dir, lib_name), output_file) | |
162 _LOGGER.info('Created "%s".', output_file) | |
163 | |
164 def CreateImportLib(self, imports_file, output_file): | |
165 # Read the imports file. | |
166 imports = self._ReadImportsFile(imports_file) | |
167 | |
168 # Creates the requested import library in the output directory. | |
169 self._CreateImportLib(imports['dll_name'], | |
170 imports['imports'], | |
171 imports.get('architecture', 'x86'), | |
172 output_file) | |
173 | |
174 | |
175 def main(): | |
176 parser = optparse.OptionParser(usage=_USAGE) | |
177 parser.add_option('-o', '--output-file', | |
178 help='Specifies the output file path.') | |
179 parser.add_option('-k', '--keep-temp-dir', | |
180 action='store_true', | |
181 help='Keep the temporary directory.') | |
182 parser.add_option('-v', '--verbose', | |
183 action='store_true', | |
184 help='Verbose logging.') | |
185 | |
186 options, args = parser.parse_args() | |
187 | |
188 print args | |
189 if len(args) != 1: | |
190 parser.error('You must provide an imports file.') | |
191 | |
192 if not options.output_file: | |
193 parser.error('You must provide an output file.') | |
194 | |
195 options.output_file = os.path.abspath(options.output_file) | |
196 | |
197 if options.verbose: | |
198 logging.basicConfig(level=logging.INFO) | |
199 else: | |
200 logging.basicConfig(level=logging.WARN) | |
201 | |
202 | |
203 temp_dir = tempfile.mkdtemp() | |
204 _LOGGER.info('Created temporary directory "%s."', temp_dir) | |
205 try: | |
206 # Create a generator and create the import lib. | |
207 generator = _ImportLibraryGenerator(temp_dir) | |
208 | |
209 ret = generator.CreateImportLib(args[0], options.output_file) | |
210 except Exception, e: | |
211 _LOGGER.exception('Failed to create imports.') | |
212 ret = 1 | |
213 finally: | |
214 if not options.keep_temp_dir: | |
215 shutil.rmtree(temp_dir) | |
216 _LOGGER.info('Deleted temporary directory "%s."', temp_dir) | |
217 | |
218 return ret | |
219 | |
220 | |
221 if __name__ == '__main__': | |
222 sys.exit(main()) | |
OLD | NEW |