Skip to content

Commit

Permalink
Add support for importing Swift XCFrameworks through Swift interface …
Browse files Browse the repository at this point in the history
…files (#2044)

Previously, support for importing Swift based XCFrameworks was limited
to Swift interoperability with Objective-C targets through imported
Clang
module map files. This change adopts recently introduced swift_common
API to compile Swift module interface files imported by XCFrameworks.

(cherry picked from commit 3ed9049)
  • Loading branch information
keith authored Aug 12, 2023
1 parent 95b1305 commit 565726a
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 33 deletions.
118 changes: 88 additions & 30 deletions apple/internal/apple_xcframework_import.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -195,19 +195,19 @@ def _get_xcframework_library_from_paths(*, target_triplet, xcframework):
headers = filter_by_library_identifier(files_by_category.header_imports)
module_maps = filter_by_library_identifier(files_by_category.module_map_imports)

swiftmodules = [
f
for f in files_by_category.swift_module_imports
if _matches_library(f) and
f.basename.startswith(target_triplet.architecture)
]
swiftmodules = framework_import_support.filter_swift_module_files_for_architecture(
architecture = target_triplet.architecture,
swift_module_files = filter_by_library_identifier(
files_by_category.swift_module_imports,
),
)

swift_module_interfaces = [
f
for f in files_by_category.swift_interface_imports
if _matches_library(f) and
f.basename.startswith(target_triplet.architecture)
]
swift_module_interfaces = framework_import_support.filter_swift_module_files_for_architecture(
architecture = target_triplet.architecture,
swift_module_files = filter_by_library_identifier(
files_by_category.swift_interface_imports,
),
)

framework_files = filter_by_library_identifier(xcframework.files)

Expand Down Expand Up @@ -281,8 +281,9 @@ def _get_xcframework_library_with_xcframework_processor(
dir_name = paths.join(library_path, "Headers"),
**intermediates_common
)
modules_dir_path = paths.join(library_path, "Modules")
module_map_file = intermediates.file(
file_name = paths.join(library_path, "Modules", "module.modulemap"),
file_name = paths.join(modules_dir_path, "module.modulemap"),
**intermediates_common
)

Expand Down Expand Up @@ -315,6 +316,30 @@ def _get_xcframework_library_with_xcframework_processor(
module_map_file,
]

swiftinterface_file = None
if files_by_category.swift_interface_imports:
swiftinterface_path = paths.join(
modules_dir_path,
"{module_name}.swiftmodule".format(
module_name = xcframework.bundle_name,
),
"{architecture}.swiftinterface".format(
architecture = target_triplet.architecture,
),
)
swiftinterface_file = intermediates.file(
file_name = swiftinterface_path,
**intermediates_common
)
args.add_all(
framework_import_support.filter_swift_module_files_for_architecture(
architecture = target_triplet.architecture,
swift_module_files = files_by_category.swift_interface_imports,
),
before_each = "--swiftinterface_file",
)
outputs.append(swiftinterface_file)

xcframework_processor_tool = apple_mac_toolchain_info.resolved_xcframework_processor_tool

apple_support.run(
Expand Down Expand Up @@ -347,7 +372,7 @@ def _get_xcframework_library_with_xcframework_processor(
includes = includes,
clang_module_map = module_map_file,
swiftmodule = [],
swift_module_interface = None,
swift_module_interface = swiftinterface_file,
framework_files = [],
)

Expand Down Expand Up @@ -473,14 +498,30 @@ def _apple_dynamic_xcframework_import_impl(ctx):
)
providers.append(apple_dynamic_framework_info)

# Create _SwiftInteropInfo provider if applicable
swift_interop_info = framework_import_support.swift_interop_info_with_dependencies(
deps = deps,
module_name = xcframework.bundle_name,
module_map_imports = [xcframework_library.clang_module_map],
)
if swift_interop_info:
providers.append(swift_interop_info)
if xcframework_library.swift_module_interface:
# Create SwiftInfo provider
swift_toolchain = ctx.attr._toolchain[SwiftToolchainInfo]
providers.append(
framework_import_support.swift_info_from_module_interface(
actions = actions,
ctx = ctx,
deps = deps,
disabled_features = disabled_features,
features = features,
module_name = xcframework.bundle_name,
swift_toolchain = swift_toolchain,
swiftinterface_file = xcframework_library.swift_module_interface,
),
)
else:
# Create SwiftInteropInfo provider for swift_clang_module_aspect
swift_interop_info = framework_import_support.swift_interop_info_with_dependencies(
deps = deps,
module_name = xcframework.bundle_name,
module_map_imports = [xcframework_library.clang_module_map],
)
if swift_interop_info:
providers.append(swift_interop_info)

return providers

Expand Down Expand Up @@ -595,14 +636,30 @@ def _apple_static_xcframework_import_impl(ctx):
)
providers.append(cc_info)

