import os
import os.path
import re
import shutil
import subprocess
import sys
import tempfile
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
import whole_archive
LINKER_DRIVER_ARG_PREFIX = '-Wcrl,'
LINKER_DRIVER_COMPILER_ARG_PREFIX = '-Wcrl,driver,'
OBJECT_PATH_LTO = 'object_path_lto'
class LinkerDriver(object):
def __init__(self, args):
"""Creates a new linker driver.
Args:
args: list of string, Arguments to the script.
"""
self._args = args
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),
]
self._driver_path = None
self._install_name_tool_cmd = ['xcrun', 'install_name_tool']
self._dsymutil_cmd = ['xcrun', 'dsymutil']
self._strip_cmd = ['xcrun', 'strip']
self._linker_output = None
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.
"""
linker_driver_actions = {}
compiler_driver_args = []
for index, arg in enumerate(self._args[1:]):
if arg.startswith(LINKER_DRIVER_COMPILER_ARG_PREFIX):
assert not self._driver_path
self._driver_path = arg[len(LINKER_DRIVER_COMPILER_ARG_PREFIX
):]
elif arg.startswith(LINKER_DRIVER_ARG_PREFIX):
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:
BAD_RUSTC_ARGS = '-Wl,-plugin-opt=O[0-9],-plugin-opt=mcpu=.*'
if not re.match(BAD_RUSTC_ARGS, arg):
compiler_driver_args.append(arg)
if not self._driver_path:
raise RuntimeError(
"Usage: linker_driver.py -Wcrl,driver,<compiler-driver> "
"[linker-args]...")
if self._object_path_lto is not None:
compiler_driver_args.append('-Wl,-object_path_lto,{}'.format(
os.path.relpath(self._object_path_lto.name)))
if self._get_linker_output() is None:
raise ValueError(
'Could not find path to linker output (-o or --output)')
compiler_driver_args = whole_archive.wrap_with_whole_archive(
compiler_driver_args, is_apple=True)
linker_driver_outputs = [self._get_linker_output()]
try:
env = os.environ.copy()
env['ZERO_AR_DATE'] = '1'
subprocess.check_call([self._driver_path] + compiler_driver_args,
env=env)
for action in self._actions:
name = action[0]
if name in linker_driver_actions:
linker_driver_outputs += linker_driver_actions[name]()
except:
map(_remove_path, linker_driver_outputs)
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):]
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_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)