Skip to content

Commit

Permalink
Add support for importing Swift frameworks through Swift interface fi…
Browse files Browse the repository at this point in the history
…les (#2045)

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.

(cherry picked from commit ede1610)
  • Loading branch information
keith authored Aug 14, 2023
1 parent 11a6599 commit 5e8f919
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 53 deletions.
127 changes: 81 additions & 46 deletions apple/internal/apple_framework_import.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ 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 = framework_import_support.classify_framework_imports(
ctx.var,
framework_imports,
)
Expand All @@ -230,8 +230,8 @@ def _apple_dynamic_framework_import_impl(ctx):
debug_info_binaries = debug_info_binaries,
dsyms = dsym_imports,
framework_imports = (
framework_imports_by_category.binary_imports +
framework_imports_by_category.bundling_imports
framework.binary_imports +
framework.bundling_imports
),
))

Expand All @@ -243,7 +243,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 = [] if ctx.attr.bundle_only else framework_imports_by_category.binary_imports,
dynamic_framework_file = [] if ctx.attr.bundle_only else framework.binary_imports,
)
providers.append(objc_provider)

Expand All @@ -255,13 +255,13 @@ 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,
kind = "dynamic",
label = label,
libraries = [] if ctx.attr.bundle_only else framework_imports_by_category.binary_imports,
swiftmodule_imports = framework_imports_by_category.swift_module_imports,
libraries = [] if ctx.attr.bundle_only else framework.binary_imports,
swiftmodule_imports = framework.swift_module_imports,
)
providers.append(cc_info)

Expand All @@ -275,16 +275,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 @@ -311,26 +329,24 @@ def _apple_static_framework_import_impl(ctx):
DefaultInfo(runfiles = ctx.runfiles(files = ctx.files.data)),
]

framework_imports_by_category = framework_import_support.classify_framework_imports(
framework = framework_import_support.classify_framework_imports(
ctx.var,
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 \
framework_imports_by_category.swift_module_imports or \
has_swift:
toolchain = ctx.attr._toolchain[SwiftToolchainInfo]
if framework.swift_interface_imports or framework.swift_module_imports or has_swift:
toolchain = ctx.attr._swift_toolchain[SwiftToolchainInfo]
providers.append(SwiftUsageInfo())

# The Swift toolchain propagates Swift-specific linker flags (e.g.,
Expand All @@ -343,13 +359,13 @@ def _apple_static_framework_import_impl(ctx):

if _is_debugging(compilation_mode):
swiftmodule = _swiftmodule_for_cpu(
framework_imports_by_category.swift_module_imports,
framework.swift_module_imports,
target_triplet.architecture,
)
if swiftmodule:
additional_objc_provider_fields.update(_ensure_swiftmodule_is_embedded(swiftmodule))

# Create apple_common.Objc provider.
# Create apple_common.Objc provider
additional_objc_providers.extend([
dep[apple_common.Objc]
for dep in deps
Expand All @@ -362,7 +378,7 @@ 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,
),
)
Expand All @@ -382,7 +398,7 @@ def _apple_static_framework_import_impl(ctx):
linkopts.append("-weak_framework")
linkopts.append(sdk_framework)

# Create CcInfo provider.
# Create CcInfo provider
providers.append(
framework_import_support.cc_info_with_dependencies(
actions = actions,
Expand All @@ -394,28 +410,46 @@ 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,
kind = "static",
label = label,
libraries = framework_imports_by_category.binary_imports,
libraries = framework.binary_imports,
linkopts = linkopts,
swiftmodule_imports = framework_imports_by_category.swift_module_imports,
swiftmodule_imports = framework.swift_module_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 = ctx.attr._swift_toolchain[SwiftToolchainInfo]
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 AppleFrameworkImportBundleInfo provider.
bundle_files = [x for x in framework_imports if ".bundle/" in x.short_path]
Expand All @@ -429,6 +463,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 @@ -498,7 +533,7 @@ apple_static_framework_import = rule(
fragments = ["cpp", "objc"],
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 @@ -564,8 +599,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 frameworks that do not
include Swift interface files but require linking the Swift libraries.
A boolean indicating if the target has Swift source code. This helps flag Apple frameworks that do
not include Swift interface or Swift module files.
""",
default = False,
),
Expand Down
2 changes: 1 addition & 1 deletion doc/rules-apple.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ objc_library(
| <a id="apple_static_framework_import-data"></a>data | List of files needed by this target at runtime.<br><br>Files and targets named in the <code>data</code> attribute will appear in the <code>*.runfiles</code> area of this target, if it has one. This may include data files needed by a binary or library, or other programs needed by it. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
| <a id="apple_static_framework_import-deps"></a>deps | A list of targets that are dependencies of the target being built, which will provide headers and be linked into that target. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
| <a id="apple_static_framework_import-framework_imports"></a>framework_imports | The list of files under a .framework directory which are provided to Apple based targets that depend on this target. | <a href="https://bazel.build/concepts/labels">List of labels</a> | required | |
| <a id="apple_static_framework_import-has_swift"></a>has_swift | A boolean indicating if the target has Swift source code. This helps flag frameworks that do not include Swift interface files but require linking the Swift libraries. | Boolean | optional | <code>False</code> |
| <a id="apple_static_framework_import-has_swift"></a>has_swift | A boolean indicating if the target has Swift source code. This helps flag Apple frameworks that do not include Swift interface or Swift module files. | Boolean | optional | <code>False</code> |
| <a id="apple_static_framework_import-sdk_dylibs"></a>sdk_dylibs | Names of SDK .dylib libraries to link with. For instance, <code>libz</code> or <code>libarchive</code>. <code>libc++</code> is included automatically if the binary has any C++ or Objective-C++ sources in its dependency tree. When linking a binary, all libraries named in that binary's transitive dependency graph are used. | List of strings | optional | <code>[]</code> |
| <a id="apple_static_framework_import-sdk_frameworks"></a>sdk_frameworks | Names of SDK frameworks to link with (e.g. <code>AddressBook</code>, <code>QuartzCore</code>). <code>UIKit</code> and <code>Foundation</code> are always included when building for the iOS, tvOS and watchOS platforms. For macOS, only <code>Foundation</code> is always included. When linking a top level binary, all SDK frameworks listed in that binary's transitive dependency graph are linked. | List of strings | optional | <code>[]</code> |
| <a id="apple_static_framework_import-weak_sdk_frameworks"></a>weak_sdk_frameworks | Names of SDK frameworks to weakly link with. For instance, <code>MediaAccessibility</code>. In difference to regularly linked SDK frameworks, symbols from weakly linked frameworks do not cause an error if they are not present at runtime. | List of strings | optional | <code>[]</code> |
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 @@ -209,6 +209,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],
)

# Verify ios_application with imported static framework that has data attribute
# bundles the framework's own .bundle/ and its data resources in the final binary.
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 @@ -1870,6 +1870,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 = common.min_os_ios.baseline,
tags = common.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 @@ -92,6 +92,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 @@ -98,13 +98,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

0 comments on commit 5e8f919

Please sign in to comment.