Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(66)

Side by Side Diff: tools/win/split_link/split_link.py

Issue 15067010: split_link tool, config, and scripts for windows build (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: . Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 *
M-A Ruel 2013/05/14 00:39:09 Could you enumerate instead? It contains a ton of
scottmg 2013/05/14 03:35:07 Done.
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
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.extend(rsp.read().splitlines())
29 else:
30 rsp_expanded.append(arg)
31
32 # Use CommandLineToArgvW so we match link.exe parsing.
33 try:
34 size = c_int()
35 ptr = windll.shell32.CommandLineToArgvW(
36 create_unicode_buffer(' '.join(rsp_expanded)),
37 byref(size))
38 ref = c_wchar_p * size.value
39 raw = ref.from_address(ptr)
40 args = [arg for arg in raw]
41 finally:
42 windll.kernel32.LocalFree(ptr)
43
44 inputs = []
45 flags = []
46 for arg in args:
47 lower_arg = arg.lower()
48 # We'll be replacing this ourselves.
49 if lower_arg.startswith('/out:'):
50 continue
51 if (not lower_arg.startswith('/') and
52 lower_arg.endswith(('.obj', '.lib', '.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_file, 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_file = input_file.lower()
82 if any(re.search(spec, input_file) for spec in description_all):
83 return -1
84 # Or pick which particular one it belongs in.
85 for i, spec_list in enumerate(description_parts):
86 if any(re.search(spec, input_file) for spec in spec_list):
87 return i
88 raise ValueError("couldn't find location for %s" % input_file)
89
90
91 def ParseOutExternals(output):
92 """Given the stdout of link.exe, parse the errors messages to retrieve all
93 symbols that are unresolved."""
94 result = set()
95 # Styles of messages for unresolved externals, and a boolean to indicate
96 # whether the error message emits the symbols with or without a leading
97 # underscore.
98 unresolved_regexes = [
99 (re.compile(r' : error LNK2019: unresolved external symbol ".*" \((.*)\)'
100 r' referenced in function'),
101 False),
102 (re.compile(r' : error LNK2001: unresolved external symbol ".*" \((.*)\)$'),
103 False),
104 (re.compile(r' : error LNK2019: unresolved external symbol (.*)'
105 r' referenced in function '),
106 True),
107 (re.compile(r' : error LNK2001: unresolved external symbol (.*)$'),
108 True),
109 ]
110 for line in output.splitlines():
111 line = line.strip()
112 for i, (regex, strip_leading_underscore) in enumerate(unresolved_regexes):
113 mo = regex.search(line)
114 if mo:
115 if strip_leading_underscore:
116 result.add(mo.group(1)[1:])
117 else:
118 result.add(mo.group(1))
119 break
120
121 mo = re.search(r'fatal error LNK1120: (\d+) unresolved externals', output)
122 # Make sure we have the same number that the linker thinks we have.
123 assert mo or not result
124 if len(result) != int(mo.group(1)):
125 print output
126 print 'Expecting %d, got %d' % (int(mo.group(1)), len(result))
127 assert len(result) == int(mo.group(1))
128 return sorted(result)
129
130
131 def AsCommandLineArgs(list):
132 """Intended for output to a response file. Quotes all arguments."""
133 return '\n'.join('"' + x + '"' for x in list)
134
135
136 def RunLinker(flags, index, inputs, phase):
137 """Invoke the linker and return the stdout, returncode and target name."""
138 rspfile = 'part%d_%s.rsp' % (index, phase)
139 import os
M-A Ruel 2013/05/14 00:39:09 ?
scottmg 2013/05/14 03:35:07 No idea. Done.
140 with open(rspfile, 'w') as f:
141 print >>f, AsCommandLineArgs(inputs)
142 print >>f, AsCommandLineArgs(flags)
143 output_name = 'chrome%d.dll' % index
144 print >>f, '/ENTRY:ChromeEmptyEntry@12'
145 print >>f, '/OUT:' + output_name
146 # Log('[[[\n' + open(rspfile).read() + '\n]]]')
147 link_exe = GetOriginalLinkerPath()
148 popen = subprocess.Popen([link_exe, '@' + rspfile], stdout=subprocess.PIPE)
149 stdout, _ = popen.communicate()
150 return stdout, popen.returncode, output_name
151
152
153 def GenerateDefFiles(unresolved_by_part):
154 """Given a list of unresolved externals, generate a .def file that will
155 cause all those symbols to be exported."""
156 deffiles = []
157 Log('generating .def files')
158 for i, part in enumerate(unresolved_by_part):
159 deffile = 'part%d.def' % i
160 with open(deffile, 'w') as f:
161 print >>f, 'LIBRARY chrome%d.dll' % i
162 print >>f, 'EXPORTS'
163 for j, part in enumerate(unresolved_by_part):
164 if i == j:
165 continue
166 print >>f, '\n'.join(' ' + export for export in part)
167 deffiles.append(deffile)
168 return deffiles
169
170
171 def BuildImportLibs(flags, inputs_by_part, deffiles):
172 """Run the linker to generate an import library."""
173 import_libs = []
174 Log('building import libs')
175 for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)):
176 libfile = 'part%d.lib' % i
177 flags_with_implib_and_deffile = flags + ['/IMPLIB:%s' % libfile,
178 '/DEF:%s' % deffile]
179 RunLinker(flags_with_implib_and_deffile, i, inputs, 'implib')
180 import_libs.append(libfile)
181 return import_libs
182
183
184 def AttemptLink(flags, inputs_by_part, unresolved_by_part, deffiles,
185 import_libs):
186 """Try to run the linker for all parts using the current round of
187 generated import libs and .def files. If the link fails, update the
188 unresolved externals list per part."""
189 dlls = []
190 all_succeeded = True
191 new_externals = []
192 Log('unresolveds now: %r' % [len(part) for part in unresolved_by_part])
193 for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)):
194 Log('running link, part %d' % i)
195 others_implibs = import_libs[:]
196 others_implibs.pop(i)
197 inputs_with_implib = inputs + filter(lambda x: x, others_implibs)
198 if deffile:
199 flags = flags + ['/DEF:%s' % deffile, '/LTCG']
200 stdout, rc, output = RunLinker(flags, i, inputs_with_implib, 'final')
201 if rc != 0:
202 all_succeeded = False
203 new_externals.append(ParseOutExternals(stdout))
204 else:
205 new_externals.append([])
206 dlls.append(output)
207 combined_externals = [sorted(set(prev) | set(new))
208 for prev, new in zip(unresolved_by_part, new_externals)]
209 return all_succeeded, dlls, combined_externals
210
211
212 def main():
213 flags, inputs = GetFlagsAndInputs(sys.argv[1:])
214 with open('../../build/split_link_partition.json') as partition:
215 description = eval(partition.read())
M-A Ruel 2013/05/14 00:39:09 If you eval() it, it's not json. Please don't make
scottmg 2013/05/14 03:35:07 Done.
216 inputs_by_part = []
217 description_parts = description['parts']
218 # We currently assume that if a symbols isn't in dll 0, then it's in dll 1
219 # when generating def files. Otherwise, we'd need to do more complex things
220 # to figure out where each symbol actually is to assign it to the correct
221 # .def file.
222 num_parts = len(description_parts)
223 assert num_parts == 2, "Can't handle > 2 dlls currently"
224 description_parts.reverse()
225 inputs_by_part = [[] for x in range(num_parts)]
M-A Ruel 2013/05/14 00:39:09 [[] for _ in range(num_parts)] This would have be
scottmg 2013/05/14 03:35:07 Done.
226 for input in inputs:
227 i = PartFor(input, description_parts, description['all'])
228 if i == -1:
229 for part in inputs_by_part:
230 part.append(input)
231 else:
232 inputs_by_part[i].append(input)
233 inputs_by_part.reverse()
234
235 unresolved_by_part = [[] for x in range(num_parts)]
236 import_libs = [None] * num_parts
237 deffiles = [None] * num_parts
238
239 for i in range(5):
240 Log('--- starting pass %d' % i)
241 ok, dlls, unresolved_by_part = AttemptLink(
242 flags, inputs_by_part, unresolved_by_part, deffiles, import_libs)
243 if ok:
244 break
245 deffiles = GenerateDefFiles(unresolved_by_part)
246 import_libs = BuildImportLibs(flags, inputs_by_part, deffiles)
247 else:
248 raise SystemExit('Failed to link.')
M-A Ruel 2013/05/14 00:39:09 ? return 1
scottmg 2013/05/14 03:35:07 Done.
249 Log('built %r' % dlls)
250
251 return 0
252
253
254 if __name__ == '__main__':
255 sys.exit(main())
OLDNEW
« tools/win/split_link/split_link.cc ('K') | « tools/win/split_link/split_link.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698