OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 |
| 3 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. |
| 6 |
| 7 import os |
| 8 import os.path |
| 9 import shutil |
| 10 import subprocess |
| 11 import sys |
| 12 |
| 13 # The linker_driver.py is responsible for forwarding a linker invocation to |
| 14 # the compiler driver, while processing special arguments itself. |
| 15 # |
| 16 # Usage: linker_driver.py clang++ main.o -L. -llib -o prog -Wcrl,dsym,out |
| 17 # |
| 18 # On Mac, the logical step of linking is handled by three discrete tools to |
| 19 # perform the image link, debug info link, and strip. The linker_driver.py |
| 20 # combines these three steps into a single tool. |
| 21 # |
| 22 # The command passed to the linker_driver.py should be the compiler driver |
| 23 # invocation for the linker. It is first invoked unaltered (except for the |
| 24 # removal of the special driver arguments, described below). Then the driver |
| 25 # performs additional actions, based on these arguments: |
| 26 # |
| 27 # -Wcrl,dsym,<dsym_path_prefix> |
| 28 # After invoking the linker, this will run `dsymutil` on the linker's |
| 29 # output, producing a dSYM bundle, stored at dsym_path_prefix. As an |
| 30 # example, if the linker driver were invoked with: |
| 31 # "... -o out/gn/obj/foo/libbar.dylib ... -Wcrl,dsym,out/gn ..." |
| 32 # The resulting dSYM would be out/gn/libbar.dylib.dSYM/. |
| 33 # |
| 34 # -Wcrl,strip,<strip_arguments> |
| 35 # After invoking the linker, and optionally dsymutil, this will run |
| 36 # the strip command on the linker's output. strip_arguments are |
| 37 # comma-separated arguments to be passed to the strip command. |
| 38 |
| 39 def Main(args): |
| 40 """Main function for the linker driver. Separates out the arguments for |
| 41 the main compiler driver and the linker driver, then invokes all the |
| 42 required tools. |
| 43 |
| 44 Args: |
| 45 args: list of string, Arguments to the script. |
| 46 """ |
| 47 |
| 48 if len(args) < 2: |
| 49 raise RuntimeError("Usage: linker_driver.py [linker-invocation]") |
| 50 |
| 51 # Collect arguments to the linker driver (this script) and remove them from |
| 52 # the arguments being passed to the compiler driver. |
| 53 linker_driver_actions = {} |
| 54 compiler_driver_args = [] |
| 55 for arg in args[1:]: |
| 56 if arg.startswith(_LINKER_DRIVER_ARG_PREFIX): |
| 57 # Convert driver actions into a map of name => lambda to invoke. |
| 58 driver_action = ProcessLinkerDriverArg(arg) |
| 59 assert driver_action[0] not in linker_driver_actions |
| 60 linker_driver_actions[driver_action[0]] = driver_action[1] |
| 61 else: |
| 62 compiler_driver_args.append(arg) |
| 63 |
| 64 linker_driver_outputs = [_FindLinkerOutput(compiler_driver_args)] |
| 65 |
| 66 try: |
| 67 # Run the linker by invoking the compiler driver. |
| 68 subprocess.check_call(compiler_driver_args) |
| 69 |
| 70 # Run the linker driver actions, in the order specified by the actions list. |
| 71 for action in _LINKER_DRIVER_ACTIONS: |
| 72 name = action[0] |
| 73 if name in linker_driver_actions: |
| 74 linker_driver_outputs += linker_driver_actions[name](args) |
| 75 except: |
| 76 # If a linker driver action failed, remove all the outputs to make the |
| 77 # build step atomic. |
| 78 map(_RemovePath, linker_driver_outputs) |
| 79 |
| 80 # Re-report the original failure. |
| 81 raise |
| 82 |
| 83 |
| 84 def ProcessLinkerDriverArg(arg): |
| 85 """Processes a linker driver argument and returns a tuple containing the |
| 86 name and unary lambda to invoke for that linker driver action. |
| 87 |
| 88 Args: |
| 89 arg: string, The linker driver argument. |
| 90 |
| 91 Returns: |
| 92 A 2-tuple: |
| 93 0: The driver action name, as in _LINKER_DRIVER_ACTIONS. |
| 94 1: An 1-ary lambda that takes the full list of arguments passed to |
| 95 Main(). The lambda should call the linker driver action that |
| 96 corresponds to the argument and return a list of outputs from the |
| 97 action. |
| 98 """ |
| 99 if not arg.startswith(_LINKER_DRIVER_ARG_PREFIX): |
| 100 raise ValueError('%s is not a linker driver argument' % (arg,)) |
| 101 |
| 102 sub_arg = arg[len(_LINKER_DRIVER_ARG_PREFIX):] |
| 103 |
| 104 for driver_action in _LINKER_DRIVER_ACTIONS: |
| 105 (name, action) = driver_action |
| 106 if sub_arg.startswith(name): |
| 107 return (name, |
| 108 lambda full_args: action(sub_arg[len(name):], full_args)) |
| 109 |
| 110 raise ValueError('Unknown linker driver argument: %s' % (arg,)) |
| 111 |
| 112 |
| 113 def RunDsymUtil(dsym_path_prefix, full_args): |
| 114 """Linker driver action for -Wcrl,dsym,<dsym-path-prefix>. Invokes dsymutil |
| 115 on the linker's output and produces a dsym file at |dsym_file| path. |
| 116 |
| 117 Args: |
| 118 dsym_path_prefix: string, The path at which the dsymutil output should be |
| 119 located. |
| 120 full_args: list of string, Full argument list for the linker driver. |
| 121 |
| 122 Returns: |
| 123 list of string, Build step outputs. |
| 124 """ |
| 125 if not len(dsym_path_prefix): |
| 126 raise ValueError('Unspecified dSYM output file') |
| 127 |
| 128 linker_out = _FindLinkerOutput(full_args) |
| 129 (head, tail) = os.path.split(linker_out) |
| 130 dsym_out = os.path.join(dsym_path_prefix, tail + '.dSYM') |
| 131 |
| 132 # Remove old dSYMs before invoking dsymutil. |
| 133 _RemovePath(dsym_out) |
| 134 subprocess.check_call(['xcrun', 'dsymutil', '-o', dsym_out, linker_out]) |
| 135 return [dsym_out] |
| 136 |
| 137 |
| 138 def RunStrip(strip_args_string, full_args): |
| 139 """Linker driver action for -Wcrl,strip,<strip_arguments>. |
| 140 |
| 141 Args: |
| 142 strip_args_string: string, Comma-separated arguments for `strip`. |
| 143 full_args: list of string, Full arguments for the linker driver. |
| 144 |
| 145 Returns: |
| 146 list of string, Build step outputs. |
| 147 """ |
| 148 strip_command = ['xcrun', 'strip'] |
| 149 if len(strip_args_string) > 0: |
| 150 strip_command += strip_args_string.split(',') |
| 151 strip_command.append(_FindLinkerOutput(full_args)) |
| 152 subprocess.check_call(strip_command) |
| 153 return [] |
| 154 |
| 155 |
| 156 def _FindLinkerOutput(full_args): |
| 157 """Finds the output of the linker by looking for the output flag in its |
| 158 argument list. As this is a required linker argument, raises an error if it |
| 159 cannot be found. |
| 160 """ |
| 161 return full_args[full_args.index('-o') + 1] |
| 162 |
| 163 |
| 164 def _RemovePath(path): |
| 165 """Removes the file or directory at |path| if it exists.""" |
| 166 if os.path.exists(path): |
| 167 if os.path.isdir(path): |
| 168 shutil.rmtree(path) |
| 169 else: |
| 170 os.unlink(path) |
| 171 |
| 172 |
| 173 _LINKER_DRIVER_ARG_PREFIX = '-Wcrl,' |
| 174 |
| 175 """List of linker driver actions. The sort order of this list affects the |
| 176 order in which the actions are invoked. The first item in the tuple is the |
| 177 argument's -Wcrl,<sub_argument> and the second is the function to invoke. |
| 178 """ |
| 179 _LINKER_DRIVER_ACTIONS = [ |
| 180 ('dsym,', RunDsymUtil), |
| 181 ('strip,', RunStrip), |
| 182 ] |
| 183 |
| 184 |
| 185 if __name__ == '__main__': |
| 186 Main(sys.argv) |
| 187 sys.exit(0) |
OLD | NEW |