Skip to content

Test the just-built libraries when building unittests on Darwin #34302

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmake/modules/AddSwiftUnittests.cmake
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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:]))