-
Notifications
You must be signed in to change notification settings - Fork 1.6k
294 lines (248 loc) · 10 KB
/
build-macos.yml
File metadata and controls
294 lines (248 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
name: Build macOS
on:
workflow_call:
secrets:
APPLE_CERTIFICATE:
required: true
APPLE_CERTIFICATE_PASSWORD:
required: true
KEYCHAIN_PASSWORD:
required: true
APPLE_SIGNING_IDENTITY:
required: true
APPLE_ID:
required: true
APPLE_TEAM_ID:
required: true
APPLE_PASSWORD:
required: true
workflow_dispatch:
inputs:
skip_build:
description: 'Skip build and use artifacts from a previous run'
required: false
default: false
type: boolean
run_id:
description: 'Run ID to download artifacts from (leave empty for latest)'
required: false
type: string
push:
branches: [main]
jobs:
build:
name: Build macOS ${{ matrix.target }}
if: ${{ !inputs.skip_build }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: macos-13 # Intel
target: x86_64-apple-darwin
arch: x86_64
- os: macos-14 # Apple Silicon
target: aarch64-apple-darwin
arch: aarch64
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install
- name: Import Apple certificates
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# Create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# Import certificate from secrets
echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH
# Create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# Import certificate to keychain
security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
- name: Build native
env:
CI: true
run: bun run tauri build
- name: Upload architecture-specific artifacts
uses: actions/upload-artifact@v4
with:
name: macos-${{ matrix.arch }}
path: |
src-tauri/target/release/bundle/macos/opcode.app
src-tauri/target/release/bundle/dmg/*.dmg
retention-days: 1
universal:
name: Create Universal Binary
needs: [build]
if: ${{ !cancelled() && (needs.build.result == 'success' || needs.build.result == 'skipped') }}
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Download artifacts from current workflow
if: ${{ !inputs.skip_build }}
uses: actions/download-artifact@v4
with:
pattern: macos-*
path: artifacts
- name: Download artifacts from specific run
if: ${{ inputs.skip_build && inputs.run_id != '' }}
uses: dawidd6/action-download-artifact@v3
with:
workflow: build-macos.yml
run_id: ${{ inputs.run_id }}
name: macos-*
path: artifacts
- name: Download artifacts from latest run
if: ${{ inputs.skip_build && inputs.run_id == '' }}
uses: dawidd6/action-download-artifact@v3
with:
workflow: build-macos.yml
workflow_conclusion: success
name: macos-*
path: artifacts
- name: List downloaded artifacts
run: |
echo "📁 Artifact structure:"
find artifacts -type f -name "*.app" -o -name "*.dmg" | head -20
echo ""
echo "📁 Full directory structure:"
ls -la artifacts/
ls -la artifacts/macos-aarch64/ || echo "macos-aarch64 directory not found"
ls -la artifacts/macos-x86_64/ || echo "macos-x86_64 directory not found"
- name: Import Apple certificates
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# Create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# Import certificate from secrets
echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH
# Create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# Import certificate to keychain
security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
- name: Create universal app
run: |
# Create temp directory
mkdir -p dmg_temp
# Extract zip files if they exist
if [ -f "artifacts/macos-aarch64.zip" ]; then
echo "📦 Extracting macos-aarch64.zip..."
unzip -q artifacts/macos-aarch64.zip -d artifacts/macos-aarch64/
fi
if [ -f "artifacts/macos-x86_64.zip" ]; then
echo "📦 Extracting macos-x86_64.zip..."
unzip -q artifacts/macos-x86_64.zip -d artifacts/macos-x86_64/
fi
# Find the actual app paths
AARCH64_APP=$(find artifacts/macos-aarch64 -name "opcode.app" -type d | head -1)
X86_64_APP=$(find artifacts/macos-x86_64 -name "opcode.app" -type d | head -1)
if [ -z "$AARCH64_APP" ] || [ -z "$X86_64_APP" ]; then
echo "❌ Could not find app bundles"
echo "AARCH64_APP: $AARCH64_APP"
echo "X86_64_APP: $X86_64_APP"
exit 1
fi
echo "✅ Found app bundles:"
echo " ARM64: $AARCH64_APP"
echo " x86_64: $X86_64_APP"
# Copy ARM64 app as base
cp -R "$AARCH64_APP" dmg_temp/
# Create universal binary using lipo
lipo -create -output dmg_temp/opcode.app/Contents/MacOS/opcode \
"$AARCH64_APP/Contents/MacOS/opcode" \
"$X86_64_APP/Contents/MacOS/opcode"
# Ensure executable permissions are set
chmod +x dmg_temp/opcode.app/Contents/MacOS/opcode
echo "✅ Universal binary created"
lipo -info dmg_temp/opcode.app/Contents/MacOS/opcode
- name: Sign app bundle
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
run: |
codesign --sign "$APPLE_SIGNING_IDENTITY" \
--timestamp \
--options runtime \
--force \
--deep \
--entitlements src-tauri/entitlements.plist \
dmg_temp/opcode.app
- name: Create DMG
run: |
hdiutil create -volname "opcode Installer" \
-srcfolder dmg_temp \
-ov -format UDZO opcode.dmg
- name: Sign DMG
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
run: |
codesign --sign "$APPLE_SIGNING_IDENTITY" \
--timestamp \
--force opcode.dmg
- name: Notarize DMG
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
run: |
# Store notarization credentials
xcrun notarytool store-credentials "notarytool-profile" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_PASSWORD"
# Submit for notarization
xcrun notarytool submit opcode.dmg \
--keychain-profile "notarytool-profile" \
--wait
- name: Staple notarization
run: xcrun stapler staple opcode.dmg
- name: Verify DMG
run: |
spctl -a -t open -vvv --context context:primary-signature opcode.dmg
echo "✅ DMG verification complete"
- name: Create artifacts directory
run: |
mkdir -p dist/macos-universal
cp opcode.dmg dist/macos-universal/
# Also save the app bundle using ditto to preserve permissions and signatures
ditto -c -k --sequesterRsrc --keepParent \
dmg_temp/opcode.app dist/macos-universal/opcode.app.zip
# Generate checksum
shasum -a 256 dist/macos-universal/* > dist/macos-universal/checksums.txt
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: macos-universal
path: dist/macos-universal/*
- name: Cleanup
if: always()
run: |
echo "🧹 Cleaning up temporary directories..."
rm -rf dmg_temp temp_x86 artifacts
# Clean up keychain
if [ -n "$RUNNER_TEMP" ] && [ -f "$RUNNER_TEMP/app-signing.keychain-db" ]; then
security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" || true
fi
echo "✅ Cleanup complete"