From d4736fe59f96e47e681b9fac704030a014e49f52 Mon Sep 17 00:00:00 2001 From: isaac <> Date: Sun, 31 May 2026 15:04:50 +0200 Subject: [PATCH] Revert tgwatch signing --- Telegram/BUILD | 8 -- Telegram/prebuilt_watchos.bzl | 103 +++---------------- Telegram/prebuilt_watchos_build.sh | 128 +++++++++++++++++++++++ Telegram/prebuilt_watchos_compile.sh | 60 ----------- Telegram/prebuilt_watchos_patch.sh | 146 --------------------------- 5 files changed, 140 insertions(+), 305 deletions(-) create mode 100755 Telegram/prebuilt_watchos_build.sh delete mode 100755 Telegram/prebuilt_watchos_compile.sh delete mode 100755 Telegram/prebuilt_watchos_patch.sh diff --git a/Telegram/BUILD b/Telegram/BUILD index 59cc632763..0ed745668f 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -1746,14 +1746,6 @@ xcode_provisioning_profile( apple_prebuilt_watchos_application( name = "TelegramWatchApp", - # The watch app's bundle id must be `.watchkitapp` for the host - # ios_application's child-bundle-id prefix check; the rule derives the - # companion (host) bundle id from this by stripping `.watchkitapp` and the - # patch worker writes both CFBundleIdentifier and WKCompanionAppBundleIdentifier - # into the embedded watch app's Info.plist. - bundle_id = "{telegram_bundle_id}.watchkitapp".format( - telegram_bundle_id = telegram_bundle_id, - ), tags = ["manual"], ) diff --git a/Telegram/prebuilt_watchos.bzl b/Telegram/prebuilt_watchos.bzl index 69f25f04f1..f4ce9d66ad 100644 --- a/Telegram/prebuilt_watchos.bzl +++ b/Telegram/prebuilt_watchos.bzl @@ -1,25 +1,9 @@ """Embeds the standalone, xcodebuild-built tgwatch watch app into the Bazel iOS build. -`apple_prebuilt_watchos_application` builds the watch app in two actions and exposes +`apple_prebuilt_watchos_application` runs `xcodebuild` (via prebuilt_watchos_build.sh) +against an exported tgwatch source tree, optionally codesigns the result, and exposes it through the providers that `ios_application(watch_application = ...)` consumes: - 1. PrebuiltWatchosCompile (prebuilt_watchos_compile.sh) — runs `xcodebuild` against - the exported tgwatch source tree with PLACEHOLDER version/api values, emitting an - unsigned .app archive. Depends only on the source snapshot. - 2. PrebuiltWatchosPatchSign (prebuilt_watchos_patch.sh) — rewrites the six per-build - Info.plist keys (version, build number, api id/hash, watch CFBundleIdentifier, - WKCompanionAppBundleIdentifier) on the compiled app, then optionally codesigns - it. The watch + companion bundle ids must track the host's `telegram_bundle_id` - (rules_apple validates both via the parent ios_application's - bundle_verification_targets), so they live in the patch action — not in the - xcodebuild snapshot — to keep the compile cached across host-bundle-id changes. - -Splitting them lets Bazel cache the (expensive, ~4-min) compile whenever only the -version/build number/api/identity/bundle-id change — those values never reach the -compiled binary, only the Info.plist. - -The providers exposed: - * AppleBundleInfo — bundle metadata (the host reads only `.product_type`). * AppleEmbeddableInfo — `watch_bundles` (the zipped .app placed under Watch/). @@ -48,19 +32,6 @@ def _apple_prebuilt_watchos_application_impl(ctx): api_hash = ctx.var.get("watchApiHash", "placeholder") identity = ctx.var.get("watchSigningIdentity", "") - # The watch bundle id is `.watchkitapp`; strip the suffix to recover the - # host bundle id, which the patch worker writes to WKCompanionAppBundleIdentifier - # (and the watch's own CFBundleIdentifier needs to be the watch bundle id, not the - # hardcoded one baked in by xcodebuild from the snapshot's pbxproj). The host - # ios_application validates both: child CFBundleIdentifier must start with - # `.`, and child WKCompanionAppBundleIdentifier must equal the host's - # bundle id (see rules_apple's bundle_verification_targets in ios_rules.bzl). - watch_bundle_id = ctx.attr.bundle_id - _watchkitapp_suffix = ".watchkitapp" - if not watch_bundle_id.endswith(_watchkitapp_suffix): - fail("apple_prebuilt_watchos_application bundle_id must end with '.watchkitapp' (got %r)" % watch_bundle_id) - host_bundle_id = watch_bundle_id[:-len(_watchkitapp_suffix)] - # The provisioning profile is an external, machine-specific absolute path passed via # --define rather than a Bazel label, so the gitignored profile need not be exposed as # a target. The local action reads it directly. Empty => unsigned build; when set but @@ -72,69 +43,25 @@ def _apple_prebuilt_watchos_application_impl(ctx): # build version from buildNumber (Make.py always emits --define=buildNumber). build_number = ctx.var.get("buildNumber", "1") archive = ctx.actions.declare_file(ctx.label.name + ".zip") - # Intermediate output of the compile action: the unsigned, placeholder-version .app. - # Keyed only on the source snapshot, so version/build/api/identity changes reuse it. - compiled_archive = ctx.actions.declare_file(ctx.label.name + "_compiled.zip") # The host ios_application reads the watch app's Info.plist (via AppleBundleInfo.infoplist) # to verify WKCompanionAppBundleIdentifier against the host bundle id, so expose it as a # separate output (resources.bzl bundle_verification crashes on a None infoplist). infoplist = ctx.actions.declare_file(ctx.label.name + "_Info.plist") - # The compile action runs xcodebuild locally (needs the host's Xcode + SwiftPM - # network access), but its output — an unsigned, placeholder-version .app — is - # portable across machines that share the same Xcode SDK, so it IS shared via the - # remote cache: `no-remote-exec` (not `no-remote`) lets Bazel read/write the - # `--remote_cache` while still pinning execution local on cache miss. This is the - # big win when CI builders share a remote cache — a peer's xcodebuild result is - # reused instead of every fresh worker paying the ~4-min build. Caveat: if the - # build fleet runs different Xcode major versions, mismatched artifacts could be - # served (the action key does not include the Xcode version); align Xcode across - # builders, or downgrade to `no-remote-cache` to be safe. - compile_exec_requirements = { - "no-sandbox": "1", - "no-remote-exec": "1", - "local": "1", - "requires-network": "1", - } - - # The patch+sign action cannot share results across machines: its inputs include - # the absolute `--watchProvisioningProfile` path and the codesigning identity is - # resolved from the local keychain, both machine-specific. Remote-cache lookups - # would essentially never hit and uploads would just waste bandwidth, so keep the - # umbrella `no-remote` here. - patch_exec_requirements = { + # Track the in-repo snapshot so the watch build re-runs only when it changes. + inputs = [ctx.file._worker, ctx.file.versions_json] + ctx.files.srcs + exec_requirements = { "no-sandbox": "1", "no-remote": "1", "local": "1", "requires-network": "1", } - # Action 1 — compile. Inputs are ONLY the in-repo snapshot (+ the worker), so this - # (expensive) xcodebuild re-runs only when the watch sources change, not when the - # version, build number, api id/hash or signing identity change. ctx.actions.run( executable = "/bin/bash", arguments = [ - ctx.file._compile_worker.path, + ctx.file._worker.path, source_path, - compiled_archive.path, - ], - inputs = [ctx.file._compile_worker] + ctx.files.srcs, - outputs = [compiled_archive], - mnemonic = "PrebuiltWatchosCompile", - progress_message = "Compiling watch app via xcodebuild", - execution_requirements = compile_exec_requirements, - use_default_shell_env = True, - ) - - # Action 2 — patch Info.plist (version/build/api) + optionally sign. Cheap; re-runs - # on any of those changes without re-running the compile above. versions.json (the - # marketing version) is an input here only, so a version bump skips the compile. - ctx.actions.run( - executable = "/bin/bash", - arguments = [ - ctx.file._patch_worker.path, - compiled_archive.path, archive.path, api_id, api_hash, @@ -143,14 +70,12 @@ def _apple_prebuilt_watchos_application_impl(ctx): infoplist.path, ctx.file.versions_json.path, build_number, - host_bundle_id, - watch_bundle_id, ], - inputs = [ctx.file._patch_worker, compiled_archive, ctx.file.versions_json], + inputs = inputs, outputs = [archive, infoplist], - mnemonic = "PrebuiltWatchosPatchSign", - progress_message = "Patching%s watch app Info.plist" % (" + signing" if profile else ""), - execution_requirements = patch_exec_requirements, + mnemonic = "PrebuiltWatchosBuild", + progress_message = "Building%s watch app via xcodebuild" % (" + signing" if profile else ""), + execution_requirements = exec_requirements, use_default_shell_env = True, ) @@ -205,12 +130,8 @@ apple_prebuilt_watchos_application = rule( default = "//:versions.json", doc = "Source of the marketing version (key 'app'), kept in sync with the host app.", ), - "_compile_worker": attr.label( - default = "//Telegram:prebuilt_watchos_compile.sh", - allow_single_file = True, - ), - "_patch_worker": attr.label( - default = "//Telegram:prebuilt_watchos_patch.sh", + "_worker": attr.label( + default = "//Telegram:prebuilt_watchos_build.sh", allow_single_file = True, ), }, diff --git a/Telegram/prebuilt_watchos_build.sh b/Telegram/prebuilt_watchos_build.sh new file mode 100755 index 0000000000..1e1a4d537b --- /dev/null +++ b/Telegram/prebuilt_watchos_build.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# Worker for the apple_prebuilt_watchos_application Bazel rule. +# +# Builds the tgwatch watch app via xcodebuild (device, Release, UNSIGNED), then +# — if a provisioning profile is supplied — codesigns the app and its nested +# frameworks with the watchkitapp provisioning profile and a matching identity, +# and finally zips the .app into the rule's output archive. +# +# The host ios_application embeds this archive under Watch/ and re-seals the host; +# it does NOT re-sign the watch app, so the watch signing must happen here. +# +# Args: +# $1 source_path Execroot-relative path to the committed in-repo snapshot +# (Telegram/WatchApp), which contains tgwatch.xcodeproj. +# $2 output_zip Path (declared by Bazel) to write the .app archive to +# $3 api_id TG_API_ID build setting +# $4 api_hash TG_API_HASH build setting +# $5 identity Codesigning identity (SHA1 hash); empty => derived from $6's cert +# $6 profile Path to the watchkitapp .mobileprovision; empty => unsigned build +set -euo pipefail + +SRC="$1"; OUT_ZIP="$2"; API_ID="$3"; API_HASH="$4"; IDENTITY="${5:-}"; PROFILE="${6:-}"; INFOPLIST_OUT="${7:-}"; VERSIONS_JSON="${8:-}"; BUILD_NUMBER="${9:-1}" + +if [ ! -e "$SRC/tgwatch.xcodeproj" ]; then + echo "error: no tgwatch.xcodeproj at $SRC (re-sync the Telegram/WatchApp snapshot via tgwatch/tools/export-sources.sh)" >&2 + exit 1 +fi + +# Match the host app's version (rules_apple requires the embedded watch app's +# CFBundleShortVersionString/CFBundleVersion to equal the parent's). +MARKETING_VERSION="0.1" +if [ -n "$VERSIONS_JSON" ]; then + MARKETING_VERSION="$(python3 -c "import json,sys; print(json.load(open(sys.argv[1]))['app'])" "$VERSIONS_JSON")" +fi + +DD="$(mktemp -d)" +trap 'rm -rf "$DD"' EXIT + +# Build from a writable copy so xcodebuild/SwiftPM never write into the (possibly +# in-repo, read-only) source tree — e.g. SwiftPM's Package.resolved or the workspace. +# The tree is small (~12M); a plain cp on each (uncached) build is acceptable. +WORKSRC="$DD/src" +mkdir -p "$WORKSRC" +cp -R "$SRC/." "$WORKSRC/" + +xcodebuild \ + -project "$WORKSRC/tgwatch.xcodeproj" \ + -scheme "tgwatch Watch App" \ + -configuration Release \ + -destination 'generic/platform=watchOS' \ + -derivedDataPath "$DD" \ + -quiet \ + CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" \ + TG_API_ID="$API_ID" TG_API_HASH="$API_HASH" \ + MARKETING_VERSION="$MARKETING_VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \ + build 1>&2 + +APP="$(find "$DD/Build/Products" -maxdepth 2 -name 'tgwatch Watch App.app' -type d | head -1)" +if [ -z "$APP" ]; then + echo "error: built watch .app not found under $DD/Build/Products" >&2 + exit 1 +fi + +# Expose the watch app's Info.plist (the host reads it to verify the companion +# bundle-id linkage). Codesigning does not alter Info.plist content, so capture it now. +if [ -n "$INFOPLIST_OUT" ]; then + cp "$APP/Info.plist" "$INFOPLIST_OUT" +fi + +if [ -n "$IDENTITY" ] && [ -z "$PROFILE" ]; then + echo "error: a signing identity was given but no provisioning profile (set --watchProvisioningProfile=)" >&2 + exit 1 +fi + +# Sign the watch app whenever a provisioning profile is available. When no explicit +# identity is supplied, derive it from the certificate embedded in that profile, so +# the watch app is signed with the same distribution/development identity as the host +# app (resolved from the shared codesigning material) — required for App Store, where +# every nested bundle must carry the Apple submission certificate. Without a profile +# the app is left unsigned (the host does not re-sign it). +if [ -n "$PROFILE" ]; then + cp "$PROFILE" "$APP/embedded.mobileprovision" + ENT="$(mktemp)" + trap 'rm -rf "$DD" "$ENT" "$ENT.plist"' EXIT + security cms -D -i "$APP/embedded.mobileprovision" > "$ENT.plist" + if ! /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' "$ENT.plist" > "$ENT" 2>/dev/null; then + echo "error: provisioning profile has no Entitlements key: $PROFILE" >&2 + exit 1 + fi + + if [ -z "$IDENTITY" ]; then + # The identity is the SHA-1 of the profile's first embedded certificate, which is + # exactly how codesign / the keychain reference it. The matching private key must + # be in the keychain (it is: the same cert signs the host app). + IDENTITY="$(python3 -c "import sys,plistlib,subprocess,hashlib; d=plistlib.loads(subprocess.run(['security','cms','-D','-i',sys.argv[1]],capture_output=True).stdout); print(hashlib.sha1(d['DeveloperCertificates'][0]).hexdigest().upper())" "$APP/embedded.mobileprovision")" + if [ -z "$IDENTITY" ]; then + echo "error: could not derive a signing identity from the provisioning profile (no DeveloperCertificates): $PROFILE" >&2 + exit 1 + fi + echo "note: signing watch app with identity $IDENTITY derived from $(basename "$PROFILE")" >&2 + fi + + # Distribution profiles (App Store / Ad Hoc) set get-task-allow=false and require a + # secure timestamp; development builds set it true and can skip the timestamp (faster, + # no round-trip to Apple's timestamp service). + TS_FLAG="--timestamp" + if /usr/libexec/PlistBuddy -c 'Print :get-task-allow' "$ENT" 2>/dev/null | grep -qi '^true$'; then + TS_FLAG="--timestamp=none" + fi + + # Sign inside-out: nested frameworks first, then the app bundle. + if [ -d "$APP/Frameworks" ]; then + for fw in "$APP/Frameworks/"*; do + [ -e "$fw" ] || continue + codesign --force "$TS_FLAG" --sign "$IDENTITY" "$fw" 1>&2 + done + fi + codesign --force "$TS_FLAG" --sign "$IDENTITY" --entitlements "$ENT" "$APP" 1>&2 + codesign --verify --deep --strict "$APP" 1>&2 +else + echo "warning: no watch provisioning profile supplied; the watch app will be UNSIGNED and will be rejected by the App Store. Pass --watchProvisioningProfile, or build with codesigning material that includes the watchkitapp profile." >&2 +fi + +# $OUT_ZIP is execroot-relative; the action's cwd is the execroot, so do NOT cd +# (that would resolve $OUT_ZIP against the DerivedData dir). --keepParent makes the +# archive root the .app itself even when $APP is an absolute path. +rm -f "$OUT_ZIP" +/usr/bin/ditto -c -k --keepParent "$APP" "$OUT_ZIP" diff --git a/Telegram/prebuilt_watchos_compile.sh b/Telegram/prebuilt_watchos_compile.sh deleted file mode 100755 index d85a089ade..0000000000 --- a/Telegram/prebuilt_watchos_compile.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env bash -# Compile worker for the apple_prebuilt_watchos_application Bazel rule (action 1 of 2). -# -# Builds the tgwatch watch app via xcodebuild (device, Release, UNSIGNED) with -# PLACEHOLDER version/api values, then zips the .app into the rule's intermediate -# archive. It deliberately depends only on the watch source snapshot — version, -# build number, api id/hash and signing are all applied later by -# prebuilt_watchos_patch.sh, so this (expensive, ~4-min) action stays cached across -# version/build/identity changes. -# -# Args: -# $1 source_path Execroot-relative path to the committed in-repo snapshot -# (Telegram/WatchApp), which contains tgwatch.xcodeproj. -# $2 output_zip Path (declared by Bazel) to write the unsigned .app archive to. -set -euo pipefail - -SRC="$1"; OUT_ZIP="$2" - -if [ ! -e "$SRC/tgwatch.xcodeproj" ]; then - echo "error: no tgwatch.xcodeproj at $SRC (re-sync the Telegram/WatchApp snapshot via tgwatch/tools/export-sources.sh)" >&2 - exit 1 -fi - -DD="$(mktemp -d)" -trap 'rm -rf "$DD"' EXIT - -# Build from a writable copy so xcodebuild/SwiftPM never write into the (possibly -# in-repo, read-only) source tree — e.g. SwiftPM's Package.resolved or the workspace. -# The tree is small (~12M); a plain cp on each (uncached) build is acceptable. -WORKSRC="$DD/src" -mkdir -p "$WORKSRC" -cp -R "$SRC/." "$WORKSRC/" - -# Version/api are placeholders here; prebuilt_watchos_patch.sh overwrites the four -# Info.plist keys afterward. They only ever land in the Info.plist (via $(...) -# substitution and a runtime Bundle.main lookup), never in the compiled binary, so -# the build output is independent of them. -xcodebuild \ - -project "$WORKSRC/tgwatch.xcodeproj" \ - -scheme "tgwatch Watch App" \ - -configuration Release \ - -destination 'generic/platform=watchOS' \ - -derivedDataPath "$DD" \ - -quiet \ - CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" \ - TG_API_ID=0 TG_API_HASH=placeholder \ - MARKETING_VERSION=0.0 CURRENT_PROJECT_VERSION=0 \ - build 1>&2 - -APP="$(find "$DD/Build/Products" -maxdepth 2 -name 'tgwatch Watch App.app' -type d | head -1)" -if [ -z "$APP" ]; then - echo "error: built watch .app not found under $DD/Build/Products" >&2 - exit 1 -fi - -# $OUT_ZIP is execroot-relative; the action's cwd is the execroot, so do NOT cd -# (that would resolve $OUT_ZIP against the DerivedData dir). --keepParent makes the -# archive root the .app itself even when $APP is an absolute path. -rm -f "$OUT_ZIP" -/usr/bin/ditto -c -k --keepParent "$APP" "$OUT_ZIP" diff --git a/Telegram/prebuilt_watchos_patch.sh b/Telegram/prebuilt_watchos_patch.sh deleted file mode 100755 index 4c32f4982d..0000000000 --- a/Telegram/prebuilt_watchos_patch.sh +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env bash -# Patch + sign worker for the apple_prebuilt_watchos_application Bazel rule (action 2 of 2). -# -# Takes the unsigned, placeholder-version watch .app archive produced by -# prebuilt_watchos_compile.sh, rewrites the six per-build Info.plist values -# (CFBundleShortVersionString, CFBundleVersion, TG_API_ID, TG_API_HASH, -# CFBundleIdentifier, WKCompanionAppBundleIdentifier) — none of which affect the -# compiled binary — then — if a provisioning profile is supplied — codesigns the -# app and its nested frameworks with the watchkitapp provisioning profile and a -# matching identity, and finally zips the .app into the rule's output archive. -# -# Bundle-id rewriting is needed because xcodebuild bakes the snapshot's pbxproj -# PRODUCT_BUNDLE_IDENTIFIER (ph.telegra.Telegraph.watchkitapp) into the compiled -# Info.plist, and WKCompanionAppBundleIdentifier is hardcoded in the snapshot's -# source Info.plist — but the host ios_application's bundle id varies by codesigning -# configuration (e.g. org.telegram.TelegramInternal for development) and rules_apple -# requires the child CFBundleIdentifier to start with the host bundle id and -# WKCompanionAppBundleIdentifier to equal it (bundle_verification_targets in -# ios_rules.bzl). Doing the rewrite here keeps the expensive xcodebuild action -# cached across host-bundle-id changes. -# -# Splitting this from the compile step lets Bazel cache the (expensive) xcodebuild -# whenever only the version/build number/api/identity/bundle-id change. -# -# The host ios_application embeds this archive under Watch/ and re-seals the host; -# it does NOT re-sign the watch app, so the watch signing must happen here. -# -# Args: -# $1 input_zip Compiled (unsigned, placeholder-version) .app archive from action 1 -# $2 output_zip Path (declared by Bazel) to write the final .app archive to -# $3 api_id TG_API_ID Info.plist value -# $4 api_hash TG_API_HASH Info.plist value -# $5 identity Codesigning identity (SHA1 hash); empty => derived from $6's cert -# $6 profile Path to the watchkitapp .mobileprovision; empty => unsigned build -# $7 infoplist_out Path (declared by Bazel) to copy the patched Info.plist to -# $8 versions_json versions.json (key 'app' => CFBundleShortVersionString) -# $9 build_number CFBundleVersion -# $10 host_bundle_id WKCompanionAppBundleIdentifier value (host app bundle id) -# $11 watch_bundle_id CFBundleIdentifier value (must be ".watchkitapp") -set -euo pipefail - -IN_ZIP="$1"; OUT_ZIP="$2"; API_ID="$3"; API_HASH="$4"; IDENTITY="${5:-}"; PROFILE="${6:-}"; INFOPLIST_OUT="${7:-}"; VERSIONS_JSON="${8:-}"; BUILD_NUMBER="${9:-1}"; HOST_BUNDLE_ID="${10:-}"; WATCH_BUNDLE_ID="${11:-}" - -if [ -z "$HOST_BUNDLE_ID" ] || [ -z "$WATCH_BUNDLE_ID" ]; then - echo "error: host_bundle_id and watch_bundle_id must both be supplied (got host=$HOST_BUNDLE_ID watch=$WATCH_BUNDLE_ID)" >&2 - exit 1 -fi - -# Match the host app's version (rules_apple requires the embedded watch app's -# CFBundleShortVersionString/CFBundleVersion to equal the parent's). -MARKETING_VERSION="0.1" -if [ -n "$VERSIONS_JSON" ]; then - MARKETING_VERSION="$(python3 -c "import json,sys; print(json.load(open(sys.argv[1]))['app'])" "$VERSIONS_JSON")" -fi - -DD="$(mktemp -d)" -trap 'rm -rf "$DD"' EXIT - -/usr/bin/ditto -x -k "$IN_ZIP" "$DD" -APP="$(find "$DD" -maxdepth 2 -name 'tgwatch Watch App.app' -type d | head -1)" -if [ -z "$APP" ]; then - echo "error: compiled watch .app not found inside $IN_ZIP" >&2 - exit 1 -fi - -# Overwrite the placeholder values baked in at compile time. All six keys already -# exist in the compiled (binary-format) Info.plist, so PlistBuddy Set preserves their -# (string) type — matching what $(...) substitution produced and what Secrets.swift -# expects from Bundle.main.object(forInfoDictionaryKey:). The bundle-id keys track -# the host's `telegram_bundle_id` (see the file header for why); xcodebuild bakes -# `ph.telegra.Telegraph.watchkitapp` / `ph.telegra.Telegraph` here from the -# snapshot's pbxproj/Info.plist regardless of what host the rest of the build uses. -PLIST="$APP/Info.plist" -/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $MARKETING_VERSION" "$PLIST" -/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "$PLIST" -/usr/libexec/PlistBuddy -c "Set :TG_API_ID $API_ID" "$PLIST" -/usr/libexec/PlistBuddy -c "Set :TG_API_HASH $API_HASH" "$PLIST" -/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $WATCH_BUNDLE_ID" "$PLIST" -/usr/libexec/PlistBuddy -c "Set :WKCompanionAppBundleIdentifier $HOST_BUNDLE_ID" "$PLIST" - -# Expose the patched watch Info.plist (the host reads it to verify the companion -# bundle-id linkage and the child version). Codesigning does not alter Info.plist -# content, so capture it now. -if [ -n "$INFOPLIST_OUT" ]; then - cp "$PLIST" "$INFOPLIST_OUT" -fi - -if [ -n "$IDENTITY" ] && [ -z "$PROFILE" ]; then - echo "error: a signing identity was given but no provisioning profile (set --watchProvisioningProfile=)" >&2 - exit 1 -fi - -# Sign the watch app whenever a provisioning profile is available. When no explicit -# identity is supplied, derive it from the certificate embedded in that profile, so -# the watch app is signed with the same distribution/development identity as the host -# app (resolved from the shared codesigning material) — required for App Store, where -# every nested bundle must carry the Apple submission certificate. Without a profile -# the app is left unsigned (the host does not re-sign it). -if [ -n "$PROFILE" ]; then - cp "$PROFILE" "$APP/embedded.mobileprovision" - ENT="$(mktemp)" - trap 'rm -rf "$DD" "$ENT" "$ENT.plist"' EXIT - security cms -D -i "$APP/embedded.mobileprovision" > "$ENT.plist" - if ! /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' "$ENT.plist" > "$ENT" 2>/dev/null; then - echo "error: provisioning profile has no Entitlements key: $PROFILE" >&2 - exit 1 - fi - - if [ -z "$IDENTITY" ]; then - # The identity is the SHA-1 of the profile's first embedded certificate, which is - # exactly how codesign / the keychain reference it. The matching private key must - # be in the keychain (it is: the same cert signs the host app). - IDENTITY="$(python3 -c "import sys,plistlib,subprocess,hashlib; d=plistlib.loads(subprocess.run(['security','cms','-D','-i',sys.argv[1]],capture_output=True).stdout); print(hashlib.sha1(d['DeveloperCertificates'][0]).hexdigest().upper())" "$APP/embedded.mobileprovision")" - if [ -z "$IDENTITY" ]; then - echo "error: could not derive a signing identity from the provisioning profile (no DeveloperCertificates): $PROFILE" >&2 - exit 1 - fi - echo "note: signing watch app with identity $IDENTITY derived from $(basename "$PROFILE")" >&2 - fi - - # Distribution profiles (App Store / Ad Hoc) set get-task-allow=false and require a - # secure timestamp; development builds set it true and can skip the timestamp (faster, - # no round-trip to Apple's timestamp service). - TS_FLAG="--timestamp" - if /usr/libexec/PlistBuddy -c 'Print :get-task-allow' "$ENT" 2>/dev/null | grep -qi '^true$'; then - TS_FLAG="--timestamp=none" - fi - - # Sign inside-out: nested frameworks first, then the app bundle. - if [ -d "$APP/Frameworks" ]; then - for fw in "$APP/Frameworks/"*; do - [ -e "$fw" ] || continue - codesign --force "$TS_FLAG" --sign "$IDENTITY" "$fw" 1>&2 - done - fi - codesign --force "$TS_FLAG" --sign "$IDENTITY" --entitlements "$ENT" "$APP" 1>&2 - codesign --verify --deep --strict "$APP" 1>&2 -else - echo "warning: no watch provisioning profile supplied; the watch app will be UNSIGNED and will be rejected by the App Store. Pass --watchProvisioningProfile, or build with codesigning material that includes the watchkitapp profile." >&2 -fi - -# $OUT_ZIP is execroot-relative; the action's cwd is the execroot, so do NOT cd -# (that would resolve $OUT_ZIP against the temp dir). --keepParent makes the archive -# root the .app itself even when $APP is an absolute path. -rm -f "$OUT_ZIP" -/usr/bin/ditto -c -k --keepParent "$APP" "$OUT_ZIP"