• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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