From e5dc9d0c8f792d98ecca697b1237e3eef2c0e6c2 Mon Sep 17 00:00:00 2001 From: winebarrel Date: Sun, 9 Aug 2026 17:48:27 +0900 Subject: [PATCH] Add a Makefile and a notarized release target make release builds with the Developer ID identity and a secure timestamp, submits to the notary service, staples the ticket, and writes dist/Macstify.zip with a SHA-256 checksum.txt. The submitted zip and the distributed zip are separate files: stapling applies to the bundle, so the distributable has to be re-zipped after the ticket comes back. CODESIGN_IDENTITY is matched by prefix, so no name or team id is hardcoded. Both it and NOTARY_PROFILE can be overridden on the command line. Also commits what Xcode wrote while the project was open: automatic signing with a development team, and ENABLE_HARDENED_RUNTIME on the Macstify target. Notarization refuses a bundle without the hardened runtime, and Xcode sets it on both configurations - for a bundle that runs inside legacyScreenSaver rather than as its own process, the flag changes nothing at runtime either way. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 1 + Macstify.xcodeproj/project.pbxproj | 18 +++++++ Makefile | 80 ++++++++++++++++++++++++++++++ README.md | 42 ++++++++++------ 4 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 Makefile diff --git a/.gitignore b/.gitignore index 65680dd..f74c8a5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ xcuserdata/ ## Build output build/ DerivedData/ +dist/ ## Obj-C/Swift specific *.hmap diff --git a/Macstify.xcodeproj/project.pbxproj b/Macstify.xcodeproj/project.pbxproj index 9f2b369..8c3e8af 100644 --- a/Macstify.xcodeproj/project.pbxproj +++ b/Macstify.xcodeproj/project.pbxproj @@ -245,7 +245,11 @@ 15111ED6B6AF4060A89A5695 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_TEAM = 97A8B2WE2P; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(HOME)/Library/Screen Savers"; LD_RUNPATH_SEARCH_PATHS = ( @@ -255,6 +259,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = jp.winebarrel.Macstify; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; WRAPPER_EXTENSION = saver; }; @@ -263,7 +268,10 @@ 3C31C29A14694C2FACC94A7E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_TEAM = 97A8B2WE2P; INFOPLIST_FILE = Preview/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -271,13 +279,18 @@ ); PRODUCT_BUNDLE_IDENTIFIER = jp.winebarrel.MacstifyPreview; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Debug; }; 3DE8C5B086DB4FFFB2ED726E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_TEAM = 97A8B2WE2P; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(HOME)/Library/Screen Savers"; LD_RUNPATH_SEARCH_PATHS = ( @@ -287,6 +300,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = jp.winebarrel.Macstify; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; WRAPPER_EXTENSION = saver; }; @@ -397,7 +411,10 @@ EDF8207572564CBAB1873417 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_TEAM = 97A8B2WE2P; INFOPLIST_FILE = Preview/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -405,6 +422,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = jp.winebarrel.MacstifyPreview; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Release; }; diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4fa7772 --- /dev/null +++ b/Makefile @@ -0,0 +1,80 @@ +PROJECT := Macstify.xcodeproj +SCHEME := Macstify +PREVIEW_SCHEME := MacstifyPreview +CONFIGURATION := Release +DERIVED_DATA := build +PRODUCTS := $(DERIVED_DATA)/Build/Products/$(CONFIGURATION) +SAVER := $(PRODUCTS)/Macstify.saver +PREVIEW := $(PRODUCTS)/MacstifyPreview.app/Contents/MacOS/MacstifyPreview +INSTALL_DIR := $(HOME)/Library/Screen Savers +DIST_DIR := dist +DIST := $(DIST_DIR)/Macstify.zip +CHECKSUM := $(DIST_DIR)/checksum.txt +SUBMISSION := $(DERIVED_DATA)/submission.zip + +# codesign resolves this by prefix, and there is only one such identity. +CODESIGN_IDENTITY ?= Developer ID Application +NOTARY_PROFILE ?= macstify + +XCODEBUILD := xcodebuild -project $(PROJECT) -configuration $(CONFIGURATION) -derivedDataPath $(DERIVED_DATA) + +.DEFAULT_GOAL := build + +.PHONY: build +build: + $(XCODEBUILD) -scheme $(SCHEME) build + +.PHONY: install +install: build + mkdir -p "$(INSTALL_DIR)" + rm -rf "$(INSTALL_DIR)/Macstify.saver" + cp -R $(SAVER) "$(INSTALL_DIR)/" + @echo + @echo 'Installed. macOS caches the loaded bundle, so if an older build is' + @echo 'still running: killall legacyScreenSaver, then reopen System Settings.' + +.PHONY: uninstall +uninstall: + rm -rf "$(INSTALL_DIR)/Macstify.saver" + +.PHONY: preview +preview: + $(XCODEBUILD) -scheme $(PREVIEW_SCHEME) build + $(PREVIEW) + +.PHONY: lint +lint: + swiftlint --strict + +.PHONY: format +format: + swiftformat Sources Preview + +# Signs with Developer ID rather than the ad-hoc signature a plain build +# produces, then notarizes. Gatekeeper rejects anything less once the bundle +# reaches another Mac and picks up a quarantine flag. Stapling attaches the +# ticket to the bundle so it validates without a network round trip. +.PHONY: release +release: + $(XCODEBUILD) -scheme $(SCHEME) \ + CODE_SIGN_STYLE=Manual \ + CODE_SIGN_IDENTITY="$(CODESIGN_IDENTITY)" \ + OTHER_CODE_SIGN_FLAGS="--timestamp" \ + build + codesign --verify --strict --verbose=2 $(SAVER) + rm -f $(SUBMISSION) + ditto -c -k --keepParent $(SAVER) $(SUBMISSION) + xcrun notarytool submit $(SUBMISSION) --keychain-profile "$(NOTARY_PROFILE)" --wait + xcrun stapler staple $(SAVER) + xcrun stapler validate $(SAVER) + mkdir -p $(DIST_DIR) + rm -f $(DIST) $(CHECKSUM) + ditto -c -k --keepParent $(SAVER) $(DIST) + cd $(DIST_DIR) && shasum -a 256 $(notdir $(DIST)) > $(notdir $(CHECKSUM)) + @echo + @echo "Ready to distribute:" + @cat $(CHECKSUM) + +.PHONY: clean +clean: + rm -rf $(DERIVED_DATA) dist diff --git a/README.md b/README.md index cfdd9c5..9cf53b0 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,7 @@ Requires macOS 13 or later. Universal (Apple silicon and Intel). ## Install ```sh -xcodebuild -project Macstify.xcodeproj -scheme Macstify -configuration Release -derivedDataPath build build -mkdir -p ~/Library/"Screen Savers" -cp -R build/Build/Products/Release/Macstify.saver ~/Library/"Screen Savers"/ +make install ``` Then open System Settings → Screen Saver and pick **Macstify**. @@ -52,7 +50,8 @@ snapshot path drive it directly. `CGRect` and a `CGContext`. `MacstifyPreview` is a development harness around it: ```sh -xcodebuild -project Macstify.xcodeproj -scheme MacstifyPreview -configuration Release -derivedDataPath build build +make preview # build it and open the live window + P=build/Build/Products/Release/MacstifyPreview.app/Contents/MacOS/MacstifyPreview $P # live window @@ -69,12 +68,8 @@ snapshots are written at 2x, as on a Retina display. The screenshots above were produced with `--snapshot`. -CI builds and analyses both schemes and runs the same lint and format checks you can run locally: - -```sh -swiftlint --strict -swiftformat Sources Preview --lint -``` +CI builds and analyses both schemes and runs the same checks `make lint` and `make format` run +locally. ### Thumbnail @@ -91,11 +86,30 @@ $P --snapshot Resources/thumbnail@2x.png --preview --size 90x58 --speed 4 --fram sips -Z 90 Resources/thumbnail@2x.png --out Resources/thumbnail.png ``` -## Signing +## Releasing + +`make install` signs with whatever certificate the project is configured for, which is enough for a +saver you build and install yourself. Copying that bundle to another Mac is not: it arrives +quarantined, and Gatekeeper refuses anything without a Developer ID signature and a notarization +ticket. + +```sh +make release +``` + +builds with the Developer ID identity and a secure timestamp, submits the bundle to Apple's notary +service, staples the returned ticket, and writes `dist/Macstify.zip` alongside a SHA-256 +`dist/checksum.txt`. The zip that gets submitted and the zip that gets distributed are different +files — stapling applies to the bundle, so the distributable has to be re-zipped afterwards. + +It needs a `Developer ID Application` certificate in the keychain and notary credentials stored +under the profile name `macstify`: + +```sh +xcrun notarytool store-credentials macstify --apple-id you@example.com --team-id TEAMID +``` -The build is ad-hoc signed, which is enough for a saver you build and install yourself. Copying the -bundle to another Mac means it arrives quarantined, and Gatekeeper will refuse it — that needs a -Developer ID signature and notarization. +Override the defaults with `make release CODESIGN_IDENTITY="..." NOTARY_PROFILE="..."`. ## License