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 # A utility script to create minimal import libs from an import description | |
M-A Ruel
2013/02/07 21:14:30
Make that a docstring
| |
7 # file. | |
8 import ast | |
9 import logging | |
10 import optparse | |
11 import os | |
12 import os.path | |
13 import shutil | |
14 import subprocess | |
15 import sys | |
16 import tempfile | |
17 | |
18 | |
19 _USAGE = """\ | |
20 Usage: %prog [options] [imports-file] | |
21 | |
22 Creates a pair of import libraries from imports-file. | |
M-A Ruel
2013/02/07 21:14:30
Often, I make this the docstring and then use:
op
| |
23 | |
24 Note: this script uses the microsoft assembler (ml.exe) and the library tool | |
25 (lib.exe), both of which must be in path. | |
26 """ | |
27 | |
28 | |
29 _ASM_STUB_HEADER = """\ | |
30 ; This file is autogenerated, do not edit. | |
31 .386 | |
32 .MODEL FLAT, C | |
33 .CODE | |
34 | |
35 ; Stubs to provide mangled names to lib.exe for the | |
36 ; correct generation of import libs. | |
37 """ | |
38 | |
39 | |
40 _DEF_STUB_HEADER = """\ | |
41 ; This file is autogenerated, do not edit. | |
42 | |
43 ; Export declarations for generating import libs. | |
44 """ | |
45 | |
46 | |
47 _LOGGER = logging.getLogger() | |
48 | |
49 | |
50 | |
51 class _Error(Exception): | |
52 pass | |
53 | |
54 | |
55 def _Shell(cmd, **kw): | |
56 ret = subprocess.call(cmd, **kw) | |
57 _LOGGER.info('Running "%s" returned %d.', cmd, ret) | |
58 if ret != 0: | |
59 raise _Error('Command "%s" returned %d.' % (cmd, ret)) | |
60 | |
61 | |
62 def _ReadImportsFile(imports_file): | |
63 # Slurp the imports file. | |
64 return ast.literal_eval(open(imports_file).read()) | |
65 | |
66 | |
67 def _WriteStubsFile(import_names, output_file): | |
68 output_file.write(_ASM_STUB_HEADER) | |
69 | |
70 for name in import_names: | |
71 output_file.write('%s PROC\n' % name) | |
72 output_file.write('%s ENDP\n' % name) | |
73 | |
74 output_file.write('END\n') | |
75 | |
76 | |
77 def _WriteDefFile(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 | |
86 def _CreateImportLib(dll_name, imports, temp_dir, output_file): | |
87 # For each library write an assmbly file containing empty declarations | |
M-A Ruel
2013/02/07 21:14:30
assembly
And I'd make the whole thing a docstring
| |
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, these two things cause lib.exe to generate an import lib | |
101 # with public symbols named like "__imp__AddClipboardFormatListener@4", | |
102 # binding to names like "AddClipboardFormatListener". | |
103 # | |
104 # All of this is perpetrated in a temporary directory, as the intermediate | |
105 # artifacts are quick and easy to produce, and of no interest to anyone | |
106 # after the fact. | |
107 | |
108 # Create an .asm file to provide stdcall-like stub names to lib.exe. | |
109 asm_name = dll_name + '.asm' | |
110 _LOGGER.info('Writing asm file "%s".', asm_name) | |
111 with open(os.path.join(temp_dir, asm_name), 'w') as stubs_file: | |
112 _WriteStubsFile(imports, stubs_file) | |
113 | |
114 # Invoke on the assembler to compile it to .obj. | |
115 obj_name = dll_name + '.obj' | |
116 cmdline = ['ml.exe', '/nologo', '/c', asm_name, '/Fo', obj_name] | |
117 _Shell(cmdline, cwd=temp_dir) | |
118 | |
119 # Create the corresponding .def file. This file has the non stdcall-adorned | |
120 # names, that need to correlate to the ASM above. | |
121 def_name = dll_name + '.def' | |
122 _LOGGER.info('Writing def file "%s".', def_name) | |
123 with open(os.path.join(temp_dir, def_name), 'w') as def_file: | |
124 _WriteDefFile(dll_name, imports, def_file) | |
125 | |
126 # Invoke on lib.exe to create the import library. | |
127 # We generate everything into the temporary directory, as the .exp export | |
128 # files will be generated at the same path as the import library, and we | |
129 # don't want those files potentially gunking the works. | |
130 dll_base_name, ext = os.path.splitext(dll_name) | |
131 lib_name = dll_base_name + '.lib' | |
132 cmdline = ['lib.exe', | |
133 obj_name, | |
134 '/def:%s' % def_name, | |
135 '/out:%s' % lib_name] | |
136 | |
137 _Shell(cmdline, cwd=temp_dir) | |
138 | |
139 # Copy the .lib file to the output directory. | |
140 shutil.copyfile(os.path.join(temp_dir, lib_name), output_file) | |
141 _LOGGER.info('Created "%s".', output_file) | |
142 | |
143 | |
144 def _CreateImportLibs(imports, temp_dir, output_dir): | |
145 dll_name = imports['dll_name'] | |
146 dll_base_name, ext = os.path.splitext(dll_name) | |
147 | |
148 # Creates an import library for the hard imports named DLL-imports.lib, | |
149 # e.g. user32-imports.lib. This import library will bind to dll_name. | |
150 _CreateImportLib(dll_name, | |
151 imports['imports'], | |
152 temp_dir, | |
153 os.path.join(output_dir, dll_base_name + '-imports.lib')) | |
154 # Creates an import library for the delay imports named DLL-delay.lib, | |
155 # e.g. user32-delay.lib. This import library will bind to dll_name-delay.dll, | |
156 # e.g. user32-delay.dll. | |
157 _CreateImportLib(dll_base_name + '-delay' + ext, | |
158 imports['delay_imports'], | |
159 temp_dir, | |
160 os.path.join(output_dir, dll_base_name + '-delay.lib')) | |
161 | |
162 | |
163 def main(): | |
164 parser = optparse.OptionParser(usage=_USAGE) | |
165 parser.add_option('-o', '--output-dir', | |
166 dest='output_dir', | |
167 help='Specifies the output directory.') | |
168 parser.add_option('-k', '--keep-temp-dir', | |
169 dest='keep_temp_dir', | |
170 action='store_true', | |
171 default=False, | |
172 help='Keep the temporary directory.') | |
173 parser.add_option('-v', '--verbose', | |
174 dest='verbose', | |
175 action='store_true', | |
176 default=False, | |
177 help='Verbose logging.') | |
178 | |
179 options, args = parser.parse_args() | |
180 if len(args) != 1: | |
181 parser.error('Must provide an imports file.') | |
182 | |
183 if not options.output_dir: | |
184 parser.error('Must provide an output directory.') | |
185 | |
186 options.output_dir = os.path.abspath(options.output_dir) | |
187 | |
188 if options.verbose: | |
189 logging.basicConfig(level=logging.INFO) | |
190 else: | |
191 logging.basicConfig(level=logging.WARN) | |
192 | |
193 # Read the imports file. | |
194 imports = _ReadImportsFile(args[0]) | |
195 | |
196 temp_dir = tempfile.mkdtemp() | |
197 _LOGGER.info('Created temporary directory "%s."', temp_dir) | |
198 try: | |
199 ret = _CreateImportLibs(imports, temp_dir, options.output_dir) | |
200 except Exception, e: | |
201 _LOGGER.exception('Failed to create imports.') | |
202 ret = 1 | |
203 finally: | |
204 if not options.keep_temp_dir: | |
205 shutil.rmtree(temp_dir) | |
206 _LOGGER.info('Deleted temporary directory "%s."', temp_dir) | |
207 | |
208 return ret | |
209 | |
210 | |
211 if __name__ == '__main__': | |
212 sys.exit(main()) | |
OLD | NEW |