summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/build/toolchain/apple/linker_driver.py
blob: c21e18a0fb060bc3d11ced10d584f03137431b84 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
#!/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

# On mac, the values of these globals are modified when parsing -Wcrl, flags. On
# ios, the script uses the defaults.
DSYMUTIL_INVOKE = ['xcrun', 'dsymutil']
STRIP_INVOKE = ['xcrun', 'strip']

# Setting this flag will emit a deterministic binary by stripping dates from the
# N_OSO field.
DETERMINISTIC_FLAG = '--deterministic'

# 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,dsymutilpath,<dsymutil_path>
#       Sets the path to the dsymutil to run with -Wcrl,dsym, in which case
#       `xcrun` is not used to invoke it.
#
#   -Wcrl,unstripped,<unstripped_path_prefix>
#       After invoking the linker, and before strip, this will save a copy of
#       the unstripped linker output in the directory unstripped_path_prefix.
#
#   -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.
#
#   -Wcrl,strippath,<strip_path>
#       Sets the path to the strip to run with -Wcrl,strip, in which case
#       `xcrun` is not used to invoke it.


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 = []
  deterministic = False
  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]
    elif arg == DETERMINISTIC_FLAG:
      deterministic = True
    else:
      compiler_driver_args.append(arg)

  linker_driver_outputs = [_FindLinkerOutput(compiler_driver_args)]

  try:
    # Zero the mtime in OSO fields for deterministic builds.
    # https://crbug.com/330262.
    env = os.environ.copy()
    if deterministic:
      env['ZERO_AR_DATE'] = '1'
    # Run the linker by invoking the compiler driver.
    subprocess.check_call(compiler_driver_args, env=env)

    # 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)
  base = os.path.basename(linker_out)
  dsym_out = os.path.join(dsym_path_prefix, base + '.dSYM')

  # Remove old dSYMs before invoking dsymutil.
  _RemovePath(dsym_out)

  tools_paths = _FindToolsPaths(full_args)
  if os.environ.get('PATH'):
    tools_paths.append(os.environ['PATH'])
  dsymutil_env = os.environ.copy()
  dsymutil_env['PATH'] = ':'.join(tools_paths)
  subprocess.check_call(DSYMUTIL_INVOKE + ['-o', dsym_out, linker_out],
                        env=dsymutil_env)
  return [dsym_out]


def SetDsymutilPath(dsymutil_path, full_args):
  """Linker driver action for -Wcrl,dsymutilpath,<dsymutil_path>.

  Sets the invocation command for dsymutil, which allows the caller to specify
  an alternate dsymutil. This action is always processed before the RunDsymUtil
  action.

  Args:
    dsymutil_path: string, The path to the dsymutil binary to run
    full_args: list of string, Full argument list for the linker driver.

  Returns:
    No output - this step is run purely for its side-effect.
  """
  global DSYMUTIL_INVOKE
  DSYMUTIL_INVOKE = [dsymutil_path]
  return []


def RunSaveUnstripped(unstripped_path_prefix, full_args):
  """Linker driver action for -Wcrl,unstripped,<unstripped_path_prefix>. Copies
  the linker output to |unstripped_path_prefix| before stripping.

  Args:
    unstripped_path_prefix: string, The path at which the unstripped 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(unstripped_path_prefix):
    raise ValueError('Unspecified unstripped output file')

  linker_out = _FindLinkerOutput(full_args)
  base = os.path.basename(linker_out)
  unstripped_out = os.path.join(unstripped_path_prefix, base + '.unstripped')

  shutil.copyfile(linker_out, unstripped_out)
  return [unstripped_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 = list(STRIP_INVOKE)
  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 SetStripPath(strip_path, full_args):
  """Linker driver action for -Wcrl,strippath,<strip_path>.

  Sets the invocation command for strip, which allows the caller to specify
  an alternate strip. This action is always processed before the RunStrip
  action.

  Args:
    strip_path: string, The path to the strip binary to run
    full_args: list of string, Full argument list for the linker driver.

  Returns:
    No output - this step is run purely for its side-effect.
  """
  global STRIP_INVOKE
  STRIP_INVOKE = [strip_path]
  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.
  """
  # The linker_driver.py script may be used to wrap either the compiler linker
  # (uses -o to configure the output) or lipo (uses -output to configure the
  # output). Since wrapping the compiler linker is the most likely possibility
  # use try/except and fallback to checking for -output if -o is not found.
  try:
    output_flag_index = full_args.index('-o')
  except ValueError:
    output_flag_index = full_args.index('-output')
  return full_args[output_flag_index + 1]


def _FindToolsPaths(full_args):
  """Finds all paths where the script should look for additional tools."""
  paths = []
  for idx, arg in enumerate(full_args):
    if arg in ['-B', '--prefix']:
      paths.append(full_args[idx + 1])
    elif arg.startswith('-B'):
      paths.append(arg[2:])
    elif arg.startswith('--prefix='):
      paths.append(arg[9:])
  return paths


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 = [
    ('dsymutilpath,', SetDsymutilPath),
    ('dsym,', RunDsymUtil),
    ('unstripped,', RunSaveUnstripped),
    ('strippath,', SetStripPath),
    ('strip,', RunStrip),
]

if __name__ == '__main__':
  Main(sys.argv)
  sys.exit(0)