Skip to content

Commit

Permalink
Add proper support for custom Swift toolchains (#1025)
Browse files Browse the repository at this point in the history
Previously we did some amount of handling if you passed a custom
toolchain identifier. This didn't handle the case that the toolchain
required new compatibility libraries to actually link your binary. To
handle this we have to pass a new -L to the path in the root of the
toolchain. We do this with a custom env var that we replace, similar to
the DEVELOPER_DIR variable. Ideally this would be cached in memory, but
we can't do that without editing bazel, luckily xcrun has a cache that
makes this likely fast enough (about 0.01s for a cached invocation).
  • Loading branch information
keith authored Mar 22, 2023
1 parent 422c015 commit b9a4991
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 19 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ uses `clang`.

## Building with Custom Toolchains

**macOS hosts:** You can build with a custom toolchain installed in
`/Library/Developer/Toolchains` instead of Xcode's default. To do so, pass the
following flag to Bazel:
**macOS hosts:** You can build with a custom Swift toolchain (downloaded
from https://swift.org/download) instead of Xcode's default. To do so,
pass the following flag to Bazel:

```lang-none
--define=SWIFT_CUSTOM_TOOLCHAIN=toolchain.id
--action_env=TOOLCHAINS=toolchain.id
```

where `toolchain.id` is the value of the `CFBundleIdentifier` key in the
Where `toolchain.id` is the value of the `CFBundleIdentifier` key in the
toolchain's Info.plist file.

To list the available toolchains and their bundle identifiers, you can run:
Expand Down
40 changes: 29 additions & 11 deletions swift/internal/xcode_swift_toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ def _sdk_developer_framework_dir(apple_toolchain, target_triple, xcode_config):
def _swift_linkopts_providers(
apple_toolchain,
target_triple,
toolchain_label):
toolchain_label,
toolchain_root):
"""Returns providers containing flags that should be passed to the linker.
The providers returned by this function will be used as implicit
Expand All @@ -207,6 +208,8 @@ def _swift_linkopts_providers(
target_triple: The target triple `struct`.
toolchain_label: The label of the Swift toolchain that will act as the
owner of the linker input propagating the flags.
toolchain_root: The path to a custom Swift toolchain that could contain
libraries required to link the binary
Returns:
A `struct` containing the following fields:
Expand All @@ -216,14 +219,22 @@ def _swift_linkopts_providers(
* `objc_info`: An `apple_common.Objc` provider that will provide
linker flags to binaries that depend on Swift targets.
"""
linkopts = []
if toolchain_root:
# This -L has to come before Xcode's to make sure libraries are
# overridden when applicable
linkopts.append("-L{}/usr/lib/swift/{}".format(
toolchain_root,
target_triples.platform_name_for_swift(target_triple),
))

swift_lib_dir = paths.join(
apple_toolchain.developer_dir(),
"Toolchains/XcodeDefault.xctoolchain/usr/lib/swift",
target_triples.platform_name_for_swift(target_triple),
)

linkopts = [
"-Wl,-rpath,/usr/lib/swift",
linkopts.extend([
"-L{}".format(swift_lib_dir),
"-L/usr/lib/swift",
# TODO(b/112000244): These should get added by the C++ Starlark API,
Expand All @@ -232,7 +243,8 @@ def _swift_linkopts_providers(
# variables not provided by cc_common. Figure out how to handle this
# correctly.
"-Wl,-objc_abi_version,2",
]
"-Wl,-rpath,/usr/lib/swift",
])

return struct(
cc_info = CcInfo(
Expand Down Expand Up @@ -571,12 +583,6 @@ def _xcode_swift_toolchain_impl(ctx):

xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig]

swift_linkopts_providers = _swift_linkopts_providers(
apple_toolchain = apple_toolchain,
target_triple = target_triple,
toolchain_label = ctx.label,
)

# `--define=SWIFT_USE_TOOLCHAIN_ROOT=<path>` is a rapid development feature
# that lets you build *just* a custom `swift` driver (and `swiftc`
# symlink), rather than a full toolchain, and point compilation actions at
Expand All @@ -591,10 +597,22 @@ def _xcode_swift_toolchain_impl(ctx):
# script, use `--define=SWIFT_CUSTOM_TOOLCHAIN=<id>` as shown below.
swift_executable = get_swift_executable_for_toolchain(ctx)
toolchain_root = ctx.var.get("SWIFT_USE_TOOLCHAIN_ROOT")
custom_toolchain = ctx.var.get("SWIFT_CUSTOM_TOOLCHAIN")

# TODO: Remove SWIFT_CUSTOM_TOOLCHAIN for the next major release
custom_toolchain = ctx.var.get("SWIFT_CUSTOM_TOOLCHAIN") or ctx.configuration.default_shell_env.get("TOOLCHAINS")
custom_xcode_toolchain_root = None
if toolchain_root and custom_toolchain:
fail("Do not use SWIFT_USE_TOOLCHAIN_ROOT and SWIFT_CUSTOM_TOOLCHAIN" +
"in the same build.")
elif custom_toolchain:
custom_xcode_toolchain_root = "__BAZEL_CUSTOM_XCODE_TOOLCHAIN_PATH__"

swift_linkopts_providers = _swift_linkopts_providers(
apple_toolchain = apple_toolchain,
target_triple = target_triple,
toolchain_label = ctx.label,
toolchain_root = toolchain_root or custom_xcode_toolchain_root,
)

# Compute the default requested features and conditional ones based on Xcode
# version.
Expand Down
11 changes: 10 additions & 1 deletion tools/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,18 @@ cc_library(
"//tools:clang-cl": [
"-Xclang",
"-fno-split-cold-code",
"/std:c++17",
],
"//tools:msvc": [
"/std:c++17",
],
"//conditions:default": [
"-std=c++17",
],
"//conditions:default": [],
}),
deps = [
":process",
],
)

cc_library(
Expand Down
58 changes: 56 additions & 2 deletions tools/common/bazel_substitutions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@
#include "tools/common/bazel_substitutions.h"

#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <map>
#include <sstream>
#include <string>

#include "tools/common/process.h"

namespace bazel_rules_swift {
namespace {

Expand All @@ -30,6 +34,11 @@ static const char kBazelXcodeDeveloperDir[] = "__BAZEL_XCODE_DEVELOPER_DIR__";
// at runtime.
static const char kBazelXcodeSdkRoot[] = "__BAZEL_XCODE_SDKROOT__";

// The placeholder string used by the Apple and Swift rules to be replaced with
// the absolute path to the custom toolchain being used
static const char kBazelToolchainPath[] =
"__BAZEL_CUSTOM_XCODE_TOOLCHAIN_PATH__";

// Returns the value of the given environment variable, or the empty string if
// it wasn't set.
std::string GetAppleEnvironmentVariable(const char *name) {
Expand All @@ -45,6 +54,48 @@ std::string GetAppleEnvironmentVariable(const char *name) {
return env_value;
}

std::string GetToolchainPath() {
#if !defined(__APPLE__)
return "";
#endif

char *toolchain_id = getenv("TOOLCHAINS");
if (toolchain_id == nullptr) {
return "";
}

std::ostringstream output_stream;
int exit_code =
RunSubProcess({"xcrun", "--find", "clang", "--toolchain", toolchain_id},
&output_stream, /*stdout_to_stderr=*/true);
if (exit_code != 0) {
std::cerr << output_stream.str() << "Error: TOOLCHAINS was set to '"
<< toolchain_id << "' but xcrun failed when searching for that ID"
<< std::endl;
exit(EXIT_FAILURE);
}

if (output_stream.str().empty()) {
std::cerr << "Error: TOOLCHAINS was set to '" << toolchain_id
<< "' but no toolchain with that ID was found" << std::endl;
exit(EXIT_FAILURE);
} else if (output_stream.str().find("XcodeDefault.xctoolchain") !=
std::string::npos) {
// NOTE: Ideally xcrun would fail if the toolchain we asked for didn't exist
// but it falls back to the DEVELOPER_DIR instead, so we have to check the
// output ourselves.
std::cerr << "Error: TOOLCHAINS was set to '" << toolchain_id
<< "' but the default toolchain was found, that likely means a "
"matching "
<< "toolchain isn't installed" << std::endl;
exit(EXIT_FAILURE);
}

std::filesystem::path toolchain_path(output_stream.str());
// Remove usr/bin/clang components to get the root of the custom toolchain
return toolchain_path.parent_path().parent_path().parent_path().string();
}

} // namespace

BazelPlaceholderSubstitutions::BazelPlaceholderSubstitutions() {
Expand All @@ -56,8 +107,11 @@ BazelPlaceholderSubstitutions::BazelPlaceholderSubstitutions() {
{kBazelXcodeDeveloperDir, PlaceholderResolver([]() {
return GetAppleEnvironmentVariable("DEVELOPER_DIR");
})},
{kBazelXcodeSdkRoot,
PlaceholderResolver([]() { return GetAppleEnvironmentVariable("SDKROOT"); })},
{kBazelXcodeSdkRoot, PlaceholderResolver([]() {
return GetAppleEnvironmentVariable("SDKROOT");
})},
{kBazelToolchainPath,
PlaceholderResolver([]() { return GetToolchainPath(); })},
};
}

Expand Down

0 comments on commit b9a4991

Please sign in to comment.