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