-
Notifications
You must be signed in to change notification settings - Fork 107
Expand file tree
/
Copy pathintegration-mobile-test-qvac-lib-infer-llamacpp-embed.yml
More file actions
772 lines (661 loc) · 29.6 KB
/
Copy pathintegration-mobile-test-qvac-lib-infer-llamacpp-embed.yml
File metadata and controls
772 lines (661 loc) · 29.6 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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
name: Run Mobile Integration Tests (Qvac-lib-infer-llamacpp-embed)
on:
workflow_call:
inputs:
ref:
description: "Git ref to checkout"
type: string
required: false
repository:
description: "Repository to checkout"
type: string
required: false
workdir:
description: "Package working directory in monorepo (e.g., packages/qvac-lib-infer-llamacpp-embed)"
type: string
required: false
default: "packages/qvac-lib-infer-llamacpp-embed"
workflow_dispatch:
inputs:
ref:
description: "Git ref (branch/tag/SHA) to test"
type: string
required: false
default: main
package:
description: "Full NPM package spec to test (default: @qvac/llamacpp-embed@latest)"
type: string
required: false
default: "@qvac/llamacpp-embed@latest"
workdir:
description: "Package working directory in monorepo (e.g., packages/qvac-lib-infer-llamacpp-embed)"
type: string
required: false
default: "packages/qvac-lib-infer-llamacpp-embed"
env:
NODE_VERSION: "lts/*"
ADDON_NAME: "@qvac/llamacpp-embed"
PREBUILD_ARTIFACT_PREFIX: "llamacpp-embed-"
TEST_FRAMEWORK_REF: "main"
APP_BUNDLE_ID: "io.tether.test.qvac"
PKG_DIR: ${{ inputs.workdir || github.event.inputs.workdir || 'packages/qvac-lib-infer-llamacpp-embed' }}
jobs:
build-and-test:
name: Build ${{ matrix.platform }} and Run E2E Tests
runs-on: ${{ matrix.runner }}
timeout-minutes: 120
continue-on-error: true
permissions:
contents: read
packages: read
pull-requests: write
strategy:
fail-fast: false
matrix:
include:
- platform: Android
os: ubuntu-24.04
runner: ai-run-linux
- platform: iOS
os: macos-14
runner: macos-14
steps:
- name: Free up disk space
if: matrix.platform == 'Android'
run: |
echo "Disk space before cleanup:"
df -h
sudo rm -rf /usr/share/dotnet || true
sudo rm -rf /opt/ghc || true
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
sudo rm -rf /opt/hostedtoolcache/go || true
sudo rm -rf /opt/hostedtoolcache/Python || true
sudo rm -rf /opt/hostedtoolcache/Ruby || true
sudo rm -rf /usr/local/lib/android/sdk/ndk || true
sudo rm -rf /usr/local/share/boost || true
sudo rm -rf /usr/share/swift || true
sudo docker image prune --all --force || true
sudo apt-get clean || true
echo "Disk space after cleanup:"
df -h
- name: Checkout addon repository
uses: actions/checkout@v4
with:
path: addon
ref: ${{ inputs.ref || github.ref }}
repository: ${{ inputs.repository || github.repository }}
token: ${{ secrets.PAT_TOKEN }}
fetch-depth: 0
- name: Checkout mobile test framework
uses: actions/checkout@v4
with:
repository: tetherto/qvac-test-addon-mobile
token: ${{ secrets.PAT_TOKEN }}
path: test-framework
ref: ${{ env.TEST_FRAMEWORK_REF }}
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Configure scoped registry for @qvac and @tetherto packages
env:
GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GIT_PAT: ${{ secrets.PAT_TOKEN }}
run: |
echo "Configuring scoped registry for @tetherto and @qvac packages..."
cd addon
cat > .npmrc <<NPMRC
always-auth=true
registry=https://registry.npmjs.org/
@qvac:registry=https://registry.npmjs.org/
@tetherto:registry=https://npm.pkg.github.com/
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
//npm.pkg.github.com/:_authToken=${GPR_TOKEN}
NPMRC
cd ../test-framework
cat > .npmrc <<NPMRC
always-auth=true
registry=https://registry.npmjs.org/
@qvac:registry=https://registry.npmjs.org/
@tetherto:registry=https://npm.pkg.github.com/
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
//npm.pkg.github.com/:_authToken=${GPR_TOKEN}
NPMRC
git config --global url."https://${GIT_PAT}:@github.com/".insteadOf "https://github.com/"
- name: Install global dependencies
run: |
echo "Installing global dependencies..."
npm install -g @expo/cli@latest
- name: Download Android prebuilds (from artifacts)
if: matrix.platform == 'Android' && github.event_name != 'workflow_dispatch'
uses: actions/download-artifact@v4
with:
pattern: ${{ env.PREBUILD_ARTIFACT_PREFIX }}android-*
path: addon/${{ env.PKG_DIR }}/prebuilds
merge-multiple: true
continue-on-error: true
- name: Download iOS prebuilds (from artifacts)
if: matrix.platform == 'iOS' && github.event_name != 'workflow_dispatch'
uses: actions/download-artifact@v4
with:
pattern: ${{ env.PREBUILD_ARTIFACT_PREFIX }}ios-*
path: addon/${{ env.PKG_DIR }}/prebuilds
merge-multiple: true
continue-on-error: true
- name: Download prebuilds (from npm - workflow_dispatch)
if: github.event_name == 'workflow_dispatch'
working-directory: ./addon/${{ env.PKG_DIR }}
run: |
PACKAGE_SPEC="${{ inputs.package }}"
echo "📦 Downloading $PACKAGE_SPEC from npm for manual trigger..."
PACKAGE_NAME="${PACKAGE_SPEC%@*}"
if ! npm pack "$PACKAGE_SPEC"; then
echo "ERROR: Failed to download $PACKAGE_SPEC from npm"
echo "Please check that the package exists at https://www.npmjs.com/package/$PACKAGE_NAME"
exit 1
fi
tar -xzf *.tgz
if [ ! -d "package/prebuilds" ]; then
echo "ERROR: No prebuilds directory found in package"
echo "The downloaded package may not contain prebuilt binaries"
exit 1
fi
mv package/prebuilds ./prebuilds
rm -rf package *.tgz
echo "✅ Prebuilds downloaded from npm:"
ls -la prebuilds/
- name: Verify and prepare prebuilds
working-directory: ./addon/${{ env.PKG_DIR }}
run: |
echo "Checking for prebuilds..."
if [ -d "prebuilds" ] && [ "$(ls -A prebuilds)" ]; then
echo "✅ Prebuilds found from artifacts:"
ls -la prebuilds/
else
echo "⚠️ No prebuilds from artifacts, checking source..."
if [ -d "prebuilds" ] && [ "$(ls -A prebuilds)" ]; then
echo "✅ Prebuilds found in source:"
ls -la prebuilds/
else
echo "❌ ERROR: No prebuilds found!"
echo " This workflow requires prebuilds to be available."
echo " Either:"
echo " 1. Run this workflow after prebuild job completes"
echo " 2. Or commit prebuilds to the repository"
exit 1
fi
fi
if npm run mobile:copy-prebuilds 2>/dev/null; then
echo "✅ Mobile prebuilds prepared"
else
echo "⚠️ mobile:copy-prebuilds script not available or failed"
fi
- name: Remove desktop prebuilds to save disk space
working-directory: ./addon/${{ env.PKG_DIR }}
run: |
echo "Removing desktop prebuilds to save disk space (keeping Android + iOS)..."
echo "Before cleanup:"
du -sh prebuilds/* 2>/dev/null || true
rm -rf prebuilds/darwin-* prebuilds/win32-* prebuilds/linux-* 2>/dev/null || true
echo "After cleanup (Android + iOS only):"
du -sh prebuilds/* 2>/dev/null || true
df -h
- name: Verify test files exist
working-directory: ./addon/${{ env.PKG_DIR }}
run: |
echo "Verifying addon has mobile tests..."
if [ ! -d "test/mobile" ]; then
echo "❌ ERROR: test/mobile directory not found!"
echo ""
echo "This workflow requires the addon to have mobile tests at:"
echo " test/mobile/"
echo ""
echo "Please create this directory with your test files."
echo "See qvac-test-addon-mobile README for test file format."
exit 1
fi
CJS_COUNT=$(find test/mobile -name "*.cjs" -type f | wc -l)
if [ "$CJS_COUNT" -eq 0 ]; then
echo "❌ ERROR: No .cjs test files found in test/mobile!"
exit 1
fi
echo "✅ Mobile test files found:"
ls -la test/mobile/*.cjs
if [ -d "test/mobile/testAssets" ]; then
echo ""
echo "✅ Test assets found:"
ls -lah test/mobile/testAssets/
else
echo ""
echo "ℹ️ No testAssets directory (this is optional)"
fi
- name: Install Ninja build tool
if: matrix.platform == 'iOS'
run: |
echo "📦 Installing Ninja build system..."
brew install ninja
ninja --version
echo "✅ Ninja installed successfully"
- name: Install addon dependencies
working-directory: ./addon/${{ env.PKG_DIR }}
run: |
echo "Installing addon dependencies..."
npm install
- name: Validate mobile tests are up-to-date
working-directory: ./addon/${{ env.PKG_DIR }}
run: npm run test:mobile:validate
- name: Pack addon
working-directory: ./addon/${{ env.PKG_DIR }}
run: |
echo "Packing addon..."
if npm run build:pack 2>/dev/null; then
echo "✅ Addon packed using build:pack script"
else
echo "📦 Using npm pack directly..."
mkdir -p dist
npm pack --pack-destination dist
fi
PACK_FILE=$(ls dist/*.tgz | head -1)
if [ -f "$PACK_FILE" ]; then
SIZE=$(du -h "$PACK_FILE" | cut -f1)
echo "✅ Pack file created: $PACK_FILE (Size: $SIZE)"
else
echo "❌ Pack file not found in dist/"
exit 1
fi
- name: Setup test framework dependencies
working-directory: ./test-framework
run: |
echo "Setting up mobile test framework..."
npm install
echo "✅ Test framework dependencies installed"
- name: Build test app with addon
working-directory: ./test-framework
run: |
echo "Building test app with addon..."
echo "This will:"
echo " 1. Install the addon package"
echo " 2. Extract test code from addon's test/mobile/ directory"
echo " 3. Auto-detect and order test files by dependencies"
echo " 4. Generate backend.cjs with test functions"
echo " 5. Generate e2e tests for each test function"
echo " 6. Copy testAssets if available"
echo " 7. Bundle the app"
echo ""
ADDON_PATH="${GITHUB_WORKSPACE}/addon/${{ env.PKG_DIR }}"
npm run build "$ADDON_PATH" "$ADDON_PATH/test/mobile"
echo ""
echo "✅ Test app built successfully"
if [ ! -f "backend/backend.cjs" ]; then
echo "❌ ERROR: backend/backend.cjs was not generated!"
exit 1
fi
if [ ! -f "e2e/tests/app.test.js" ]; then
echo "❌ ERROR: e2e/tests/app.test.js was not generated!"
exit 1
fi
if [ ! -f "backend/app.bundle" ]; then
echo "❌ ERROR: backend/app.bundle was not created!"
exit 1
fi
echo "✅ All required files generated successfully"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "EXTRACTED TEST FUNCTIONS:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [ -f "app/testConfig.js" ]; then
cat app/testConfig.js
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- name: Display build summary
if: always()
working-directory: ./test-framework
run: |
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📊 BUILD SUMMARY"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Platform: ${{ matrix.platform }}"
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "Package: ${{ inputs.package }}"
else
echo "Addon: ${{ env.ADDON_NAME }} (from PR artifacts)"
fi
echo ""
echo "Generated Files:"
echo " backend/backend.cjs: $([ -f backend/backend.cjs ] && echo '✅' || echo '❌')"
echo " backend/app.bundle: $([ -f backend/app.bundle ] && echo '✅' || echo '❌')"
echo " app/testConfig.js: $([ -f app/testConfig.js ] && echo '✅' || echo '❌')"
echo " app/assetManifest.js: $([ -f app/assetManifest.js ] && echo '✅' || echo '❌')"
echo " e2e/tests/app.test.js: $([ -f e2e/tests/app.test.js ] && echo '✅' || echo '❌')"
echo ""
echo "Test Assets:"
if [ -d "testAssets" ]; then
ASSET_COUNT=$(find testAssets -type f | wc -l)
echo " ✅ $ASSET_COUNT file(s) in testAssets/"
else
echo " ℹ️ No testAssets (optional)"
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Android-specific steps
- name: Set up JDK 17
if: matrix.platform == 'Android'
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "temurin"
- name: Setup Android SDK
if: matrix.platform == 'Android'
uses: android-actions/setup-android@v3
- name: Generate Android project
if: matrix.platform == 'Android'
working-directory: ./test-framework
run: |
echo "Generating Android project with Expo..."
npx expo prebuild --platform android --clean
- name: Build Android APK
if: matrix.platform == 'Android'
id: build_apk
working-directory: ./test-framework
run: |
echo "Building Android APK for Device Farm..."
export JAVA_HOME=$JAVA_HOME_17_X64
echo "Bundling JavaScript code..."
npm run bundle
if [ $? -ne 0 ]; then
echo "❌ Bundle failed"
exit 1
fi
echo "✅ Bundle completed successfully"
cd android
echo "Building APK with Gradle (RELEASE with embedded JS bundle)..."
./gradlew assembleRelease \
-PreactNativeArchitectures=arm64-v8a \
--no-daemon \
--no-build-cache \
--stacktrace
cd ..
APK_PATH=$(find android/app/build/outputs/apk -name "*.apk" | grep "release" | grep -v "unaligned" | head -1)
if [ -f "$APK_PATH" ]; then
APK_ABSOLUTE_PATH="${GITHUB_WORKSPACE}/test-framework/$APK_PATH"
SIZE=$(du -h "$APK_PATH" | cut -f1)
echo "✅ APK built successfully: $APK_PATH (Size: $SIZE)"
echo "apk_path=$APK_ABSOLUTE_PATH" >> $GITHUB_OUTPUT
echo "app_type=ANDROID_APP" >> $GITHUB_OUTPUT
echo "app_name=test-app-${{ matrix.platform }}.apk" >> $GITHUB_OUTPUT
echo "Cleaning up build intermediates..."
rm -rf android/app/build/intermediates
rm -rf android/.gradle
df -h
else
echo "❌ APK file not found"
echo "Searching in android/app/build/outputs/apk:"
find android/app/build/outputs/apk -type f 2>/dev/null || echo "Directory not found"
exit 1
fi
# iOS-specific steps
- name: Set up Xcode version
if: matrix.platform == 'iOS'
run: |
echo "Available Xcode versions:"
ls /Applications | grep Xcode || echo "No Xcode apps found"
echo ""
echo "Current Xcode (before switch):"
xcodebuild -version
if [ -d "/Applications/Xcode_16.1.app" ]; then
echo ""
echo "✅ Switching to Xcode 16.1..."
sudo xcode-select -s /Applications/Xcode_16.1.app
elif [ -d "/Applications/Xcode_16.1.0.app" ]; then
echo ""
echo "✅ Switching to Xcode 16.1.0..."
sudo xcode-select -s /Applications/Xcode_16.1.0.app
elif [ -d "/Applications/Xcode_16.2.app" ]; then
echo ""
echo "⚠️ Using Xcode 16.2 (16.1 not found)..."
sudo xcode-select -s /Applications/Xcode_16.2.app
else
echo ""
echo "❌ ERROR: No suitable Xcode version found (need >= 16.1)"
exit 1
fi
echo ""
echo "Current Xcode (after switch):"
xcodebuild -version
echo ""
echo "Available iOS SDKs:"
xcodebuild -showsdks | grep -i ios
- name: Install CocoaPods
if: matrix.platform == 'iOS'
run: |
sudo gem install cocoapods
pod --version
- name: Create Keychain and Import Certificate
if: matrix.platform == 'iOS'
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.APPLE_DISTRIBUTION_CERTIFICATE }}
P12_PASSWORD: ${{ secrets.APPLE_P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.APPLE_PROVISIONING_PROFILE }}
KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
run: |
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
PP_UUID=$(/usr/libexec/PlistBuddy -c 'Print :UUID' /dev/stdin <<< $(security cms -D -i $PP_PATH))
echo "PP_UUID=$PP_UUID" >> $GITHUB_ENV
echo "Provisioning Profile UUID: $PP_UUID"
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$PP_UUID.mobileprovision
security find-identity -p codesigning -v
- name: Verify provisioning profile
if: matrix.platform == 'iOS'
run: |
echo "🔍 Verifying provisioning profile..."
echo "PP_UUID: $PP_UUID"
PP_FILE=~/Library/MobileDevice/Provisioning\ Profiles/$PP_UUID.mobileprovision
if [ ! -f "$PP_FILE" ]; then
echo "❌ Provisioning profile file not found at: $PP_FILE"
ls -la ~/Library/MobileDevice/Provisioning\ Profiles/
exit 1
fi
echo "📋 Provisioning Profile Details:"
security cms -D -i "$PP_FILE" > /tmp/profile.plist
PROFILE_NAME=$(/usr/libexec/PlistBuddy -c "Print :Name" /tmp/profile.plist 2>/dev/null || echo "Unknown")
PROFILE_BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :Entitlements:application-identifier" /tmp/profile.plist 2>/dev/null || echo "Unknown")
PROFILE_TEAM_ID=$(/usr/libexec/PlistBuddy -c "Print :Entitlements:com.apple.developer.team-identifier" /tmp/profile.plist 2>/dev/null || echo "Unknown")
HAS_DEVICES=$(/usr/libexec/PlistBuddy -c "Print :ProvisionedDevices" /tmp/profile.plist 2>/dev/null && echo "yes" || echo "no")
PROVISIONS_ALL=$(/usr/libexec/PlistBuddy -c "Print :ProvisionsAllDevices" /tmp/profile.plist 2>/dev/null || echo "false")
HAS_GET_TASK_ALLOW=$(/usr/libexec/PlistBuddy -c "Print :Entitlements:get-task-allow" /tmp/profile.plist 2>/dev/null || echo "false")
if [[ "$PROVISIONS_ALL" == "true" ]]; then
PROFILE_TYPE="Enterprise"
EXPORT_METHOD="enterprise"
elif [[ "$HAS_DEVICES" == "yes" && "$HAS_GET_TASK_ALLOW" == "true" ]]; then
PROFILE_TYPE="Development"
EXPORT_METHOD="development"
elif [[ "$HAS_DEVICES" == "yes" && "$HAS_GET_TASK_ALLOW" == "false" ]]; then
PROFILE_TYPE="Ad Hoc"
EXPORT_METHOD="ad-hoc"
else
PROFILE_TYPE="App Store"
EXPORT_METHOD="app-store"
fi
echo " Name: $PROFILE_NAME"
echo " Type: $PROFILE_TYPE"
echo " Export Method: $EXPORT_METHOD"
echo " Application ID: $PROFILE_BUNDLE_ID"
echo " Team ID: $PROFILE_TEAM_ID"
echo " Expected Bundle ID: ${{ env.APP_BUNDLE_ID }}"
echo "EXPORT_METHOD=$EXPORT_METHOD" >> $GITHUB_ENV
BUNDLE_ID_ONLY=$(echo "$PROFILE_BUNDLE_ID" | sed 's/^[^.]*\.//')
if [[ "$BUNDLE_ID_ONLY" != "${{ env.APP_BUNDLE_ID }}" ]]; then
echo ""
echo "❌ ERROR: Provisioning profile bundle ID mismatch!"
echo " Profile has: $BUNDLE_ID_ONLY"
echo " Expected: ${{ env.APP_BUNDLE_ID }}"
echo ""
echo "The provisioning profile was created for a different bundle identifier."
echo "Please create a new provisioning profile for: ${{ env.APP_BUNDLE_ID }}"
exit 1
fi
echo "✅ Provisioning profile matches expected bundle ID"
- name: Generate iOS project
if: matrix.platform == 'iOS'
working-directory: ./test-framework
run: |
echo "Generating iOS project with Expo..."
npx expo prebuild --platform ios --clean
- name: Install iOS dependencies
if: matrix.platform == 'iOS'
working-directory: ./test-framework/ios
run: |
echo "Installing CocoaPods dependencies..."
pod install --repo-update
- name: Build and Archive iOS App
if: matrix.platform == 'iOS'
id: build_ios
working-directory: ./test-framework
run: |
echo "Building iOS app for Device Farm..."
echo "Bundling JavaScript code..."
npm run bundle
if [ $? -ne 0 ]; then
echo "❌ Bundle failed"
exit 1
fi
echo "✅ Bundle completed successfully"
cd ios
SCHEME_NAME=$(xcodebuild -list | grep -A 1 "Schemes:" | grep -v "Schemes:" | head -1 | xargs)
echo "Detected scheme: $SCHEME_NAME"
echo "🔍 Checking project configuration..."
BUNDLE_ID=$(xcodebuild -showBuildSettings -workspace $SCHEME_NAME.xcworkspace -scheme "$SCHEME_NAME" -configuration Release -destination "generic/platform=iOS" 2>/dev/null | grep PRODUCT_BUNDLE_IDENTIFIER | head -1 | awk '{print $3}')
echo "Bundle Identifier in project: $BUNDLE_ID"
if [[ "$BUNDLE_ID" != "${{ env.APP_BUNDLE_ID }}" ]]; then
echo "⚠️ Warning: Bundle ID mismatch in Xcode project!"
echo " Expected: ${{ env.APP_BUNDLE_ID }}"
echo " Found: $BUNDLE_ID"
fi
echo "🔍 Provisioning profile UUID: $PP_UUID"
security cms -D -i ~/Library/MobileDevice/Provisioning\ Profiles/$PP_UUID.mobileprovision | grep -A 5 "application-identifier\|Name\|TeamIdentifier" | head -20 || echo "Could not read profile details"
xcodebuild -workspace $SCHEME_NAME.xcworkspace \
-scheme "$SCHEME_NAME" \
-sdk iphoneos \
-configuration Release \
-destination "generic/platform=iOS" \
-archivePath $RUNNER_TEMP/$SCHEME_NAME.xcarchive \
CODE_SIGN_STYLE=Manual \
PROVISIONING_PROFILE_SPECIFIER="$PP_UUID" \
CODE_SIGN_IDENTITY="Apple Distribution" \
DEVELOPMENT_TEAM="${{ secrets.APPLE_TEAM_ID }}" \
clean archive
- name: Export IPA
if: matrix.platform == 'iOS'
id: export_ipa
working-directory: ./test-framework/ios
run: |
SCHEME_NAME=$(xcodebuild -list | grep -A 1 "Schemes:" | grep -v "Schemes:" | head -1 | xargs)
echo "📦 Using export method: $EXPORT_METHOD"
EXPORT_OPTS_PATH=$RUNNER_TEMP/ExportOptions.plist
cat > $EXPORT_OPTS_PATH << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>$EXPORT_METHOD</string>
<key>teamID</key>
<string>${{ secrets.APPLE_TEAM_ID }}</string>
<key>signingStyle</key>
<string>manual</string>
<key>provisioningProfiles</key>
<dict>
<key>${{ env.APP_BUNDLE_ID }}</key>
<string>$PP_UUID</string>
</dict>
</dict>
</plist>
EOF
echo "📋 Export options:"
cat $EXPORT_OPTS_PATH
xcodebuild -exportArchive \
-archivePath $RUNNER_TEMP/$SCHEME_NAME.xcarchive \
-exportOptionsPlist $EXPORT_OPTS_PATH \
-exportPath $RUNNER_TEMP/build
IPA_FILE=$(find $RUNNER_TEMP/build -name "*.ipa" | head -1)
if [ -f "$IPA_FILE" ]; then
echo "✅ IPA exported: $IPA_FILE"
echo "apk_path=$IPA_FILE" >> $GITHUB_OUTPUT
echo "app_type=IOS_APP" >> $GITHUB_OUTPUT
echo "app_name=test-app-${{ matrix.platform }}.ipa" >> $GITHUB_OUTPUT
else
echo "❌ IPA file not found"
exit 1
fi
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-west-2
- name: Upload App to Device Farm
id: upload_app
run: |
if [ "${{ matrix.platform }}" == "Android" ]; then
APP_PATH="${{ steps.build_apk.outputs.apk_path }}"
APP_TYPE="${{ steps.build_apk.outputs.app_type }}"
APP_NAME="${{ steps.build_apk.outputs.app_name }}"
else
APP_PATH="${{ steps.export_ipa.outputs.apk_path }}"
APP_TYPE="${{ steps.export_ipa.outputs.app_type }}"
APP_NAME="${{ steps.export_ipa.outputs.app_name }}"
fi
echo "📤 Uploading app to AWS Device Farm..."
UPLOAD_RESPONSE=$(aws devicefarm create-upload \
--project-arn "${{ secrets.AWS_DEVICE_FARM_PROJECT_ARN_LLAMACPP_EMBED }}" \
--name "$APP_NAME" \
--type "$APP_TYPE" \
--output json)
if [ $? -ne 0 ]; then
echo "❌ Error creating upload in Device Farm"
echo "Response: $UPLOAD_RESPONSE"
exit 1
fi
APP_UPLOAD_URL=$(echo $UPLOAD_RESPONSE | jq -r '.upload.url')
APP_UPLOAD_ARN=$(echo $UPLOAD_RESPONSE | jq -r '.upload.arn')
echo "app_upload_arn=$APP_UPLOAD_ARN" >> $GITHUB_OUTPUT
echo "App upload ARN: $APP_UPLOAD_ARN"
echo "Uploading app file: $APP_PATH"
curl -T "$APP_PATH" "$APP_UPLOAD_URL"
if [ $? -ne 0 ]; then
echo "❌ Error uploading app file using curl"
exit 1
fi
echo "⏳ Waiting for upload to be processed..."
MAX_ATTEMPTS=30
ATTEMPT=1
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
STATUS=$(aws devicefarm get-upload --arn "$APP_UPLOAD_ARN" --query "upload.status" --output text)
echo "Status (attempt $ATTEMPT/$MAX_ATTEMPTS): $STATUS"
if [ "$STATUS" = "SUCCEEDED" ]; then
echo "✅ App upload successful"
break
fi
if [ "$STATUS" = "FAILED" ]; then
echo "❌ Upload failed"
aws devicefarm get-upload --arn "$APP_UPLOAD_ARN"
exit 1
fi
sleep 10
ATTEMPT=$((ATTEMPT + 1))
done