Skip to content

Commit f92f668

Browse files
kntkymtaaronsky
andauthored
Support multiple modules on app_intents for applications (#2879)
# Background resolve #2878 # Approach ## As-is 1. Generate App Intents metadata for a target passed to app_intents (only a single target is supported). 1. Bundle the output from step 1 into the application. ## To-be ### When using Xcode 26 or later 1. Generate App Intents metadata (Metadata.appintents) for each target passed to app_intents. 1. Generate an additional App Intents metadata file for a dummy intermediate target by passing the metadata files from `1.` to `--static-metadata-file-list`. - `appintentmetadataprocessor` merges the metadata files into a single metadata. 1. Bundle the output from step 2 into the application. Note: `appintentmetadataprocessor` supports `--static-metadata-file-list` starting with the Xcode 26 toolchain. ### Otherwise Keep the current behavior. # Sample sample project https://github.com/kntkymt/ios-app-intents-sample/tree/main/bazel-multi-app-intents --------- Co-authored-by: Aaron Sky <[email protected]>
1 parent 2000428 commit f92f668

File tree

11 files changed

+285
-37
lines changed

11 files changed

+285
-37
lines changed

apple/internal/partials/app_intents_metadata_bundle.bzl

Lines changed: 84 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
load("@bazel_skylib//lib:partial.bzl", "partial")
1818
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
19+
load("//apple/internal:intermediates.bzl", "intermediates")
1920
load("//apple/internal:processor.bzl", "processor")
2021
load(
2122
"//apple/internal/providers:app_intents_info.bzl",
@@ -45,33 +46,89 @@ def _app_intents_metadata_bundle_partial_impl(
4546
# available archs.
4647
first_cc_toolchain_key = cc_toolchains.keys()[0]
4748

48-
metadata_bundle = generate_app_intents_metadata_bundle(
49-
actions = actions,
50-
apple_fragment = platform_prerequisites.apple_fragment,
51-
constvalues_files = [
52-
swiftconstvalues_file
53-
for dep in deps[first_cc_toolchain_key]
54-
for swiftconstvalues_file in dep[AppIntentsInfo].swiftconstvalues_files
55-
],
56-
intents_module_names = [
57-
intent_module_name
58-
for dep in deps[first_cc_toolchain_key]
59-
for intent_module_name in dep[AppIntentsInfo].intent_module_names
60-
],
61-
label = label,
62-
platform_prerequisites = platform_prerequisites,
63-
source_files = [
64-
swift_source_file
65-
for dep in deps[first_cc_toolchain_key]
66-
for swift_source_file in dep[AppIntentsInfo].swift_source_files
67-
],
68-
target_triples = [
69-
cc_toolchain[cc_common.CcToolchainInfo].target_gnu_system_name
70-
for cc_toolchain in cc_toolchains.values()
71-
],
72-
xcode_version_config = platform_prerequisites.xcode_version_config,
73-
json_tool = json_tool,
74-
)
49+
metadata_bundle = None
50+
if platform_prerequisites.xcode_version_config.xcode_version() >= apple_common.dotted_version("26.0"):
51+
per_dep_metadata_bundles = []
52+
for dep in deps[first_cc_toolchain_key]:
53+
per_dep_metadata_bundles.append(
54+
generate_app_intents_metadata_bundle(
55+
actions = actions,
56+
apple_fragment = platform_prerequisites.apple_fragment,
57+
constvalues_files = dep[AppIntentsInfo].swiftconstvalues_files,
58+
intents_module_names = dep[AppIntentsInfo].intent_module_names,
59+
label = label.relative(label.name + dep[AppIntentsInfo].intent_module_names[0]),
60+
dependency_metadata_bundles = [],
61+
platform_prerequisites = platform_prerequisites,
62+
source_files = dep[AppIntentsInfo].swift_source_files,
63+
target_triples = [
64+
cc_toolchain[cc_common.CcToolchainInfo].target_gnu_system_name
65+
for cc_toolchain in cc_toolchains.values()
66+
],
67+
xcode_version_config = platform_prerequisites.xcode_version_config,
68+
json_tool = json_tool,
69+
),
70+
)
71+
72+
# Merge multiple intent metadatas into a single.
73+
dummy_source_file = intermediates.file(
74+
actions = actions,
75+
target_name = label.name,
76+
output_discriminator = None,
77+
file_name = "{}_app_intents_dummy_source.swift".format(label.name),
78+
)
79+
dummy_constvalues_file = intermediates.file(
80+
actions = actions,
81+
target_name = label.name,
82+
output_discriminator = None,
83+
file_name = "{}_app_intents_dummy_constvalues.swiftconstvalues".format(label.name),
84+
)
85+
actions.write(output = dummy_source_file, content = "")
86+
actions.write(output = dummy_constvalues_file, content = "[]")
87+
metadata_bundle = generate_app_intents_metadata_bundle(
88+
actions = actions,
89+
apple_fragment = platform_prerequisites.apple_fragment,
90+
constvalues_files = [dummy_constvalues_file],
91+
intents_module_names = ["{}AppIntents".format(label.name)],
92+
label = label,
93+
dependency_metadata_bundles = per_dep_metadata_bundles,
94+
platform_prerequisites = platform_prerequisites,
95+
source_files = [dummy_source_file],
96+
target_triples = [
97+
cc_toolchain[cc_common.CcToolchainInfo].target_gnu_system_name
98+
for cc_toolchain in cc_toolchains.values()
99+
],
100+
xcode_version_config = platform_prerequisites.xcode_version_config,
101+
json_tool = json_tool,
102+
)
103+
else:
104+
metadata_bundle = generate_app_intents_metadata_bundle(
105+
actions = actions,
106+
apple_fragment = platform_prerequisites.apple_fragment,
107+
constvalues_files = [
108+
swiftconstvalues_file
109+
for dep in deps[first_cc_toolchain_key]
110+
for swiftconstvalues_file in dep[AppIntentsInfo].swiftconstvalues_files
111+
],
112+
intents_module_names = [
113+
intent_module_name
114+
for dep in deps[first_cc_toolchain_key]
115+
for intent_module_name in dep[AppIntentsInfo].intent_module_names
116+
],
117+
label = label,
118+
dependency_metadata_bundles = [],
119+
platform_prerequisites = platform_prerequisites,
120+
source_files = [
121+
swift_source_file
122+
for dep in deps[first_cc_toolchain_key]
123+
for swift_source_file in dep[AppIntentsInfo].swift_source_files
124+
],
125+
target_triples = [
126+
cc_toolchain[cc_common.CcToolchainInfo].target_gnu_system_name
127+
for cc_toolchain in cc_toolchains.values()
128+
],
129+
xcode_version_config = platform_prerequisites.xcode_version_config,
130+
json_tool = json_tool,
131+
)
75132

76133
bundle_location = processor.location.bundle
77134
if str(platform_prerequisites.platform_type) == "macos":

apple/internal/resource_actions/app_intents.bzl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def generate_app_intents_metadata_bundle(
3434
constvalues_files,
3535
intents_module_names,
3636
label,
37+
dependency_metadata_bundles,
3738
platform_prerequisites,
3839
source_files,
3940
target_triples,
@@ -49,6 +50,8 @@ def generate_app_intents_metadata_bundle(
4950
intents_module_names: List of Strings with the module names corresponding to the modules
5051
found which have intents compiled.
5152
label: Label for the current target (`ctx.label`).
53+
dependency_metadata_bundles: List of Metadata.appintents bundles of dependency modules,
54+
only works on Xcode 26+ toolchain.
5255
platform_prerequisites: Struct containing information on the platform being targeted.
5356
source_files: List of Swift source files implementing the AppIntents protocol.
5457
target_triples: List of Apple target triples from `CcToolchainInfo` providers.
@@ -67,6 +70,21 @@ def generate_app_intents_metadata_bundle(
6770
dir_name = "Metadata.appintents",
6871
)
6972

73+
static_metadata_file_list = None
74+
if dependency_metadata_bundles:
75+
static_metadata_file_list = intermediates.file(
76+
actions = actions,
77+
target_name = label.name,
78+
output_discriminator = None,
79+
file_name = "{}.DependencyStaticMetadataFileList".format(label.name),
80+
)
81+
82+
static_metadata_file_list_content = "\n".join([
83+
"{}{}".format(f.path, "/extract.actionsdata")
84+
for f in dependency_metadata_bundles
85+
]) + "\n"
86+
actions.write(output = static_metadata_file_list, content = static_metadata_file_list_content)
87+
7088
args = actions.args()
7189
args.add("/usr/bin/xcrun")
7290
args.add("appintentsmetadataprocessor")
@@ -97,6 +115,11 @@ Could not find a module name for app_intents. One is required for App Intents me
97115
before_each = "--source-files",
98116
)
99117
transitive_inputs = [depset(source_files)]
118+
if dependency_metadata_bundles:
119+
transitive_inputs.append(depset(dependency_metadata_bundles))
120+
if static_metadata_file_list:
121+
args.add("--static-metadata-file-list", static_metadata_file_list.path)
122+
transitive_inputs.append(depset([static_metadata_file_list]))
100123
args.add("--sdk-root", apple_support.path_placeholders.sdkroot())
101124
platform_type_string = str(platform_prerequisites.platform_type)
102125
platform_family = _PLATFORM_TYPE_TO_PLATFORM_FAMILY[platform_type_string]

test/starlark_tests/ios_application_tests.bzl

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -806,18 +806,28 @@ def ios_application_test_suite(name):
806806
],
807807
)
808808

809-
# Test app that has two Intents defined as top level modules generates an error message.
810-
analysis_failure_message_test(
811-
name = "{}_with_two_app_intents_and_two_modules_fails".format(name),
809+
# Test app with App Intents from multiple modules includes both intents.
810+
archive_contents_test(
811+
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_simulator_test".format(name),
812+
build_type = "simulator",
812813
target_under_test = "//test/starlark_tests/targets_under_test/ios:app_with_app_intent_and_widget_configuration_intent",
813-
expected_error = (
814-
"App Intents must have only one module name for metadata generation to work correctly."
815-
).format(
816-
package = "//test/starlark_tests/targets_under_test/ios",
817-
),
818-
tags = [
819-
name,
814+
text_test_file = "$BUNDLE_ROOT/Metadata.appintents/extract.actionsdata",
815+
text_test_values = [
816+
".*HelloWorldIntent.*",
817+
".*FavoriteSoup.*",
820818
],
819+
tags = [name],
820+
)
821+
archive_contents_test(
822+
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_device_test".format(name),
823+
build_type = "device",
824+
target_under_test = "//test/starlark_tests/targets_under_test/ios:app_with_app_intent_and_widget_configuration_intent",
825+
text_test_file = "$BUNDLE_ROOT/Metadata.appintents/extract.actionsdata",
826+
text_test_values = [
827+
".*HelloWorldIntent.*",
828+
".*FavoriteSoup.*",
829+
],
830+
tags = [name],
821831
)
822832

823833
# Test app with App Intents generates and bundles Metadata.appintents bundle for fat binaries.

test/starlark_tests/macos_application_tests.bzl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,30 @@ def macos_application_test_suite(name):
388388
tags = [name],
389389
)
390390

391+
# Test app with App Intents from multiple modules includes both intents.
392+
archive_contents_test(
393+
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_simulator_test".format(name),
394+
build_type = "simulator",
395+
target_under_test = "//test/starlark_tests/targets_under_test/macos:app_with_app_intent_and_widget_configuration_intent",
396+
text_test_file = "$RESOURCE_ROOT/Metadata.appintents/extract.actionsdata",
397+
text_test_values = [
398+
".*HelloWorldIntent.*",
399+
".*FavoriteSoup.*",
400+
],
401+
tags = [name],
402+
)
403+
archive_contents_test(
404+
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_device_test".format(name),
405+
build_type = "device",
406+
target_under_test = "//test/starlark_tests/targets_under_test/macos:app_with_app_intent_and_widget_configuration_intent",
407+
text_test_file = "$RESOURCE_ROOT/Metadata.appintents/extract.actionsdata",
408+
text_test_values = [
409+
".*HelloWorldIntent.*",
410+
".*FavoriteSoup.*",
411+
],
412+
tags = [name],
413+
)
414+
391415
apple_verification_test(
392416
name = "{}_app_intents_metadata_json_keys_sorted_test".format(name),
393417
build_type = "simulator",

test/starlark_tests/resources/BUILD

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,13 @@ swift_library(
14141414
tags = common.fixture_tags,
14151415
)
14161416

1417+
swift_library(
1418+
name = "extra_app_intent",
1419+
srcs = ["extra_app_intent.swift"],
1420+
linkopts = ["-Wl,-framework,AppIntents"],
1421+
tags = common.fixture_tags,
1422+
)
1423+
14171424
swift_library(
14181425
name = "swift_concurrency_main_lib",
14191426
srcs = ["concurrency_main.swift"],
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2024 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import AppIntents
16+
17+
struct ExtraIntent: AppIntent {
18+
static var title: LocalizedStringResource = "Extra Intent"
19+
static var description = IntentDescription("Additional app intent.")
20+
21+
func perform() async throws -> some ProvidesDialog {
22+
return .result(dialog: "This is an extra intent")
23+
}
24+
}

test/starlark_tests/targets_under_test/macos/BUILD

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2803,6 +2803,23 @@ macos_application(
28032803
],
28042804
)
28052805

2806+
macos_application(
2807+
name = "app_with_app_intent_and_widget_configuration_intent",
2808+
app_intents = [
2809+
"//test/starlark_tests/resources:app_intent",
2810+
"//test/starlark_tests/resources:widget_configuration_intent",
2811+
],
2812+
bundle_id = "com.google.example",
2813+
infoplists = [
2814+
"//test/starlark_tests/resources:Info.plist",
2815+
],
2816+
minimum_os_version = common.min_os_macos.app_intents_support,
2817+
tags = common.fixture_tags,
2818+
deps = [
2819+
"//test/starlark_tests/resources:objc_main_lib_with_common_lib",
2820+
],
2821+
)
2822+
28062823
# ---------------------------------------------------------------------------------------
28072824
# Targets for base_bundle_id and bundle_id flows with and without shared capabilities.
28082825

test/starlark_tests/targets_under_test/tvos/BUILD

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,24 @@ tvos_application(
15031503
],
15041504
)
15051505

1506+
tvos_application(
1507+
name = "app_with_app_intent_and_extra_app_intent",
1508+
app_intents = [
1509+
"//test/starlark_tests/resources:app_intent",
1510+
"//test/starlark_tests/resources:extra_app_intent",
1511+
],
1512+
bundle_id = "com.google.example",
1513+
infoplists = [
1514+
"//test/starlark_tests/resources:Info.plist",
1515+
],
1516+
minimum_os_version = common.min_os_tvos.app_intents_support,
1517+
provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision",
1518+
tags = common.fixture_tags,
1519+
deps = [
1520+
"//test/starlark_tests/resources:swift_uikit_appdelegate",
1521+
],
1522+
)
1523+
15061524
# ---------------------------------------------------------------------------------------
15071525
# Targets for base_bundle_id and bundle_id flows with and without shared capabilities.
15081526

test/starlark_tests/targets_under_test/watchos/BUILD

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,25 @@ watchos_application(
13061306
],
13071307
)
13081308

1309+
watchos_application(
1310+
name = "app_with_app_intent_and_widget_configuration_intent",
1311+
app_intents = [
1312+
"//test/starlark_tests/resources:app_intent",
1313+
"//test/starlark_tests/resources:widget_configuration_intent",
1314+
],
1315+
bundle_id = "com.google.example",
1316+
entitlements = "//test/starlark_tests/resources:entitlements.plist",
1317+
infoplists = [
1318+
"//test/starlark_tests/resources:WatchosAppInfo.plist",
1319+
],
1320+
minimum_os_version = common.min_os_watchos.app_intents_support,
1321+
provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision",
1322+
tags = common.fixture_tags,
1323+
deps = [
1324+
"//test/starlark_tests/resources:watchkit_single_target_app_main_lib",
1325+
],
1326+
)
1327+
13091328
# ---------------------------------------------------------------------------------------
13101329
# Targets for base_bundle_id and bundle_id flows with and without shared capabilities.
13111330

test/starlark_tests/tvos_application_tests.bzl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,30 @@ def tvos_application_test_suite(name):
611611
tags = [name],
612612
)
613613

614+
# Test app with App Intents from multiple modules includes both intents.
615+
archive_contents_test(
616+
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_simulator_test".format(name),
617+
build_type = "simulator",
618+
target_under_test = "//test/starlark_tests/targets_under_test/tvos:app_with_app_intent_and_extra_app_intent",
619+
text_test_file = "$BUNDLE_ROOT/Metadata.appintents/extract.actionsdata",
620+
text_test_values = [
621+
".*HelloWorldIntent.*",
622+
".*ExtraIntent.*",
623+
],
624+
tags = [name],
625+
)
626+
archive_contents_test(
627+
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_device_test".format(name),
628+
build_type = "device",
629+
target_under_test = "//test/starlark_tests/targets_under_test/tvos:app_with_app_intent_and_extra_app_intent",
630+
text_test_file = "$BUNDLE_ROOT/Metadata.appintents/extract.actionsdata",
631+
text_test_values = [
632+
".*HelloWorldIntent.*",
633+
".*ExtraIntent.*",
634+
],
635+
tags = [name],
636+
)
637+
614638
apple_verification_test(
615639
name = "{}_app_intents_metadata_json_keys_sorted_test".format(name),
616640
build_type = "simulator",

0 commit comments

Comments
 (0)