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