Index: build/toolchain/mac/linker_driver.py |
diff --git a/build/toolchain/mac/linker_driver.py b/build/toolchain/mac/linker_driver.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..50ba540c34b877ebd9f74e398013cd47ddf190cc |
--- /dev/null |
+++ b/build/toolchain/mac/linker_driver.py |
@@ -0,0 +1,187 @@ |
+#!/usr/bin/env python |
+ |
+# Copyright 2016 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+import os |
+import os.path |
+import shutil |
+import subprocess |
+import sys |
+ |
+# The linker_driver.py is responsible for forwarding a linker invocation to |
+# the compiler driver, while processing special arguments itself. |
+# |
+# Usage: linker_driver.py clang++ main.o -L. -llib -o prog -Wcrl,dsym,out |
+# |
+# On Mac, the logical step of linking is handled by three discrete tools to |
+# perform the image link, debug info link, and strip. The linker_driver.py |
+# combines these three steps into a single tool. |
+# |
+# The command passed to the linker_driver.py should be the compiler driver |
+# invocation for the linker. It is first invoked unaltered (except for the |
+# removal of the special driver arguments, described below). Then the driver |
+# performs additional actions, based on these arguments: |
+# |
+# -Wcrl,dsym,<dsym_path_prefix> |
+# After invoking the linker, this will run `dsymutil` on the linker's |
+# output, producing a dSYM bundle, stored at dsym_path_prefix. As an |
+# example, if the linker driver were invoked with: |
+# "... -o out/gn/obj/foo/libbar.dylib ... -Wcrl,dsym,out/gn ..." |
+# The resulting dSYM would be out/gn/libbar.dylib.dSYM/. |
+# |
+# -Wcrl,strip,<strip_arguments> |
+# After invoking the linker, and optionally dsymutil, this will run |
+# the strip command on the linker's output. strip_arguments are |
+# comma-separated arguments to be passed to the strip command. |
+ |
+def Main(args): |
+ """Main function for the linker driver. Separates out the arguments for |
+ the main compiler driver and the linker driver, then invokes all the |
+ required tools. |
+ |
+ Args: |
+ args: list of string, Arguments to the script. |
+ """ |
+ |
+ if len(args) < 2: |
+ raise RuntimeError("Usage: linker_driver.py [linker-invocation]") |
+ |
+ # Collect arguments to the linker driver (this script) and remove them from |
+ # the arguments being passed to the compiler driver. |
+ linker_driver_actions = {} |
+ compiler_driver_args = [] |
+ for arg in args[1:]: |
+ if arg.startswith(_LINKER_DRIVER_ARG_PREFIX): |
+ # Convert driver actions into a map of name => lambda to invoke. |
+ driver_action = ProcessLinkerDriverArg(arg) |
+ assert driver_action[0] not in linker_driver_actions |
+ linker_driver_actions[driver_action[0]] = driver_action[1] |
+ else: |
+ compiler_driver_args.append(arg) |
+ |
+ linker_driver_outputs = [_FindLinkerOutput(compiler_driver_args)] |
+ |
+ try: |
+ # Run the linker by invoking the compiler driver. |
+ subprocess.check_call(compiler_driver_args) |
+ |
+ # Run the linker driver actions, in the order specified by the actions list. |
+ for action in _LINKER_DRIVER_ACTIONS: |
+ name = action[0] |
+ if name in linker_driver_actions: |
+ linker_driver_outputs += linker_driver_actions[name](args) |
+ except: |
+ # If a linker driver action failed, remove all the outputs to make the |
+ # build step atomic. |
+ map(_RemovePath, linker_driver_outputs) |
+ |
+ # Re-report the original failure. |
+ raise |
+ |
+ |
+def ProcessLinkerDriverArg(arg): |
+ """Processes a linker driver argument and returns a tuple containing the |
+ name and unary lambda to invoke for that linker driver action. |
+ |
+ Args: |
+ arg: string, The linker driver argument. |
+ |
+ Returns: |
+ A 2-tuple: |
+ 0: The driver action name, as in _LINKER_DRIVER_ACTIONS. |
+ 1: An 1-ary lambda that takes the full list of arguments passed to |
+ Main(). The lambda should call the linker driver action that |
+ corresponds to the argument and return a list of outputs from the |
+ action. |
+ """ |
+ if not arg.startswith(_LINKER_DRIVER_ARG_PREFIX): |
+ raise ValueError('%s is not a linker driver argument' % (arg,)) |
+ |
+ sub_arg = arg[len(_LINKER_DRIVER_ARG_PREFIX):] |
+ |
+ for driver_action in _LINKER_DRIVER_ACTIONS: |
+ (name, action) = driver_action |
+ if sub_arg.startswith(name): |
+ return (name, |
+ lambda full_args: action(sub_arg[len(name):], full_args)) |
+ |
+ raise ValueError('Unknown linker driver argument: %s' % (arg,)) |
+ |
+ |
+def RunDsymUtil(dsym_path_prefix, full_args): |
+ """Linker driver action for -Wcrl,dsym,<dsym-path-prefix>. Invokes dsymutil |
+ on the linker's output and produces a dsym file at |dsym_file| path. |
+ |
+ Args: |
+ dsym_path_prefix: string, The path at which the dsymutil output should be |
+ located. |
+ full_args: list of string, Full argument list for the linker driver. |
+ |
+ Returns: |
+ list of string, Build step outputs. |
+ """ |
+ if not len(dsym_path_prefix): |
+ raise ValueError('Unspecified dSYM output file') |
+ |
+ linker_out = _FindLinkerOutput(full_args) |
+ (head, tail) = os.path.split(linker_out) |
+ dsym_out = os.path.join(dsym_path_prefix, tail + '.dSYM') |
+ |
+ # Remove old dSYMs before invoking dsymutil. |
+ _RemovePath(dsym_out) |
+ subprocess.check_call(['xcrun', 'dsymutil', '-o', dsym_out, linker_out]) |
+ return [dsym_out] |
+ |
+ |
+def RunStrip(strip_args_string, full_args): |
+ """Linker driver action for -Wcrl,strip,<strip_arguments>. |
+ |
+ Args: |
+ strip_args_string: string, Comma-separated arguments for `strip`. |
+ full_args: list of string, Full arguments for the linker driver. |
+ |
+ Returns: |
+ list of string, Build step outputs. |
+ """ |
+ strip_command = ['xcrun', 'strip'] |
+ if len(strip_args_string) > 0: |
+ strip_command += strip_args_string.split(',') |
+ strip_command.append(_FindLinkerOutput(full_args)) |
+ subprocess.check_call(strip_command) |
+ return [] |
+ |
+ |
+def _FindLinkerOutput(full_args): |
+ """Finds the output of the linker by looking for the output flag in its |
+ argument list. As this is a required linker argument, raises an error if it |
+ cannot be found. |
+ """ |
+ return full_args[full_args.index('-o') + 1] |
+ |
+ |
+def _RemovePath(path): |
+ """Removes the file or directory at |path| if it exists.""" |
+ if os.path.exists(path): |
+ if os.path.isdir(path): |
+ shutil.rmtree(path) |
+ else: |
+ os.unlink(path) |
+ |
+ |
+_LINKER_DRIVER_ARG_PREFIX = '-Wcrl,' |
+ |
+"""List of linker driver actions. The sort order of this list affects the |
+order in which the actions are invoked. The first item in the tuple is the |
+argument's -Wcrl,<sub_argument> and the second is the function to invoke. |
+""" |
+_LINKER_DRIVER_ACTIONS = [ |
+ ('dsym,', RunDsymUtil), |
+ ('strip,', RunStrip), |
+] |
+ |
+ |
+if __name__ == '__main__': |
+ Main(sys.argv) |
+ sys.exit(0) |