OLD | NEW |
---|---|
(Empty) | |
1 # Copyright 2013 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """Takes the same arguments as Windows link.exe, and a definition of libraries | |
6 to split into subcomponents. Does multiple passes of link.exe invocation to | |
7 determine exports between parts and generates .def and import libraries to | |
8 cause symbols to be available to other parts.""" | |
9 | |
10 from ctypes import * | |
11 import os | |
12 import re | |
13 import subprocess | |
14 import sys | |
15 | |
16 | |
17 def Log(message): | |
18 print 'split_link:', message | |
19 | |
20 | |
21 def GetFlagsAndInputs(argv): | |
22 """Parse the command line intended for link.exe and return the flags and | |
M-A Ruel
2013/05/13 21:55:59
In general, docstrings should start with an impera
scottmg
2013/05/13 23:00:15
Sorry, don't understand this one. You want "Parses
M-A Ruel
2013/05/14 00:39:09
Yes.
http://google-styleguide.googlecode.com/svn/t
| |
23 input files.""" | |
24 rsp_expanded = [] | |
25 for arg in argv: | |
26 if arg[0] == '@': | |
27 with open(arg[1:]) as rsp: | |
28 rsp_expanded.append(rsp.read().replace('\r', '').replace('\n', ' ')) | |
M-A Ruel
2013/05/13 21:55:59
open() should remove any CR when reading in text m
scottmg
2013/05/13 23:00:15
Done.
| |
29 else: | |
30 rsp_expanded.append(arg) | |
31 | |
32 # Use CommandLineToArgvW so we match link.exe parsing (I think?). | |
33 size = c_int() | |
34 ptr = windll.shell32.CommandLineToArgvW( | |
35 create_unicode_buffer(' '.join(rsp_expanded)), | |
36 byref(size)) | |
37 ref = c_wchar_p * size.value | |
38 raw = ref.from_address(ptr) | |
39 args = [arg for arg in raw] | |
M-A Ruel
2013/05/13 21:55:59
It's not clear why you need args as a named variab
scottmg
2013/05/13 23:00:15
Done.
| |
40 windll.kernel32.LocalFree(ptr) | |
41 | |
42 inputs = [] | |
43 flags = [] | |
44 for arg in args: | |
45 lower_arg = arg.lower() | |
46 # We'll be replacing this ourselves. | |
47 if lower_arg.startswith('/out:'): | |
48 continue | |
49 if (not lower_arg.startswith('/') and | |
50 (lower_arg.endswith('.obj') or | |
M-A Ruel
2013/05/13 21:55:59
lower_arg.endswith(('.lib', '.obj', '.res'))
scottmg
2013/05/13 23:00:15
http://docs.python.org/2/library/stdtypes.html#str
| |
51 lower_arg.endswith('.lib') or | |
52 lower_arg.endswith('.res'))): | |
53 inputs.append(arg) | |
54 else: | |
55 flags.append(arg) | |
56 | |
57 return flags, inputs | |
58 | |
59 | |
60 def GetOriginalLinkerPath(): | |
61 _winreg = None | |
62 if sys.platform == 'win32': | |
63 import _winreg | |
64 elif sys.platform == 'cygwin': | |
65 import cygwinreg as _winreg | |
66 | |
67 try: | |
68 val = _winreg.QueryValue(_winreg.HKEY_CURRENT_USER, | |
69 'Software\\Chromium\\split_link_installed') | |
70 if os.path.exists(val): | |
71 return val | |
72 except WindowsError: | |
73 pass | |
74 | |
75 raise SystemExit("Couldn't read linker location from registry") | |
76 | |
77 | |
78 def PartFor(input, description_parts, description_all): | |
79 """Determine which part a given link input should be put into (or all).""" | |
80 # Check if it should go in all parts. | |
81 input = input.lower() | |
M-A Ruel
2013/05/13 21:55:59
I don't like using input as a variable name since
scottmg
2013/05/13 23:00:15
file? ;) Done.
M-A Ruel
2013/05/14 00:39:09
Yeah, I recommend avoiding "file" too.
| |
82 for spec in description_all: | |
M-A Ruel
2013/05/13 21:55:59
if any(re.search(spec, input) for spec in descript
scottmg
2013/05/13 23:00:15
Done.
| |
83 if re.search(spec, input): | |
84 return -1 | |
85 # Or pick which particular one it belongs in. | |
86 for i, spec_list in enumerate(description_parts): | |
87 for spec in spec_list: | |
M-A Ruel
2013/05/13 21:55:59
if any(re.search(spec, input) for spec in spec_lis
scottmg
2013/05/13 23:00:15
Done.
| |
88 if re.search(spec, input): | |
89 return i | |
90 raise ValueError("couldn't find location for %s" % input) | |
91 | |
92 | |
93 def ParseOutExternals(output): | |
94 """Given the stdout of link.exe, parse the errors messages to retrieve all | |
95 symbols that are unresolved.""" | |
96 result = set() | |
97 # Styles of messages for unresolved externals, and a boolean to indicate | |
98 # whether the error message emits the symbols with or without a leading | |
99 # underscore. | |
100 unresolved_regexes = [ | |
101 (re.compile(r' : error LNK2019: unresolved external symbol ".*" \((.*)\)' | |
102 r' referenced in function'), | |
103 False), | |
104 (re.compile(r' : error LNK2001: unresolved external symbol ".*" \((.*)\)$'), | |
105 False), | |
106 (re.compile(r' : error LNK2019: unresolved external symbol (.*)' | |
107 r' referenced in function '), | |
108 True), | |
109 (re.compile(r' : error LNK2001: unresolved external symbol (.*)$'), | |
110 True), | |
111 ] | |
112 for line in output.splitlines(): | |
113 line = line.strip() | |
114 for i, (regex, strip_leading_underscore) in enumerate(unresolved_regexes): | |
115 mo = regex.search(line) | |
116 if mo: | |
117 if strip_leading_underscore: | |
118 result.add(mo.group(1)[1:]) | |
119 else: | |
120 result.add(mo.group(1)) | |
121 break | |
122 | |
123 mo = re.search(r'fatal error LNK1120: (\d+) unresolved externals', output) | |
124 # Make sure we have the same number that the linker thinks we have. | |
125 assert mo or not result | |
126 if len(result) != int(mo.group(1)): | |
127 print output | |
128 print 'Expecting %d, got %d' % (int(mo.group(1)), len(result)) | |
129 assert len(result) == int(mo.group(1)) | |
130 return sorted(result) | |
131 | |
132 | |
133 def AsCommandLineArgs(list): | |
134 """Intended for output to a response file. Quotes all arguments.""" | |
M-A Ruel
2013/05/13 21:55:59
What about an arg that is already quoted? If you d
scottmg
2013/05/13 23:00:15
They shouldn't be quoted because they've come from
| |
135 return '\n'.join('"' + x + '"' for x in list) | |
136 | |
137 | |
138 def RunLinker(flags, index, inputs, phase): | |
139 """Invoke the linker and return the stdout, returncode and target name.""" | |
140 rspfile = 'part%d_%s.rsp' % (index, phase) | |
141 import os | |
142 with open(rspfile, 'w') as f: | |
M-A Ruel
2013/05/13 21:55:59
You may want to force CRLF EOL if you plan to have
scottmg
2013/05/13 23:00:15
Not sure if it'll work on cygwin (ctypes?). This i
M-A Ruel
2013/05/14 00:39:09
Then you should make it look like you are trying t
| |
143 print >>f, AsCommandLineArgs(inputs) | |
144 print >>f, AsCommandLineArgs(flags) | |
145 output_name = 'chrome%d.dll' % index | |
146 print >>f, '/ENTRY:ChromeEmptyEntry@12' | |
147 print >>f, '/OUT:' + output_name | |
148 # Log('[[[\n' + open(rspfile).read() + '\n]]]') | |
149 link_exe = GetOriginalLinkerPath() | |
150 popen = subprocess.Popen( | |
151 [link_exe, '@' + rspfile], stdout=subprocess.PIPE, shell=True) | |
M-A Ruel
2013/05/13 21:55:59
shell=True is really needed?
scottmg
2013/05/13 23:00:15
Done.
| |
152 stdout, _ = popen.communicate() | |
153 return stdout, popen.returncode, output_name | |
154 | |
155 | |
156 def GenerateDefFiles(unresolved_by_part): | |
157 """Given a list of unresolved externals, generate a .def file that will | |
158 cause all those symbols to be exported.""" | |
159 deffiles = [] | |
160 Log('generating .def files') | |
161 for i, part in enumerate(unresolved_by_part): | |
162 deffile = 'part%d.def' % i | |
163 with open(deffile, 'w') as f: | |
164 print >>f, 'LIBRARY chrome%d.dll' % i | |
165 print >>f, 'EXPORTS' | |
166 for j, part in enumerate(unresolved_by_part): | |
167 if i == j: | |
168 continue | |
169 print >>f, '\n'.join(' ' + export for export in part) | |
170 deffiles.append(deffile) | |
171 return deffiles | |
172 | |
173 | |
174 def BuildImportLibs(flags, inputs_by_part, deffiles): | |
175 """Run the linker to generate an import library.""" | |
176 import_libs = [] | |
177 Log('building import libs') | |
178 for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)): | |
179 libfile = 'part%d.lib' % i | |
180 flags_with_implib_and_deffile = flags + ['/IMPLIB:%s' % libfile, | |
181 '/DEF:%s' % deffile] | |
182 RunLinker(flags_with_implib_and_deffile, i, inputs, 'implib') | |
183 import_libs.append(libfile) | |
184 return import_libs | |
185 | |
186 | |
187 def AttemptLink(flags, inputs_by_part, unresolved_by_part, deffiles, | |
188 import_libs): | |
189 """Try to run the linker for all parts using the current round of | |
190 generated import libs and .def files. If the link fails, update the | |
191 unresolved externals list per part.""" | |
192 dlls = [] | |
193 all_succeeded = True | |
194 new_externals = [] | |
195 Log('unresolveds now: %r' % [len(part) for part in unresolved_by_part]) | |
196 for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)): | |
197 Log('running link, part %d' % i) | |
198 others_implibs = import_libs[:] | |
199 others_implibs.pop(i) | |
200 inputs_with_implib = inputs + filter(lambda x: x, others_implibs) | |
201 if deffile: | |
202 flags = flags + ['/DEF:%s' % deffile, '/LTCG'] | |
203 stdout, rc, output = RunLinker(flags, i, inputs_with_implib, 'final') | |
204 if rc != 0: | |
205 all_succeeded = False | |
206 new_externals.append(ParseOutExternals(stdout)) | |
207 else: | |
208 new_externals.append([]) | |
209 dlls.append(output) | |
210 combined_externals = [sorted(set(prev) | set(new)) | |
211 for prev, new in zip(unresolved_by_part, new_externals)] | |
212 return all_succeeded, dlls, combined_externals | |
213 | |
214 | |
215 def main(): | |
216 flags, inputs = GetFlagsAndInputs(sys.argv[1:]) | |
217 with open('../../build/split_link_partition.json') as partition: | |
218 description = eval(partition.read()) | |
219 inputs_by_part = [] | |
220 description_parts = description['parts'] | |
221 # We currently assume that if a symbols isn't in dll 0, then it's in dll 1 | |
222 # when generating def files. Otherwise, we'd need to do more complex things | |
223 # to figure out where each symbol actually is to assign it to the correct | |
224 # .def file. | |
225 num_parts = len(description_parts) | |
226 assert num_parts == 2, "Can't handle > 2 dlls currently" | |
227 description_parts.reverse() | |
228 inputs_by_part = [[] for x in range(num_parts)] | |
229 for input in inputs: | |
230 i = PartFor(input, description_parts, description['all']) | |
231 if i == -1: | |
232 for part in inputs_by_part: | |
233 part.append(input) | |
234 else: | |
235 inputs_by_part[i].append(input) | |
236 inputs_by_part.reverse() | |
237 | |
238 unresolved_by_part = [[] for x in range(num_parts)] | |
239 import_libs = [None] * num_parts | |
240 deffiles = [None] * num_parts | |
241 | |
242 for i in range(5): | |
243 Log('--- starting pass %d' % i) | |
244 ok, dlls, unresolved_by_part = AttemptLink( | |
M-A Ruel
2013/05/13 21:55:59
Could it be possible to make it deterministic?
scottmg
2013/05/13 23:00:15
Not that I could figure out. The linker seems to t
| |
245 flags, inputs_by_part, unresolved_by_part, deffiles, import_libs) | |
246 if ok: | |
247 break | |
248 deffiles = GenerateDefFiles(unresolved_by_part) | |
249 import_libs = BuildImportLibs(flags, inputs_by_part, deffiles) | |
250 else: | |
251 raise SystemExit('Failed to link.') | |
252 Log('built %r' % dlls) | |
253 | |
M-A Ruel
2013/05/13 21:55:59
return 0
scottmg
2013/05/13 23:00:15
Done.
| |
254 | |
255 if __name__ == '__main__': | |
256 sys.exit(main()) | |
OLD | NEW |