• 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"""
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