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