Skip to content

Commit

Permalink
Support extensions within single-target watchOS apps (#2309)
Browse files Browse the repository at this point in the history
### What has changed
- Adds support for app extensions within single target watchOS apps.
- Extension-based watchOS apps can now define their `watchos_extension`
either in `extension` or in `extensions`, the former being deprecated.
- ~Removes `extension` all-together from `watchos_application`.~

### Notes
- ~This is a breaking change for projects that use extension-based
`watchos_application`, they would need to migrate from `extension =
":WatchKitExtension"` to `extensions = [":WatchKitExtension"]`.
Initially I tried to keep both `extension` and `extensions` params to
avoid that, but it breaks other things. For instance, [rules_xcodeproj
here](https://github.com/MobileNativeFoundation/rules_xcodeproj/blob/8d900a9bbfdcceabe779a9fc269d7226bb9121e7/xcodeproj/internal/top_level_targets.bzl#L241-L244).~
- This is my first PR to this repo, please let me know if I have missed
anything, I'd be happy to iterate on this PR if need be!
  • Loading branch information
markvasiv authored Nov 16, 2023
1 parent 75e15fd commit 519c703
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 12 deletions.
75 changes: 67 additions & 8 deletions apple/internal/watchos_rules.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,25 @@ later will be met with a rejection.
Please remove the assigned watchOS 2 app `extension` and make sure a valid watchOS application
delegate is referenced in the single-target `watchos_application`'s `deps`.
""")

if len(ctx.attr.extensions) > 1:
fail("""
Extension-based watchOS applications do not support embedded app extensions via the `extensions` attribute.
Please remove app extensions from this target, and instead add them via `watchos_extension`'s `extensions` attribute.
""")

watch_extension = None
if ctx.attr.extension:
watch_extension = ctx.attr.extension
elif len(ctx.attr.extensions) == 1 and ctx.attr.extensions[0][AppleBundleInfo].product_type == apple_product_type.watch2_extension:
watch_extension = ctx.attr.extensions[0]
else:
fail("""
Extension-based watchOS applications require a valid `watchos_extension`.
Please add a `watchos_extension` to this target `extensions` attribute.
""")

rule_descriptor = rule_support.rule_descriptor(
Expand Down Expand Up @@ -799,7 +818,7 @@ reproducible error case.".format(

bundle_verification_targets = [
struct(
target = ctx.attr.extension,
target = watch_extension,
parent_bundle_id_reference = [
"NSExtension",
"NSExtensionAttributes",
Expand Down Expand Up @@ -857,7 +876,7 @@ reproducible error case.".format(
bundle_location = processor.location.watch,
bundle_name = bundle_name,
embed_target_dossiers = True,
embedded_targets = [ctx.attr.extension],
embedded_targets = [watch_extension],
entitlements = entitlements.codesigning,
label_name = label.name,
platform_prerequisites = platform_prerequisites,
Expand All @@ -869,7 +888,7 @@ reproducible error case.".format(
actions = actions,
bundle_extension = bundle_extension,
bundle_name = bundle_name,
debug_dependencies = [ctx.attr.extension],
debug_dependencies = [watch_extension],
dsym_info_plist_template = apple_mac_toolchain_info.dsym_info_plist_template,
executable_name = executable_name,
label_name = label.name,
Expand All @@ -880,7 +899,7 @@ reproducible error case.".format(
),
partials.embedded_bundles_partial(
bundle_embedded_bundles = True,
embeddable_targets = [ctx.attr.extension],
embeddable_targets = [watch_extension],
platform_prerequisites = platform_prerequisites,
watch_bundles = [archive],
),
Expand All @@ -907,7 +926,7 @@ reproducible error case.".format(
apple_mac_toolchain_info = apple_mac_toolchain_info,
binary_artifact = binary_artifact,
bundle_dylibs = True,
dependency_targets = [ctx.attr.extension],
dependency_targets = [watch_extension],
label_name = label.name,
platform_prerequisites = platform_prerequisites,
),
Expand All @@ -919,7 +938,7 @@ reproducible error case.".format(
partials.apple_symbols_file_partial(
actions = actions,
binary_artifact = binary_artifact,
dependency_targets = [ctx.attr.extension],
dependency_targets = [watch_extension],
dsym_binaries = {},
label_name = label.name,
include_symbols_in_bundle = False,
Expand Down Expand Up @@ -1436,6 +1455,15 @@ def _watchos_single_target_application_impl(ctx):
fail("""
Single-target watchOS applications do not support watchOS 2 extensions or their delegates.
Please remove the assigned watchOS 2 app `extension` and make sure a valid watchOS application
delegate is referenced in the single-target `watchos_application`'s `deps`.
""")

for extenstion in ctx.attr.extensions:
if extenstion[AppleBundleInfo].product_type == apple_product_type.watch2_extension:
fail("""
Single-target watchOS applications do not support watchOS 2 extensions or their delegates.
Please remove the assigned watchOS 2 app `extension` and make sure a valid watchOS application
delegate is referenced in the single-target `watchos_application`'s `deps`.
""")
Expand All @@ -1461,7 +1489,9 @@ delegate is referenced in the single-target `watchos_application`'s `deps`.
shared_capabilities = ctx.attr.shared_capabilities,
)
cc_toolchain_forwarder = ctx.split_attr._cc_toolchain_forwarder
embeddable_targets = ctx.attr.deps
embeddable_targets = (
ctx.attr.deps + ctx.attr.frameworks + ctx.attr.extensions
)
executable_name = ctx.attr.executable_name
features = features_support.compute_enabled_features(
requested_features = ctx.features,
Expand Down Expand Up @@ -1534,6 +1564,8 @@ delegate is referenced in the single-target `watchos_application`'s `deps`.
rule_descriptor = rule_descriptor,
)

bundle_verification_targets = [struct(target = ext) for ext in ctx.attr.extensions]

processor_partials = [
partials.apple_bundle_info_partial(
actions = actions,
Expand Down Expand Up @@ -1612,6 +1644,11 @@ delegate is referenced in the single-target `watchos_application`'s `deps`.
platform_prerequisites = platform_prerequisites,
watch_bundles = [archive],
),
partials.extension_safe_validation_partial(
is_extension_safe = True,
rule_label = label,
targets_to_validate = ctx.attr.frameworks,
),
partials.framework_import_partial(
actions = actions,
apple_mac_toolchain_info = apple_mac_toolchain_info,
Expand All @@ -1620,12 +1657,13 @@ delegate is referenced in the single-target `watchos_application`'s `deps`.
platform_prerequisites = platform_prerequisites,
provisioning_profile = provisioning_profile,
rule_descriptor = rule_descriptor,
targets = ctx.attr.deps,
targets = embeddable_targets,
),
partials.resources_partial(
actions = actions,
apple_mac_toolchain_info = apple_mac_toolchain_info,
bundle_extension = bundle_extension,
bundle_verification_targets = bundle_verification_targets,
bundle_id = bundle_id,
bundle_name = bundle_name,
executable_name = executable_name,
Expand All @@ -1635,6 +1673,7 @@ delegate is referenced in the single-target `watchos_application`'s `deps`.
resource_deps = resource_deps,
rule_descriptor = rule_descriptor,
rule_label = label,
targets_to_avoid = ctx.attr.frameworks,
top_level_infoplists = top_level_infoplists,
top_level_resources = top_level_resources,
version = ctx.attr.version,
Expand All @@ -1648,6 +1687,15 @@ delegate is referenced in the single-target `watchos_application`'s `deps`.
label_name = label.name,
platform_prerequisites = platform_prerequisites,
),
partials.apple_symbols_file_partial(
actions = actions,
binary_artifact = binary_artifact,
dependency_targets = embeddable_targets,
dsym_binaries = debug_outputs.dsym_binaries,
label_name = label.name,
include_symbols_in_bundle = False,
platform_prerequisites = platform_prerequisites,
),
]

if platform_prerequisites.platform.is_device:
Expand Down Expand Up @@ -1741,6 +1789,17 @@ It is considered an error if the watchOS 2 application extension is assigned to
watchOS application, which is constructed if the `watchos_application` target is assigned `deps`.
This attribute will not support additional types of `watchos_extension`s in the future.
This attribute is deprecated, please use `extensions` instead.
""",
),
"extensions": attr.label_list(
providers = [[AppleBundleInfo, WatchosExtensionBundleInfo]],
doc = """
In case of single-target watchOS app, a list of watchOS application extensions to include in the final watch app bundle.
In case of an extension-based watchOS app, a list with a single element,
the watchOS 2 `watchos_extension` that is required to be bundled within a watchOS 2 app.
""",
),
"storyboards": attr.label_list(
Expand Down
7 changes: 4 additions & 3 deletions doc/rules-watchos.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
watchos_application(<a href="#watchos_application-name">name</a>, <a href="#watchos_application-deps">deps</a>, <a href="#watchos_application-resources">resources</a>, <a href="#watchos_application-additional_linker_inputs">additional_linker_inputs</a>, <a href="#watchos_application-app_icons">app_icons</a>, <a href="#watchos_application-app_intents">app_intents</a>,
<a href="#watchos_application-bundle_id">bundle_id</a>, <a href="#watchos_application-bundle_id_suffix">bundle_id_suffix</a>, <a href="#watchos_application-bundle_name">bundle_name</a>, <a href="#watchos_application-codesign_inputs">codesign_inputs</a>, <a href="#watchos_application-codesignopts">codesignopts</a>,
<a href="#watchos_application-entitlements">entitlements</a>, <a href="#watchos_application-entitlements_validation">entitlements_validation</a>, <a href="#watchos_application-executable_name">executable_name</a>, <a href="#watchos_application-exported_symbols_lists">exported_symbols_lists</a>,
<a href="#watchos_application-extension">extension</a>, <a href="#watchos_application-families">families</a>, <a href="#watchos_application-frameworks">frameworks</a>, <a href="#watchos_application-infoplists">infoplists</a>, <a href="#watchos_application-ipa_post_processor">ipa_post_processor</a>, <a href="#watchos_application-linkopts">linkopts</a>,
<a href="#watchos_application-minimum_deployment_os_version">minimum_deployment_os_version</a>, <a href="#watchos_application-minimum_os_version">minimum_os_version</a>, <a href="#watchos_application-platform_type">platform_type</a>,
<a href="#watchos_application-extension">extension</a>, <a href="#watchos_application-extensions">extensions</a>, <a href="#watchos_application-families">families</a>, <a href="#watchos_application-frameworks">frameworks</a>, <a href="#watchos_application-infoplists">infoplists</a>, <a href="#watchos_application-ipa_post_processor">ipa_post_processor</a>,
<a href="#watchos_application-linkopts">linkopts</a>, <a href="#watchos_application-minimum_deployment_os_version">minimum_deployment_os_version</a>, <a href="#watchos_application-minimum_os_version">minimum_os_version</a>, <a href="#watchos_application-platform_type">platform_type</a>,
<a href="#watchos_application-provisioning_profile">provisioning_profile</a>, <a href="#watchos_application-shared_capabilities">shared_capabilities</a>, <a href="#watchos_application-stamp">stamp</a>, <a href="#watchos_application-storyboards">storyboards</a>, <a href="#watchos_application-strings">strings</a>, <a href="#watchos_application-version">version</a>)
</pre>

Expand All @@ -37,7 +37,8 @@ Builds and bundles a watchOS Application.
| <a id="watchos_application-entitlements_validation"></a>entitlements_validation | An `entitlements_validation_mode` to control the validation of the requested entitlements against the provisioning profile to ensure they are supported. | String | optional | `"loose"` |
| <a id="watchos_application-executable_name"></a>executable_name | The desired name of the executable, if the bundle has an executable. If this attribute is not set, then the name of the `bundle_name` attribute will be used if it is set; if not, then the name of the target will be used instead. | String | optional | `""` |
| <a id="watchos_application-exported_symbols_lists"></a>exported_symbols_lists | A list of targets containing exported symbols lists files for the linker to control symbol resolution.<br><br>Each file is expected to have a list of global symbol names that will remain as global symbols in the compiled binary owned by this framework. All other global symbols will be treated as if they were marked as `__private_extern__` (aka `visibility=hidden`) and will not be global in the output file.<br><br>See the man page documentation for `ld(1)` on macOS for more details. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="watchos_application-extension"></a>extension | The watchOS 2 `watchos_extension` that is required to be bundled within a watchOS 2 application.<br><br>It is considered an error if the watchOS 2 application extension is assigned to a single target watchOS application, which is constructed if the `watchos_application` target is assigned `deps`.<br><br>This attribute will not support additional types of `watchos_extension`s in the future. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="watchos_application-extension"></a>extension | The watchOS 2 `watchos_extension` that is required to be bundled within a watchOS 2 application.<br><br>It is considered an error if the watchOS 2 application extension is assigned to a single target watchOS application, which is constructed if the `watchos_application` target is assigned `deps`.<br><br>This attribute will not support additional types of `watchos_extension`s in the future.<br><br>This attribute is deprecated, please use `extensions` instead. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="watchos_application-extensions"></a>extensions | In case of single-target watchOS app, a list of watchOS application extensions to include in the final watch app bundle.<br><br>In case of an extension-based watchOS app, a list with a single element, the watchOS 2 `watchos_extension` that is required to be bundled within a watchOS 2 app. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="watchos_application-families"></a>families | A list of device families supported by this rule. At least one must be specified. | List of strings | optional | `["watch"]` |
| <a id="watchos_application-frameworks"></a>frameworks | A list of framework targets (see [`watchos_framework`](https://github.com/bazelbuild/rules_apple/blob/master/doc/rules-watchos.md#watchos_framework)) that this target depends on. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="watchos_application-infoplists"></a>infoplists | A list of .plist files that will be merged to form the Info.plist for this target. At least one file must be specified. Please see [Info.plist Handling](https://github.com/bazelbuild/rules_apple/blob/master/doc/common_info.md#infoplist-handling) for what is supported. | <a href="https://bazel.build/concepts/labels">List of labels</a> | required | |
Expand Down
2 changes: 1 addition & 1 deletion examples/watchos/HelloWorld/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ watchos_application(
name = "HelloWorld-WatchApplication",
app_icons = ["//examples/resources:WatchAppIcon.xcassets"],
bundle_id = "com.example.hello-world.watch",
extension = ":HelloWorld-WatchExtension",
extensions = [":HelloWorld-WatchExtension"],
infoplists = [":WatchApp-Info.plist"],
minimum_os_version = "4.0",
storyboards = [
Expand Down
53 changes: 53 additions & 0 deletions test/starlark_tests/targets_under_test/watchos/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,28 @@ watchos_application(
],
)

watchos_application(
name = "single_target_app_with_extension",
app_icons = ["//test/starlark_tests/resources:WatchAppIcon.xcassets"],
bundle_id = "com.google.example",
entitlements = "//test/starlark_tests/resources:entitlements.plist",
extensions = [":watchos_app_extension"],
infoplists = [
"//test/starlark_tests/resources:WatchosAppInfo.plist",
],
minimum_os_version = common.min_os_watchos.single_target_app,
provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision",
resources = [
"//test/starlark_tests/resources:example_filegroup",
"//test/starlark_tests/resources:localization",
"//test/starlark_tests/resources:resource_bundle",
],
tags = common.fixture_tags,
deps = [
"//test/starlark_tests/resources:watchkit_single_target_app_main_lib",
],
)

watchos_application(
name = "app_with_ext_with_imported_fmwk",
app_icons = ["//test/starlark_tests/resources:WatchAppIcon.xcassets"],
Expand Down Expand Up @@ -507,6 +529,23 @@ ios_application(
],
)

ios_application(
name = "ios_watchos_with_watchos_extension_within_extensions",
bundle_id = "com.google",
bundle_name = "companion",
families = ["iphone"],
infoplists = [
"//test/starlark_tests/resources:Info.plist",
],
minimum_os_version = common.min_os_ios.arm_sim_support,
provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision",
tags = common.fixture_tags,
watch_application = ":watchos_app_with_extension_within_extensions",
deps = [
":swift_lib",
],
)

ios_application(
name = "ios_watchos_with_watchos_extension_and_symbols_in_bundle",
bundle_id = "com.google",
Expand Down Expand Up @@ -572,6 +611,20 @@ watchos_application(
tags = common.fixture_tags,
)

watchos_application(
name = "watchos_app_with_extension_within_extensions",
app_icons = ["//test/starlark_tests/resources:WatchAppIcon.xcassets"],
bundle_id = "com.google.example",
bundle_name = "app",
extensions = [":watchos_ext_with_extension"],
infoplists = [
"//test/starlark_tests/resources:WatchosAppInfo.plist",
],
minimum_os_version = common.min_os_watchos.baseline,
provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision",
tags = common.fixture_tags,
)

watchos_application(
name = "single_target_app_too_low_minos",
app_icons = ["//test/starlark_tests/resources:WatchAppIcon.xcassets"],
Expand Down
11 changes: 11 additions & 0 deletions test/starlark_tests/watchos_application_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,17 @@ def watchos_application_test_suite(name):
tags = [name],
)

# Tests inclusion of extensions within Watch extensions if defined with `extensions` as opposed to `extension`
archive_contents_test(
name = "{}_contains_watchos_extension_extensions".format(name),
build_type = "device",
target_under_test = "//test/starlark_tests/targets_under_test/watchos:ios_watchos_with_watchos_extension_within_extensions",
contains = [
"$BUNDLE_ROOT/Watch/app.app/PlugIns/ext.appex/PlugIns/watchos_app_extension.appex/watchos_app_extension",
],
tags = [name],
)

# Tests that the tsan support libraries are found in the app extension bundle of a watchOS app.
archive_contents_test(
name = "{}_contains_tsan_dylib_device_test".format(name),
Expand Down
Loading

0 comments on commit 519c703

Please sign in to comment.