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 for i in range(len(args)): 59 if args[i] != '--developer_dir': 60 continue 61 os.environ['DEVELOPER_DIR'] = args[i + 1] 62 del args[i:i + 2] 63 break 64 65 # Collect arguments to the linker driver (this script) and remove them from 66 # the arguments being passed to the compiler driver. 67 linker_driver_actions = {} 68 compiler_driver_args = [] 69 for arg in args[1:]: 70 if arg.startswith(_LINKER_DRIVER_ARG_PREFIX): 71 # Convert driver actions into a map of name => lambda to invoke. 72 driver_action = process_linker_driver_arg(arg) 73 assert driver_action[0] not in linker_driver_actions 74 linker_driver_actions[driver_action[0]] = driver_action[1] 75 else: 76 compiler_driver_args.append(arg) 77 78 linker_driver_outputs = [_find_linker_output(compiler_driver_args)] 79 80 try: 81 # Run the linker by invoking the compiler driver. 82 subprocess.check_call(compiler_driver_args) 83 84 # Run the linker driver actions, in the order specified by the actions list. 85 for action in _LINKER_DRIVER_ACTIONS: 86 name = action[0] 87 if name in linker_driver_actions: 88 linker_driver_outputs += linker_driver_actions[name](args) 89 except: 90 # If a linker driver action failed, remove all the outputs to make the 91 # build step atomic. 92 map(_remove_path, linker_driver_outputs) 93 94 # Re-report the original failure. 95 raise 96 97 98def process_linker_driver_arg(arg): 99 """Processes a linker driver argument and returns a tuple containing the 100 name and unary lambda to invoke for that linker driver action. 101 102 Args: 103 arg: string, The linker driver argument. 104 105 Returns: 106 A 2-tuple: 107 0: The driver action name, as in _LINKER_DRIVER_ACTIONS. 108 1: An 1-ary lambda that takes the full list of arguments passed to 109 main(). The lambda should call the linker driver action that 110 corresponds to the argument and return a list of outputs from the 111 action. 112 """ 113 if not arg.startswith(_LINKER_DRIVER_ARG_PREFIX): 114 raise ValueError('%s is not a linker driver argument' % (arg,)) 115 116 sub_arg = arg[len(_LINKER_DRIVER_ARG_PREFIX):] 117 118 for driver_action in _LINKER_DRIVER_ACTIONS: 119 (name, action) = driver_action 120 if sub_arg.startswith(name): 121 return (name, 122 lambda full_args: action(sub_arg[len(name):], full_args)) 123 124 raise ValueError('Unknown linker driver argument: %s' % (arg,)) 125 126 127def run_dsym_util(dsym_path_prefix, full_args): 128 """Linker driver action for -Wcrl,dsym,<dsym-path-prefix>. Invokes dsymutil 129 on the linker's output and produces a dsym file at |dsym_file| path. 130 131 Args: 132 dsym_path_prefix: string, The path at which the dsymutil output should be 133 located. 134 full_args: list of string, Full argument list for the linker driver. 135 136 Returns: 137 list of string, Build step outputs. 138 """ 139 if not len(dsym_path_prefix): 140 raise ValueError('Unspecified dSYM output file') 141 142 linker_out = _find_linker_output(full_args) 143 base = os.path.basename(linker_out) 144 dsym_out = os.path.join(dsym_path_prefix, base + '.dSYM') 145 146 # Remove old dSYMs before invoking dsymutil. 147 _remove_path(dsym_out) 148 subprocess.check_call(['xcrun', 'dsymutil', '-o', dsym_out, linker_out]) 149 return [dsym_out] 150 151 152def run_save_unstripped(unstripped_path_prefix, full_args): 153 """Linker driver action for -Wcrl,unstripped,<unstripped_path_prefix>. Copies 154 the linker output to |unstripped_path_prefix| before stripping. 155 156 Args: 157 unstripped_path_prefix: string, The path at which the unstripped output 158 should be located. 159 full_args: list of string, Full argument list for the linker driver. 160 161 Returns: 162 list of string, Build step outputs. 163 """ 164 if not len(unstripped_path_prefix): 165 raise ValueError('Unspecified unstripped output file') 166 167 linker_out = _find_linker_output(full_args) 168 base = os.path.basename(linker_out) 169 unstripped_out = os.path.join(unstripped_path_prefix, base + '.unstripped') 170 171 shutil.copyfile(linker_out, unstripped_out) 172 return [unstripped_out] 173 174 175def run_strip(strip_args_string, full_args): 176 """Linker driver action for -Wcrl,strip,<strip_arguments>. 177 178 Args: 179 strip_args_string: string, Comma-separated arguments for `strip`. 180 full_args: list of string, Full arguments for the linker driver. 181 182 Returns: 183 list of string, Build step outputs. 184 """ 185 strip_command = ['xcrun', 'strip'] 186 if len(strip_args_string) > 0: 187 strip_command += strip_args_string.split(',') 188 strip_command.append(_find_linker_output(full_args)) 189 subprocess.check_call(strip_command) 190 return [] 191 192 193def _find_linker_output(full_args): 194 """Finds the output of the linker by looking for the output flag in its 195 argument list. As this is a required linker argument, raises an error if it 196 cannot be found. 197 """ 198 # The linker_driver.py script may be used to wrap either the compiler linker 199 # (uses -o to configure the output) or lipo (uses -output to configure the 200 # output). Since wrapping the compiler linker is the most likely possibility 201 # use try/except and fallback to checking for -output if -o is not found. 202 try: 203 output_flag_index = full_args.index('-o') 204 except ValueError: 205 output_flag_index = full_args.index('-output') 206 return full_args[output_flag_index + 1] 207 208 209def _remove_path(path): 210 """Removes the file or directory at |path| if it exists.""" 211 if os.path.exists(path): 212 if os.path.isdir(path): 213 shutil.rmtree(path) 214 else: 215 os.unlink(path) 216 217 218_LINKER_DRIVER_ARG_PREFIX = '-Wcrl,' 219 220"""List of linker driver actions. The sort order of this list affects the 221order in which the actions are invoked. The first item in the tuple is the 222argument's -Wcrl,<sub_argument> and the second is the function to invoke. 223""" 224_LINKER_DRIVER_ACTIONS = [ 225 ('dsym,', run_dsym_util), 226 ('unstripped,', run_save_unstripped), 227 ('strip,', run_strip), 228] 229 230if __name__ == '__main__': 231 main(sys.argv) 232 sys.exit(0) 233