mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Namely, in scripts that alter the search paths for dylibs, ensure to invoke `/usr/bin/xcrun` directly to run `dyld_info` and `install_name_tool`, and validate that each argument is a valid path. Addresses rdar://160463033
106 lines
4.2 KiB
Python
Executable File
106 lines
4.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# On Darwin, dynamic libraries have an install name. At link time, the
|
|
# linker can work with a dylib anywhere in the filesystem, but it will
|
|
# write the dylib's install name into the resulting image, and at load
|
|
# time that dylib will normally be expected to be found at exactly that
|
|
# path. However, if the install name in an image begins with `@rpath`,
|
|
# it will instead be searched for in the image's runtime search path
|
|
# list. That list may contain absolute paths, but it may also contain
|
|
# paths beginning with `@executable_path` or `@loader_path`, meaning the
|
|
# path containing the running executable or the image being loaded,
|
|
# respectively.
|
|
#
|
|
# Many of Swift's dylibs are meant to be installed on the system, which
|
|
# means they have install names like this:
|
|
# /usr/lib/swift/libswiftFoo.dylib
|
|
# To support back-deployment, they also provide magic override symbols
|
|
# ($ld$install_name) for all the OS versions preceding the addition of
|
|
# of the library. When the linker finds a dylib with a matching override
|
|
# for the OS deployment target, it ignores the normal install name and
|
|
# uses the override path in the linked image's load command. Swift's
|
|
# libraries use override paths that begin with `@rpath`, and Swift
|
|
# builds images with a runtime search path list that starts with
|
|
# /usr/lib/swift but then falls back on a path relative to the image;
|
|
# thus, apps will use the system libraries if available but will
|
|
# otherwise use fallback libraries.
|
|
#
|
|
# When we're working on Swift, we usually want to test the libraries
|
|
# we just built rather than the system libraries. There are two ways
|
|
# to achieve that. The first is to override dyld's runtime search path
|
|
# with DYLD_LIBRARY_PATH; this will take precedence over even an
|
|
# absolute install name. The second is to make sure the dylibs are
|
|
# loaded via an @rpath install name and then link the program with an
|
|
# rpath that will use the just-built libraries. Unfortunately, the
|
|
# toolchain will ordinarily use an absolute install name instead of
|
|
# an @rpath if the deployment target is old enough, subverting testing.
|
|
#
|
|
# This script looks for dependent dylibs with an absolute path in
|
|
# /usr/lib/swift and changes them to use @rpath.
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
def file_path(string):
|
|
if os.path.isfile(string):
|
|
return string
|
|
else:
|
|
raise argparse.ArgumentTypeError(f"{string} is not a valid path")
|
|
|
|
|
|
def main(arguments):
|
|
parser = argparse.ArgumentParser(
|
|
description='Change absolute install names to use @rpath')
|
|
parser.add_argument('bin', type=file_path, help='the binary')
|
|
|
|
args = parser.parse_args(arguments)
|
|
rpathize(args.bin)
|
|
|
|
|
|
def rpathize(filename):
|
|
dylibsOutput = None
|
|
|
|
try:
|
|
# `dyldinfo` has been replaced with `dyld_info`, so we try it first
|
|
# before falling back to `dyldinfo`
|
|
dylibsOutput = subprocess.check_output(
|
|
['/usr/bin/xcrun', 'dyld_info', '-dependents', filename],
|
|
universal_newlines=True)
|
|
except subprocess.CalledProcessError:
|
|
sys.stderr.write("falling back to 'xcrun dyldinfo' ...\n")
|
|
dylibsOutput = subprocess.check_output(
|
|
['/usr/bin/xcrun', 'dyldinfo', '-dylibs', filename],
|
|
universal_newlines=True)
|
|
|
|
# The output from dyldinfo -dylibs is a line of header followed by one
|
|
# install name per line, indented with spaces.
|
|
dylib_regex = re.compile(
|
|
r"(^|.*\s)(?P<path>/usr/lib/swift/(?P<filename>.*\.dylib))\s*$")
|
|
|
|
# Build a command to invoke install_name_tool.
|
|
command = ['/usr/bin/xcrun', 'install_name_tool']
|
|
for line in dylibsOutput.splitlines():
|
|
match = dylib_regex.match(line)
|
|
if match:
|
|
command.append('-change')
|
|
command.append(match.group('path'))
|
|
command.append('@rpath/' + match.group('filename'))
|
|
continue
|
|
|
|
# Don't run the command if we didn't find any dylibs to change:
|
|
# it's invalid to invoke install_name_tool without any operations.
|
|
if len(command) == 2:
|
|
return
|
|
|
|
# The last argument is the filename to operate on.
|
|
command.append(filename)
|
|
|
|
subprocess.check_call(command)
|
|
|
|
|
|
sys.exit(main(sys.argv[1:]))
|