Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for importing Swift XCFrameworks through Swift interface files #2044

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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