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