diff --git a/apple/internal/apple_xcframework_import.bzl b/apple/internal/apple_xcframework_import.bzl index 0c0908be4..bb666860f 100644 --- a/apple/internal/apple_xcframework_import.bzl +++ b/apple/internal/apple_xcframework_import.bzl @@ -158,8 +158,10 @@ def _get_xcframework_library( headers: List of File referencing XCFramework library header files. This can be either a single tree artifact or a list of regular artifacts. clang_module_map: File referencing the XCFramework library Clang modulemap file. - swift_module_interface: File referencing the XCFramework library Swift module interface - file (`.swiftinterface`). + swift_module_interface: File referencing the XCFramework library's primary Swift + module interface file (`.swiftinterface`). + swift_module_interfaces: List of File referencing all XCFramework library Swift + module interface files required during compilation. """ xcframework_library = None if not parse_xcframework_info_plist: @@ -255,6 +257,7 @@ def _get_xcframework_library_from_paths(*, target_triplet, xcframework): includes = includes, clang_module_map = module_maps[0] if module_maps else None, swiftmodule = swiftmodules, + swift_module_interfaces = swift_module_interfaces, swift_module_interface = swift_module_interfaces[0] if swift_module_interfaces else None, ) @@ -416,6 +419,7 @@ def _get_xcframework_library_with_xcframework_processor( includes = includes, clang_module_map = module_map_file, swiftmodule = [], + swift_module_interfaces = [swiftinterface_file] if swiftinterface_file else [], swift_module_interface = swiftinterface_file, framework_files = [], ) @@ -535,7 +539,7 @@ def _apple_dynamic_xcframework_import_impl(ctx): kind = "dynamic", label = label, libraries = [] if ctx.attr.bundle_only else [xcframework_library.binary], - swiftinterface_imports = [xcframework_library.swift_module_interface] if xcframework_library.swift_module_interface else [], + swiftinterface_imports = xcframework_library.swift_module_interfaces, swiftmodule_imports = xcframework_library.swiftmodule, ) providers.append(cc_info) @@ -674,7 +678,7 @@ def _apple_static_xcframework_import_impl(ctx): libraries = [xcframework_library.binary], framework_includes = xcframework_library.framework_includes, linkopts = sdk_linkopts + linkopts, - swiftinterface_imports = [xcframework_library.swift_module_interface] if xcframework_library.swift_module_interface else [], + swiftinterface_imports = xcframework_library.swift_module_interfaces, swiftmodule_imports = xcframework_library.swiftmodule, # User-specified includes are relative to the platform directory inside the xcframework. # For framework XCFrameworks, binary is inside .framework bundle, so go up one level. diff --git a/apple/internal/framework_import_support.bzl b/apple/internal/framework_import_support.bzl index 780fb5a4e..c0bfdfe9e 100644 --- a/apple/internal/framework_import_support.bzl +++ b/apple/internal/framework_import_support.bzl @@ -392,20 +392,26 @@ def _get_swift_module_files_with_target_triplet(target_triplet, swift_module_fil if target_triplet.environment != "device": environment = "-" + target_triplet.environment - target_triplet_file = files.get_file_with_name( - files = module_files.to_list(), - name = "{architecture}-{vendor}-{os}{environment}".format( - architecture = target_triplet.architecture, - environment = environment, - os = target_triplet.os, - vendor = target_triplet.vendor, - ), + module_files_list = module_files.to_list() + target_triplet_name = "{architecture}-{vendor}-{os}{environment}".format( + architecture = target_triplet.architecture, + environment = environment, + os = target_triplet.os, + vendor = target_triplet.vendor, ) - architecture_file = files.get_file_with_name( - files = module_files.to_list(), - name = target_triplet.architecture, - ) - filtered_files.append(target_triplet_file or architecture_file) + + for file_name in [ + target_triplet_name, + target_triplet_name + ".private", + target_triplet.architecture, + target_triplet.architecture + ".private", + ]: + matching_file = files.get_file_with_name( + files = module_files_list, + name = file_name, + ) + if matching_file: + filtered_files.append(matching_file) return filtered_files diff --git a/test/starlark_tests/apple_dynamic_xcframework_import_tests.bzl b/test/starlark_tests/apple_dynamic_xcframework_import_tests.bzl index ecd7cc4ba..01bf17bdd 100644 --- a/test/starlark_tests/apple_dynamic_xcframework_import_tests.bzl +++ b/test/starlark_tests/apple_dynamic_xcframework_import_tests.bzl @@ -18,6 +18,10 @@ load( "//apple/build_settings:build_settings.bzl", "build_settings_labels", ) +load( + "//test/starlark_tests/rules:action_inputs_test.bzl", + "make_action_inputs_test_rule", +) load( "//test/starlark_tests/rules:analysis_failure_message_test.bzl", "analysis_failure_message_test", @@ -49,6 +53,10 @@ analysis_output_group_info_files_with_xcframework_processor_test = make_analysis build_settings_labels.parse_xcframework_info_plist: True, }) +action_inputs_with_ios_x86_64_platform_test = make_action_inputs_test_rule({ + "//command_line_option:platforms": str(Label("@build_bazel_apple_support//platforms:ios_x86_64")), +}) + def apple_dynamic_xcframework_import_test_suite(name): """Test suite for apple_dynamic_xcframework_import. @@ -130,6 +138,16 @@ def apple_dynamic_xcframework_import_test_suite(name): ], tags = [name], ) + action_inputs_with_ios_x86_64_platform_test( + name = "{}_declares_private_swiftinterface_inputs".format(name), + target_under_test = "//test/starlark_tests/targets_under_test/ios:dynamic_swift_xcframework_with_private_swiftinterface_depending_swift_lib", + mnemonic = "SwiftCompile", + expected_inputs = [ + "Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/x86_64.swiftinterface", + "Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/x86_64.private.swiftinterface", + ], + tags = [name], + ) archive_contents_test( name = "{}_contains_implementation_deps_imported_xcframework_framework_files".format(name), build_type = "simulator", diff --git a/test/starlark_tests/rules/action_inputs_test.bzl b/test/starlark_tests/rules/action_inputs_test.bzl new file mode 100644 index 000000000..5e948526d --- /dev/null +++ b/test/starlark_tests/rules/action_inputs_test.bzl @@ -0,0 +1,132 @@ +# Copyright 2026 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. + +"""Rules for testing the contents of action input artifacts.""" + +load("@bazel_skylib//lib:collections.bzl", "collections") +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "unittest") + +def _action_inputs_test_impl(ctx): + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + actions = analysistest.target_actions(env) + mnemonic = ctx.attr.mnemonic + matching_actions = [ + action + for action in actions + if action.mnemonic == mnemonic + ] + + if not matching_actions: + actual_mnemonics = collections.uniq( + [action.mnemonic for action in actions], + ) + unittest.fail( + env, + ("Target '{}' registered no actions with the mnemonic '{}' " + + "(it had {}).").format( + str(target_under_test.label), + mnemonic, + actual_mnemonics, + ), + ) + return analysistest.end(env) + + action_inputs = [] + for action in matching_actions: + action_inputs.append([ + file.short_path + for file in action.inputs.to_list() + ]) + + if ctx.attr.expected_inputs: + matched_expected_inputs = False + for inputs in action_inputs: + contains_all_expected_inputs = True + for expected_input in ctx.attr.expected_inputs: + found_expected_input = False + for actual_input in inputs: + if expected_input in actual_input: + found_expected_input = True + break + if not found_expected_input: + contains_all_expected_inputs = False + break + + if contains_all_expected_inputs: + matched_expected_inputs = True + break + + if not matched_expected_inputs: + unittest.fail( + env, + ("Expected at least one '{}' action for target '{}' to contain all expected " + + "inputs {}. Actual inputs: {}").format( + mnemonic, + str(target_under_test.label), + ctx.attr.expected_inputs, + action_inputs, + ), + ) + return analysistest.end(env) + + if ctx.attr.not_expected_inputs: + for inputs in action_inputs: + for not_expected_input in ctx.attr.not_expected_inputs: + for actual_input in inputs: + if not_expected_input in actual_input: + unittest.fail( + env, + ("Expected '{}' action for target '{}' to not contain input '{}', " + + "but it did: {}").format( + mnemonic, + str(target_under_test.label), + not_expected_input, + inputs, + ), + ) + return analysistest.end(env) + + return analysistest.end(env) + +def make_action_inputs_test_rule(config_settings = {}): + """Returns a new `action_inputs_test`-like rule with custom configs.""" + return analysistest.make( + _action_inputs_test_impl, + attrs = { + "expected_inputs": attr.string_list( + doc = """\ +A list of path substrings that must all be present in the inputs of at least +one action with the requested mnemonic. +""", + ), + "mnemonic": attr.string( + mandatory = True, + doc = """\ +The mnemonic of the action to inspect on the target under test. It is expected +that at least one action with this mnemonic exists. +""", + ), + "not_expected_inputs": attr.string_list( + doc = """\ +A list of path substrings that must not be present in the inputs of any action +with the requested mnemonic. +""", + ), + }, + config_settings = config_settings, + ) + +action_inputs_test = make_action_inputs_test_rule() diff --git a/test/starlark_tests/targets_under_test/apple/BUILD b/test/starlark_tests/targets_under_test/apple/BUILD index 37589c18e..45dd98ad1 100644 --- a/test/starlark_tests/targets_under_test/apple/BUILD +++ b/test/starlark_tests/targets_under_test/apple/BUILD @@ -1491,6 +1491,53 @@ generate_dynamic_xcframework( tags = common.fixture_tags, ) +genrule( + name = "swift_3p_xcframework_with_generated_header_and_private_swiftinterface", + testonly = True, + srcs = ["//test/starlark_tests/targets_under_test/ios:ios_swift_dynamic_xcframework"], + outs = [ + "Swift3PFmwkWithGenHeader.xcframework/Info.plist", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64/Swift3PFmwkWithGenHeader.framework/Info.plist", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64/Swift3PFmwkWithGenHeader.framework/Swift3PFmwkWithGenHeader", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64/Swift3PFmwkWithGenHeader.framework/Headers/Swift3PFmwkWithGenHeader.h", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/arm64.swiftdoc", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/arm64.swiftinterface", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/arm64.private.swiftinterface", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64/Swift3PFmwkWithGenHeader.framework/Modules/module.modulemap", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Swift3PFmwkWithGenHeader", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Headers/Swift3PFmwkWithGenHeader.h", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Info.plist", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/arm64.swiftdoc", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/arm64.swiftinterface", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/arm64.private.swiftinterface", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/x86_64.swiftdoc", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/x86_64.swiftinterface", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/x86_64.private.swiftinterface", + "Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Modules/module.modulemap", + ], + cmd = """ +set -eu + +for src in $(SRCS); do + rel_path="$${src#*Swift3PFmwkWithGenHeader.xcframework/}" + output_path="$(RULEDIR)/Swift3PFmwkWithGenHeader.xcframework/$$rel_path" + mkdir -p "$$(dirname "$$output_path")" + cp "$$src" "$$output_path" +done + +cp \ + "$(RULEDIR)/Swift3PFmwkWithGenHeader.xcframework/ios-arm64/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/arm64.swiftinterface" \ + "$(RULEDIR)/Swift3PFmwkWithGenHeader.xcframework/ios-arm64/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/arm64.private.swiftinterface" +cp \ + "$(RULEDIR)/Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/arm64.swiftinterface" \ + "$(RULEDIR)/Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/arm64.private.swiftinterface" +cp \ + "$(RULEDIR)/Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/x86_64.swiftinterface" \ + "$(RULEDIR)/Swift3PFmwkWithGenHeader.xcframework/ios-arm64_x86_64-simulator/Swift3PFmwkWithGenHeader.framework/Modules/Swift3PFmwkWithGenHeader.swiftmodule/x86_64.private.swiftinterface" +""", + tags = common.fixture_tags, +) + # --------------------------------------------------------------------------------------- # Targets for Apple Core ML library tests. diff --git a/test/starlark_tests/targets_under_test/ios/BUILD b/test/starlark_tests/targets_under_test/ios/BUILD index 9326e0a6f..f09d1f80a 100644 --- a/test/starlark_tests/targets_under_test/ios/BUILD +++ b/test/starlark_tests/targets_under_test/ios/BUILD @@ -2988,6 +2988,15 @@ apple_dynamic_xcframework_import( xcframework_imports = [":ios_swift_dynamic_xcframework"], ) +apple_dynamic_xcframework_import( + name = "ios_imported_swift_dynamic_xcframework_with_private_swiftinterface", + features = ["-swift.layering_check"], + tags = common.fixture_tags, + xcframework_imports = [ + "//test/starlark_tests/targets_under_test/apple:swift_3p_xcframework_with_generated_header_and_private_swiftinterface", + ], +) + genrule( name = "ios_swift_dynamic_xcframework", srcs = ["//test/starlark_tests/targets_under_test/apple:ios_swift_3p_xcframework_with_generated_header"], @@ -3012,6 +3021,13 @@ genrule( tags = common.fixture_tags, ) +swift_library( + name = "dynamic_swift_xcframework_with_private_swiftinterface_depending_swift_lib", + srcs = [":swift_with_dynamic_swift_framework_src"], + tags = common.fixture_tags, + deps = [":ios_imported_swift_dynamic_xcframework_with_private_swiftinterface"], +) + # Swift app with imported Objective-C static framework. ios_application( name = "swift_app_with_imported_static_fmwk",