Skip to content

Commit

Permalink
Add support for importing Swift frameworks through Swift interface files
Browse files Browse the repository at this point in the history
Previously, support for importing Swift based frameworks 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 Apple frameworks.

PiperOrigin-RevId: 464877564
  • Loading branch information
stravinskii authored and swiple-rules-gardener committed Aug 2, 2022
1 parent 1361426 commit ede1610
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 50 deletions.
121 changes: 77 additions & 44 deletions apple/internal/apple_framework_import.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -119,18 +119,16 @@ def _apple_dynamic_framework_import_impl(ctx):
grep_includes = ctx.file._grep_includes

providers = []
framework_imports_by_category = framework_import_support.classify_framework_imports(
framework_imports,
)
framework = framework_import_support.classify_framework_imports(framework_imports)

# Create AppleFrameworkImportInfo provider.
target_triplet = cc_toolchain_info_support.get_apple_clang_triplet(cc_toolchain)
providers.append(framework_import_support.framework_import_info_with_dependencies(
build_archs = [target_triplet.architecture],
deps = deps,
framework_imports = (
framework_imports_by_category.binary_imports +
framework_imports_by_category.bundling_imports
framework.binary_imports +
framework.bundling_imports
),
))

Expand All @@ -142,7 +140,7 @@ def _apple_dynamic_framework_import_impl(ctx):
]
objc_provider = framework_import_support.objc_provider_with_dependencies(
additional_objc_providers = transitive_objc_providers,
dynamic_framework_file = framework_imports_by_category.binary_imports,
dynamic_framework_file = framework.binary_imports,
)
providers.append(objc_provider)

Expand All @@ -154,9 +152,9 @@ def _apple_dynamic_framework_import_impl(ctx):
deps = deps,
disabled_features = disabled_features,
features = features,
framework_includes = _framework_search_paths(framework_imports_by_category.header_imports),
framework_includes = _framework_search_paths(framework.header_imports),
grep_includes = grep_includes,
header_imports = framework_imports_by_category.header_imports,
header_imports = framework.header_imports,
label = label,
)
providers.append(cc_info)
Expand All @@ -170,16 +168,34 @@ def _apple_dynamic_framework_import_impl(ctx):
framework_files = depset(framework_imports),
))

# Create _SwiftInteropInfo provider.
# For now, Swift interop is restricted only to a Clang module map inside
# the framework.
swift_interop_info = framework_import_support.swift_interop_info_with_dependencies(
deps = deps,
module_name = framework_imports_by_category.bundle_name,
module_map_imports = framework_imports_by_category.module_map_imports,
)
if swift_interop_info:
providers.append(swift_interop_info)
if framework.swift_interface_imports:
# Create SwiftInfo provider
swift_toolchain = swift_common.get_toolchain(ctx, "_swift_toolchain")
swiftinterface_files = framework_import_support.filter_swift_module_files_for_architecture(
architecture = target_triplet.architecture,
swift_module_files = framework.swift_interface_imports,
)
providers.append(
framework_import_support.swift_info_from_module_interface(
actions = actions,
ctx = ctx,
deps = deps,
disabled_features = disabled_features,
features = features,
module_name = framework.bundle_name,
swift_toolchain = swift_toolchain,
swiftinterface_file = swiftinterface_files[0],
),
)
else:
# Create _SwiftInteropInfo provider.
swift_interop_info = framework_import_support.swift_interop_info_with_dependencies(
deps = deps,
module_name = framework.bundle_name,
module_map_imports = framework.module_map_imports,
)
if swift_interop_info:
providers.append(swift_interop_info)

return providers

Expand All @@ -202,23 +218,21 @@ def _apple_static_framework_import_impl(ctx):
grep_includes = ctx.file._grep_includes

providers = []
framework_imports_by_category = framework_import_support.classify_framework_imports(
framework_imports,
)
framework = framework_import_support.classify_framework_imports(framework_imports)

# Create AppleFrameworkImportInfo provider.
# Create AppleFrameworkImportInfo provider
target_triplet = cc_toolchain_info_support.get_apple_clang_triplet(cc_toolchain)
providers.append(framework_import_support.framework_import_info_with_dependencies(
build_archs = [target_triplet.architecture],
deps = deps,
))

