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

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 import _winreg
11 import ctypes
12 import os
13 import re
14 import subprocess
15 import sys
16
17
18 def Log(message):
19 print 'split_link:', message
20
21
22 def GetFlagsAndInputs(argv):
23 """Parses the command line intended for link.exe and return the flags and
24 input files."""
25 rsp_expanded = []
26 for arg in argv:
27 if arg[0] == '@':
28 with open(arg[1:]) as rsp:
29 rsp_expanded.extend(rsp.read().splitlines())
30 else:
31 rsp_expanded.append(arg)
32
33 # Use CommandLineToArgvW so we match link.exe parsing.
34 try:
35 size = ctypes.c_int()
36 ptr = ctypes.windll.shell32.CommandLineToArgvW(
37 ctypes.create_unicode_buffer(' '.join(rsp_expanded)),
38 ctypes.byref(size))
39 ref = ctypes.c_wchar_p * size.value
40 raw = ref.from_address(ptr)
41 args = [arg for arg in raw]
42 finally:
43 ctypes.windll.kernel32.LocalFree(ptr)
44
45 inputs = []
46 flags = []
47 for arg in args:
48 lower_arg = arg.lower()
49 # We'll be replacing this ourselves.
50 if lower_arg.startswith('/out:'):
51 continue
52 if (not lower_arg.startswith('/') and
53 lower_arg.endswith(('.obj', '.lib', '.res'))):
54 inputs.append(arg)
55 else:
56 flags.append(arg)
57
58 return flags, inputs
59
60
61 def GetOriginalLinkerPath():
62 try:
63 val = _winreg.QueryValue(_winreg.HKEY_CURRENT_USER,
64 'Software\\Chromium\\split_link_installed')
65 if os.path.exists(val):
66 return val
67 except WindowsError:
68 pass
69
70 raise SystemExit("Couldn't read linker location from registry")
71
72
73 def PartFor(input_file, description_parts, description_all):
74 """Determine which part a given link input should be put into (or all)."""
M-A Ruel 2013/05/14 19:38:08 Determines and friend.
scottmg 2013/05/14 19:45:51 Done.
75 # Check if it should go in all parts.
76 input_file = input_file.lower()
77 if any(re.search(spec, input_file) for spec in description_all):
78 return -1
79 # Or pick which particular one it belongs in.
80 for i, spec_list in enumerate(description_parts):
81 if any(re.search(spec, input_file) for spec in spec_list):
82 return i
83 raise ValueError("couldn't find location for %s" % input_file)
84
85
86 def ParseOutExternals(output):
87 """Given the stdout of link.exe, parse the errors messages to retrieve all
88 symbols that are unresolved."""
89 result = set()
90 # Styles of messages for unresolved externals, and a boolean to indicate
91 # whether the error message emits the symbols with or without a leading
92 # underscore.
93 unresolved_regexes = [
94 (re.compile(r' : error LNK2019: unresolved external symbol ".*" \((.*)\)'
95 r' referenced in function'),
96 False),
97 (re.compile(r' : error LNK2001: unresolved external symbol ".*" \((.*)\)$'),
98 False),
99 (re.compile(r' : error LNK2019: unresolved external symbol (.*)'
100 r' referenced in function '),
101 True),
102 (re.compile(r' : error LNK2001: unresolved external symbol (.*)$'),
103 True),
104 ]
105 for line in output.splitlines():
106 line = line.strip()
107 for regex, strip_leading_underscore in unresolved_regexes:
108 mo = regex.search(line)
109 if mo:
110 if strip_leading_underscore:
111 result.add(mo.group(1)[1:])
112 else:
113 result.add(mo.group(1))
114 break
115
116 mo = re.search(r'fatal error LNK1120: (\d+) unresolved externals', output)
117 # Make sure we have the same number that the linker thinks we have.
118 assert mo or not result
119 if len(result) != int(mo.group(1)):
120 print output
121 print 'Expecting %d, got %d' % (int(mo.group(1)), len(result))
122 assert len(result) == int(mo.group(1))
123 return sorted(result)
124
125
126 def AsCommandLineArgs(items):
127 """Intended for output to a response file. Quotes all arguments."""
128 return '\n'.join('"' + x + '"' for x in items)
129
130
131 def OutputNameForIndex(index):
132 """Gets the final output DLL name, given a zero-based index."""
133 if index == 0:
134 return "chrome.dll"
135 else:
136 return 'chrome%d.dll' % index
137
138
139 def RunLinker(flags, index, inputs, phase):
140 """Invoke the linker and return the stdout, returncode and target name."""
141 rspfile = 'part%d_%s.rsp' % (index, phase)
142 with open(rspfile, 'w') as f:
143 print >> f, AsCommandLineArgs(inputs)
144 print >> f, AsCommandLineArgs(flags)
145 output_name = OutputNameForIndex(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([link_exe, '@' + rspfile], stdout=subprocess.PIPE)
151 stdout, _ = popen.communicate()
152 return stdout, popen.returncode, output_name
153
154
155 def GenerateDefFiles(unresolved_by_part):
156 """Given a list of unresolved externals, generate a .def file that will
157 cause all those symbols to be exported."""
158 deffiles = []
159 Log('generating .def files')
160 for i, part in enumerate(unresolved_by_part):
161 deffile = 'part%d.def' % i
162 with open(deffile, 'w') as f:
163 print >> f, 'LIBRARY %s' % OutputNameForIndex(i)
164 print >> f, 'EXPORTS'
165 for j, part in enumerate(unresolved_by_part):
166 if i == j:
167 continue
168 print >> f, '\n'.join(' ' + export for export in part)
169 deffiles.append(deffile)
170 return deffiles
171
172
173 def BuildImportLibs(flags, inputs_by_part, deffiles):
174 """Run the linker to generate an import library."""
175 import_libs = []
176 Log('building import libs')
177 for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)):
178 libfile = 'part%d.lib' % i
179 flags_with_implib_and_deffile = flags + ['/IMPLIB:%s' % libfile,
180 '/DEF:%s' % deffile]
181 RunLinker(flags_with_implib_and_deffile, i, inputs, 'implib')
182 import_libs.append(libfile)
183 return import_libs
184
185
186 def AttemptLink(flags, inputs_by_part, unresolved_by_part, deffiles,
187 import_libs):
188 """Try to run the linker for all parts using the current round of
189 generated import libs and .def files. If the link fails, update the
190 unresolved externals list per part."""
191 dlls = []
192 all_succeeded = True
193 new_externals = []
194 Log('unresolveds now: %r' % [len(part) for part in unresolved_by_part])
195 for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)):
196 Log('running link, part %d' % i)
197 others_implibs = import_libs[:]
198 others_implibs.pop(i)
199 inputs_with_implib = inputs + filter(lambda x: x, others_implibs)
200 if deffile:
201 flags = flags + ['/DEF:%s' % deffile, '/LTCG']
202 stdout, rc, output = RunLinker(flags, i, inputs_with_implib, 'final')
203 if rc != 0:
204 all_succeeded = False
205 new_externals.append(ParseOutExternals(stdout))
206 else:
207 new_externals.append([])
208 dlls.append(output)
209 combined_externals = [sorted(set(prev) | set(new))
210 for prev, new in zip(unresolved_by_part, new_externals)]
211 return all_succeeded, dlls, combined_externals
212
213
214 def main():
215 flags, inputs = GetFlagsAndInputs(sys.argv[1:])
216 with open('../../build/split_link_partition.py') as partition:
M-A Ruel 2013/05/14 19:38:08 This put assumptions on the cwd. Would you want to
scottmg 2013/05/14 19:45:51 Done.
217 description = eval(partition.read())
218 inputs_by_part = []
219 description_parts = description['parts']
220 # We currently assume that if a symbols isn't in dll 0, then it's in dll 1
221 # when generating def files. Otherwise, we'd need to do more complex things
222 # to figure out where each symbol actually is to assign it to the correct
223 # .def file.
224 num_parts = len(description_parts)
225 assert num_parts == 2, "Can't handle > 2 dlls currently"
226 description_parts.reverse()
227 inputs_by_part = [[] for _ in range(num_parts)]
228 for input_file in inputs:
229 i = PartFor(input_file, description_parts, description['all'])
230 if i == -1:
231 for part in inputs_by_part:
232 part.append(input_file)
233 else:
234 inputs_by_part[i].append(input_file)
235 inputs_by_part.reverse()
236
237 unresolved_by_part = [[] for _ in range(num_parts)]
238 import_libs = [None] * num_parts
239 deffiles = [None] * num_parts
240
241 for i in range(5):
242 Log('--- starting pass %d' % i)
243 ok, dlls, unresolved_by_part = AttemptLink(
244 flags, inputs_by_part, unresolved_by_part, deffiles, import_libs)
245 if ok:
246 break
247 deffiles = GenerateDefFiles(unresolved_by_part)
248 import_libs = BuildImportLibs(flags, inputs_by_part, deffiles)
249 else:
250 return 1
251 Log('built %r' % dlls)
252
253 return 0
254
255
256 if __name__ == '__main__':
257 sys.exit(main())
OLDNEW
« tools/win/split_link/install_split_link.py ('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