Skip to content

Commit 4cb3362

Browse files
allevatoadincebic
authored andcommitted
Add rpaths for the toolchain's compatibility back-deployment libraries to binaries built with swift_{binary, compiler_plugin, test}.
This is necessary to ensure that these binaries run correctly when using APIs like `Span` that are back-deployed on older OSes. Since the `swift_*` rules don't do bundling of their own, we simply point to the libraries in the toolchain itself (using the `xcode-select`-dependent symlinks, as we were already doing for tests). To actually distribute such a binary, it's the responsibility of the user to include the required dylibs with it (or, better, use a rule from rules_apple to perform the bundling). I've explicitly ignored the swift-5.0 and swift-5.5 directory since we have no plans to deploy anything from these rules to such old OSes. PiperOrigin-RevId: 817593332 (cherry picked from commit 2f5aa6e)
1 parent 3dfbf33 commit 4cb3362

File tree

1 file changed

+129
-40
lines changed

1 file changed

+129
-40
lines changed

swift/toolchains/xcode_swift_toolchain.bzl

Lines changed: 129 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -108,21 +108,45 @@ load(
108108
)
109109
load("//swift/toolchains/config:tool_config.bzl", "ToolConfigInfo")
110110

111-
def _platform_developer_framework_dir(
112-
apple_toolchain,
113-
target_triple):
111+
visibility("public")
112+
113+
# These are symlink locations known to be used by xcode-select in various Xcode
114+
# and macOS versions to point to the selected `Developer` directory.
115+
_DEVELOPER_DIR_SYMLINKS = [
116+
"/private/var/select/developer_dir",
117+
"/var/db/xcode_select_link",
118+
]
119+
120+
def _swift_developer_lib_dir(platform_framework_dir):
121+
"""Returns the directory containing extra Swift developer libraries.
122+
123+
Args:
124+
platform_framework_dir: The developer platform framework directory for
125+
the current platform.
126+
127+
Returns:
128+
The directory containing extra Swift-specific development libraries and
129+
swiftmodules.
130+
"""
131+
return paths.join(
132+
paths.dirname(paths.dirname(platform_framework_dir)),
133+
"usr",
134+
"lib",
135+
)
136+
137+
def _platform_developer_framework_dir(developer_dir, target_triple):
114138
"""Returns the Developer framework directory for the platform.
115139
116140
Args:
117-
apple_toolchain: The `apple_common.apple_toolchain()` object.
141+
developer_dir: The developer directory.
118142
target_triple: The triple of the platform being targeted.
119143
120144
Returns:
121145
The path to the Developer framework directory for the platform if one
122146
exists, otherwise `None`.
123147
"""
124148
return paths.join(
125-
apple_toolchain.developer_dir(),
149+
developer_dir,
126150
"Platforms",
127151
"{}.platform".format(
128152
target_triples.bazel_apple_platform(target_triple).name_in_plist,
@@ -150,11 +174,46 @@ def _sdk_developer_framework_dir(apple_toolchain, target_triple):
150174

151175
return paths.join(apple_toolchain.sdk_dir(), "Developer/Library/Frameworks")
152176

177+
def _swift_compatibility_lib_paths(*, target_triple, xcode_config):
178+
"""Returns the paths to the Swift compatibility libraries in the toolchain.
179+
180+
The returned paths are relative to the `Developer` directory; they do not
181+
contain the Bazel placeholder that would be substituted with the actual
182+
`Developer` directory at execution time.
183+
184+
Args:
185+
target_triple: The target triple `struct`.
186+
xcode_config: The `apple_common.XcodeVersionConfig` provider.
187+
188+
Returns:
189+
A list of paths to the Swift compatibility libraries in the toolchain.
190+
"""
191+
192+
# We choose to ignore swift-5.0 and swift-5.5 because they correspond to
193+
# such old OS versions that nobody is targeting with rules like
194+
# `swift_binary` and `swift_test`. (And if they were, they would already be
195+
# broken before this addition.)
196+
versions = []
197+
if _is_xcode_at_least_version(xcode_config, "26.0"):
198+
versions.append("6.2")
199+
200+
platform_name = target_triples.platform_name_for_swift(target_triple)
201+
return [
202+
"Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-{}/{}".format(
203+
version,
204+
platform_name,
205+
)
206+
for version in versions
207+
]
208+
153209
def _swift_linkopts_cc_info(
154210
apple_toolchain,
211+
platform_developer_framework_dir,
212+
sdk_developer_framework_dir,
155213
target_triple,
156214
toolchain_label,
157-
toolchain_root):
215+
toolchain_root,
216+
xcode_config):
158217
"""Returns a `CcInfo` containing flags that should be passed to the linker.
159218
160219
The providers returned by this function will be used as implicit
@@ -163,11 +222,14 @@ def _swift_linkopts_cc_info(
163222
164223
Args:
165224
apple_toolchain: The `apple_common.apple_toolchain()` object.
225+
platform_developer_framework_dir: The platform developer framework dir.
226+
sdk_developer_framework_dir: The SDK developer framework dir.
166227
target_triple: The target triple `struct`.
167228
toolchain_label: The label of the Swift toolchain that will act as the
168229
owner of the linker input propagating the flags.
169230
toolchain_root: The path to a custom Swift toolchain that could contain
170231
libraries required to link the binary
232+
xcode_config: The `apple_common.XcodeVersionConfig` provider.
171233
172234
Returns:
173235
A `CcInfo` provider that will provide linker flags to binaries that
@@ -189,6 +251,12 @@ def _swift_linkopts_cc_info(
189251
)
190252

191253
linkopts.extend([
254+
"-F{}".format(path)
255+
for path in compact([
256+
platform_developer_framework_dir,
257+
sdk_developer_framework_dir,
258+
])
259+
] + [
192260
"-L{}".format(swift_lib_dir),
193261
"-L/usr/lib/swift",
194262
# TODO(b/112000244): This should get added by the C++ Starlark API,
@@ -197,9 +265,41 @@ def _swift_linkopts_cc_info(
197265
# variables not provided by cc_common. Figure out how to handle this
198266
# correctly.
199267
"-Wl,-objc_abi_version,2",
200-
"-Wl,-rpath,/usr/lib/swift",
201268
])
202269

270+
# Compute the necessary rpaths for back-deployed compatibility libraries.
271+
# Note that this implies that a `swift_{binary,compiler_plugin,test}` isn't
272+
# portable to a different machine unless that machine also has the correct
273+
# version of Xcode selected. Users who need to build a binary that can be
274+
# moved to machines with different or no Xcode should use an appropriate
275+
# rule from rules_apple, which ensures that the dylibs are bundled as part
276+
# of the application.
277+
#
278+
# The system `/usr/lib/swift` must always be first in the list, to ensure
279+
# that system libraries are preferred over those in the toolchain.
280+
swift_compatibility_lib_dirs = _swift_compatibility_lib_paths(
281+
target_triple = target_triple,
282+
xcode_config = xcode_config,
283+
)
284+
rpaths = ["/usr/lib/swift"] + [
285+
paths.join(developer_dir_symlink, compatibility_dir)
286+
for developer_dir_symlink in _DEVELOPER_DIR_SYMLINKS
287+
for compatibility_dir in swift_compatibility_lib_dirs
288+
]
289+
linkopts += [
290+
"-Wl,-rpath,{}".format(rpath)
291+
for rpath in rpaths
292+
]
293+
294+
# Add the linker path to the directory containing the dylib with Swift
295+
# extensions for the XCTest module.
296+
if platform_developer_framework_dir:
297+
linkopts.extend([
298+
"-L{}".format(
299+
_swift_developer_lib_dir(platform_developer_framework_dir),
300+
),
301+
])
302+
203303
return CcInfo(
204304
linking_context = cc_common.create_linking_context(
205305
linker_inputs = depset([
@@ -211,41 +311,28 @@ def _swift_linkopts_cc_info(
211311
),
212312
)
213313

214-
def _test_linking_context(
215-
apple_toolchain,
216-
target_triple,
217-
toolchain_label,
218-
xcode_config):
314+
def _test_linking_context(target_triple, toolchain_label):
219315
"""Returns a `CcLinkingContext` containing linker flags for test binaries.
220316
221317
Args:
222-
apple_toolchain: The `apple_common.apple_toolchain()` object.
223318
target_triple: The target triple `struct`.
224319
toolchain_label: The label of the Swift toolchain that will act as the
225320
owner of the linker input propagating the flags.
226-
xcode_config: The Xcode configuration.
227321
228322
Returns:
229323
A `CcLinkingContext` that will provide linker flags to `swift_test`
230324
binaries.
231325
"""
232-
platform_developer_framework_dir = _platform_developer_framework_dir(
233-
apple_toolchain,
234-
target_triple,
235-
)
236-
237-
# Weakly link to XCTest. It's possible that machine that links the test
238-
# binary will have Xcode installed at a different path than the machine that
239-
# runs the binary. To handle this, the binary `dlopen`s XCTest at startup
240-
# using the path Bazel passes in the test action's environment.
241-
linkopts = [
242-
"-Wl,-weak_framework,XCTest",
243-
"-Wl,-weak-lXCTestSwiftSupport",
244-
]
245-
if _is_xcode_at_least_version(xcode_config, "16.0"):
246-
linkopts.append("-Wl,-weak_framework,Testing")
247326

248-
if platform_developer_framework_dir:
327+
# We use these as the rpaths for linking tests so that the required
328+
# libraries are found if Xcode is installed in a different location on the
329+
# machine that runs the tests than the machine used to link them.
330+
linkopts = []
331+
for developer_dir in _DEVELOPER_DIR_SYMLINKS:
332+
platform_developer_framework_dir = _platform_developer_framework_dir(
333+
developer_dir,
334+
target_triple,
335+
)
249336
linkopts.extend([
250337
"-Wl,-rpath,{}".format(path)
251338
for path in compact([
@@ -646,10 +733,8 @@ def _xcode_swift_toolchain_impl(ctx):
646733
swiftcopts.extend(ctx.attr._copts[BuildSettingInfo].value)
647734

648735
test_linking_context = _test_linking_context(
649-
apple_toolchain = apple_toolchain,
650736
target_triple = target_triple,
651737
toolchain_label = ctx.label,
652-
xcode_config = xcode_config,
653738
)
654739

655740
# `--define=SWIFT_USE_TOOLCHAIN_ROOT=<path>` is a rapid development feature
@@ -675,11 +760,23 @@ def _xcode_swift_toolchain_impl(ctx):
675760
elif custom_toolchain:
676761
custom_xcode_toolchain_root = "__BAZEL_CUSTOM_XCODE_TOOLCHAIN_PATH__"
677762

763+
platform_developer_framework_dir = _platform_developer_framework_dir(
764+
apple_toolchain.developer_dir(),
765+
target_triple,
766+
)
767+
sdk_developer_framework_dir = _sdk_developer_framework_dir(
768+
apple_toolchain,
769+
target_triple,
770+
)
771+
678772
swift_linkopts_cc_info = _swift_linkopts_cc_info(
679773
apple_toolchain = apple_toolchain,
774+
platform_developer_framework_dir = platform_developer_framework_dir,
775+
sdk_developer_framework_dir = sdk_developer_framework_dir,
680776
target_triple = target_triple,
681777
toolchain_label = ctx.label,
682778
toolchain_root = toolchain_root or custom_xcode_toolchain_root,
779+
xcode_config = xcode_config,
683780
)
684781

685782
# Compute the default requested features and conditional ones based on Xcode
@@ -759,21 +856,13 @@ def _xcode_swift_toolchain_impl(ctx):
759856
xcode_config = xcode_config,
760857
)
761858
swift_toolchain_developer_paths = []
762-
platform_developer_framework_dir = _platform_developer_framework_dir(
763-
apple_toolchain,
764-
target_triple,
765-
)
766859
if platform_developer_framework_dir:
767860
swift_toolchain_developer_paths.append(
768861
struct(
769862
developer_path_label = "platform",
770863
path = platform_developer_framework_dir,
771864
),
772865
)
773-
sdk_developer_framework_dir = _sdk_developer_framework_dir(
774-
apple_toolchain,
775-
target_triple,
776-
)
777866
if sdk_developer_framework_dir:
778867
swift_toolchain_developer_paths.append(
779868
struct(

0 commit comments

Comments
 (0)