1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 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 7import os 8import os.path 9import shutil 10import subprocess 11import sys 12 13""" 14The linker_driver.py is responsible for forwarding a linker invocation to 15the compiler driver, while processing special arguments itself. 16 17Usage: linker_driver.py clang++ main.o -L. -llib -o prog -Wcrl,dsym,out 18 19On Mac, the logical step of linking is handled by three discrete tools to 20perform the image link, debug info link, and strip. The linker_driver.py 21combines these three steps into a single tool. 22 23The command passed to the linker_driver.py should be the compiler driver 24invocation for the linker. It is first invoked unaltered (except for the 25removal of the special driver arguments, described below). Then the driver 26performs additional actions, based on these arguments: 27 28 -Wcrl,dsym,<dsym_path_prefix> 29 After invoking the linker, this will run `dsymutil` on the linker's 30 output, producing a dSYM bundle, stored at dsym_path_prefix. As an 31 example, if the linker driver were invoked with: 32 "... -o out/gn/obj/foo/libbar.dylib ... -Wcrl,dsym,out/gn ..." 33 The resulting dSYM would be out/gn/libbar.dylib.dSYM/. 34 35 -Wcrl,unstripped,<unstripped_path_prefix> 36 After invoking the linker, and before strip, this will save a copy of 37 the unstripped linker output in the directory unstripped_path_prefix. 38 39 -Wcrl,strip,<strip_arguments> 40 After invoking the linker, and optionally dsymutil, this will run 41 the strip command on the linker's output. strip_arguments are 42 comma-separated arguments to be passed to the strip command. 43""" 44 45 46def main(args): 47 """main function for the linker driver. Separates out the arguments for 48 the main compiler driver and the linker driver, then invokes all the 49 required tools. 50 51 Args: 52 args: list of string, Arguments to the script. 53 """ 54 55 if len(args) < 2: 56 raise RuntimeError("Usage: linker_driver.py [linker-invocation]") 57 58 i = 0 59 while i < len(args): 60 arg = args[i] 61 if arg == '--developer': 62 if i + 1 < len(args) and not args[i + 1].startswith('--'): 63 os.environ['DEVELOPER_DIR'] = args[i + 1] 64 del args[i:i + 2] 65 else: 66 i += 1 67 else: 68 i += 1 69 70 # Collect arguments to the linker driver (this script) and remove them from 71 # the arguments being passed to the compiler driver. 72 linker_driver_actions = {} 73 compiler_driver_args = [] 74 for arg in args[1:]: 75 if arg.startswith(_LINKER_DRIVER_ARG_PREFIX): 76 # Convert driver actions into a map of name => lambda to invoke. 77 driver_action = process_linker_driver_arg(arg) 78 assert driver_action[0] not in linker_driver_actions 79 linker_driver_actions[driver_action[0]] = driver_action[1] 80 else: 81 compiler_driver_args.append(arg) 82 83 linker_driver_outputs = [_find_linker_output(compiler_driver_args)] 84 85 try: 86 # Run the linker by invoking the compiler driver. 87 subprocess.check_call(compiler_driver_args) 88 89 # Run the linker driver actions, in the order specified by the actions list. 90 for action in _LINKER_DRIVER_ACTIONS: 91 name = action[0] 92 if name in linker_driver_actions: 93 linker_driver_outputs += linker_driver_actions[name](args) 94 except: 95 # If a linker driver action failed, remove all the outputs to make the 96 # build step atomic. 97 map(_remove_path, linker_driver_outputs) 98 99 # Re-report the original failure. 100 raise 101 102 103def process_linker_driver_arg(arg): 104 """Processes a linker driver argument and returns a tuple containing the 105 name and unary lambda to invoke for that linker driver action. 106 107 Args: 108 arg: string, The linker driver argument. 109 110 Returns: 111 A 2-tuple: 112 0: The driver action name, as in _LINKER_DRIVER_ACTIONS. 113 1: An 1-ary lambda that takes the full list of arguments passed to 114 main(). The lambda should call the linker driver action that 115 corresponds to the argument and return a list of outputs from the 116 action. 117 """ 118 if not arg.startswith(_LINKER_DRIVER_ARG_PREFIX): 119 raise ValueError('%s is not a linker driver argument' % (arg,)) 120 121 sub_arg = arg[len(_LINKER_DRIVER_ARG_PREFIX):] 122 123 for driver_action in _LINKER_DRIVER_ACTIONS: 124 (name, action) = driver_action 125 if sub_arg.startswith(name): 126 return (name, 127 lambda full_args: action(sub_arg[len(name):], full_args)) 128 129 raise ValueError('Unknown linker driver argument: %s' % (arg,)) 130 131 132def run_dsym_util(dsym_path_prefix, full_args): 133 """Linker driver action for -Wcrl,dsym,<dsym-path-prefix>. Invokes dsymutil 134 on the linker's output and produces a dsym file at |dsym_file| path. 135 136 Args: 137 dsym_path_prefix: string, The path at which the dsymutil output should be 138 located. 139 full_args: list of string, Full argument list for the linker driver. 140 141 Returns: 142 list of string, Build step outputs. 143 """ 144 if not len(dsym_path_prefix): 145 raise ValueError('Unspecified dSYM output file') 146 147 linker_out = _find_linker_output(full_args) 148 base = os.path.basename(linker_out) 149 dsym_out = os.path.join(dsym_path_prefix, base + '.dSYM') 150 151 # Remove old dSYMs before invoking dsymutil. 152 _remove_path(dsym_out) 153 subprocess.check_call(['xcrun', 'dsymutil', '-o', dsym_out, linker_out]) 154 return [dsym_out] 155 156 157def run_save_unstripped(unstripped_path_prefix, full_args): 158 """Linker driver action for -Wcrl,unstripped,<unstripped_path_prefix>. Copies 159 the linker output to |unstripped_path_prefix| before stripping. 160 161 Args: 162 unstripped_path_prefix: string, The path at which the unstripped output 163 should be located. 164 full_args: list of string, Full argument list for the linker driver. 165 166 Returns: 167 list of string, Build step outputs. 168 """ 169 if not len(unstripped_path_prefix): 170 raise ValueError('Unspecified unstripped output file') 171 172 linker_out = _find_linker_output(full_args) 173 base = os.path.basename(linker_out) 174 unstripped_out = os.path.join(unstripped_path_prefix, base + '.unstripped') 175 176 shutil.copyfile(linker_out, unstripped_out) 177 return [unstripped_out] 178 179 180def run_strip(strip_args_string, full_args): 181 """Linker driver action for -Wcrl,strip,<strip_arguments>. 182 183 Args: 184 strip_args_string: string, Comma-separated arguments for `strip`. 185 full_args: list of string, Full arguments for the linker driver. 186 187 Returns: 188 list of string, Build step outputs. 189 """ 190 strip_command = ['xcrun', 'strip'] 191 if len(strip_args_string) > 0: 192 strip_command += strip_args_string.split(',') 193 strip_command.append(_find_linker_output(full_args)) 194 subprocess.check_call(strip_command) 195 return [] 196 197 198def _find_linker_output(full_args): 199 """Finds the output of the linker by looking for the output flag in its 200 argument list. As this is a required linker argument, raises an error if it 201 cannot be found. 202 """ 203 # The linker_driver.py script may be used to wrap either the compiler linker 204 # (uses -o to configure the output) or lipo (uses -output to configure the 205 # output). Since wrapping the compiler linker is the most likely possibility 206 # use try/except and fallback to checking for -output if -o is not found. 207 try: 208 output_flag_index = full_args.index('-o') 209 except ValueError: 210 output_flag_index = full_args.index('-output') 211 return full_args[output_flag_index + 1] 212 213 214def _remove_path(path): 215 """Removes the file or directory at |path| if it exists.""" 216 if os.path.exists(path): 217 if os.path.isdir(path): 218 shutil.rmtree(path) 219 else: 220 os.unlink(path) 221 222 223_LINKER_DRIVER_ARG_PREFIX = '-Wcrl,' 224 225"""List of linker driver actions. The sort order of this list affects the 226order in which the actions are invoked. The first item in the tuple is the 227argument's -Wcrl,<sub_argument> and the second is the function to invoke. 228""" 229_LINKER_DRIVER_ACTIONS = [ 230 ('dsym,', run_dsym_util), 231 ('unstripped,', run_save_unstripped), 232 ('strip,', run_strip), 233] 234 235if __name__ == '__main__': 236 main(sys.argv) 237 sys.exit(0) 238