From 5286ff9f6b06c469fcc4add8e4003b9b485f4c7c Mon Sep 17 00:00:00 2001 From: Tony Allevato Date: Sun, 20 Feb 2022 09:28:51 -0800 Subject: [PATCH] Use the target triple from the C++ toolchain instead of deriving it from values in the Apple configuration fragment. When using `--host_cpu=darwin_x86_64` on an ARM (Apple Silicon) host, the value returned by the Apple configuration fragment is wrong, and we end up compiling Swift for `arm64`. Since the C++ toolchain already has the correct triple verbatim as we want it in the `target_gnu_system_name` field, just use that, and add some generally useful utilities for manipulating the triples and their components. Note to open-source rules maintainers: This change requires/assumes that your C++ toolchain configuration returns a complete target triple that includes minimum OS version and target environment; for example, `x86_64-apple-ios13.0-simulator`. PiperOrigin-RevId: 429897884 (cherry picked from commit 62b33ed5ff5d699cb1573309e0714e30f4d6e225) --- swift/internal/BUILD | 7 + swift/internal/target_triples.bzl | 216 +++++++++++++++++++++++ swift/internal/xcode_swift_toolchain.bzl | 145 +++++++-------- 3 files changed, 288 insertions(+), 80 deletions(-) create mode 100644 swift/internal/target_triples.bzl diff --git a/swift/internal/BUILD b/swift/internal/BUILD index f63b536e6..a089f1966 100644 --- a/swift/internal/BUILD +++ b/swift/internal/BUILD @@ -323,6 +323,12 @@ bzl_library( deps = [":providers"], ) +bzl_library( + name = "target_triples", + srcs = ["target_triples.bzl"], + visibility = ["//swift:__subpackages__"], +) + bzl_library( name = "toolchain_config", srcs = ["toolchain_config.bzl"], @@ -352,6 +358,7 @@ bzl_library( ":feature_names", ":features", ":providers", + ":target_triples", ":toolchain_config", ":utils", "@bazel_skylib//lib:collections", diff --git a/swift/internal/target_triples.bzl b/swift/internal/target_triples.bzl new file mode 100644 index 000000000..6b53d1c4d --- /dev/null +++ b/swift/internal/target_triples.bzl @@ -0,0 +1,216 @@ +# Copyright 2022 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utility functions to inspect and manipulate target triples.""" + +def _make(*, cpu, vendor, os, environment = None): + """Creates a target triple struct from the given values. + + Args: + cpu: The CPU of the triple (e.g., `x86_64` or `arm`). + vendor: The vendor component of the triple (e.g., `apple` or + `unknown`). + os: The operating system or platform name of the triple (e.g., `macos` + or `linux`). + environment: The environment or ABI component of the triple, if it was + present. If this argument is omitted, it defaults to `None`. + + Returns: + A `struct` containing the fields `cpu`, `vendor`, `os`, and + `environment` which correspond to the arguments passed to this function. + """ + if not cpu or not vendor or not os: + fail("A triple must have a non-empty CPU, vendor, and OS.") + + return struct( + cpu = cpu, + vendor = vendor, + os = os, + environment = environment, + ) + +def _normalize_apple_cpu(cpu): + """Normalizes the CPU component of an Apple target triple. + + This function is equivalent to `getArchForAppleTargetSpecificModuleTrple` in + https://github.com/apple/swift/blob/main/lib/Basic/Platform.cpp. + """ + if cpu in ("arm64", "aarch64"): + return "arm64" + if cpu in ("arm64_32", "aarch64_32"): + return "arm64_32" + if cpu in ("x86_64", "amd64"): + return "x86_64" + if cpu in ("i386", "i486", "i586", "i686", "i786", "i886", "i986"): + return "i386" + if not cpu: + return "unknown" + return cpu + +def _normalize_apple_environment(environment): + """Normalizes the environment component of an Apple target triple. + + This function is equivalent to + `getEnvironmentForAppleTargetSpecificModuleTriple` in + https://github.com/apple/swift/blob/main/lib/Basic/Platform.cpp. + """ + if environment == "unknown" or not environment: + return None + return environment + +def _normalize_apple_os(os, *, unversioned = False): + """Normalizes the OS component of an Apple target triple. + + This function is equivalent to `getOSForAppleTargetSpecificModuleTriple` in + https://github.com/apple/swift/blob/main/lib/Basic/Platform.cpp. + """ + os_name, version = _split_os_version(os) + if os_name in ("macos", "macosx", "darwin"): + os_name = "macos" + elif not os_name: + os_name = "unknown" + return os_name if unversioned else (os_name + version) + +def _normalize_for_swift(triple, *, unversioned = False): + """Normalizes a target triple for use with the Swift compiler. + + This function performs that normalization, as well as other normalization + implemented in + https://github.com/apple/swift/blob/main/lib/Basic/Platform.cpp. It is named + _specifically_ `normalize_for_swift` to differentiate it from the behavior + defined in the `llvm::Triple::normalize` method, which has slightly + different semantics. + + Args: + triple: A target triple struct, as returned by `target_triples.make` or + `target_triples.parse`. + unversioned: A Boolean value indicating whether any OS version number + component should be removed from the triple, if present. + + Returns: + A target triple struct containing the normalized triple. + """ + os = _normalize_apple_os(triple.os, unversioned = unversioned) + if os.startswith(("ios", "macos", "tvos", "watchos")): + return _make( + cpu = _normalize_apple_cpu(triple.cpu), + vendor = "apple", + os = os, + environment = _normalize_apple_environment(triple.environment), + ) + + return triple + +def _parse(triple_string): + """Parses a target triple string and returns its fields as a struct. + + Args: + triple_string: A string representing a target triple. + + Returns: + A `struct` containing the following fields: + + * `cpu`: The CPU of the triple (e.g., `x86_64` or `arm`). + * `vendor`: The vendor component of the triple (e.g., `apple` or + `unknown`). + * `os`: The operating system or platform name of the triple (e.g., + `darwin` or `linux`). + * `environment`: The environment or ABI component of the triple, if + it was present. This component may be `None`. + """ + components = triple_string.split("-") + return _make( + cpu = components[0], + vendor = components[1], + os = components[2], + environment = components[3] if len(components) > 3 else None, + ) + +def _platform_name_for_swift(triple): + """Returns the platform name used by Swift to refer to the triple. + + The platform name is used as the name of the subdirectory under the Swift + resource directory where libraries and modules are stored. On some + platforms, such as Apple operating systems, this name encodes both OS and + environment information from the triple: for example, + `x86_64-apple-ios-simulator` has a platform name of `iphonesimulator` + (matching the platform name from Xcode). + + Args: + triple: A target triple struct, as returned by `target_triples.make` or + `target_triples.parse`. + + Returns: + A string representing the platform name. + """ + os = _normalize_apple_os(_unversioned_os(triple), unversioned = True) + if os == "macos": + return "macosx" + + is_simulator = (triple.environment == "simulator") + if os == "ios": + return "iphonesimulator" if is_simulator else "iphoneos" + if os == "tvos": + return "appletvsimulator" if is_simulator else "appletvos" + if os == "watchos": + return "watchsimulator" if is_simulator else "watchos" + + # Fall back to the operating system name if we aren't one of the cases + # covered above. If more platforms need to be supported in the future, add + # them here. + return os + +def _str(triple): + """Returns the string representation of the target triple. + + Args: + triple: A target triple struct, as returned by `target_triples.make` or + `target_triples.parse`. + + Returns: + The string representation of the target triple. + """ + result = "{}-{}-{}".format(triple.cpu, triple.vendor, triple.os) + if triple.environment: + result += "-{}".format(triple.environment) + return result + +def _split_os_version(os): + """Splits the OS version number from the end of the given component. + + Args: + os: The OS component of a target triple. + + Returns: + A tuple containing two elements: the operating system name and the + version number. If there was no version number, then the second element + will be the empty string. + """ + for index in range(len(os)): + if os[index].isdigit(): + return (os[:index], os[index:]) + return (os, "") + +def _unversioned_os(triple): + """Returns the operating system of the triple without the version number.""" + return _split_os_version(triple.os)[0] + +target_triples = struct( + make = _make, + normalize_for_swift = _normalize_for_swift, + parse = _parse, + platform_name_for_swift = _platform_name_for_swift, + str = _str, + unversioned_os = _unversioned_os, +) diff --git a/swift/internal/xcode_swift_toolchain.bzl b/swift/internal/xcode_swift_toolchain.bzl index 50769a3b8..97b724035 100644 --- a/swift/internal/xcode_swift_toolchain.bzl +++ b/swift/internal/xcode_swift_toolchain.bzl @@ -53,6 +53,7 @@ load( "SwiftPackageConfigurationInfo", "SwiftToolchainInfo", ) +load(":target_triples.bzl", "target_triples") load( ":utils.bzl", "collect_implicit_deps_providers", @@ -61,6 +62,26 @@ load( "resolve_optional_tool", ) +# Maps (operating system, environment) pairs from target triples to the legacy +# Bazel core `apple_common.platform` values, since we still use some APIs that +# require these. +_TRIPLE_OS_TO_PLATFORM = { + ("ios", None): apple_common.platform.ios_device, + ("ios", "simulator"): apple_common.platform.ios_simulator, + ("macos", None): apple_common.platform.macos, + ("tvos", None): apple_common.platform.tvos_device, + ("tvos", "simulator"): apple_common.platform.tvos_simulator, + ("watchos", None): apple_common.platform.watchos_device, + ("watchos", "simulator"): apple_common.platform.watchos_simulator, +} + +def _bazel_apple_platform(target_triple): + """Returns the `apple_common.platform` value for the given target triple.""" + return _TRIPLE_OS_TO_PLATFORM[( + target_triples.unversioned_os(target_triple), + target_triple.environment, + )] + def _swift_developer_lib_dir(platform_framework_dir): """Returns the directory containing extra Swift developer libraries. @@ -125,13 +146,13 @@ def _command_line_objc_copts(compilation_mode, objc_fragment): def _platform_developer_framework_dir( apple_toolchain, - apple_fragment, + target_triple, xcode_config): """Returns the Developer framework directory for the platform. Args: - apple_fragment: The `apple` configuration fragment. apple_toolchain: The `apple_common.apple_toolchain()` object. + target_triple: The triple of the platform being targeted. xcode_config: The Xcode configuration. Returns: @@ -141,21 +162,27 @@ def _platform_developer_framework_dir( # All platforms have a `Developer/Library/Frameworks` directory in their # platform root, except for watchOS prior to Xcode 12.5. - platform_type = apple_fragment.single_arch_platform.platform_type if ( - platform_type == apple_common.platform_type.watchos and + target_triples.unversioned_os(target_triple) == "watchos" and not _is_xcode_at_least_version(xcode_config, "12.5") ): return None - return apple_toolchain.platform_developer_framework_dir(apple_fragment) + return paths.join( + apple_toolchain.developer_dir(), + "Platforms", + "{}.platform".format( + _bazel_apple_platform(target_triple).name_in_plist, + ), + "Developer/Library/Frameworks", + ) -def _sdk_developer_framework_dir(apple_toolchain, apple_fragment, xcode_config): +def _sdk_developer_framework_dir(apple_toolchain, target_triple, xcode_config): """Returns the Developer framework directory for the SDK. Args: - apple_fragment: The `apple` configuration fragment. apple_toolchain: The `apple_common.apple_toolchain()` object. + target_triple: The triple of the platform being targeted. xcode_config: The Xcode configuration. Returns: @@ -166,22 +193,17 @@ def _sdk_developer_framework_dir(apple_toolchain, apple_fragment, xcode_config): # All platforms have a `Developer/Library/Frameworks` directory in their SDK # root except for macOS (all versions of Xcode so far), and watchOS (prior # to Xcode 12.5). - platform_type = apple_fragment.single_arch_platform.platform_type - if ( - platform_type == apple_common.platform_type.macos or - ( - platform_type == apple_common.platform_type.watchos and - not _is_xcode_at_least_version(xcode_config, "12.5") - ) - ): + os = target_triples.unversioned_os(target_triple) + if (os == "macos" or + (os == "watchos" and + not _is_xcode_at_least_version(xcode_config, "12.5"))): return None return paths.join(apple_toolchain.sdk_dir(), "Developer/Library/Frameworks") def _swift_linkopts_providers( - apple_fragment, apple_toolchain, - platform, + target_triple, toolchain_label, xcode_config): """Returns providers containing flags that should be passed to the linker. @@ -191,9 +213,8 @@ def _swift_linkopts_providers( will link to the standard libraries correctly. Args: - apple_fragment: The `apple` configuration fragment. apple_toolchain: The `apple_common.apple_toolchain()` object. - platform: The `apple_platform` value describing the target platform. + 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. xcode_config: The Xcode configuration. @@ -208,18 +229,18 @@ def _swift_linkopts_providers( """ platform_developer_framework_dir = _platform_developer_framework_dir( apple_toolchain, - apple_fragment, + target_triple, xcode_config, ) sdk_developer_framework_dir = _sdk_developer_framework_dir( apple_toolchain, - apple_fragment, + target_triple, xcode_config, ) swift_lib_dir = paths.join( apple_toolchain.developer_dir(), "Toolchains/XcodeDefault.xctoolchain/usr/lib/swift", - platform.name_in_plist.lower(), + target_triples.platform_name_for_swift(target_triple), ) linkopts = [ @@ -315,7 +336,6 @@ def _resource_directory_configurator(developer_dir, _prerequisites, args): def _all_action_configs( additional_objc_copts, additional_swiftc_copts, - apple_fragment, apple_toolchain, generated_header_rewriter, needs_resource_directory, @@ -329,14 +349,13 @@ def _all_action_configs( previously passed directly by Bazel). additional_swiftc_copts: Additional Swift compiler flags obtained from the `swift` configuration fragment. - apple_fragment: The `apple` configuration fragment. apple_toolchain: The `apple_common.apple_toolchain()` object. generated_header_rewriter: An executable that will be invoked after compilation to rewrite the generated header, or None if this is not desired. needs_resource_directory: If True, the toolchain needs the resource directory passed explicitly to the compiler. - target_triple: The target triple. + target_triple: The triple of the platform being targeted. xcode_config: The Xcode configuration. Returns: @@ -344,12 +363,12 @@ def _all_action_configs( """ platform_developer_framework_dir = _platform_developer_framework_dir( apple_toolchain, - apple_fragment, + target_triple, xcode_config, ) sdk_developer_framework_dir = _sdk_developer_framework_dir( apple_toolchain, - apple_fragment, + target_triple, xcode_config, ) developer_framework_dirs = compact([ @@ -367,7 +386,10 @@ def _all_action_configs( swift_action_names.DUMP_AST, ], configurators = [ - swift_toolchain_config.add_arg("-target", target_triple), + swift_toolchain_config.add_arg( + "-target", + target_triples.str(target_triple), + ), swift_toolchain_config.add_arg( "-sdk", apple_toolchain.sdk_dir(), @@ -590,40 +612,13 @@ def _is_xcode_at_least_version(xcode_config, desired_version): desired_version_value = apple_common.dotted_version(desired_version) return current_version >= desired_version_value -def _swift_apple_target_triple(cpu, platform, version): - """Returns a target triple string for an Apple platform. - - Args: - cpu: The CPU of the target. - platform: The `apple_platform` value describing the target platform. - version: The target platform version as a dotted version string. - - Returns: - A target triple string describing the platform. - """ - platform_string = str(platform.platform_type) - if platform_string == "macos": - platform_string = "macosx" - - environment = "" - if not platform.is_device: - environment = "-simulator" - - return "{cpu}-apple-{platform}{version}{environment}".format( - cpu = cpu, - environment = environment, - platform = platform_string, - version = version, - ) - -def _xcode_env(xcode_config, platform): +def _xcode_env(target_triple, xcode_config): """Returns a dictionary containing Xcode-related environment variables. Args: + target_triple: The triple of the platform being targeted. xcode_config: The `XcodeVersionConfig` provider that contains information about the current Xcode configuration. - platform: The `apple_platform` value describing the target platform - being built. Returns: A `dict` containing Xcode-related environment variables that should be @@ -631,36 +626,28 @@ def _xcode_env(xcode_config, platform): """ return dicts.add( apple_common.apple_host_system_env(xcode_config), - apple_common.target_apple_env(xcode_config, platform), + apple_common.target_apple_env( + xcode_config, + _bazel_apple_platform(target_triple), + ), ) def _xcode_swift_toolchain_impl(ctx): - apple_fragment = ctx.fragments.apple cpp_fragment = ctx.fragments.cpp apple_toolchain = apple_common.apple_toolchain() cc_toolchain = find_cpp_toolchain(ctx) - # TODO(https://github.com/bazelbuild/bazel/issues/14291): Always use the - # value from ctx.fragments.apple.single_arch_cpu - if cc_toolchain.cpu.startswith("darwin_"): - cpu = cc_toolchain.cpu[len("darwin_"):] - else: - cpu = apple_fragment.single_arch_cpu + target_triple = target_triples.normalize_for_swift( + target_triples.parse(cc_toolchain.target_gnu_system_name), + ) - platform = apple_fragment.single_arch_platform xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig] - target_os_version = xcode_config.minimum_os_for_platform_type( - platform.platform_type, - ) - target = _swift_apple_target_triple(cpu, platform, target_os_version) - swift_linkopts_providers = _swift_linkopts_providers( - apple_fragment, - apple_toolchain, - platform, - ctx.label, - xcode_config, + apple_toolchain = apple_toolchain, + target_triple = target_triple, + toolchain_label = ctx.label, + xcode_config = xcode_config, ) # `--define=SWIFT_USE_TOOLCHAIN_ROOT=` is a rapid development feature @@ -717,7 +704,7 @@ def _xcode_swift_toolchain_impl(ctx): if _is_xcode_at_least_version(xcode_config, "12.5"): requested_features.append(SWIFT_FEATURE_SUPPORTS_SYSTEM_MODULE_FLAG) - env = _xcode_env(platform = platform, xcode_config = xcode_config) + env = _xcode_env(target_triple = target_triple, xcode_config = xcode_config) execution_requirements = xcode_config.execution_info() generated_header_rewriter = resolve_optional_tool( ctx, @@ -739,11 +726,10 @@ def _xcode_swift_toolchain_impl(ctx): ctx.fragments.objc, ), additional_swiftc_copts = ctx.fragments.swift.copts(), - apple_fragment = apple_fragment, apple_toolchain = apple_toolchain, generated_header_rewriter = generated_header_rewriter, needs_resource_directory = swift_executable or toolchain_root, - target_triple = target, + target_triple = target_triple, xcode_config = xcode_config, ) @@ -883,7 +869,6 @@ for incremental compilation using a persistent mode. ), doc = "Represents a Swift compiler toolchain provided by Xcode.", fragments = [ - "apple", "cpp", "objc", "swift",