# Create _SwiftInteropInfo provider if applicable
swift_interop_info = framework_import_support.swift_interop_info_with_dependencies(
deps = deps,
module_name = xcframework.bundle_name,
module_map_imports = [xcframework_library.clang_module_map],
)
if swift_interop_info:
providers.append(swift_interop_info)
if xcframework_library.swift_module_interface:
# Create SwiftInfo provider
swift_toolchain = ctx.attr._toolchain[SwiftToolchainInfo]
providers.append(
framework_import_support.swift_info_from_module_interface(
actions = actions,
ctx = ctx,
deps = deps,
disabled_features = disabled_features,
features = features,
module_name = xcframework.bundle_name,
swift_toolchain = swift_toolchain,
swiftinterface_file = xcframework_library.swift_module_interface,
),
)
else:
# Create SwiftInteropInfo provider for swift_clang_module_aspect
swift_interop_info = framework_import_support.swift_interop_info_with_dependencies(
deps = deps,
module_name = xcframework.bundle_name,
module_map_imports = [xcframework_library.clang_module_map],
)
if swift_interop_info:
providers.append(swift_interop_info)

# Create AppleFrameworkImportBundleInfo provider.
bundle_files = [x for x in xcframework_library.framework_files if ".bundle/" in x.short_path]
Expand Down Expand Up @@ -637,6 +694,7 @@ objc_library(
implementation = _apple_dynamic_xcframework_import_impl,
attrs = dicts.add(
rule_factory.common_tool_attributes,
swift_common.toolchain_attrs(),
{
"xcframework_imports": attr.label_list(
allow_empty = False,
Expand Down
72 changes: 72 additions & 0 deletions apple/internal/framework_import_support.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,26 @@ def _libraries_to_link_for_static_framework(

return libraries_to_link

def _filter_swift_module_files_for_architecture(architecture, swift_module_files):
"""Filters Swift module files for a given architecture.
Traverses a list of Swift module files (.swiftdoc, .swiftinterface, .swiftmodule) and selects
the effective files based on target architecture and file's basename without extension.
Args:
architecture: Effective target architecture (e.g. 'x86_64', 'arm64').
swift_module_files: List of Swift module files to filter using architecture.
Returns:
List of Swift module files for given architecture.
"""
files = []
for file in swift_module_files:
filename, _ = paths.split_extension(file.basename)
if filename == architecture:
files.append(file)

return files

def _framework_import_info_with_dependencies(
*,
build_archs,
Expand Down Expand Up @@ -407,6 +427,56 @@ def _objc_provider_with_dependencies(
objc_provider_fields.update(**additional_objc_provider_fields)
return apple_common.new_objc_provider(**objc_provider_fields)

def _swift_info_from_module_interface(
*,
actions,
ctx,
deps,
disabled_features,
features,
module_name,
swift_toolchain,
swiftinterface_file):
"""Returns SwiftInfo provider for a pre-compiled Swift module compiling it's interface file.
Args:
actions: The actions provider from `ctx.actions`.
ctx: The Starlark context for a rule target being built.
deps: List of dependencies for a given target to retrieve transitive CcInfo providers.
disabled_features: List of features to be disabled for cc_common.compile
features: List of features to be enabled for cc_common.compile.
module_name: Swift module name.
swift_toolchain: SwiftToolchainInfo provider for current target.
swiftinterface_file: `.swiftinterface` File to compile.
Returns:
A SwiftInfo provider.
"""
swift_infos = [dep[SwiftInfo] for dep in deps if SwiftInfo in dep]
module_context = swift_common.compile_module_interface(
actions = actions,
compilation_contexts = [
dep[CcInfo].compilation_context
for dep in deps
if CcInfo in dep
],
feature_configuration = swift_common.configure_features(
ctx = ctx,
swift_toolchain = swift_toolchain,
requested_features = features,
unsupported_features = disabled_features,
),
module_name = module_name,
swiftinterface_file = swiftinterface_file,
swift_infos = swift_infos,
swift_toolchain = swift_toolchain,
)

return swift_common.create_swift_info(
modules = [module_context],
swift_infos = swift_infos,
)

def _swift_interop_info_with_dependencies(deps, module_name, module_map_imports):
"""Return a Swift interop provider for the framework if it has a module map."""
if not module_map_imports:
Expand All @@ -425,7 +495,9 @@ framework_import_support = struct(
cc_info_with_dependencies = _cc_info_with_dependencies,
classify_file_imports = _classify_file_imports,
classify_framework_imports = _classify_framework_imports,
filter_swift_module_files_for_architecture = _filter_swift_module_files_for_architecture,
framework_import_info_with_dependencies = _framework_import_info_with_dependencies,
objc_provider_with_dependencies = _objc_provider_with_dependencies,
swift_info_from_module_interface = _swift_info_from_module_interface,
swift_interop_info_with_dependencies = _swift_interop_info_with_dependencies,
)
37 changes: 37 additions & 0 deletions test/starlark_tests/apple_dynamic_xcframework_import_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ def apple_dynamic_xcframework_import_test_suite(name):
],
tags = [name],
)
archive_contents_test(
name = "{}_swift_contains_imported_swift_xcframework_framework_files".format(name),
build_type = "simulator",
target_under_test = "//test/starlark_tests/targets_under_test/ios:swift_app_with_imported_swift_xcframework",
contains = [
"$BUNDLE_ROOT/Frameworks/SwiftFmwkWithGenHeader.framework/Info.plist",
"$BUNDLE_ROOT/Frameworks/SwiftFmwkWithGenHeader.framework/SwiftFmwkWithGenHeader",
],
not_contains = [
"$BUNDLE_ROOT/Frameworks/SwiftFmwkWithGenHeader.framework/Headers/",
"$BUNDLE_ROOT/Frameworks/SwiftFmwkWithGenHeader.framework/Modules/",
],
binary_test_file = "$BINARY",
macho_load_commands_contain = [
"name @rpath/SwiftFmwkWithGenHeader.framework/SwiftFmwkWithGenHeader (offset 24)",
],
tags = [name],
)

# Verify the correct XCFramework library was bundled and sliced for the required architecture.
binary_contents_test(
Expand Down Expand Up @@ -163,6 +181,25 @@ def apple_dynamic_xcframework_import_test_suite(name):
target_features = ["apple.parse_xcframework_info_plist"],
tags = [name],
)
archive_contents_test(
name = "{}_swift_contains_imported_swift_xcframework_framework_files_with_xcframework_import_tool".format(name),
build_type = "simulator",
target_under_test = "//test/starlark_tests/targets_under_test/ios:swift_app_with_imported_swift_xcframework",
contains = [
"$BUNDLE_ROOT/Frameworks/SwiftFmwkWithGenHeader.framework/Info.plist",
"$BUNDLE_ROOT/Frameworks/SwiftFmwkWithGenHeader.framework/SwiftFmwkWithGenHeader",
],
not_contains = [
"$BUNDLE_ROOT/Frameworks/SwiftFmwkWithGenHeader.framework/Headers/",
"$BUNDLE_ROOT/Frameworks/SwiftFmwkWithGenHeader.framework/Modules/",
],
binary_test_file = "$BINARY",
macho_load_commands_contain = [
"name @rpath/SwiftFmwkWithGenHeader.framework/SwiftFmwkWithGenHeader (offset 24)",
],
target_features = ["apple.parse_xcframework_info_plist"],
tags = [name],
)

# Verify importing XCFramework with dynamic libraries (i.e. not Apple frameworks) fails.
analysis_failure_message_test(
Expand Down
41 changes: 41 additions & 0 deletions test/starlark_tests/apple_static_xcframework_import_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ def apple_static_xcframework_import_test_suite(name):
contains = ["$BUNDLE_ROOT/Frameworks/libswiftCore.dylib"],
tags = [name],
)
archive_contents_test(
name = "{}_swift_with_imported_swift_static_fmwk_contains_symbols_and_bundles_swift_std_libraries".format(name),
build_type = "simulator",
target_under_test = "//test/starlark_tests/targets_under_test/ios:swift_app_with_imported_swift_xcframework_with_static_library",
binary_test_file = "$BINARY",
binary_test_architecture = "x86_64",
binary_contains_symbols = [
"_OBJC_CLASS_$__TtC34generated_swift_static_xcframework11SharedClass",
],
contains = ["$BUNDLE_ROOT/Frameworks/libswiftCore.dylib"],
tags = [name],
)

# Verify Swift standard libraries are bundled for an imported XCFramework that has a Swift
# static library containing no module interface files (.swiftmodule directory) and where the
Expand All @@ -110,6 +122,35 @@ def apple_static_xcframework_import_test_suite(name):
tags = [name],
)

# Verify ios_application bundles Framework files when using xcframework_processor_tool.
archive_contents_test(
name = "{}_ios_application_with_imported_static_xcframework_includes_symbols_with_xcframework_import_tool".format(name),
build_type = "simulator",
target_under_test = "//test/starlark_tests/targets_under_test/ios:app_with_imported_xcframework_with_static_library",
binary_test_file = "$BINARY",
binary_test_architecture = "x86_64",
binary_contains_symbols = [
"-[SharedClass doSomethingShared]",
"_OBJC_CLASS_$_SharedClass",
],
not_contains = ["$BUNDLE_ROOT/Frameworks/"],
target_features = ["apple.parse_xcframework_info_plist"],
tags = [name],
)
archive_contents_test(
name = "{}_swift_with_imported_swift_static_fmwk_contains_symbols_and_bundles_swift_std_libraries_with_xcframework_import_tool".format(name),
build_type = "simulator",
target_under_test = "//test/starlark_tests/targets_under_test/ios:swift_app_with_imported_swift_xcframework_with_static_library",
binary_test_file = "$BINARY",
binary_test_architecture = "x86_64",
binary_contains_symbols = [
"_OBJC_CLASS_$__TtC34generated_swift_static_xcframework11SharedClass",
],
contains = ["$BUNDLE_ROOT/Frameworks/libswiftCore.dylib"],
target_features = ["apple.parse_xcframework_info_plist"],
tags = [name],
)

native.test_suite(
name = name,
tags = [name],
Expand Down
1 change: 1 addition & 0 deletions test/starlark_tests/targets_under_test/apple/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,7 @@ apple_static_xcframework_import(

apple_static_xcframework_import(
name = "ios_imported_swift_static_xcframework",
features = ["-swift.layering_check"],
tags = common.fixture_tags,
visibility = ["//visibility:public"],
xcframework_imports = ["//test/testdata/xcframeworks:generated_swift_static_xcframework"],
Expand Down
Loading

0 comments on commit 565726a

Please sign in to comment.