# Collect transitive Objc/CcInfo providers from Swift toolchain.
# Collect transitive Objc/CcInfo providers from Swift toolchain
additional_cc_infos = []
additional_objc_providers = []
additional_objc_provider_fields = {}
if framework_imports_by_category.swift_interface_imports or has_swift:
toolchain = swift_common.get_toolchain(ctx)
if framework.swift_interface_imports or has_swift:
toolchain = swift_common.get_toolchain(ctx, "_swift_toolchain")
providers.append(SwiftUsageInfo())

# The Swift toolchain propagates Swift-specific linker flags (e.g.,
Expand All @@ -229,7 +243,7 @@ def _apple_static_framework_import_impl(ctx):
additional_objc_providers.extend(toolchain.implicit_deps_providers.objc_infos)
additional_cc_infos.extend(toolchain.implicit_deps_providers.cc_infos)

# Create apple_common.Objc provider.
# Create apple_common.Objc provider
additional_objc_providers.extend([
dep[apple_common.Objc]
for dep in deps
Expand All @@ -242,12 +256,12 @@ def _apple_static_framework_import_impl(ctx):
alwayslink = alwayslink,
sdk_dylib = sdk_dylibs,
sdk_framework = sdk_frameworks,
static_framework_file = framework_imports_by_category.binary_imports,
static_framework_file = framework.binary_imports,
weak_sdk_framework = weak_sdk_frameworks,
),
)

# Create CcInfo provider.
# Create CcInfo provider
providers.append(
framework_import_support.cc_info_with_dependencies(
actions = actions,
Expand All @@ -258,26 +272,44 @@ def _apple_static_framework_import_impl(ctx):
disabled_features = disabled_features,
features = features,
framework_includes = _framework_search_paths(
framework_imports_by_category.header_imports,
framework.header_imports,
),
grep_includes = grep_includes,
header_imports = framework_imports_by_category.header_imports,
header_imports = framework.header_imports,
label = label,
),
)

# Create _SwiftInteropInfo provider.
# For now, Swift interop is restricted only to a Clang module map inside
# the framework.
swift_interop_info = framework_import_support.swift_interop_info_with_dependencies(
deps = deps,
module_name = framework_imports_by_category.bundle_name,
module_map_imports = framework_imports_by_category.module_map_imports,
)
if swift_interop_info:
providers.append(swift_interop_info)
if framework.swift_interface_imports:
# Create SwiftInfo provider
swift_toolchain = swift_common.get_toolchain(ctx, "_swift_toolchain")
swiftinterface_files = framework_import_support.filter_swift_module_files_for_architecture(
architecture = target_triplet.architecture,
swift_module_files = framework.swift_interface_imports,
)
providers.append(
framework_import_support.swift_info_from_module_interface(
actions = actions,
ctx = ctx,
deps = deps,
disabled_features = disabled_features,
features = features,
module_name = framework.bundle_name,
swift_toolchain = swift_toolchain,
swiftinterface_file = swiftinterface_files[0],
),
)
else:
# Create SwiftInteropInfo provider for swift_clang_module_aspect
swift_interop_info = framework_import_support.swift_interop_info_with_dependencies(
deps = deps,
module_name = framework.bundle_name,
module_map_imports = framework.module_map_imports,
)
if swift_interop_info:
providers.append(swift_interop_info)

