Skip to content

Commit

Permalink
Test the just-built dylibs when building unittests on Darwin
Browse files Browse the repository at this point in the history
  • Loading branch information
rjmccall committed Oct 15, 2020
1 parent 476890c commit 1cc3a57
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 0 deletions.
5 changes: 5 additions & 0 deletions cmake/modules/AddSwiftUnittests.cmake
Expand Up @@ -40,8 +40,13 @@ function(add_swift_unittest test_dirname)
endif()

if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin")
# Add an @rpath to the swift library directory.
set_target_properties(${test_dirname} PROPERTIES
BUILD_RPATH ${SWIFT_LIBRARY_OUTPUT_INTDIR}/swift/macosx)
# Force all the swift libraries to be found via rpath.
add_custom_command(TARGET "${test_dirname}" POST_BUILD
COMMAND "${SWIFT_SOURCE_DIR}/utils/swift-rpathize.py"
"$<TARGET_FILE:${test_dirname}>")
elseif("${SWIFT_HOST_VARIANT}" STREQUAL "android")
swift_android_lib_for_arch(${SWIFT_HOST_VARIANT_ARCH} android_system_libs)
set_property(TARGET "${test_dirname}" APPEND PROPERTY LINK_DIRECTORIES
Expand Down
86 changes: 86 additions & 0 deletions utils/swift-rpathize.py
@@ -0,0 +1,86 @@
#!/usr/bin/env python

# 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 re
import subprocess
import sys


def main(arguments):
parser = argparse.ArgumentParser(
description='Change absolute install names to use @rpath')
parser.add_argument('bin', help='the binary')

args = parser.parse_args(arguments)
rpathize(args.bin)


def rpathize(filename):
dylibsOutput = subprocess.check_output(
['xcrun', 'dyldinfo', '-dylibs', filename])

# 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 = ['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) == 1:
return

# The last argument is the filename to operate on.
command.append(filename)

subprocess.check_call(command)


sys.exit(main(sys.argv[1:]))

0 comments on commit 1cc3a57

Please sign in to comment.