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

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 *
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.append(rsp.read().replace('\r', '').replace('\n', ' '))
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]
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
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()
82 for spec in description_all:
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:
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."""
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 print os.getcwd()
143 with open(rspfile, 'w') as f:
144 print >>f, AsCommandLineArgs(inputs)
145 print >>f, AsCommandLineArgs(flags)
146 output_name = 'chrome%d.dll' % index
147 print >>f, '/ENTRY:ChromeEmptyEntry@12'
148 print >>f, '/OUT:' + output_name
149 # Log('[[[\n' + open(rspfile).read() + '\n]]]')
150 link_exe = GetOriginalLinkerPath()
151 popen = subprocess.Popen(
152 [link_exe, '@' + rspfile], stdout=subprocess.PIPE, shell=True)
153 stdout, _ = popen.communicate()
154 return stdout, popen.returncode, output_name
155
156
157 def GenerateDefFiles(unresolved_by_part):
158 """Given a list of unresolved externals, generate a .def file that will
159 cause all those symbols to be exported."""
160 deffiles = []
161 Log('generating .def files')
162 for i, part in enumerate(unresolved_by_part):
163 deffile = 'part%d.def' % i
164 with open(deffile, 'w') as f:
165 print >>f, 'LIBRARY chrome%d.dll' % i
166 print >>f, 'EXPORTS'
167 for j, part in enumerate(unresolved_by_part):
168 if i == j:
169 continue
170 print >>f, '\n'.join(' ' + export for export in part)
171 deffiles.append(deffile)
172 return deffiles
173
174
175 def BuildImportLibs(flags, inputs_by_part, deffiles):
176 """Run the linker to generate an import library."""
177 import_libs = []
178 Log('building import libs')
179 for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)):
180 libfile = 'part%d.lib' % i
181 flags_with_implib_and_deffile = flags + ['/IMPLIB:%s' % libfile,
182 '/DEF:%s' % deffile]
183 RunLinker(flags_with_implib_and_deffile, i, inputs, 'implib')
184 import_libs.append(libfile)
185 return import_libs
186
187
188 def AttemptLink(flags, inputs_by_part, unresolved_by_part, deffiles,
189 import_libs):
190 """Try to run the linker for all parts using the current round of
191 generated import libs and .def files. If the link fails, update the
192 unresolved externals list per part."""
193 dlls = []
194 all_succeeded = True
195 new_externals = []
196 Log('unresolveds now: %r' % [len(part) for part in unresolved_by_part])
197 for i, (inputs, deffile) in enumerate(zip(inputs_by_part, deffiles)):
198 Log('running link, part %d' % i)
199 others_implibs = import_libs[:]
200 others_implibs.pop(i)
201 inputs_with_implib = inputs + filter(lambda x: x, others_implibs)
202 if deffile:
203 flags = flags + ['/DEF:%s' % deffile, '/LTCG']
204 stdout, rc, output = RunLinker(flags, i, inputs_with_implib, 'final')
205 if rc != 0:
206 all_succeeded = False
207 new_externals.append(ParseOutExternals(stdout))
208 else:
209 new_externals.append([])
210 dlls.append(output)
211 combined_externals = [sorted(set(prev) | set(new))
212 for prev, new in zip(unresolved_by_part, new_externals)]
213 return all_succeeded, dlls, combined_externals
214
215
216 def main():
217 flags, inputs = GetFlagsAndInputs(sys.argv[1:])
218 with open('../../build/split_link_partition.json') as partition:
219 description = eval(partition.read())
220 inputs_by_part = []
221 description_parts = description['parts']
222 # We currently assume that if a symbols isn't in dll 0, then it's in dll 1
223 # when generating def files. Otherwise, we'd need to do more complex things
224 # to figure out where each symbol actually is to assign it to the correct
225 # .def file.
226 num_parts = len(description_parts)
227 assert num_parts == 2, "Can't handle > 2 dlls currently"
228 description_parts.reverse()
229 inputs_by_part = [[] for x in range(num_parts)]
230 for input in inputs:
231 i = PartFor(input, description_parts, description['all'])
232 if i == -1:
233 for part in inputs_by_part:
234 part.append(input)
235 else:
236 inputs_by_part[i].append(input)
237 inputs_by_part.reverse()
238
239 unresolved_by_part = [[] for x in range(num_parts)]
240 import_libs = [None] * num_parts
241 deffiles = [None] * num_parts
242
243 for i in range(5):
244 Log('--- starting pass %d' % i)
245 ok, dlls, unresolved_by_part = AttemptLink(
246 flags, inputs_by_part, unresolved_by_part, deffiles, import_libs)
247 if ok:
248 break
249 deffiles = GenerateDefFiles(unresolved_by_part)
250 import_libs = BuildImportLibs(flags, inputs_by_part, deffiles)
251 else:
252 raise SystemExit('Failed to link.')
253 Log('built %r' % dlls)
254
255
256 if __name__ == '__main__':
257 sys.exit(main())
OLDNEW
« chrome/split_dll_fake_entry.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