Revert tgwatch signing
This commit is contained in:
parent
f6dc52c533
commit
d4736fe59f
5 changed files with 140 additions and 305 deletions
|
|
@ -1746,14 +1746,6 @@ xcode_provisioning_profile(
|
|||
|
||||
apple_prebuilt_watchos_application(
|
||||
name = "TelegramWatchApp",
|
||||
# The watch app's bundle id must be `<host>.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"],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 `<host>.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
|
||||
# `<host>.`, 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,
|
||||
),
|
||||
},
|
||||
|
|
|
|||
128
Telegram/prebuilt_watchos_build.sh
Executable file
128
Telegram/prebuilt_watchos_build.sh
Executable file
|
|
@ -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=<abs path>)" >&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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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 "<host_bundle_id>.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=<abs path>)" >&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"
|
||||
Loading…
Add table
Add a link
Reference in a new issue