diff --git a/apple/build_settings/build_settings.bzl b/apple/build_settings/build_settings.bzl index 8858ac1980..9818c6f742 100644 --- a/apple/build_settings/build_settings.bzl +++ b/apple/build_settings/build_settings.bzl @@ -35,6 +35,12 @@ Declare a code signing identity, to be used in all code signing flows related to "use_tree_artifacts_outputs": struct( doc = """ Enables Bazel's tree artifacts for Apple bundle rules (instead of archives). +""", + default = False, + ), + "no_library_evolution": struct( + doc = """ +Disables library evolution and prevents the emission of .swiftinterface files. """, default = False, ), diff --git a/apple/internal/partials/swift_framework.bzl b/apple/internal/partials/swift_framework.bzl index f160e59195..4a586ceacf 100644 --- a/apple/internal/partials/swift_framework.bzl +++ b/apple/internal/partials/swift_framework.bzl @@ -70,14 +70,25 @@ issue with a reproducible error case. found_module_name = swift_module.name - bundle_interface = swift_info_support.declare_swiftinterface( - actions = actions, - arch = arch, - label_name = label_name, - output_discriminator = output_discriminator, - swiftinterface = swift_module.swift.swiftinterface, - ) - bundle_files.append((processor.location.bundle, modules_parent, depset([bundle_interface]))) + # A swiftinterface will not be present when library evolution is disabled, if so, fallback to swiftmodule. + if swift_module.swift.swiftinterface: + bundle_interface = swift_info_support.declare_swiftinterface( + actions = actions, + arch = arch, + label_name = label_name, + output_discriminator = output_discriminator, + swiftinterface = swift_module.swift.swiftinterface, + ) + bundle_files.append((processor.location.bundle, modules_parent, depset([bundle_interface]))) + else: + bundle_swiftmodule = swift_info_support.declare_swiftmodule( + actions = actions, + arch = arch, + label_name = label_name, + output_discriminator = output_discriminator, + swiftmodule = swift_module.swift.swiftmodule, + ) + bundle_files.append((processor.location.bundle, modules_parent, depset([bundle_swiftmodule]))) bundle_doc = swift_info_support.declare_swiftdoc( actions = actions, diff --git a/apple/internal/swift_info_support.bzl b/apple/internal/swift_info_support.bzl index 6531fecbb7..22bf14ff64 100644 --- a/apple/internal/swift_info_support.bzl +++ b/apple/internal/swift_info_support.bzl @@ -81,7 +81,7 @@ swift_library dependencies.\ """, ) - if not all([module.name, module.swift.swiftdoc, module.swift.swiftinterface]): + if not all([module.name, module.swift.swiftdoc]) or not (module.swift.swiftmodule or module.swift.swiftinterface): fail( """\ error: Could not find all required artifacts and information to build a Swift framework. \ @@ -246,6 +246,38 @@ def _declare_swiftinterface( ) return bundle_interface +def _declare_swiftmodule( + *, + actions, + arch, + label_name, + output_discriminator, + swiftmodule): + """Declares the swiftmodule for this Swift framework. + + Args: + actions: The actions provider from `ctx.actions`. + arch: The cpu architecture that the generated swiftdoc belongs to. + label_name: Name of the target being built. + output_discriminator: A string to differentiate between different target intermediate files + or `None`. + swiftmodule: A File referencing the swiftmodule file from a SwiftInfo provider. + + Returns: + A File referencing the intermediate swiftmodule. + """ + bundle_module = intermediates.file( + actions = actions, + target_name = label_name, + output_discriminator = output_discriminator, + file_name = "{}.swiftmodule".format(arch), + ) + actions.symlink( + target_file = swiftmodule, + output = bundle_module, + ) + return bundle_module + swift_info_support = struct( verify_found_module_name = _verify_found_module_name, modules_from_avoid_deps = _modules_from_avoid_deps, @@ -254,4 +286,5 @@ swift_info_support = struct( declare_generated_header = _declare_generated_header, declare_swiftdoc = _declare_swiftdoc, declare_swiftinterface = _declare_swiftinterface, + declare_swiftmodule = _declare_swiftmodule, ) diff --git a/apple/internal/transition_support.bzl b/apple/internal/transition_support.bzl index 113f23255a..b1c5051bfa 100644 --- a/apple/internal/transition_support.bzl +++ b/apple/internal/transition_support.bzl @@ -249,6 +249,7 @@ def _command_line_options( default_platforms = [settings[_CPU_TO_DEFAULT_PLATFORM_FLAG[cpu]]] if _is_bazel_7 else [] return { build_settings_labels.use_tree_artifacts_outputs: force_bundle_outputs if force_bundle_outputs else settings[build_settings_labels.use_tree_artifacts_outputs], + build_settings_labels.no_library_evolution: settings[build_settings_labels.no_library_evolution], "//command_line_option:apple configuration distinguisher": "applebin_" + platform_type, "//command_line_option:apple_platform_type": platform_type, "//command_line_option:apple_platforms": apple_platforms, @@ -361,7 +362,10 @@ def _command_line_options_for_xcframework_platform( environment = target_environment, platform_type = platform_type, ): _command_line_options( - emit_swiftinterface = True, + emit_swiftinterface = _should_emit_swiftinterface( + settings, + is_xcframework = True, + ), environment_arch = resolved_environment_arch, minimum_os_version = minimum_os_version, platform_type = platform_type, @@ -372,11 +376,28 @@ def _command_line_options_for_xcframework_platform( return output_dictionary +def _should_emit_swiftinterface(settings, is_xcframework = False): + """Determines if a .swiftinterface file should be generated for Swift dependencies. + + By providing the option to disable the emission of these files, it allows consumers to opt out + of library evolution features, offering flexibility in how Swift dependencies are integrated and managed. + """ + + # Do not emit swiftinterface file when library evolution is disabled for a given build + if settings[build_settings_labels.no_library_evolution]: + return False + + # For iOS and tvOS static frameworks, it's historically been required for the underlying swift_library targets + # to generate a Swift interface file, though this is primarily for legacy support. `swiftmodule` files may + # be used as an alternative. The introduction of a private attribute `_emitswiftinterface` within these + # rules allows for selective configuration of Swift build settings across the build graph. + return is_xcframework or hasattr(attr, "_emitswiftinterface") + def _apple_rule_base_transition_impl(settings, attr): """Rule transition for Apple rules using Bazel CPUs and a valid Apple split transition.""" platform_type = attr.platform_type return _command_line_options( - emit_swiftinterface = hasattr(attr, "_emitswiftinterface"), + emit_swiftinterface = _should_emit_swiftinterface(settings), environment_arch = _environment_archs(platform_type, settings)[0], minimum_os_version = attr.minimum_os_version, platform_type = platform_type, @@ -389,6 +410,7 @@ def _apple_rule_base_transition_impl(settings, attr): # - https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java _apple_rule_common_transition_inputs = [ build_settings_labels.use_tree_artifacts_outputs, + build_settings_labels.no_library_evolution, "//command_line_option:apple_crosstool_top", ] + _CPU_TO_DEFAULT_PLATFORM_FLAG.values() _apple_rule_base_transition_inputs = _apple_rule_common_transition_inputs + [ @@ -407,6 +429,7 @@ _apple_platform_transition_inputs = _apple_platforms_rule_base_transition_inputs ] _apple_rule_base_transition_outputs = [ build_settings_labels.use_tree_artifacts_outputs, + build_settings_labels.no_library_evolution, "//command_line_option:apple configuration distinguisher", "//command_line_option:apple_platform_type", "//command_line_option:apple_platforms", @@ -447,7 +470,7 @@ def _apple_platforms_rule_base_transition_impl(settings, attr): environment_arch = _environment_archs(platform_type, settings)[0] return _command_line_options( apple_platforms = settings["//command_line_option:apple_platforms"], - emit_swiftinterface = hasattr(attr, "_emitswiftinterface"), + emit_swiftinterface = _should_emit_swiftinterface(settings), environment_arch = environment_arch, minimum_os_version = minimum_os_version, platform_type = platform_type, @@ -470,7 +493,7 @@ def _apple_platforms_rule_bundle_output_base_transition_impl(settings, attr): environment_arch = _environment_archs(platform_type, settings)[0] return _command_line_options( apple_platforms = settings["//command_line_option:apple_platforms"], - emit_swiftinterface = hasattr(attr, "_emitswiftinterface"), + emit_swiftinterface = _should_emit_swiftinterface(settings), environment_arch = environment_arch, force_bundle_outputs = True, minimum_os_version = minimum_os_version, @@ -547,10 +570,7 @@ def _apple_platform_split_transition_impl(settings, attr): output_dictionary = {} invalid_requested_archs = [] - # iOS and tvOS static frameworks require underlying swift_library targets generate a Swift - # interface file. These rules define a private attribute called `_emitswiftinterface` that - # let's this transition flip rules_swift config down the build graph. - emit_swiftinterface = hasattr(attr, "_emitswiftinterface") + emit_swiftinterface = _should_emit_swiftinterface(settings) if settings["//command_line_option:incompatible_enable_apple_toolchain_resolution"]: platforms = (