# Create AppleResourceInfo provider.
# Create AppleResourceInfo provider
bundle_files = [x for x in framework_imports if ".bundle/" in x.short_path]
if bundle_files:
parent_dir_param = partial.make(
Expand All @@ -299,6 +331,7 @@ apple_dynamic_framework_import = rule(
fragments = ["cpp"],
attrs = dicts.add(
rule_factory.common_tool_attributes,
swift_common.toolchain_attrs(toolchain_attr_name = "_swift_toolchain"),
{
"framework_imports": attr.label_list(
allow_empty = False,
Expand Down Expand Up @@ -339,7 +372,7 @@ apple_static_framework_import = rule(
fragments = ["cpp"],
attrs = dicts.add(
rule_factory.common_tool_attributes,
swift_common.toolchain_attrs(),
swift_common.toolchain_attrs(toolchain_attr_name = "_swift_toolchain"),
{
"framework_imports": attr.label_list(
allow_empty = False,
Expand Down Expand Up @@ -395,8 +428,8 @@ reference any other symbols in the object file that adds that conformance.
),
"has_swift": attr.bool(
doc = """
A boolean indicating if the target has Swift source code. This helps flag XCFrameworks that do not
include Swift interface files.
A boolean indicating if the target has Swift source code. This helps flag Apple frameworks that do
not include Swift interface files.
""",
default = False,
),
Expand Down
11 changes: 11 additions & 0 deletions test/starlark_tests/ios_application_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,17 @@ def ios_application_test_suite(name):
not_contains = ["$BUNDLE_ROOT/Frameworks/iOSStaticFramework.framework"],
tags = [name],
)
archive_contents_test(
name = "{}_swift_with_imported_swift_static_fmwk_contains_symbols_and_not_bundles_files".format(name),
build_type = "simulator",
target_under_test = "//test/starlark_tests/targets_under_test/ios:swift_app_with_imported_swift_static_fmwk",
binary_test_file = "$BINARY",
binary_test_architecture = "x86_64",
binary_contains_symbols = ["_OBJC_CLASS_$__TtC23iOSSwiftStaticFramework11SharedClass"],
contains = ["$BUNDLE_ROOT/Frameworks/libswiftCore.dylib"],
not_contains = ["$BUNDLE_ROOT/Frameworks/iOSSwiftStaticFramework.framework"],
tags = [name],
)

apple_verification_test(
name = "{}_fmwk_with_imported_fmwk_codesign_test".format(name),
Expand Down
40 changes: 40 additions & 0 deletions test/starlark_tests/targets_under_test/ios/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,46 @@ EOF
""",
)

# Swift app with imported Swift static framework.
ios_application(
name = "swift_app_with_imported_swift_static_fmwk",
bundle_id = "com.google.example",
families = [
"iphone",
"ipad",
],
infoplists = [
"//test/starlark_tests/resources:Info.plist",
],
minimum_os_version = "8.0",
tags = FIXTURE_TAGS,
deps = [
":swift_static_fmwk_depending_swift_lib",
"//test/starlark_tests/resources:swift_main_lib",
],
)

swift_library(
name = "swift_static_fmwk_depending_swift_lib",
srcs = ["SwiftStaticFmwkDependingSwiftLib.swift"],
module_name = "swift_static_fmwk_depending_swift_lib",
deps = ["//test/starlark_tests/targets_under_test/apple:iOSImportedSwiftStaticFramework"],
)

genrule(
name = "swift_static_fmwk_depending_swift_lib_file",
outs = ["SwiftStaticFmwkDependingSwiftLib.swift"],
cmd = """cat > $@ <<EOF
import iOSSwiftStaticFramework
func main() {
let sharedClass = SharedClass()
sharedClass.doSomethingShared()
}
EOF
""",
)

# -----------------------------------------------------------------------------------------
# Targets for Apple dynamic XCFramework import tests.

Expand Down
4 changes: 4 additions & 0 deletions test/testdata/fmwk/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ generate_import_framework(
swift_library(
name = "swift_framework_lib",
srcs = ["SharedClass.swift"],
features = [
"swift.emit_swiftinterface",
"swift.enable_library_evolution",
],
generates_header = True,
module_name = "iOSSwiftStaticFramework",
)
Expand Down
18 changes: 12 additions & 6 deletions test/testdata/fmwk/generate_framework.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,19 @@ def _generate_import_framework_impl(ctx):
),
)

# Apple framework and XCFrameworks import rules use Swift interface files to flag an
# imported framework contains Swift, and thus propagate Swift toolchain specific flags up
# the build graph.
if include_module_interface_files:
module_interface = actions.declare_file(architectures[0] + ".swiftinterface")
actions.write(output = module_interface, content = "I'm a mock .swiftinterface file")
module_interfaces.append(module_interface)
swiftinterface = generation_support.get_file_with_extension(
files = swift_library_files,
extension = "swiftinterface",
)
module_interfaces.append(
generation_support.copy_file(
actions = actions,
base_path = "intermediates",
file = swiftinterface,
target_filename = "%s.swiftinterface" % architectures[0],
),
)

# Create framework bundle
framework_files = generation_support.create_framework(
Expand Down

1 comment on commit ede1610

@keith
Copy link
Member

@keith keith commented on ede1610 Aug 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.