From b106010153c9777480119bada7e42a7e2c2eaf1b Mon Sep 17 00:00:00 2001 From: Luis Padron Date: Tue, 16 Dec 2025 00:52:09 -0500 Subject: [PATCH] Support setting minimum OS version in `-target` See the linked issue in rules_swift_package_manager for rational for this change. We need to allow targets to set their target triple with the min os version. Changes in apple_support to export the Xcode SDK information allows us to pipe this down to the rules. Related: https://github.com/cgrindel/rules_swift_package_manager/issues/892 --- doc/api.md | 7 +- doc/providers.md | 3 +- doc/rules.md | 23 +++-- swift/internal/attrs.bzl | 15 +++ swift/internal/compiling.bzl | 6 ++ swift/internal/target_triples.bzl | 29 ++++++ swift/providers.bzl | 6 ++ swift/swift_binary.bzl | 1 + swift/swift_library.bzl | 1 + swift/swift_test.bzl | 7 ++ swift/toolchains/xcode_swift_toolchain.bzl | 62 +++++++++++- test/BUILD | 3 + test/fixtures/minimum_os_version/BUILD | 36 +++++++ test/fixtures/minimum_os_version/empty.swift | 1 + test/minimum_os_version_tests.bzl | 100 +++++++++++++++++++ 15 files changed, 287 insertions(+), 13 deletions(-) create mode 100644 test/fixtures/minimum_os_version/BUILD create mode 100644 test/fixtures/minimum_os_version/empty.swift create mode 100644 test/minimum_os_version_tests.bzl diff --git a/doc/api.md b/doc/api.md index 98622bb28..e4d0ff07c 100644 --- a/doc/api.md +++ b/doc/api.md @@ -166,9 +166,9 @@ A C++ `FeatureConfiguration` value (see
 swift_common.compile(*, actions, additional_inputs, cc_infos, copts, defines, exec_group,
                      extra_swift_infos, feature_configuration, generated_header_name, is_test,
-                     include_dev_srch_paths, module_name, package_name, plugins, private_cc_infos,
-                     private_swift_infos, srcs, swift_infos, swift_toolchain, target_name,
-                     toolchain_type, workspace_name)
+                     include_dev_srch_paths, minimum_os_version, module_name, package_name, plugins,
+                     private_cc_infos, private_swift_infos, srcs, swift_infos, swift_toolchain,
+                     target_name, toolchain_type, workspace_name)
 
Compiles a Swift module. @@ -189,6 +189,7 @@ Compiles a Swift module. | generated_header_name | The name of the Objective-C generated header that should be generated for this module. If omitted, no header will be generated. | `None` | | is_test | Deprecated. This argument will be removed in the next major release. Use the `include_dev_srch_paths` attribute instead. Represents if the `testonly` value of the context. | `None` | | include_dev_srch_paths | A `bool` that indicates whether the developer framework search paths will be added to the compilation command. | `None` | +| minimum_os_version | A string representing the minimum OS version that this target should be compiled for (e.g., "17.0"). If provided, this is used to create a versioned target triple for the `-target` flag. If `None`, the default minimum OS version from the SDK is used. | `None` | | module_name | The name of the Swift module being compiled. This must be present and valid; use `derive_swift_module_name` to generate a default from the target's label if needed. | none | | package_name | The semantic package of the name of the Swift module being compiled. | none | | plugins | A list of `SwiftCompilerPluginInfo` providers that represent plugins that should be loaded by the compiler. | `[]` | diff --git a/doc/providers.md b/doc/providers.md index cc8e6f712..1402493bf 100644 --- a/doc/providers.md +++ b/doc/providers.md @@ -114,7 +114,7 @@ SwiftToolchainInfo(action_configsdeveloper_dirs, entry_point_linkopts_provider, feature_allowlists, generated_header_module_implicit_deps_providers, implicit_deps_providers, module_aliases, package_configurations, requested_features, root_dir, swift_worker, - test_configuration, tool_configs, unsupported_features) + test_configuration, tool_configs, unsupported_features, xcode_sdk_info) Propagates information about a Swift toolchain to compilation and linking rules @@ -144,5 +144,6 @@ that use the toolchain. | test_configuration | `Struct` containing the following fields:

* `binary_name`: A template string used to compute the name of the output binary for `swift_test` rules. Any occurrences of the string `"{name}"` will be substituted by the name of the target.

* `env`: A `dict` of environment variables to be set when running tests that were built with this toolchain.

* `execution_requirements`: A `dict` of execution requirements for tests that were built with this toolchain.

* `objc_test_discovery`: A Boolean value indicating whether test targets should discover tests dynamically using the Objective-C runtime.

