Chromium Code Reviews| OLD | NEW |
|---|---|
| (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()) | |
| OLD | NEW |