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

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: fixes 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 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())
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