* `test_linking_contexts`: A list of `CcLinkingContext`s that provide additional flags to use when linking test binaries.

This is used, for example, with Xcode-based toolchains to ensure that the `xctest` helper and coverage tools are found in the correct developer directory when running tests. | | tool_configs | This field is an internal implementation detail of the build rules. | | unsupported_features | `List` of `string`s. Features that should be implicitly disabled by default for targets built using this toolchain, unless overridden by the user by listing them in the `features` attribute of a target/package or in the `--features` command line flag.

These features determine various compilation and debugging behaviors of the Swift build rules, and they are also passed to the C++ APIs used when linking (so features defined in CROSSTOOL may be used here). | +| xcode_sdk_info | An optional `XcodeSdkVariantInfo` provider from `apple_support` that contains information about the current Xcode SDK, including:

This field may be `None` on unsupported platforms. | diff --git a/doc/rules.md b/doc/rules.md index 8c5b63dce..d87c86397 100644 --- a/doc/rules.md +++ b/doc/rules.md @@ -42,7 +42,7 @@ On this page:
 swift_binary(name, deps, srcs, data, additional_linker_inputs, copts, defines, env, linkopts,
-             malloc, module_name, package_name, plugins, stamp, swiftc_inputs)
+             malloc, minimum_os_version, module_name, package_name, plugins, stamp, swiftc_inputs)
 
Compiles and links Swift code into an executable binary. @@ -74,6 +74,7 @@ please use one of the platform-specific application rules in | env | Specifies additional environment variables to set when the test is executed by `bazel run` or `bazel test`.

The values of these environment variables are subject to `$(location)` and "Make variable" substitution.

NOTE: The environment variables are not set when you run the target outside of Bazel (for example, by manually executing the binary in `bazel-bin/`). | Dictionary: String -> String | optional | `{}` | | linkopts | Additional linker options that should be passed to `clang`. These strings are subject to `$(location ...)` expansion. | List of strings | optional | `[]` | | malloc | Override the default dependency on `malloc`.

By default, Swift binaries are linked against `@bazel_tools//tools/cpp:malloc"`, which is an empty library and the resulting binary will use libc's `malloc`. This label must refer to a `cc_library` rule. | Label | optional | `"@bazel_tools//tools/cpp:malloc"` | +| minimum_os_version | The minimum OS version that this target should be built for.

If not specified, the default minimum OS version is determined by the SDK's minimum supported OS version for the target platform. This ensures that code is always compiled for a deployment target that the SDK supports.

The effective minimum OS version used for compilation is the maximum of: - The value of this attribute (if specified) - The SDK's minimum supported OS version for the platform (if available)

This attribute has no effect on platforms that do not support versioned target triples. | String | optional | `""` | | module_name | The name of the Swift module being built.

If left unspecified, the module name will be computed based on the target's build label, by stripping the leading `//` and replacing `/`, `:`, and other non-identifier characters with underscores. | String | optional | `""` | | package_name | The semantic package of the Swift target being built. Targets with the same package_name can access APIs using the 'package' access control modifier in Swift 5.9+. | String | optional | `""` | | plugins | A list of `swift_compiler_plugin` targets that should be loaded by the compiler when compiling this module and any modules that directly depend on it. | List of labels | optional | `[]` | @@ -87,7 +88,8 @@ please use one of the platform-specific application rules in
 swift_compiler_plugin(name, deps, srcs, data, additional_linker_inputs, copts, defines, linkopts,
-                      malloc, module_name, package_name, plugins, stamp, swiftc_inputs)
+                      malloc, minimum_os_version, module_name, package_name, plugins, stamp,
+                      swiftc_inputs)
 
