Skip to content

Commit df90109

Browse files
authored
Make AppIntentsMetadataProcessor outputs deterministic (#2761)
1 parent 01ecafe commit df90109

17 files changed

Lines changed: 228 additions & 4 deletions

apple/internal/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ apple_xplat_tools_toolchain(
4444
name = "xplat_tools_toolchain",
4545
build_settings = build_settings_labels.all_labels,
4646
bundletool = "//tools/bundletool",
47+
json_tool = "//tools/json_tool",
4748
versiontool = "//tools/versiontool",
4849
# Used by the rule implementations, so it needs to be public; but
4950
# should be considered an implementation detail of the rules and

apple/internal/apple_toolchains.bzl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ files/ZIPs and destinations paths to build the directory structure for those fil
112112
""",
113113
"versiontool": """\
114114
A files_to_run for a tool that acts as a wrapper for xcrun actions.
115+
""",
116+
"json_tool": """\
117+
A `files_to_run` wrapping Python's `json.tool` module (https://docs.python.org/3.5/library/json.html#module-json.tool)
118+
for deterministic JSON handling.
115119
""",
116120
},
117121
)
@@ -268,6 +272,7 @@ def _apple_xplat_tools_toolchain_impl(ctx):
268272
),
269273
bundletool = ctx.attr.bundletool.files_to_run,
270274
versiontool = ctx.attr.versiontool.files_to_run,
275+
json_tool = ctx.attr.json_tool.files_to_run,
271276
),
272277
DefaultInfo(),
273278
]
@@ -294,6 +299,14 @@ paths to build the directory structure for those files.
294299
executable = True,
295300
doc = """
296301
A `File` referencing a tool for extracting version info from builds.
302+
""",
303+
),
304+
"json_tool": attr.label(
305+
cfg = "target",
306+
executable = True,
307+
doc = """
308+
A `files_to_run` wrapping Python's `json.tool` module (https://docs.python.org/3.5/library/json.html#module-json.tool)
309+
for deterministic JSON handling.
297310
""",
298311
),
299312
},

apple/internal/ios_rules.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ def _ios_application_impl(ctx):
286286
features = features,
287287
label = label,
288288
platform_prerequisites = platform_prerequisites,
289+
json_tool = apple_xplat_toolchain_info.json_tool,
289290
),
290291
partials.apple_bundle_info_partial(
291292
actions = actions,
@@ -1313,6 +1314,7 @@ def _ios_extension_impl(ctx):
13131314
features = features,
13141315
label = label,
13151316
platform_prerequisites = platform_prerequisites,
1317+
json_tool = apple_xplat_toolchain_info.json_tool,
13161318
),
13171319
partials.clang_rt_dylibs_partial(
13181320
actions = actions,

apple/internal/macos_rules.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ def _macos_application_impl(ctx):
260260
features = features,
261261
label = label,
262262
platform_prerequisites = platform_prerequisites,
263+
json_tool = apple_xplat_toolchain_info.json_tool,
263264
),
264265
partials.apple_bundle_info_partial(
265266
actions = actions,

apple/internal/partials/app_intents_metadata_bundle.bzl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ def _app_intents_metadata_bundle_partial_impl(
3737
disabled_features,
3838
features,
3939
label,
40-
platform_prerequisites):
40+
platform_prerequisites,
41+
json_tool):
4142
"""Implementation of the AppIntents metadata bundle partial."""
4243
if not deps:
4344
# No `app_intents` were set by the rule calling this partial.
@@ -120,6 +121,7 @@ def _app_intents_metadata_bundle_partial_impl(
120121
for cc_toolchain in cc_toolchains.values()
121122
],
122123
xcode_version_config = platform_prerequisites.xcode_version_config,
124+
json_tool = json_tool,
123125
)
124126

125127
bundle_location = processor.location.bundle
@@ -143,7 +145,8 @@ def app_intents_metadata_bundle_partial(
143145
disabled_features,
144146
features,
145147
label,
146-
platform_prerequisites):
148+
platform_prerequisites,
149+
json_tool):
147150
"""Constructor for the AppIntents metadata bundle processing partial.
148151
149152
This partial generates the Metadata.appintents bundle required for AppIntents functionality.
@@ -158,6 +161,9 @@ def app_intents_metadata_bundle_partial(
158161
features: List of features to be enabled for C++ link actions.
159162
label: Label of the target being built.
160163
platform_prerequisites: Struct containing information on the platform being targeted.
164+
json_tool: A `files_to_run` wrapping Python's `json.tool` module
165+
(https://docs.python.org/3.5/library/json.html#module-json.tool) for deterministic
166+
JSON handling.
161167
Returns:
162168
A partial that generates the Metadata.appintents bundle.
163169
"""
@@ -171,4 +177,5 @@ def app_intents_metadata_bundle_partial(
171177
features = features,
172178
label = label,
173179
platform_prerequisites = platform_prerequisites,
180+
json_tool = json_tool,
174181
)

apple/internal/resource_actions/app_intents.bzl

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def generate_app_intents_metadata_bundle(
2727
label,
2828
source_files,
2929
target_triples,
30-
xcode_version_config):
30+
xcode_version_config,
31+
json_tool):
3132
"""Process and generate AppIntents metadata bundle (Metadata.appintents).
3233
3334
Args:
@@ -42,6 +43,9 @@ def generate_app_intents_metadata_bundle(
4243
source_files: List of Swift source files implementing the AppIntents protocol.
4344
target_triples: List of Apple target triples from `CcToolchainInfo` providers.
4445
xcode_version_config: The `apple_common.XcodeVersionConfig` provider from the current ctx.
46+
json_tool: A `files_to_run` wrapping Python's `json.tool` module
47+
(https://docs.python.org/3.5/library/json.html#module-json.tool) for deterministic
48+
JSON handling.
4549
Returns:
4650
File referencing the Metadata.appintents bundle.
4751
"""
@@ -102,16 +106,46 @@ an issue with the Apple BUILD rules with repro steps.
102106
))
103107
args.add("--xcode-version", xcode_version_split[3])
104108

109+
json_tool_path = json_tool.executable.path
110+
105111
apple_support.run_shell(
106112
actions = actions,
107113
apple_fragment = apple_fragment,
108114
arguments = [args],
109115
command = '''\
110116
set -euo pipefail
111117
118+
# sorts JSON file keys for deterministic output
119+
sort_json_file() {{
120+
local original_file="$1"
121+
local temp_file="${{original_file}}.sorted"
122+
123+
# Sort the JSON file keys
124+
"{json_tool_path}" --compact --sort-keys "$original_file" > "$temp_file"
125+
# Replace original with sorted version
126+
mv "$temp_file" "$original_file"
127+
}}
128+
112129
exit_status=0
113130
output=$($@ --sdk-root "$SDKROOT" --toolchain-dir "$DEVELOPER_DIR/Toolchains/XcodeDefault.xctoolchain" 2>&1) || exit_status=$?
114131
132+
# The Metadata.appintents/extract.actionsdata and version.json outputs are json
133+
# files with non-deterministic keys order.
134+
# Here we sort their keys to ensure that the output is deterministic.
135+
# This should be removed once the issue is fixed (FB19585633).
136+
actionsdata_file="{output_dir}/extract.actionsdata"
137+
version_file="{output_dir}/version.json"
138+
139+
# Sort both JSON files to ensure deterministic output
140+
sort_json_file "$version_file"
141+
sort_json_file "$actionsdata_file"
142+
143+
# Set write permission to allow rewriting files
144+
chmod +w "$version_file" "$actionsdata_file"
145+
146+
# Restore read-only permission
147+
chmod -w "$version_file" "$actionsdata_file"
148+
115149
if [[ "$exit_status" -ne 0 ]]; then
116150
echo "$output" >&2
117151
exit $exit_status
@@ -122,8 +156,12 @@ elif [[ "$output" == *"skipping writing output"* ]]; then
122156
echo "$output" >&2
123157
exit 1
124158
fi
125-
''',
159+
'''.format(
160+
output_dir = output.path,
161+
json_tool_path = json_tool_path,
162+
),
126163
inputs = depset([bundle_binary], transitive = transitive_inputs),
164+
tools = [json_tool],
127165
outputs = [output],
128166
mnemonic = "AppIntentsMetadataProcessor",
129167
xcode_config = xcode_version_config,

apple/internal/tvos_rules.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ def _tvos_application_impl(ctx):
250250
features = features,
251251
label = label,
252252
platform_prerequisites = platform_prerequisites,
253+
json_tool = apple_xplat_toolchain_info.json_tool,
253254
),
254255
partials.apple_bundle_info_partial(
255256
actions = actions,

apple/internal/visionos_rules.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ Resolved Xcode is version {xcode_version}.
257257
features = features,
258258
label = label,
259259
platform_prerequisites = platform_prerequisites,
260+
json_tool = apple_xplat_toolchain_info.json_tool,
260261
),
261262
partials.apple_bundle_info_partial(
262263
actions = actions,

apple/internal/watchos_rules.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,7 @@ reproducible error case.".format(
893893
features = features,
894894
label = label,
895895
platform_prerequisites = platform_prerequisites,
896+
json_tool = apple_xplat_toolchain_info.json_tool,
896897
),
897898
partials.binary_partial(
898899
actions = actions,
@@ -1172,6 +1173,7 @@ def _watchos_extension_impl(ctx):
11721173
features = features,
11731174
label = label,
11741175
platform_prerequisites = platform_prerequisites,
1176+
json_tool = apple_xplat_toolchain_info.json_tool,
11751177
),
11761178
partials.binary_partial(
11771179
actions = actions,
@@ -1641,6 +1643,7 @@ delegate is referenced in the single-target `watchos_application`'s `deps`.
16411643
features = features,
16421644
label = label,
16431645
platform_prerequisites = platform_prerequisites,
1646+
json_tool = apple_xplat_toolchain_info.json_tool,
16441647
),
16451648
partials.binary_partial(
16461649
actions = actions,

test/starlark_tests/ios_application_tests.bzl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,20 @@ def ios_application_test_suite(name):
860860
tags = [name],
861861
)
862862

863+
apple_verification_test(
864+
name = "{}_app_intents_metadata_json_keys_sorted_test".format(name),
865+
build_type = "simulator",
866+
target_under_test = "//test/starlark_tests/targets_under_test/ios:app_with_app_intents",
867+
verifier_script = "verifier_scripts/app_intents_metadata_json_sorted.sh",
868+
env = {
869+
"JSON_FILES": [
870+
"$BUNDLE_ROOT/Metadata.appintents/version.json",
871+
"$BUNDLE_ROOT/Metadata.appintents/extract.actionsdata",
872+
],
873+
},
874+
tags = [name],
875+
)
876+
863877
# Test dSYM binaries and linkmaps from framework embedded via 'data' are propagated correctly
864878
# at the top-level ios_application rule, and present through the 'dsysms' and 'linkmaps' output
865879
# groups.

0 commit comments

Comments
 (0)