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

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