Compiles and links a Swift compiler plugin (for example, a macro). @@ -163,6 +165,7 @@ swift_library( | defines | A list of defines to add to the compilation command line.

Note that unlike C-family languages, Swift defines do not have values; they are simply identifiers that are either defined or undefined. So strings in this list should be simple identifiers, **not** `name=value` pairs.

Each string is prepended with `-D` and added to the command line. Unlike `copts`, these flags are added for the target and every target that depends on it, so use this attribute with caution. It is preferred that you add defines directly to `copts`, only using this feature in the rare case that a library needs to propagate a symbol up to those that depend on it. | List of strings | optional | `[]` | | linkopts | Additional linker options that should be passed to `clang`. These strings are subject to `$(location ...)` expansion. | List of strings | optional | `[]` | | malloc | Override the default dependency on `malloc`.

By default, Swift binaries are linked against `@bazel_tools//tools/cpp:malloc"`, which is an empty library and the resulting binary will use libc's `malloc`. This label must refer to a `cc_library` rule. | Label | optional | `"@bazel_tools//tools/cpp:malloc"` | +| minimum_os_version | The minimum OS version that this target should be built for.

If not specified, the default minimum OS version is determined by the SDK's minimum supported OS version for the target platform. This ensures that code is always compiled for a deployment target that the SDK supports.

The effective minimum OS version used for compilation is the maximum of: - The value of this attribute (if specified) - The SDK's minimum supported OS version for the platform (if available)

This attribute has no effect on platforms that do not support versioned target triples. | String | optional | `""` | | module_name | The name of the Swift module being built.

If left unspecified, the module name will be computed based on the target's build label, by stripping the leading `//` and replacing `/`, `:`, and other non-identifier characters with underscores. | String | optional | `""` | | package_name | The semantic package of the Swift target being built. Targets with the same package_name can access APIs using the 'package' access control modifier in Swift 5.9+. | String | optional | `""` | | plugins | A list of `swift_compiler_plugin` targets that should be loaded by the compiler when compiling this module and any modules that directly depend on it. | List of labels | optional | `[]` | @@ -458,7 +461,8 @@ its transitive dependencies be propagated.
 swift_library(name, deps, srcs, data, always_include_developer_search_paths, alwayslink, copts,
               defines, generated_header_name, generates_header, library_evolution, linkopts,
-              linkstatic, module_name, package_name, plugins, private_deps, swiftc_inputs)
+              linkstatic, minimum_os_version, module_name, package_name, plugins, private_deps,
+              swiftc_inputs)
 
Compiles and links Swift code into a static library and Swift module. @@ -481,6 +485,7 @@ Compiles and links Swift code into a static library and Swift module. | library_evolution | Indicates whether the Swift code should be compiled with library evolution mode enabled.

This attribute should be used to compile a module that will be distributed as part of a client-facing (non-implementation-only) module in a library or framework that will be distributed for use outside of the Bazel build graph. Setting this to true will compile the module with the `-library-evolution` flag and emit a `.swiftinterface` file as one of the compilation outputs. | Boolean | optional | `False` | | linkopts | Additional linker options that should be passed to the linker for the binary that depends on this target. These strings are subject to `$(location ...)` and ["Make" variable](https://docs.bazel.build/versions/master/be/make-variables.html) expansion. | List of strings | optional | `[]` | | linkstatic | If True, the Swift module will be built for static linking. This will make all interfaces internal to the module that is being linked against and will inform the consuming module that the objects will be locally available (which may potentially avoid a PLT relocation). Set to `False` to build a `.so` or `.dll`. | Boolean | optional | `True` | +| minimum_os_version | The minimum OS version that this target should be built for.

If not specified, the default minimum OS version is determined by the SDK's minimum supported OS version for the target platform. This ensures that code is always compiled for a deployment target that the SDK supports.

The effective minimum OS version used for compilation is the maximum of: - The value of this attribute (if specified) - The SDK's minimum supported OS version for the platform (if available)

This attribute has no effect on platforms that do not support versioned target triples. | String | optional | `""` | | module_name | The name of the Swift module being built.

If left unspecified, the module name will be computed based on the target's build label, by stripping the leading `//` and replacing `/`, `:`, and other non-identifier characters with underscores. | String | optional | `""` | | package_name | The semantic package of the Swift target being built. Targets with the same package_name can access APIs using the 'package' access control modifier in Swift 5.9+. | String | optional | `""` | | plugins | A list of `swift_compiler_plugin` targets that should be loaded by the compiler when compiling this module and any modules that directly depend on it. | List of labels | optional | `[]` | @@ -617,8 +622,8 @@ remaining modules collected are not present in the `aliases` of the
 swift_overlay(name, deps, srcs, always_include_developer_search_paths, alwayslink, copts, defines,
-              library_evolution, linkopts, linkstatic, package_name, plugins, private_deps,
-              swiftc_inputs)
+              library_evolution, linkopts, linkstatic, minimum_os_version, package_name, plugins,
+              private_deps, swiftc_inputs)
 
A Swift overlay that sits on top of a C/Objective-C library, allowing an author @@ -706,6 +711,7 @@ almost always an anti-pattern. | library_evolution | Indicates whether the Swift code should be compiled with library evolution mode enabled.

