Skip to content
Open
110 changes: 83 additions & 27 deletions apple/internal/partials/app_intents_metadata_bundle.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -96,33 +96,89 @@ def _app_intents_metadata_bundle_partial_impl(
# available archs.
first_cc_toolchain_key = cc_toolchains.keys()[0]

metadata_bundle = generate_app_intents_metadata_bundle(
actions = actions,
apple_fragment = platform_prerequisites.apple_fragment,
bundle_binary = fat_stub_binary,
constvalues_files = [
swiftconstvalues_file
for dep in deps[first_cc_toolchain_key]
for swiftconstvalues_file in dep[AppIntentsInfo].swiftconstvalues_files
],
intents_module_names = [
intent_module_name
for dep in deps[first_cc_toolchain_key]
for intent_module_name in dep[AppIntentsInfo].intent_module_names
],
label = label,
source_files = [
swift_source_file
for dep in deps[first_cc_toolchain_key]
for swift_source_file in dep[AppIntentsInfo].swift_source_files
],
target_triples = [
cc_toolchain[cc_common.CcToolchainInfo].target_gnu_system_name
for cc_toolchain in cc_toolchains.values()
],
xcode_version_config = platform_prerequisites.xcode_version_config,
json_tool = json_tool,
)
metadata_bundle = None
if platform_prerequisites.xcode_version_config.xcode_version() >= apple_common.dotted_version("26.0"):
per_dep_metadata_bundles = []
for dep in deps[first_cc_toolchain_key]:
per_dep_metadata_bundles.append(
generate_app_intents_metadata_bundle(
actions = actions,
apple_fragment = platform_prerequisites.apple_fragment,
bundle_binary = fat_stub_binary,
constvalues_files = dep[AppIntentsInfo].swiftconstvalues_files,
intents_module_names = dep[AppIntentsInfo].intent_module_names,
label = label.relative(label.name + dep[AppIntentsInfo].intent_module_names[0]),
dependency_metadata_bundles = [],
source_files = dep[AppIntentsInfo].swift_source_files,
target_triples = [
cc_toolchain[cc_common.CcToolchainInfo].target_gnu_system_name
for cc_toolchain in cc_toolchains.values()
],
xcode_version_config = platform_prerequisites.xcode_version_config,
json_tool = json_tool,
),
)

# Merge multiple intent metadatas into a single.
dummy_source_file = intermediates.file(
actions = actions,
target_name = label.name,
output_discriminator = None,
file_name = "{}_app_intents_dummy_source.swift".format(label.name),
)
dummy_constvalues_file = intermediates.file(
actions = actions,
target_name = label.name,
output_discriminator = None,
file_name = "{}_app_intents_dummy_constvalues.swiftconstvalues".format(label.name),
)
actions.write(output = dummy_source_file, content = "")
actions.write(output = dummy_constvalues_file, content = "[]")
metadata_bundle = generate_app_intents_metadata_bundle(
actions = actions,
apple_fragment = platform_prerequisites.apple_fragment,
bundle_binary = fat_stub_binary,
constvalues_files = [dummy_constvalues_file],
intents_module_names = ["{}AppIntents".format(label.name)],
label = label,
dependency_metadata_bundles = per_dep_metadata_bundles,
source_files = [dummy_source_file],
target_triples = [
cc_toolchain[cc_common.CcToolchainInfo].target_gnu_system_name
for cc_toolchain in cc_toolchains.values()
],
xcode_version_config = platform_prerequisites.xcode_version_config,
json_tool = json_tool,
)
else:
metadata_bundle = generate_app_intents_metadata_bundle(
actions = actions,
apple_fragment = platform_prerequisites.apple_fragment,
bundle_binary = fat_stub_binary,
constvalues_files = [
swiftconstvalues_file
for dep in deps[first_cc_toolchain_key]
for swiftconstvalues_file in dep[AppIntentsInfo].swiftconstvalues_files
],
intents_module_names = [
intent_module_name
for dep in deps[first_cc_toolchain_key]
for intent_module_name in dep[AppIntentsInfo].intent_module_names
],
label = label,
dependency_metadata_bundles = [],
source_files = [
swift_source_file
for dep in deps[first_cc_toolchain_key]
for swift_source_file in dep[AppIntentsInfo].swift_source_files
],
target_triples = [
cc_toolchain[cc_common.CcToolchainInfo].target_gnu_system_name
for cc_toolchain in cc_toolchains.values()
],
xcode_version_config = platform_prerequisites.xcode_version_config,
json_tool = json_tool,
)

bundle_location = processor.location.bundle
if str(platform_prerequisites.platform_type) == "macos":
Expand Down
23 changes: 23 additions & 0 deletions apple/internal/resource_actions/app_intents.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def generate_app_intents_metadata_bundle(
constvalues_files,
intents_module_names,
label,
dependency_metadata_bundles,
source_files,
target_triples,
xcode_version_config,
Expand All @@ -40,6 +41,8 @@ def generate_app_intents_metadata_bundle(
intents_module_names: List of Strings with the module names corresponding to the modules
found which have intents compiled.
label: Label for the current target (`ctx.label`).
dependency_metadata_bundles: List of Metadata.appintents bundles of dependency modules,
only works on Xcode 26+ toolchain.
source_files: List of Swift source files implementing the AppIntents protocol.
target_triples: List of Apple target triples from `CcToolchainInfo` providers.
xcode_version_config: The `apple_common.XcodeVersionConfig` provider from the current ctx.
Expand All @@ -57,6 +60,21 @@ def generate_app_intents_metadata_bundle(
dir_name = "Metadata.appintents",
)

static_metadata_file_list = None
if dependency_metadata_bundles:
static_metadata_file_list = intermediates.file(
actions = actions,
target_name = label.name,
output_discriminator = None,
file_name = "{}.DependencyStaticMetadataFileList".format(label.name),
)

static_metadata_file_list_content = "\n".join([
"{}{}".format(f.path, "/extract.actionsdata")
for f in dependency_metadata_bundles
]) + "\n"
actions.write(output = static_metadata_file_list, content = static_metadata_file_list_content)

args = actions.args()
args.add("/usr/bin/xcrun")
args.add("appintentsmetadataprocessor")
Expand Down Expand Up @@ -84,6 +102,11 @@ Could not find a module name for app_intents. One is required for App Intents me
before_each = "--source-files",
)
transitive_inputs = [depset(source_files)]
if dependency_metadata_bundles:
transitive_inputs.append(depset(dependency_metadata_bundles))
if static_metadata_file_list:
args.add("--static-metadata-file-list", static_metadata_file_list.path)
transitive_inputs.append(depset([static_metadata_file_list]))
args.add("--sdk-root", apple_support.path_placeholders.sdkroot())
args.add_all(target_triples, before_each = "--target-triple")
if xcode_version_config.xcode_version() >= apple_common.dotted_version("15.0"):
Expand Down
30 changes: 20 additions & 10 deletions test/starlark_tests/ios_application_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -793,18 +793,28 @@ def ios_application_test_suite(name):
],
)

