Skip to content

Commit

Permalink
Add support for including headers when importing XCFrameworks assembl…
Browse files Browse the repository at this point in the history
…ed with static libraries (bazelbuild#1353)

The paths are interpreted relative to the single platform directory inside the
XCFramework for the platform being built.
  • Loading branch information
thii authored Feb 4, 2022
1 parent 2fb3945 commit c040885
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 22 deletions.
53 changes: 37 additions & 16 deletions apple/internal/apple_framework_import.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,19 @@ def _objc_provider_with_dependencies(ctx, objc_provider_fields, additional_objc_
] + additional_objc_infos
return apple_common.new_objc_provider(**objc_provider_fields)

def _cc_info_with_dependencies(ctx, header_imports, additional_cc_infos = [], is_framework = True):
def _cc_info_with_dependencies(
ctx,
header_imports,
additional_cc_infos = [],
includes = [],
is_framework = True):
"""Returns a new CcInfo which includes transitive Cc dependencies."""
framework_search_paths = _framework_search_paths(header_imports) if is_framework else []
cc_info = CcInfo(
compilation_context = cc_common.create_compilation_context(
headers = depset(header_imports),
framework_includes = depset(framework_search_paths),
includes = depset(includes),
),
)
dep_cc_infos = [dep[CcInfo] for dep in ctx.attr.deps]
Expand Down Expand Up @@ -388,19 +394,13 @@ def _get_framework_name(framework_imports):
framework_dir = framework_groups.keys()[0]
return paths.split_extension(paths.basename(framework_dir))[0]

def _get_xcframework_name(xcframework_imports):
"""Returns the XCFramework name (the directory name without .xcframework)."""
def _process_xcframework_imports(ctx):
xcframework_groups = _grouped_xcframework_files(ctx.files.xcframework_imports)

# We can just take the first key because the rule implementation guarantees
# that we only have files for a single framework.
xcframework_groups = _grouped_xcframework_files(xcframework_imports)
xcframework_path = xcframework_groups.keys()[0]
return paths.split_extension(paths.basename(xcframework_path))[0]

def _get_xcframework_imports(ctx):
xcframework_groups = _grouped_xcframework_files(ctx.files.xcframework_imports)
xcframework_name = _get_xcframework_name(ctx.files.xcframework_imports)
xcframework_path = xcframework_groups.keys()[0]
xcframework_name = paths.split_extension(paths.basename(xcframework_path))[0]

library_identifier = None
if ctx.attr.library_identifiers:
Expand All @@ -426,6 +426,8 @@ def _get_xcframework_imports(ctx):
fail("Failed to figure out library identifiers. Please provide a " +
"dictionary of library identifiers to `library_identifiers`.")

single_platform_dir = paths.join(xcframework_path, library_identifier)

# XCFramework with static frameworks
platform_path = "{}/{}/{}.framework".format(xcframework_path, library_identifier, xcframework_name)
framework_imports_for_platform = [f for f in ctx.files.xcframework_imports if platform_path in f.path]
Expand All @@ -440,14 +442,14 @@ def _get_xcframework_imports(ctx):
if not framework_imports_for_platform:
fail("Couldn't find framework or library at path `{}`".format(platform_path))

return framework_imports_for_platform
return xcframework_name, xcframework_path, single_platform_dir, framework_imports_for_platform

def _common_dynamic_framework_import_impl(ctx, is_xcframework):
"""Common implementation for the apple_dynamic_framework_import and apple_dynamic_xcframework_import rules."""
providers = []

if is_xcframework:
framework_imports = _get_xcframework_imports(ctx)
_, _, _, framework_imports = _process_xcframework_imports(ctx)
else:
framework_imports = ctx.files.framework_imports

Expand Down Expand Up @@ -516,8 +518,7 @@ def _common_static_framework_import_impl(ctx, is_xcframework):
providers = []

if is_xcframework:
framework_imports = _get_xcframework_imports(ctx)
framework_name = _get_xcframework_name(framework_imports)
framework_name, xcframework_path, single_platform_dir, framework_imports = _process_xcframework_imports(ctx)
else:
framework_imports = ctx.files.framework_imports
framework_name = _get_framework_name(framework_imports)
Expand Down Expand Up @@ -616,8 +617,16 @@ def _common_static_framework_import_impl(ctx, is_xcframework):
providers.append(
_objc_provider_with_dependencies(ctx, objc_provider_fields, additional_objc_infos),
)

includes = []
if is_xcframework and not is_framework and ctx.attr.includes:
includes.extend([
paths.join(single_platform_dir, x)
for x in ctx.attr.includes
])

providers.append(
_cc_info_with_dependencies(ctx, header_imports, additional_cc_infos, is_framework),
_cc_info_with_dependencies(ctx, header_imports, additional_cc_infos, includes, is_framework),
)

# For now, Swift interop is restricted only to a Clang module map inside
Expand Down Expand Up @@ -879,14 +888,26 @@ objc_library(
""",
)

# TODO: Support for adding `includes` for XCFrameworks with static archives
apple_static_xcframework_import = rule(
implementation = _apple_static_xcframework_import_impl,
fragments = ["apple"],
attrs = dicts.add(
_xcframework_import_common_attrs,
swift_common.toolchain_attrs(),
{
"includes": attr.string_list(
doc = """
List of `#include/#import` search paths to add to this target and all depending
targets.
The paths are interpreted relative to the single platform directory inside the
XCFramework for the platform being built.
These flags are added for this rule and every rule that depends on it. (Note:
not the rules it depends upon!) Be very careful, since this may have
far-reaching effects.
""",
),
"sdk_dylibs": attr.string_list(
doc = """
Names of SDK .dylib libraries to link with. For instance, `libz` or
Expand Down
3 changes: 2 additions & 1 deletion doc/rules-apple.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ objc_library(
## apple_static_xcframework_import

<pre>
apple_static_xcframework_import(<a href="#apple_static_xcframework_import-name">name</a>, <a href="#apple_static_xcframework_import-alwayslink">alwayslink</a>, <a href="#apple_static_xcframework_import-deps">deps</a>, <a href="#apple_static_xcframework_import-library_identifiers">library_identifiers</a>, <a href="#apple_static_xcframework_import-sdk_dylibs">sdk_dylibs</a>,
apple_static_xcframework_import(<a href="#apple_static_xcframework_import-name">name</a>, <a href="#apple_static_xcframework_import-alwayslink">alwayslink</a>, <a href="#apple_static_xcframework_import-deps">deps</a>, <a href="#apple_static_xcframework_import-includes">includes</a>, <a href="#apple_static_xcframework_import-library_identifiers">library_identifiers</a>, <a href="#apple_static_xcframework_import-sdk_dylibs">sdk_dylibs</a>,
<a href="#apple_static_xcframework_import-sdk_frameworks">sdk_frameworks</a>, <a href="#apple_static_xcframework_import-weak_sdk_frameworks">weak_sdk_frameworks</a>, <a href="#apple_static_xcframework_import-xcframework_imports">xcframework_imports</a>)
</pre>

Expand Down Expand Up @@ -173,6 +173,7 @@ objc_library(
| <a id="apple_static_xcframework_import-name"></a>name | A unique name for this target. | <a href="https://bazel.build/docs/build-ref.html#name">Name</a> | required | |
| <a id="apple_static_xcframework_import-alwayslink"></a>alwayslink | If true, any binary that depends (directly or indirectly) on this framework will link in all the object files for the framework file, even if some contain no symbols referenced by the binary. This is useful if your code isn't explicitly called by code in the binary; for example, if you rely on runtime checks for protocol conformances added in extensions in the library but do not directly reference any other symbols in the object file that adds that conformance. | Boolean | optional | False |
| <a id="apple_static_xcframework_import-deps"></a>deps | A list of targets that are dependencies of the target being built, which will provide headers (if the importing XCFramework is a dynamic framework) and can be linked into that target. | <a href="https://bazel.build/docs/build-ref.html#labels">List of labels</a> | optional | [] |
| <a id="apple_static_xcframework_import-includes"></a>includes | List of <code>#include/#import</code> search paths to add to this target and all depending targets.<br><br>The paths are interpreted relative to the single platform directory inside the XCFramework for the platform being built.<br><br>These flags are added for this rule and every rule that depends on it. (Note: not the rules it depends upon!) Be very careful, since this may have far-reaching effects. | List of strings | optional | [] |
| <a id="apple_static_xcframework_import-library_identifiers"></a>library_identifiers | An optional key-value map of platforms to the corresponding platform IDs (containing all supported architectures), relative to the XCFramework. The identifier keys should be case-insensitive variants of the values in [<code>apple_common.platform</code>](https://docs.bazel.build/versions/5.0.0/skylark/lib/apple_common.html#platform); for example, <code>ios_device</code> or <code>ios_simulator</code>. The identifier values should be case-sensitive variants of values that might be found in the <code>LibraryIdentifier</code> of an <code>Info.plist</code> file in the XCFramework's root; for example, <code>ios-arm64_i386_x86_64-simulator</code> or <code>ios-arm64_armv7</code>.<br><br>Passing this attribute should not be neccessary if the XCFramework follows the standard naming convention (that is, it was created by Xcode or Bazel). | <a href="https://bazel.build/docs/skylark/lib/dict.html">Dictionary: String -> String</a> | optional | {} |
| <a id="apple_static_xcframework_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 | [] |
| <a id="apple_static_xcframework_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 | [] |
Expand Down
6 changes: 1 addition & 5 deletions test/starlark_tests/resources/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -670,11 +670,7 @@ swift_library(
genrule(
name = "objc_importing_imported_static_xcfw",
outs = ["objc_importing_imported_static_xcfw.m"],
# Bazel doesn't do any xcframework processing like Xcode so this import has
# to be a full path.
cmd = """
echo '#import "test/starlark_tests/targets_under_test/apple/ios_static_xcframework.xcframework/ios-arm64_x86_64-simulator/Headers/shared.h"' > $@
""",
cmd = """echo '#import "shared.h"' > $@""",
tags = FIXTURE_TAGS,
)

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 @@ -455,6 +455,7 @@ apple_dynamic_xcframework_import(

apple_static_xcframework_import(
name = "ios_imported_static_xcframework",
includes = ["Headers"],
tags = ["manual"],
xcframework_imports = [":generated_ios_static_xcframework"],
)
Expand Down

0 comments on commit c040885

Please sign in to comment.