This attribute should be used to compile a module that will be distributed as part of a client-facing (non-implementation-only) module in a library or framework that will be distributed for use outside of the Bazel build graph. Setting this to true will compile the module with the `-library-evolution` flag and emit a `.swiftinterface` file as one of the compilation outputs. | Boolean | optional | `False` | | linkopts | Additional linker options that should be passed to the linker for the binary that depends on this target. These strings are subject to `$(location ...)` and ["Make" variable](https://docs.bazel.build/versions/master/be/make-variables.html) expansion. | List of strings | optional | `[]` | | linkstatic | If True, the Swift module will be built for static linking. This will make all interfaces internal to the module that is being linked against and will inform the consuming module that the objects will be locally available (which may potentially avoid a PLT relocation). Set to `False` to build a `.so` or `.dll`. | Boolean | optional | `True` | +| minimum_os_version | The minimum OS version that this target should be built for.

If not specified, the default minimum OS version is determined by the SDK's minimum supported OS version for the target platform. This ensures that code is always compiled for a deployment target that the SDK supports.

The effective minimum OS version used for compilation is the maximum of: - The value of this attribute (if specified) - The SDK's minimum supported OS version for the platform (if available)

This attribute has no effect on platforms that do not support versioned target triples. | String | optional | `""` | | package_name | The semantic package of the Swift target being built. Targets with the same package_name can access APIs using the 'package' access control modifier in Swift 5.9+. | String | optional | `""` | | plugins | A list of `swift_compiler_plugin` targets that should be loaded by the compiler when compiling this module and any modules that directly depend on it. | List of labels | optional | `[]` | | private_deps | A list of targets that are implementation-only dependencies of the target being built. Libraries/linker flags from these dependencies will be propagated to dependent for linking, but artifacts/flags required for compilation (such as .swiftmodule files, C headers, and search paths) will not be propagated.

Allowed kinds of dependencies are:

* `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` and `objc_library` (or anything propagating `CcInfo`) | List of labels | optional | `[]` | @@ -772,7 +778,7 @@ swift_proto_compiler(name, name, deps, srcs, data, additional_compiler_deps, additional_compiler_info, always_include_developer_search_paths, alwayslink, compilers, copts, defines, generated_header_name, generates_header, library_evolution, linkopts, linkstatic, - module_name, package_name, plugins, protos, swiftc_inputs) + minimum_os_version, module_name, package_name, plugins, protos, swiftc_inputs) Generates a Swift static library from one or more targets producing `ProtoInfo`. @@ -833,6 +839,7 @@ swift_proto_library( | library_evolution | Indicates whether the Swift code should be compiled with library evolution mode enabled.

This attribute should be used to compile a module that will be distributed as part of a client-facing (non-implementation-only) module in a library or framework that will be distributed for use outside of the Bazel build graph. Setting this to true will compile the module with the `-library-evolution` flag and emit a `.swiftinterface` file as one of the compilation outputs. | Boolean | optional | `False` | | linkopts | Additional linker options that should be passed to the linker for the binary that depends on this target. These strings are subject to `$(location ...)` and ["Make" variable](https://docs.bazel.build/versions/master/be/make-variables.html) expansion. | List of strings | optional | `[]` | | linkstatic | If True, the Swift module will be built for static linking. This will make all interfaces internal to the module that is being linked against and will inform the consuming module that the objects will be locally available (which may potentially avoid a PLT relocation). Set to `False` to build a `.so` or `.dll`. | Boolean | optional | `True` | +| minimum_os_version | The minimum OS version that this target should be built for.

If not specified, the default minimum OS version is determined by the SDK's minimum supported OS version for the target platform. This ensures that code is always compiled for a deployment target that the SDK supports.

The effective minimum OS version used for compilation is the maximum of: - The value of this attribute (if specified) - The SDK's minimum supported OS version for the platform (if available)

This attribute has no effect on platforms that do not support versioned target triples. | String | optional | `""` | | module_name | The name of the Swift module being built.

If left unspecified, the module name will be computed based on the target's build label, by stripping the leading `//` and replacing `/`, `:`, and other non-identifier characters with underscores. | String | optional | `""` | | package_name | The semantic package of the Swift target being built. Targets with the same package_name can access APIs using the 'package' access control modifier in Swift 5.9+. | String | optional | `""` | | plugins | A list of `swift_compiler_plugin` targets that should be loaded by the compiler when compiling this module and any modules that directly depend on it. | List of labels | optional | `[]` | @@ -924,7 +931,8 @@ swift_binary(
 swift_test(name, deps, srcs, data, additional_linker_inputs, copts, defines, discover_tests, env,
-           env_inherit, linkopts, malloc, module_name, package_name, plugins, stamp, swiftc_inputs)
+           env_inherit, linkopts, malloc, minimum_os_version, module_name, package_name, plugins,
+           stamp, swiftc_inputs)
 
Compiles and links Swift code into an executable test target. @@ -1006,6 +1014,7 @@ root of your workspace (i.e. `$(SRCROOT)`). | env_inherit | Specifies additional environment variables to inherit from the external environment when the test is executed by `bazel test`. | List of strings | optional | `[]` | | linkopts | Additional linker options that should be passed to `clang`. These strings are subject to `$(location ...)` expansion. | List of strings | optional | `[]` | | malloc | Override the default dependency on `malloc`.

By default, Swift binaries are linked against `@bazel_tools//tools/cpp:malloc"`, which is an empty library and the resulting binary will use libc's `malloc`. This label must refer to a `cc_library` rule. | Label | optional | `"@bazel_tools//tools/cpp:malloc"` | +| minimum_os_version | The minimum OS version that this target should be built for.

If not specified, the default minimum OS version is determined by the SDK's minimum supported OS version for the target platform. This ensures that code is always compiled for a deployment target that the SDK supports.

The effective minimum OS version used for compilation is the maximum of: - The value of this attribute (if specified) - The SDK's minimum supported OS version for the platform (if available)

This attribute has no effect on platforms that do not support versioned target triples. | String | optional | `""` | | module_name | The name of the Swift module being built.

If left unspecified, the module name will be computed based on the target's build label, by stripping the leading `//` and replacing `/`, `:`, and other non-identifier characters with underscores. | String | optional | `""` | | package_name | The semantic package of the Swift target being built. Targets with the same package_name can access APIs using the 'package' access control modifier in Swift 5.9+. | String | optional | `""` | | plugins | A list of `swift_compiler_plugin` targets that should be loaded by the compiler when compiling this module and any modules that directly depend on it. | List of labels | optional | `[]` | diff --git a/swift/internal/attrs.bzl b/swift/internal/attrs.bzl index d7a9ef797..9cb4f916b 100644 --- a/swift/internal/attrs.bzl +++ b/swift/internal/attrs.bzl @@ -169,6 +169,21 @@ own `swift_library` instead. doc = """\ Additional files that are referenced using `$(location ...)` in attributes that support location expansion. +""", + ), + "minimum_os_version": attr.string( + doc = """\ +The minimum OS version that this target should be built for. + +If not specified, the default minimum OS version is determined by the SDK's +minimum supported OS version for the target platform. This ensures that code +is always compiled for a deployment target that the SDK supports. + +The effective minimum OS version used for compilation is the maximum of: +- The value of this attribute (if specified) +- The SDK's minimum supported OS version for the platform (if available) + +This attribute has no effect on platforms that do not support versioned target triples. """, ), }, diff --git a/swift/internal/compiling.bzl b/swift/internal/compiling.bzl index dfbd725cf..629c3aed6 100644 --- a/swift/internal/compiling.bzl +++ b/swift/internal/compiling.bzl @@ -326,6 +326,7 @@ def compile( generated_header_name = None, is_test = None, include_dev_srch_paths = None, + minimum_os_version = None, module_name, package_name, plugins = [], @@ -366,6 +367,10 @@ def compile( Represents if the `testonly` value of the context. include_dev_srch_paths: A `bool` that indicates whether the developer framework search paths will be added to the compilation command. + minimum_os_version: A string representing the minimum OS version that + this target should be compiled for (e.g., "17.0"). If provided, this + is used to create a versioned target triple for the `-target` flag. + If `None`, the default minimum OS version from the SDK is used. generated_header_name: The name of the Objective-C generated header that should be generated for this module. If omitted, no header will be generated. @@ -665,6 +670,7 @@ to use swift_common.compile(include_dev_srch_paths = ...) instead.\ genfiles_dir = feature_configuration._genfiles_dir, include_dev_srch_paths = include_dev_srch_paths_value, is_swift = True, + minimum_os_version = minimum_os_version, module_name = module_name, original_module_name = original_module_name, package_name = package_name, diff --git a/swift/internal/target_triples.bzl b/swift/internal/target_triples.bzl index cf6ea795d..42cebf496 100644 --- a/swift/internal/target_triples.bzl +++ b/swift/internal/target_triples.bzl @@ -223,6 +223,34 @@ def _str(triple): result += "-{}".format(triple.environment) return result +def _str_with_version(triple, os_version): + """Returns a string representation of the target triple with an OS version. + + This function creates a versioned target triple string suitable for the + Swift compiler's `-target` flag. For example, given a triple like + `arm64-apple-ios` and version `17.0`, it produces `arm64-apple-ios17.0`. + + Args: + triple: A target triple struct, as returned by `target_triples.make` or + `target_triples.parse`. + os_version: A string representing the OS version to append (e.g., + `"17.0"` or `"14.0"`). If `None` or empty, the triple is returned + without a version (equivalent to calling `str()`). + + Returns: + The string representation of the target triple with the OS version + appended to the OS component. + """ + if not os_version: + return _str(triple) + + unversioned_os = _unversioned_os(triple) + versioned_os = "{}{}".format(unversioned_os, os_version) + result = "{}-{}-{}".format(triple.cpu, triple.vendor, versioned_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. @@ -250,5 +278,6 @@ target_triples = struct( parse = _parse, platform_name_for_swift = _platform_name_for_swift, str = _str, + str_with_version = _str_with_version, unversioned_os = _unversioned_os, ) diff --git a/swift/providers.bzl b/swift/providers.bzl index f7ab88f9e..e5638180d 100644 --- a/swift/providers.bzl +++ b/swift/providers.bzl @@ -481,6 +481,12 @@ command line flag. These features determine various compilation and debugging behaviors of the Swift build rules, and they are also passed to the C++ APIs used when linking (so features defined in CROSSTOOL may be used here). +""", + "xcode_sdk_info": """\ +An optional `XcodeSdkVariantInfo` provider from `apple_support` that contains +information about the current Xcode SDK, including: + +This field may be `None` on unsupported platforms. """, }, ) diff --git a/swift/swift_binary.bzl b/swift/swift_binary.bzl index 4cacfd694..2d2bb3a49 100644 --- a/swift/swift_binary.bzl +++ b/swift/swift_binary.bzl @@ -120,6 +120,7 @@ def _swift_binary_impl(ctx): defines = ctx.attr.defines, feature_configuration = feature_configuration, include_dev_srch_paths = include_dev_srch_paths, + minimum_os_version = ctx.attr.minimum_os_version, module_name = module_name, package_name = ctx.attr.package_name, plugins = get_providers(ctx.attr.plugins, SwiftCompilerPluginInfo), diff --git a/swift/swift_library.bzl b/swift/swift_library.bzl index d8220413f..f77b1ec2c 100644 --- a/swift/swift_library.bzl +++ b/swift/swift_library.bzl @@ -189,6 +189,7 @@ def _swift_library_impl(ctx): feature_configuration = feature_configuration, generated_header_name = generated_header_name, include_dev_srch_paths = include_dev_srch_paths, + minimum_os_version = ctx.attr.minimum_os_version, module_name = module_name, package_name = ctx.attr.package_name, plugins = get_providers(ctx.attr.plugins, SwiftCompilerPluginInfo), diff --git a/swift/swift_test.bzl b/swift/swift_test.bzl index 50288adb5..2d0f2af72 100644 --- a/swift/swift_test.bzl +++ b/swift/swift_test.bzl @@ -214,6 +214,7 @@ def _do_compile( cc_infos, feature_configuration, include_dev_srch_paths, + minimum_os_version = None, module_name, name, package_name, @@ -233,6 +234,9 @@ def _do_compile( feature_configuration: The feature configuration to use for compiling. include_dev_srch_paths: A `bool` that indicates whether the developer framework search paths will be added to the compilation command. + minimum_os_version: A string representing the minimum OS version that + this target should be compiled for. May be `None` to use the SDK + default. module_name: The name of the module being compiled. name: The target name or a value derived from the target name that is used to name output files generated by the action. @@ -263,6 +267,7 @@ def _do_compile( defines = ctx.attr.defines, feature_configuration = feature_configuration, include_dev_srch_paths = include_dev_srch_paths, + minimum_os_version = minimum_os_version, module_name = module_name, package_name = package_name, plugins = plugins, @@ -364,6 +369,7 @@ def _swift_test_impl(ctx): cc_infos = deps_cc_infos, feature_configuration = feature_configuration, include_dev_srch_paths = include_dev_srch_paths, + minimum_os_version = ctx.attr.minimum_os_version, module_name = module_name, package_name = ctx.attr.package_name, plugins = get_providers(ctx.attr.plugins, SwiftCompilerPluginInfo), @@ -421,6 +427,7 @@ def _swift_test_impl(ctx): cc_infos = test_runner_deps_cc_infos, feature_configuration = feature_configuration, include_dev_srch_paths = include_dev_srch_paths, + minimum_os_version = ctx.attr.minimum_os_version, module_name = module_name + "__GeneratedTestDiscoveryRunner", name = ctx.label.name + "__GeneratedTestDiscoveryRunner", package_name = ctx.attr.package_name, diff --git a/swift/toolchains/xcode_swift_toolchain.bzl b/swift/toolchains/xcode_swift_toolchain.bzl index 269413fcf..df11977d4 100644 --- a/swift/toolchains/xcode_swift_toolchain.bzl +++ b/swift/toolchains/xcode_swift_toolchain.bzl @@ -27,6 +27,10 @@ load( "@bazel_tools//tools/cpp:toolchain_utils.bzl", "use_cpp_toolchain", ) +load( + "@build_bazel_apple_support//xcode:providers.bzl", + "XcodeSdkVariantInfo", +) load("@rules_cc//cc/common:cc_common.bzl", "cc_common") load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") load( @@ -398,6 +402,49 @@ def _make_resource_directory_configurator(developer_dir): return _resource_directory_configurator +def _make_target_triple_configurator(target_triple, xcode_sdk_info): + """Creates a configurator that adds the -target flag with an optional version. + + Args: + target_triple: The triple of the platform being targeted. + xcode_sdk_info: The XcodeSdkVariantInfo provider, or None if not available. + + Returns: + A configurator function that adds the -target flag to the command line. + """ + + def _target_triple_configurator(prerequisites, args): + # Get the user-specified minimum OS version from prerequisites + user_min_os = getattr(prerequisites, "minimum_os_version", None) + + # Get the SDK's minimum supported OS version if available + sdk_min_os = None + if xcode_sdk_info and xcode_sdk_info.minimum_supported_os_version: + sdk_min_os = str(xcode_sdk_info.minimum_supported_os_version) + + # Determine the effective minimum OS version: + # - If user specified a version, use max(user_version, sdk_min_version) + # - Otherwise, use SDK minimum (or None for unversioned triple) + effective_min_os = None + if user_min_os and sdk_min_os: + # Compare versions and use the higher one + user_version = apple_common.dotted_version(user_min_os) + sdk_version = apple_common.dotted_version(sdk_min_os) + if user_version >= sdk_version: + effective_min_os = user_min_os + else: + effective_min_os = sdk_min_os + elif user_min_os: + effective_min_os = user_min_os + elif sdk_min_os: + effective_min_os = sdk_min_os + + # Generate the target triple string (versioned or unversioned) + target_str = target_triples.str_with_version(target_triple, effective_min_os) + args.add("-target", target_str) + + return _target_triple_configurator + def _all_action_configs( additional_objc_copts, additional_swiftc_copts, @@ -405,7 +452,8 @@ def _all_action_configs( generated_header_rewriter, needs_resource_directory, target_triple, - xcode_config): + xcode_config, + xcode_sdk_info): """Returns the action configurations for the Swift toolchain. Args: @@ -422,6 +470,7 @@ def _all_action_configs( directory passed explicitly to the compiler. target_triple: The triple of the platform being targeted. xcode_config: The Xcode configuration. + xcode_sdk_info: The XcodeSdkVariantInfo provider, or None if not available. Returns: The action configurations for the Swift toolchain. @@ -441,7 +490,7 @@ def _all_action_configs( SWIFT_ACTION_SYNTHESIZE_INTERFACE, ], configurators = [ - add_arg("-target", target_triples.str(target_triple)), + _make_target_triple_configurator(target_triple, xcode_sdk_info), add_arg("-sdk", apple_toolchain.sdk_dir()), ], ), @@ -741,6 +790,13 @@ def _xcode_swift_toolchain_impl(ctx): xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig] + # Extract SDK variant info if available. This provides the SDK's minimum + # supported OS version, which is used to ensure Swift code is compiled with + # a deployment target that the SDK supports. + xcode_sdk_info = None + if XcodeSdkVariantInfo in ctx.attr._xcode_config: + xcode_sdk_info = ctx.attr._xcode_config[XcodeSdkVariantInfo] + # TODO: Remove once we drop bazel 7.x support if not bazel_features.cc.swift_fragment_removed: swiftcopts = list(ctx.fragments.swift.copts()) @@ -864,6 +920,7 @@ def _xcode_swift_toolchain_impl(ctx): needs_resource_directory = swift_executable or toolchain_root, target_triple = target_triple, xcode_config = xcode_config, + xcode_sdk_info = xcode_sdk_info, ) swift_toolchain_developer_paths = [] platform_developer_framework_dir = _platform_developer_framework_dir( @@ -939,6 +996,7 @@ def _xcode_swift_toolchain_impl(ctx): ), tool_configs = all_tool_configs, unsupported_features = unsupported_features, + xcode_sdk_info = xcode_sdk_info, ) return [ diff --git a/test/BUILD b/test/BUILD index 5929d0850..2546c9e95 100644 --- a/test/BUILD +++ b/test/BUILD @@ -13,6 +13,7 @@ load(":generated_header_tests.bzl", "generated_header_test_suite") load(":imported_framework_tests.bzl", "imported_framework_test_suite") load(":interop_hints_tests.bzl", "interop_hints_test_suite") load(":mainattr_tests.bzl", "mainattr_test_suite") +load(":minimum_os_version_tests.bzl", "minimum_os_version_test_suite") load(":module_cache_settings_tests.bzl", "module_cache_settings_test_suite") load(":module_interface_tests.bzl", "module_interface_test_suite") load(":module_mapping_tests.bzl", "module_mapping_test_suite") @@ -57,6 +58,8 @@ interop_hints_test_suite(name = "interop_hints") mainattr_test_suite(name = "mainattr") +minimum_os_version_test_suite(name = "minimum_os_version") + module_cache_settings_test_suite(name = "module_cache_settings") module_interface_test_suite(name = "module_interface") diff --git a/test/fixtures/minimum_os_version/BUILD b/test/fixtures/minimum_os_version/BUILD new file mode 100644 index 000000000..bf97419d1 --- /dev/null +++ b/test/fixtures/minimum_os_version/BUILD @@ -0,0 +1,36 @@ +load("//swift:swift_binary.bzl", "swift_binary") +load("//swift:swift_library.bzl", "swift_library") +load("//swift:swift_test.bzl", "swift_test") +load("//test/fixtures:common.bzl", "FIXTURE_TAGS") + +package( + default_visibility = ["//test:__subpackages__"], +) + +############################################################################### +# Fixtures for testing minimum_os_version attribute. + +# Library with explicit minimum_os_version +swift_library( + name = "lib_with_min_os", + srcs = ["empty.swift"], + minimum_os_version = "17.0", + tags = FIXTURE_TAGS, +) + +# Binary with explicit minimum_os_version +swift_binary( + name = "bin_with_min_os", + srcs = ["empty.swift"], + minimum_os_version = "17.0", + tags = FIXTURE_TAGS, +) + +# Test with explicit minimum_os_version +swift_test( + name = "test_with_min_os", + srcs = ["empty.swift"], + discover_tests = False, + minimum_os_version = "17.0", + tags = FIXTURE_TAGS, +) diff --git a/test/fixtures/minimum_os_version/empty.swift b/test/fixtures/minimum_os_version/empty.swift new file mode 100644 index 000000000..1600de1bf --- /dev/null +++ b/test/fixtures/minimum_os_version/empty.swift @@ -0,0 +1 @@ +// Empty Swift file used for testing minimum_os_version attribute. diff --git a/test/minimum_os_version_tests.bzl b/test/minimum_os_version_tests.bzl new file mode 100644 index 000000000..69bfba1ac --- /dev/null +++ b/test/minimum_os_version_tests.bzl @@ -0,0 +1,100 @@ +# Copyright 2024 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. + +"""Tests for minimum_os_version attribute and versioned target triples.""" + +load( + "//test/rules:action_command_line_test.bzl", + "make_action_command_line_test_rule", +) + +default_test = make_action_command_line_test_rule() + +split_test = make_action_command_line_test_rule( + config_settings = { + "//command_line_option:features": [ + "swift.split_derived_files_generation", + ], + }, +) + +def minimum_os_version_test_suite(name, tags = []): + """Test suite for minimum_os_version compiler arguments. + + Args: + name: The base name to be used in targets created by this macro. + tags: Additional tags to apply to each test. + """ + all_tags = [name] + tags + + # Test that library with minimum_os_version has version in target triple. + # We check for just the version appearing after "-target" which verifies the versioned + # triple is being passed to the compiler (e.g., "-target arm64-apple-macos17.0"). + + default_test( + name = "{}_lib_with_min_os_has_version_in_triple".format(name), + expected_argv = [ + "-target", + # The version should appear in the target triple + "17.0", + ], + mnemonic = "SwiftCompile", + tags = all_tags, + target_under_test = "//test/fixtures/minimum_os_version:lib_with_min_os", + target_compatible_with = ["@platforms//os:macos"], + ) + + # Test that binary with minimum_os_version has version in target triple. + default_test( + name = "{}_bin_with_min_os_has_version_in_triple".format(name), + expected_argv = [ + "-target", + "17.0", + ], + mnemonic = "SwiftCompile", + tags = all_tags, + target_under_test = "//test/fixtures/minimum_os_version:bin_with_min_os", + target_compatible_with = ["@platforms//os:macos"], + ) + + # Test that test with minimum_os_version has version in target triple. + default_test( + name = "{}_test_with_min_os_has_version_in_triple".format(name), + expected_argv = [ + "-target", + "17.0", + ], + mnemonic = "SwiftCompile", + tags = all_tags, + target_under_test = "//test/fixtures/minimum_os_version:test_with_min_os", + target_compatible_with = ["@platforms//os:macos"], + ) + + # Test that other modes also have the version in target. + split_test( + name = "{}_split_derive_files_has_version_in_triple".format(name), + expected_argv = [ + "-target", + "17.0", + ], + mnemonic = "SwiftDeriveFiles", + tags = all_tags, + target_under_test = "//test/fixtures/minimum_os_version:lib_with_min_os", + target_compatible_with = ["@platforms//os:macos"], + ) + + native.test_suite( + name = name, + tags = all_tags, + )