From 7ff2be4d68416aebf9b885595a2030a38e9c53d3 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Wed, 22 Mar 2023 12:08:05 -0700 Subject: [PATCH] Add proper support for custom Swift toolchains (#1025) 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). --- README.md | 10 ++-- swift/internal/xcode_swift_toolchain.bzl | 40 +++++++++++----- tools/common/BUILD | 11 ++++- tools/common/bazel_substitutions.cc | 58 +++++++++++++++++++++++- 4 files changed, 100 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 9bbd7ab0f..752a2227a 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/swift/internal/xcode_swift_toolchain.bzl b/swift/internal/xcode_swift_toolchain.bzl index 76a5266c8..1cd6d27ff 100644 --- a/swift/internal/xcode_swift_toolchain.bzl +++ b/swift/internal/xcode_swift_toolchain.bzl @@ -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 @@ -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: @@ -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, @@ -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( @@ -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=` 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 @@ -591,10 +597,22 @@ def _xcode_swift_toolchain_impl(ctx): # script, use `--define=SWIFT_CUSTOM_TOOLCHAIN=` 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. diff --git a/tools/common/BUILD b/tools/common/BUILD index 9aaa7578a..6c22390e8 100644 --- a/tools/common/BUILD +++ b/tools/common/BUILD @@ -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( diff --git a/tools/common/bazel_substitutions.cc b/tools/common/bazel_substitutions.cc index 98973aa5d..46361784e 100644 --- a/tools/common/bazel_substitutions.cc +++ b/tools/common/bazel_substitutions.cc @@ -15,10 +15,14 @@ #include "tools/common/bazel_substitutions.h" #include +#include #include #include +#include #include +#include "tools/common/process.h" + namespace bazel_rules_swift { namespace { @@ -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) { @@ -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() { @@ -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(); })}, }; }