# Test app that has two Intents defined as top level modules generates an error message.
analysis_failure_message_test(
name = "{}_with_two_app_intents_and_two_modules_fails".format(name),
# Test app with App Intents from multiple modules includes both intents.
archive_contents_test(
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_simulator_test".format(name),
build_type = "simulator",
target_under_test = "//test/starlark_tests/targets_under_test/ios:app_with_app_intent_and_widget_configuration_intent",
expected_error = (
"App Intents must have only one module name for metadata generation to work correctly."
).format(
package = "//test/starlark_tests/targets_under_test/ios",
),
tags = [
name,
text_test_file = "$BUNDLE_ROOT/Metadata.appintents/extract.actionsdata",
text_test_values = [
".*HelloWorldIntent.*",
".*FavoriteSoup.*",
],
tags = [name],
)
archive_contents_test(
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_device_test".format(name),
build_type = "device",
target_under_test = "//test/starlark_tests/targets_under_test/ios:app_with_app_intent_and_widget_configuration_intent",
text_test_file = "$BUNDLE_ROOT/Metadata.appintents/extract.actionsdata",
text_test_values = [
".*HelloWorldIntent.*",
".*FavoriteSoup.*",
],
tags = [name],
)

# Test app with App Intents generates and bundles Metadata.appintents bundle for fat binaries.
Expand Down
24 changes: 24 additions & 0 deletions test/starlark_tests/macos_application_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,30 @@ def macos_application_test_suite(name):
tags = [name],
)

# Test app with App Intents from multiple modules includes both intents.
archive_contents_test(
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_simulator_test".format(name),
build_type = "simulator",
target_under_test = "//test/starlark_tests/targets_under_test/macos:app_with_app_intent_and_widget_configuration_intent",
text_test_file = "$RESOURCE_ROOT/Metadata.appintents/extract.actionsdata",
text_test_values = [
".*HelloWorldIntent.*",
".*FavoriteSoup.*",
],
tags = [name],
)
archive_contents_test(
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_device_test".format(name),
build_type = "device",
target_under_test = "//test/starlark_tests/targets_under_test/macos:app_with_app_intent_and_widget_configuration_intent",
text_test_file = "$RESOURCE_ROOT/Metadata.appintents/extract.actionsdata",
text_test_values = [
".*HelloWorldIntent.*",
".*FavoriteSoup.*",
],
tags = [name],
)

apple_verification_test(
name = "{}_app_intents_metadata_json_keys_sorted_test".format(name),
build_type = "simulator",
Expand Down
7 changes: 7 additions & 0 deletions test/starlark_tests/resources/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,13 @@ swift_library(
tags = common.fixture_tags,
)

swift_library(
name = "extra_app_intent",
srcs = ["extra_app_intent.swift"],
linkopts = ["-Wl,-framework,AppIntents"],
tags = common.fixture_tags,
)

swift_library(
name = "swift_concurrency_main_lib",
srcs = ["concurrency_main.swift"],
Expand Down
24 changes: 24 additions & 0 deletions test/starlark_tests/resources/extra_app_intent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2024 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import AppIntents

struct ExtraIntent: AppIntent {
static var title: LocalizedStringResource = "Extra Intent"
static var description = IntentDescription("Additional app intent.")

func perform() async throws -> some ProvidesDialog {
return .result(dialog: "This is an extra intent")
}
}
17 changes: 17 additions & 0 deletions test/starlark_tests/targets_under_test/macos/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2803,6 +2803,23 @@ macos_application(
],
)

macos_application(
name = "app_with_app_intent_and_widget_configuration_intent",
app_intents = [
"//test/starlark_tests/resources:app_intent",
"//test/starlark_tests/resources:widget_configuration_intent",
],
bundle_id = "com.google.example",
infoplists = [
"//test/starlark_tests/resources:Info.plist",
],
minimum_os_version = common.min_os_macos.app_intents_support,
tags = common.fixture_tags,
deps = [
"//test/starlark_tests/resources:objc_main_lib_with_common_lib",
],
)

# ---------------------------------------------------------------------------------------
# Targets for base_bundle_id and bundle_id flows with and without shared capabilities.

Expand Down
18 changes: 18 additions & 0 deletions test/starlark_tests/targets_under_test/tvos/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1503,6 +1503,24 @@ tvos_application(
],
)

tvos_application(
name = "app_with_app_intent_and_extra_app_intent",
app_intents = [
"//test/starlark_tests/resources:app_intent",
"//test/starlark_tests/resources:extra_app_intent",
],
bundle_id = "com.google.example",
infoplists = [
"//test/starlark_tests/resources:Info.plist",
],
minimum_os_version = common.min_os_tvos.app_intents_support,
provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision",
tags = common.fixture_tags,
deps = [
"//test/starlark_tests/resources:swift_uikit_appdelegate",
],
)

# ---------------------------------------------------------------------------------------
# Targets for base_bundle_id and bundle_id flows with and without shared capabilities.

Expand Down
19 changes: 19 additions & 0 deletions test/starlark_tests/targets_under_test/watchos/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,25 @@ watchos_application(
],
)

