Files
Felitendo 8b06b305f5 feat: give the manager the island the app has
FTStore Manager puts an app on a device through the same three stages FTStore
does, and had nothing to show for any of it once the screen was off. It has a
Live Activity now: the same widget the app carries, embedded as its own
extension under its own identifier, naming the app, filling a bar while the
bytes arrive, adding the rate and the time left, then Preparing and Installing.

It is the app's island with the pause button taken out. This download manager
has no pause and no screen to offer one from, so there are no intents to compile
into the extension and no paused state in the payload. The island is read-only
on every version rather than only on the older ones.

It ends when the installer answers, which is the part the app had to learn the
hard way. Open launches what was just installed and leaves the sheet standing
behind it, so ending on dismissal left the island describing an install that had
finished, until somebody came back and closed a receipt they were done with.
Dismissal still ends it, which is what covers a flow that never gets an answer
at all.

A dropped connection is now one of those answers. It used to remove the download
and tell nobody, leaving the flow spinning with no way to find out; that was
merely invisible before and would now be an island counting bytes that stopped
arriving.

None of this is visible from a build: an IPA whose extension is missing, misnamed
or misfiled installs, runs, and simply shows no island. Both of the Manager's
release workflows therefore run the same check the app's do against the built
IPA, and the ad-hoc signing step seals the nested extension. Two products now
build an extension of the same name out of projects that share their
identifiers, so the separate derived data paths carry more weight than before,
and the notes that explain them say so.
2026-09-01 08:35:02 +02:00

100 lines
4.0 KiB
Makefile
Executable File

NAME := FTStore
SCHEME := FTStore
PLATFORMS := iphoneos maccatalyst
# Derived data is keyed on the product directory, never on $(NAME) alone.
#
# manager/ is a fork of this tree: same project file name, same scheme, same
# target name, and — because it was forked rather than recreated — the same
# pbxproj GUIDs. Xcode's build database is keyed on those GUIDs, so pointing
# both products at $(TMPDIR)/FTStore leaves it unable to tell them apart. Only
# SRCROOT differs, and that is not enough: the Manager build had no
# FTStoreActivity target then, so it treated this app's Live Activity extension
# as a stale file and tried to delete it, while this build found the Manager's
# object files sitting in its own intermediates. The result was an IPA whose
# embedded FTStoreActivity.appex was a leftover from some earlier build and no
# longer matched the app around it, which is exactly what the Dynamic Island
# needs in order to render anything.
#
# The Manager has an FTStoreActivity of its own now. That does not make a
# shared derived-data path safe again — it makes the two extensions collide by
# name as well, so each build would take the other's.
#
# It never showed on the GitHub-hosted runners because every job got a fresh VM
# and therefore an empty $(TMPDIR). On the self-hosted Mac the directory
# survives between runs *and* between products, so the two builds trampled each
# other every time.
TMP := $(TMPDIR)/$(NAME)-$(notdir $(CURDIR))
CERT_JSON_URL := https://backloop.dev/pack.json
.PHONY: all clean deps $(PLATFORMS)
all: $(PLATFORMS)
clean:
rm -rf $(TMP)
rm -rf packages
rm -rf Payload
deps:
rm -rf deps || true
mkdir -p deps
curl -fsSL "$(CERT_JSON_URL)" -o cert.json
# `.ca` too: iOS refuses a chain it cannot complete, and the leaf on its own
# is one of the ways a Server install sits on "Ready" until it is dismissed.
# `-j` because the key is split across two fields to get it past secret
# scanning, and the halves join with nothing between them; the newline `-r`
# adds lands mid-line and corrupts the PEM.
jq -r '.cert, .ca' cert.json > deps/server.crt
jq -rj '.key1, .key2' cert.json > deps/server.pem
jq -r '.info.domains.commonName' cert.json > deps/commonName.txt
# `Package.resolved` is the build, and the flag below is what makes that true.
#
# Nothing here changed and the build broke anyway: BitByteData published 2.1.0,
# which raises its own floor to iOS 17, and SWCompression asks for it with an
# open `from: "2.0.0"`. Xcode resolved past the committed pin of 2.0.4, took the
# new one, and then refused the graph it had just built — SWCompression's own
# manifest says iOS 11, so a dependency needing 17 cannot go under it. The app's
# deployment target is not part of that arithmetic and raising it fixes nothing.
#
# `-onlyUsePackageVersionsFromResolvedFile` forbids the drift instead of
# reacting to it: every version comes from the checked-in file, and a dependency
# missing from it fails the build rather than being invented. The cost is that
# adding or moving a package means resolving it in Xcode and committing the
# result, which is the point.
$(PLATFORMS): deps
rm -rf _build
@if [ "$@" = "iphoneos" ]; then \
DEST="generic/platform=iOS"; \
else \
DEST="generic/platform=macOS,variant=Mac Catalyst"; \
fi; \
xcodebuild \
-project FTStore.xcodeproj \
-scheme $(SCHEME) \
-configuration Release \
-destination "$$DEST" \
-derivedDataPath $(TMP)/$@ \
-skipPackagePluginValidation \
-onlyUsePackageVersionsFromResolvedFile \
CODE_SIGNING_ALLOWED=NO \
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO
mkdir -p _build/Payload
cp -R _build/Applications/*.app _build/Payload/FTStore.app
chmod -R 0755 _build/Payload/FTStore.app
codesign --force --deep --sign - --timestamp=none _build/Payload/FTStore.app
cp deps/* _build/Payload/FTStore.app/ || true
mkdir -p packages
@if [ "$@" = "iphoneos" ]; then \
ditto -c -k --sequesterRsrc --keepParent _build/Payload "packages/FTStore.ipa"; \
else \
ditto -c -k --sequesterRsrc --keepParent _build/Payload/FTStore.app "packages/FTStore_Catalyst.zip"; \
fi