Skip to content

Commit 01672f5

Browse files
authored
Merge pull request #34302 from rjmccall/runtime-unittests-install-name
Test the just-built libraries when building unittests on Darwin
2 parents 1c7e87a + 1cc3a57 commit 01672f5

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

cmake/modules/AddSwiftUnittests.cmake

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,13 @@ function(add_swift_unittest test_dirname)
4040
endif()
4141

4242
if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin")
43+
# Add an @rpath to the swift library directory.
4344
set_target_properties(${test_dirname} PROPERTIES
4445
BUILD_RPATH ${SWIFT_LIBRARY_OUTPUT_INTDIR}/swift/macosx)
46+
# Force all the swift libraries to be found via rpath.
47+
add_custom_command(TARGET "${test_dirname}" POST_BUILD
48+
COMMAND "${SWIFT_SOURCE_DIR}/utils/swift-rpathize.py"
49+
"$<TARGET_FILE:${test_dirname}>")
4550
elseif("${SWIFT_HOST_VARIANT}" STREQUAL "android")
4651
swift_android_lib_for_arch(${SWIFT_HOST_VARIANT_ARCH} android_system_libs)
4752
set_property(TARGET "${test_dirname}" APPEND PROPERTY LINK_DIRECTORIES

utils/swift-rpathize.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env python
2+
3+
# On Darwin, dynamic libraries have an install name. At link time, the
4+
# linker can work with a dylib anywhere in the filesystem, but it will
5+
# write the dylib's install name into the resulting image, and at load
6+
# time that dylib will normally be expected to be found at exactly that
7+
# path. However, if the install name in an image begins with `@rpath`,
8+
# it will instead be searched for in the image's runtime search path
9+
# list. That list may contain absolute paths, but it may also contain
10+
# paths beginning with `@executable_path` or `@loader_path`, meaning the
11+
# path containing the running executable or the image being loaded,
12+
# respectively.
13+
#
14+
# Many of Swift's dylibs are meant to be installed on the system, which
15+
# means they have install names like this:
16+
# /usr/lib/swift/libswiftFoo.dylib
17+
# To support back-deployment, they also provide magic override symbols
18+
# ($ld$install_name) for all the OS versions preceding the addition of
19+
# of the library. When the linker finds a dylib with a matching override
20+
# for the OS deployment target, it ignores the normal install name and
21+
# uses the override path in the linked image's load command. Swift's
22+
# libraries use override paths that begin with `@rpath`, and Swift
23+
# builds images with a runtime search path list that starts with
24+
# /usr/lib/swift but then falls back on a path relative to the image;
25+
# thus, apps will use the system libraries if available but will
26+
# otherwise use fallback libraries.
27+
#
28+
# When we're working on Swift, we usually want to test the libraries
29+
# we just built rather than the system libraries. There are two ways
30+
# to achieve that. The first is to override dyld's runtime search path
31+
# with DYLD_LIBRARY_PATH; this will take precedence over even an
32+
# absolute install name. The second is to make sure the dylibs are
33+
# loaded via an @rpath install name and then link the program with an
34+
# rpath that will use the just-built libraries. Unfortunately, the
35+
# toolchain will ordinarily use an absolute install name instead of
36+
# an @rpath if the deployment target is old enough, subverting testing.
37+
#
38+
# This script looks for dependent dylibs with an absolute path in
39+
# /usr/lib/swift and changes them to use @rpath.
40+
41+
import argparse
42+
import re
43+
import subprocess
44+
import sys
45+
46+
47+
def main(arguments):
48+
parser = argparse.ArgumentParser(
49+
description='Change absolute install names to use @rpath')
50+
parser.add_argument('bin', help='the binary')
51+
52+
args = parser.parse_args(arguments)
53+
rpathize(args.bin)
54+
55+
56+
def rpathize(filename):
57+
dylibsOutput = subprocess.check_output(
58+
['xcrun', 'dyldinfo', '-dylibs', filename])
59+
60+
# The output from dyldinfo -dylibs is a line of header followed by one
61+
# install name per line, indented with spaces.
62+
dylib_regex = re.compile(
63+
r"^\s*(?P<path>/usr/lib/swift/(?P<filename>.*\.dylib))\s*$")
64+
65+
# Build a command to invoke install_name_tool.
66+
command = ['install_name_tool']
67+
for line in dylibsOutput.splitlines():
68+
match = dylib_regex.match(line)
69+
if match:
70+
command.append('-change')
71+
command.append(match.group('path'))
72+
command.append('@rpath/' + match.group('filename'))
73+
continue
74+
75+
# Don't run the command if we didn't find any dylibs to change:
76+
# it's invalid to invoke install_name_tool without any operations.
77+
if len(command) == 1:
78+
return
79+
80+
# The last argument is the filename to operate on.
81+
command.append(filename)
82+
83+
subprocess.check_call(command)
84+
85+
86+
sys.exit(main(sys.argv[1:]))

0 commit comments

Comments
 (0)