Skip to content

Commit efc8a31

Browse files
authored
Set up job to build and notarize SwiftFormat for Xcode (#2161)
1 parent f09c888 commit efc8a31

File tree

3 files changed

+168
-9
lines changed

3 files changed

+168
-9
lines changed

.github/workflows/notarize.yml

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
name: Build and Notarize SwiftFormat for Xcode
2+
3+
on:
4+
release:
5+
types: [published]
6+
workflow_dispatch:
7+
inputs:
8+
tag:
9+
description: 'Release tag (e.g. v1.2.3)'
10+
required: true
11+
12+
jobs:
13+
build-and-notarize:
14+
runs-on: macos-15
15+
16+
steps:
17+
- name: Select Xcode version
18+
uses: maxim-lobanov/setup-xcode@v1
19+
with:
20+
xcode-version: '16.3'
21+
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
with:
25+
ref: ${{ github.event.release.tag_name || inputs.tag }}
26+
27+
- name: Import Code Signing Certificate
28+
run: |
29+
# Debug: Check if secrets exist (without revealing them)
30+
if [ -z "${{ secrets.DEVELOPER_ID_CERTIFICATE_BASE64 }}" ]; then
31+
echo "ERROR: DEVELOPER_ID_CERTIFICATE_BASE64 secret is empty or not set"
32+
exit 1
33+
fi
34+
if [ -z "${{ secrets.DEVELOPER_ID_CERTIFICATE_PASSWORD }}" ]; then
35+
echo "ERROR: DEVELOPER_ID_CERTIFICATE_PASSWORD secret is empty or not set"
36+
exit 1
37+
fi
38+
39+
echo "Secrets are present, proceeding with certificate import..."
40+
41+
# Create keychain
42+
security create-keychain -p "" build.keychain
43+
security default-keychain -s build.keychain
44+
security unlock-keychain -p "" build.keychain
45+
security set-keychain-settings -t 3600 -l build.keychain
46+
47+
# Import certificate
48+
echo "${{ secrets.DEVELOPER_ID_CERTIFICATE_BASE64 }}" | base64 --decode > certificate.p12
49+
security import certificate.p12 -k build.keychain -P "${{ secrets.DEVELOPER_ID_CERTIFICATE_PASSWORD }}" -T /usr/bin/codesign
50+
security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
51+
52+
# Clean up
53+
rm certificate.p12
54+
55+
- name: Archive SwiftFormat for Xcode App
56+
run: |
57+
ARCHIVE_PATH="build/SwiftFormatForXcode.xcarchive"
58+
xcodebuild \
59+
-project SwiftFormat.xcodeproj \
60+
-scheme "SwiftFormat for Xcode" \
61+
-configuration Release \
62+
-archivePath "$ARCHIVE_PATH" \
63+
CODE_SIGN_STYLE=Manual \
64+
CODE_SIGN_IDENTITY="Developer ID Application" \
65+
DEVELOPMENT_TEAM="8VQKF583ED" \
66+
PROVISIONING_PROFILE_SPECIFIER="" \
67+
archive
68+
69+
- name: Copy and Sign App from Archive
70+
id: export-app
71+
run: |
72+
ARCHIVE_PATH="build/SwiftFormatForXcode.xcarchive"
73+
EXPORT_PATH="build/Export"
74+
mkdir -p "$EXPORT_PATH"
75+
76+
# Copy app from archive
77+
cp -R "$ARCHIVE_PATH/Products/Applications/SwiftFormat for Xcode.app" "$EXPORT_PATH/"
78+
79+
APP_PATH="$EXPORT_PATH/SwiftFormat for Xcode.app"
80+
echo "app-path=$APP_PATH" >> $GITHUB_OUTPUT
81+
echo "Copied app to: $APP_PATH"
82+
83+
# Use app from archive as-is without additional signing
84+
echo "Using app from archive without modification"
85+
86+
# Check and fix XcodeKit.framework structure
87+
XCODE_KIT_PATH="$APP_PATH/Contents/PlugIns/SwiftFormat.appex/Contents/Frameworks/XcodeKit.framework"
88+
echo "Checking XcodeKit.framework structure..."
89+
ls -la "$XCODE_KIT_PATH/Versions/"
90+
file "$XCODE_KIT_PATH/Versions/Current"
91+
92+
# Validate the signature from archive
93+
echo "Validating archive signature..."
94+
if ! codesign --verify --deep --strict "$APP_PATH"; then
95+
echo "ERROR: Archive produced invalid signature!"
96+
exit 1
97+
fi
98+
echo "Archive signature is valid"
99+
100+
# Check Gatekeeper assessment
101+
echo "Checking Gatekeeper assessment..."
102+
if ! spctl -a -v "$APP_PATH"; then
103+
echo "ERROR: Gatekeeper would reject this app!"
104+
exit 1
105+
fi
106+
echo "Gatekeeper assessment passed"
107+
108+
- name: Notarize App
109+
uses: lando/notarize-action@v2
110+
with:
111+
product-path: ${{ steps.export-app.outputs.app-path }}
112+
appstore-connect-username: ${{ secrets.NOTARIZATION_USERNAME }}
113+
appstore-connect-password: ${{ secrets.NOTARIZATION_PASSWORD }}
114+
appstore-connect-team-id: 8VQKF583ED
115+
primary-bundle-id: com.nicklockwood.SwiftFormat-for-Xcode
116+
verbose: true
117+
118+
- name: Staple Notarization
119+
id: staple
120+
run: |
121+
xcrun stapler staple "${{ steps.export-app.outputs.app-path }}"
122+
123+
- name: Get Notarization Logs
124+
if: failure()
125+
run: |
126+
echo "Getting notarization history and logs..."
127+
128+
# Show recent history
129+
echo "=== Recent Notarization History ==="
130+
xcrun notarytool history --team-id 8VQKF583ED --apple-id "${{ secrets.NOTARIZATION_USERNAME }}" --password "${{ secrets.NOTARIZATION_PASSWORD }}"
131+
132+
# Get the most recent request UUID and its detailed logs
133+
echo ""
134+
echo "=== Getting detailed logs for most recent submission ==="
135+
REQUEST_UUID=$(xcrun notarytool history --team-id 8VQKF583ED --apple-id "${{ secrets.NOTARIZATION_USERNAME }}" --password "${{ secrets.NOTARIZATION_PASSWORD }}" | grep -E "id:\s*[a-f0-9-]{36}" | head -1 | sed 's/.*id: //')
136+
137+
if [ -n "$REQUEST_UUID" ]; then
138+
echo "Getting logs for request: $REQUEST_UUID"
139+
xcrun notarytool log "$REQUEST_UUID" --team-id 8VQKF583ED --apple-id "${{ secrets.NOTARIZATION_USERNAME }}" --password "${{ secrets.NOTARIZATION_PASSWORD }}"
140+
else
141+
echo "Could not extract request UUID from history"
142+
fi
143+
144+
- name: Zip App
145+
run: |
146+
cd "$(dirname "${{ steps.export-app.outputs.app-path }}")"
147+
zip -r --symlinks SwiftFormat.for.Xcode.app.zip "SwiftFormat for Xcode.app"
148+
echo "ZIP_DIR=$(pwd)" >> $GITHUB_ENV
149+
150+
- name: Upload App Artifact
151+
uses: actions/upload-artifact@v4
152+
with:
153+
name: SwiftFormat-for-Xcode
154+
path: ${{ env.ZIP_DIR }}/SwiftFormat.for.Xcode.app.zip
155+
156+
- name: Upload to Release
157+
uses: skx/github-action-publish-binaries@master
158+
env:
159+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
160+
with:
161+
args: ${{ env.ZIP_DIR }}/SwiftFormat.for.Xcode.app.zip

CONTRIBUTING.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,3 @@ Then complete the following steps manually:
116116
* Publish a new release
117117
* All binaries are built and uploaded to the release automatically
118118
* pod trunk push --allow-warnings
119-
120-
Finally, these steps require App Store Connect access, so only Nick can do them:
121-
122-
* Select SwiftFormat for Xcode and run Product > Archive
123-
* Notarize and export built app

SwiftFormat.xcodeproj/project.pbxproj

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,11 +1485,12 @@
14851485
CLANG_WARN_SUSPICIOUS_MOVES = YES;
14861486
CODE_SIGN_ENTITLEMENTS = EditorExtension/Application/SwiftFormatter.entitlements;
14871487
CODE_SIGN_IDENTITY = "Apple Development";
1488-
CODE_SIGN_STYLE = Automatic;
1488+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
1489+
CODE_SIGN_STYLE = Manual;
14891490
COMBINE_HIDPI_IMAGES = YES;
14901491
CURRENT_PROJECT_VERSION = 1;
14911492
DEAD_CODE_STRIPPING = YES;
1492-
DEVELOPMENT_TEAM = 8VQKF583ED;
1493+
DEVELOPMENT_TEAM = "";
14931494
ENABLE_HARDENED_RUNTIME = YES;
14941495
INFOPLIST_FILE = EditorExtension/Application/Info.plist;
14951496
LD_RUNPATH_SEARCH_PATHS = (
@@ -1513,6 +1514,7 @@
15131514
CLANG_WARN_SUSPICIOUS_MOVES = YES;
15141515
CODE_SIGN_ENTITLEMENTS = EditorExtension/Extension/SwiftFormatter.entitlements;
15151516
CODE_SIGN_IDENTITY = "-";
1517+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
15161518
CODE_SIGN_STYLE = Manual;
15171519
COMBINE_HIDPI_IMAGES = YES;
15181520
DEAD_CODE_STRIPPING = YES;
@@ -1542,10 +1544,11 @@
15421544
CLANG_WARN_SUSPICIOUS_MOVES = YES;
15431545
CODE_SIGN_ENTITLEMENTS = EditorExtension/Extension/SwiftFormatter.entitlements;
15441546
CODE_SIGN_IDENTITY = "Apple Development";
1545-
CODE_SIGN_STYLE = Automatic;
1547+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
1548+
CODE_SIGN_STYLE = Manual;
15461549
COMBINE_HIDPI_IMAGES = YES;
15471550
DEAD_CODE_STRIPPING = YES;
1548-
DEVELOPMENT_TEAM = 8VQKF583ED;
1551+
DEVELOPMENT_TEAM = "";
15491552
ENABLE_HARDENED_RUNTIME = YES;
15501553
INFOPLIST_FILE = EditorExtension/Extension/Info.plist;
15511554
LD_RUNPATH_SEARCH_PATHS = (

0 commit comments

Comments
 (0)