watchos_application(
name = "app_with_app_intent_and_widget_configuration_intent",
app_intents = [
"//test/starlark_tests/resources:app_intent",
"//test/starlark_tests/resources:widget_configuration_intent",
],
bundle_id = "com.google.example",
entitlements = "//test/starlark_tests/resources:entitlements.plist",
infoplists = [
"//test/starlark_tests/resources:WatchosAppInfo.plist",
],
minimum_os_version = common.min_os_watchos.app_intents_support,
provisioning_profile = "//test/testdata/provisioning:integration_testing_ios.mobileprovision",
tags = common.fixture_tags,
deps = [
"//test/starlark_tests/resources:watchkit_single_target_app_main_lib",
],
)

# ---------------------------------------------------------------------------------------
# Targets for base_bundle_id and bundle_id flows with and without shared capabilities.

Expand Down
24 changes: 24 additions & 0 deletions test/starlark_tests/tvos_application_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,30 @@ def tvos_application_test_suite(name):
tags = [name],
)

# Test app with App Intents from multiple modules includes both intents.
archive_contents_test(
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_simulator_test".format(name),
build_type = "simulator",
target_under_test = "//test/starlark_tests/targets_under_test/tvos:app_with_app_intent_and_extra_app_intent",
text_test_file = "$BUNDLE_ROOT/Metadata.appintents/extract.actionsdata",
text_test_values = [
".*HelloWorldIntent.*",
".*ExtraIntent.*",
],
tags = [name],
)
archive_contents_test(
name = "{}_two_app_intents_modules_metadata_bundle_contents_for_device_test".format(name),
build_type = "device",
target_under_test = "//test/starlark_tests/targets_under_test/tvos:app_with_app_intent_and_extra_app_intent",
text_test_file = "$BUNDLE_ROOT/Metadata.appintents/extract.actionsdata",
text_test_values = [
".*HelloWorldIntent.*",
".*ExtraIntent.*",
],
tags = [name],
)

apple_verification_test(
name = "{}_app_intents_metadata_json_keys_sorted_test".format(name),
build_type = "simulator",
Expand Down
Loading