123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- #!/usr/bin/env python3
- # 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
- import tempfile
- # Prefix for all custom linker driver arguments.
- LINKER_DRIVER_ARG_PREFIX = '-Wcrl,'
- # Linker action to create a directory and pass it to the linker as
- # `-object_path_lto`. Special-cased since it has to run before the link.
- OBJECT_PATH_LTO = 'object_path_lto'
- # 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,installnametoolpath,<install_name_tool_path>
- # Sets the path to the `install_name_tool` to run with
- # -Wcrl,installnametool, in which case `xcrun` is not used to invoke it.
- #
- # -Wcrl,installnametool,<arguments,...>
- # After invoking the linker, this will run install_name_tool on the linker's
- # output. |arguments| are comma-separated arguments to be passed to the
- # install_name_tool command.
- #
- # -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.
- # -Wcrl,object_path_lto
- # Creates temporary directory for LTO object files.
- class LinkerDriver(object):
- def __init__(self, args):
- """Creates a new linker driver.
- Args:
- args: list of string, Arguments to the script.
- """
- if len(args) < 2:
- raise RuntimeError("Usage: linker_driver.py [linker-invocation]")
- self._args = args
- # 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.
- self._actions = [
- ('installnametoolpath,', self.set_install_name_tool_path),
- ('installnametool,', self.run_install_name_tool),
- ('dsymutilpath,', self.set_dsymutil_path),
- ('dsym,', self.run_dsymutil),
- ('unstripped,', self.run_save_unstripped),
- ('strippath,', self.set_strip_path),
- ('strip,', self.run_strip),
- ]
- # Linker driver actions can modify the these values.
- self._install_name_tool_cmd = ['xcrun', 'install_name_tool']
- self._dsymutil_cmd = ['xcrun', 'dsymutil']
- self._strip_cmd = ['xcrun', 'strip']
- # The linker output file, lazily computed in self._get_linker_output().
- self._linker_output = None
- # The temporary directory for intermediate LTO object files. If it
- # exists, it will clean itself up on script exit.
- self._object_path_lto = None
- def run(self):
- """Runs the linker driver, separating out the main compiler driver's
- arguments from the ones handled by this class. It then invokes the
- required tools, starting with the compiler driver to produce the linker
- output.
- """
- # 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 index, arg in enumerate(self._args[1:]):
- if arg.startswith(LINKER_DRIVER_ARG_PREFIX):
- # Convert driver actions into a map of name => lambda to invoke.
- driver_action = self._process_driver_arg(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)
- if self._object_path_lto is not None:
- compiler_driver_args.append('-Wl,-object_path_lto,{}'.format(
- self._object_path_lto.name))
- if self._get_linker_output() is None:
- raise ValueError(
- 'Could not find path to linker output (-o or --output)')
- linker_driver_outputs = [self._get_linker_output()]
- try:
- # Zero the mtime in OSO fields for deterministic builds.
- # https://crbug.com/330262.
- env = os.environ.copy()
- 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 self._actions:
- name = action[0]
- if name in linker_driver_actions:
- linker_driver_outputs += linker_driver_actions[name]()
- except:
- # If a linker driver action failed, remove all the outputs to make
- # the build step atomic.
- map(_remove_path, linker_driver_outputs)
- # Re-report the original failure.
- raise
- def _get_linker_output(self):
- """Returns the value of the output argument to the linker."""
- if not self._linker_output:
- for index, arg in enumerate(self._args):
- if arg in ('-o', '-output', '--output'):
- self._linker_output = self._args[index + 1]
- break
- return self._linker_output
- def _process_driver_arg(self, 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 |self._actions|.
- 1: A lambda that calls the linker driver action with its direct
- argument and returns 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):]
- # Special-cased, since it needs to run before the link.
- # TODO(lgrey): Remove if/when we start running `dsymutil`
- # through the clang driver. See https://crbug.com/1324104
- if sub_arg == OBJECT_PATH_LTO:
- self._object_path_lto = tempfile.TemporaryDirectory(
- dir=os.getcwd())
- return (OBJECT_PATH_LTO, lambda: [])
- for driver_action in self._actions:
- (name, action) = driver_action
- if sub_arg.startswith(name):
- return (name, lambda: action(sub_arg[len(name):]))
- raise ValueError('Unknown linker driver argument: %s' % (arg, ))
- def set_install_name_tool_path(self, install_name_tool_path):
- """Linker driver action for -Wcrl,installnametoolpath,<path>.
- Sets the invocation command for install_name_tool, which allows the
- caller to specify an alternate path. This action is always
- processed before the run_install_name_tool action.
- Args:
- install_name_tool_path: string, The path to the install_name_tool
- binary to run
- Returns:
- No output - this step is run purely for its side-effect.
- """
- self._install_name_tool_cmd = [install_name_tool_path]
- return []
- def run_install_name_tool(self, args_string):
- """Linker driver action for -Wcrl,installnametool,<args>. Invokes
- install_name_tool on the linker's output.
- Args:
- args_string: string, Comma-separated arguments for
- `install_name_tool`.
- Returns:
- No output - this step is run purely for its side-effect.
- """
- command = list(self._install_name_tool_cmd)
- command.extend(args_string.split(','))
- command.append(self._get_linker_output())
- subprocess.check_call(command)
- return []
- def run_dsymutil(self, dsym_path_prefix):
- """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.
- Returns:
- list of string, Build step outputs.
- """
- if not len(dsym_path_prefix):
- raise ValueError('Unspecified dSYM output file')
- linker_output = self._get_linker_output()
- base = os.path.basename(linker_output)
- dsym_out = os.path.join(dsym_path_prefix, base + '.dSYM')
- # Remove old dSYMs before invoking dsymutil.
- _remove_path(dsym_out)
- tools_paths = _find_tools_paths(self._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(self._dsymutil_cmd +
- ['-o', dsym_out, linker_output],
- env=dsymutil_env)
- return [dsym_out]
- def set_dsymutil_path(self, dsymutil_path):
- """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
- Returns:
- No output - this step is run purely for its side-effect.
- """
- self._dsymutil_cmd = [dsymutil_path]
- return []
- def run_save_unstripped(self, unstripped_path_prefix):
- """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.
- Returns:
- list of string, Build step outputs.
- """
- if not len(unstripped_path_prefix):
- raise ValueError('Unspecified unstripped output file')
- base = os.path.basename(self._get_linker_output())
- unstripped_out = os.path.join(unstripped_path_prefix,
- base + '.unstripped')
- shutil.copyfile(self._get_linker_output(), unstripped_out)
- return [unstripped_out]
- def run_strip(self, strip_args_string):
- """Linker driver action for -Wcrl,strip,<strip_arguments>.
- Args:
- strip_args_string: string, Comma-separated arguments for `strip`.
- Returns:
- list of string, Build step outputs.
- """
- strip_command = list(self._strip_cmd)
- if len(strip_args_string) > 0:
- strip_command += strip_args_string.split(',')
- strip_command.append(self._get_linker_output())
- subprocess.check_call(strip_command)
- return []
- def set_strip_path(self, strip_path):
- """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
- Returns:
- No output - this step is run purely for its side-effect.
- """
- self._strip_cmd = [strip_path]
- return []
- def _find_tools_paths(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 _remove_path(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)
- if __name__ == '__main__':
- LinkerDriver(sys.argv).run()
- sys.exit(0)
|