1#!/usr/bin/env bash
2# validate-tvos.sh - Validate tvOS Application with embedded llama.xcframework using SwiftUI
3
4# Authentication options (optional) (can be set via environment variables)
5# To use: export APPLE_ID=your.email@example.com
6# export APPLE_PASSWORD=your-app-specific-password
7# ./validate-tvos.sh
8APPLE_ID=${APPLE_ID:-""}
9APPLE_PASSWORD=${APPLE_PASSWORD:-""}
10
11# Ensure the script exits on error
12set -e
13
14# Function to print usage instructions
15print_usage() {
16 echo "Usage: ./validate-tvos.sh [OPTIONS]"
17 echo ""
18 echo "Options:"
19 echo " --help Show this help message"
20 echo " --apple-id EMAIL Apple ID email for validation"
21 echo " --apple-password PWD App-specific password for Apple ID"
22 echo ""
23 echo "Environment variables:"
24 echo " APPLE_ID Apple ID email for validation"
25 echo " APPLE_PASSWORD App-specific password for Apple ID"
26 echo ""
27 echo "Notes:"
28 echo " - Command line options take precedence over environment variables"
29 echo " - Authentication is optional. If not provided, alternative validation will be performed"
30 echo " - For APPLE_PASSWORD, use an app-specific password generated at https://appleid.apple.com/account/manage"
31}
32
33# Parse command line arguments
34while [[ $# -gt 0 ]]; do
35 case $1 in
36 --help)
37 print_usage
38 exit 0
39 ;;
40 --apple-id)
41 APPLE_ID="$2"
42 shift 2
43 ;;
44 --apple-password)
45 APPLE_PASSWORD="$2"
46 shift 2
47 ;;
48 *)
49 echo "Unknown option: $1"
50 print_usage
51 exit 1
52 ;;
53 esac
54done
55
56# Function to clean up in case of error
57cleanup() {
58 # Don't clean up temp files on error to help with debugging
59 echo "===== tvOS Validation Process Failed ====="
60 exit 1
61}
62
63# Set up trap to call cleanup function on error
64trap cleanup ERR
65
66set -e # Exit on any error
67
68ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )"
69BUILD_DIR="${ROOT_DIR}/validation-builds/ios"
70
71# Configuration
72APP_NAME="TVOSLlamaTest"
73BUNDLE_ID="org.ggml.TVOSLlamaTest"
74XCFRAMEWORK_PATH="${ROOT_DIR}/build-apple/llama.xcframework"
75TEMP_DIR="${BUILD_DIR}/temp"
76ARCHIVE_PATH="${BUILD_DIR}/${APP_NAME}.xcarchive"
77IPA_PATH="${BUILD_DIR}/${APP_NAME}.ipa"
78VALIDATION_DIR="${BUILD_DIR}/validation"
79
80# Create necessary directories
81mkdir -p "${BUILD_DIR}"
82mkdir -p "${TEMP_DIR}"
83mkdir -p "${VALIDATION_DIR}"
84
85echo "===== tvOS Validation Process Started ====="
86
87# 1. Create a simple test app project
88echo "Creating test tvOS app project..."
89mkdir -p "${TEMP_DIR}/${APP_NAME}/${APP_NAME}"
90cat > "${TEMP_DIR}/${APP_NAME}/${APP_NAME}/Info.plist" << EOF
91<?xml version="1.0" encoding="UTF-8"?>
92<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
93<plist version="1.0">
94<dict>
95 <key>CFBundleDevelopmentRegion</key>
96 <string>en</string>
97 <key>CFBundleExecutable</key>
98 <string>${APP_NAME}</string>
99 <key>CFBundleIdentifier</key>
100 <string>${BUNDLE_ID}</string>
101 <key>CFBundleInfoDictionaryVersion</key>
102 <string>6.0</string>
103 <key>CFBundleName</key>
104 <string>${APP_NAME}</string>
105 <key>CFBundlePackageType</key>
106 <string>APPL</string>
107 <key>CFBundleShortVersionString</key>
108 <string>1.0</string>
109 <key>CFBundleVersion</key>
110 <string>1</string>
111 <key>UIRequiredDeviceCapabilities</key>
112 <array>
113 <string>arm64</string>
114 </array>
115</dict>
116</plist>
117EOF
118
119# Create SwiftUI app files
120mkdir -p "${TEMP_DIR}/${APP_NAME}/${APP_NAME}/Sources"
121
122# Create App.swift
123cat > "${TEMP_DIR}/${APP_NAME}/${APP_NAME}/Sources/App.swift" << EOF
124import SwiftUI
125import llama
126
127@main
128struct LlamaTestApp: App {
129 var body: some Scene {
130 WindowGroup {
131 ContentView()
132 }
133 }
134}
135EOF
136
137# Create ContentView.swift with tvOS specific elements
138cat > "${TEMP_DIR}/${APP_NAME}/${APP_NAME}/Sources/ContentView.swift" << EOF
139import SwiftUI
140import llama
141
142struct ContentView: View {
143 // Test that we can initialize a llama context params struct
144 let params = llama_context_default_params()
145
146 var body: some View {
147 VStack(spacing: 40) {
148 Text("Llama Framework Test on tvOS")
149 .font(.largeTitle)
150 .padding()
151
152 Text("llama_context_default_params() created successfully")
153 .font(.headline)
154 .multilineTextAlignment(.center)
155 .padding()
156
157 // Display some param values to confirm the framework is working
158 Text("n_ctx: \(params.n_ctx)")
159 .font(.title2)
160
161 Text("n_batch: \(params.n_batch)")
162 .font(.title2)
163
164 Spacer()
165 }
166 .padding(50)
167 // Larger size suitable for TV display
168 }
169}
170
171struct ContentView_Previews: PreviewProvider {
172 static var previews: some View {
173 ContentView()
174 }
175}
176EOF
177
178# Create project.pbxproj, fixing the framework search paths issues
179mkdir -p "${TEMP_DIR}/${APP_NAME}/${APP_NAME}.xcodeproj"
180cat > "${TEMP_DIR}/${APP_NAME}/${APP_NAME}.xcodeproj/project.pbxproj" << 'EOF'
181// !$*UTF8*$!
182{
183 archiveVersion = 1;
184 classes = {
185 };
186 objectVersion = 54;
187 objects = {
188
189/* Begin PBXBuildFile section */
190 11111111111111111111111 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22222222222222222222222; };
191 33333333333333333333333 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44444444444444444444444; };
192 55555555555555555555555 /* llama.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66666666666666666666666; };
193 77777777777777777777777 /* llama.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 66666666666666666666666; };
194/* End PBXBuildFile section */
195
196/* Begin PBXCopyFilesBuildPhase section */
197 88888888888888888888888 /* Embed Frameworks */ = {
198 isa = PBXCopyFilesBuildPhase;
199 buildActionMask = 2147483647;
200 dstPath = "";
201 dstSubfolderSpec = 10;
202 files = (
203 77777777777777777777777 /* llama.xcframework in Embed Frameworks */,
204 );
205 name = "Embed Frameworks";
206 runOnlyForDeploymentPostprocessing = 0;
207 };
208/* End PBXCopyFilesBuildPhase section */
209
210/* Begin PBXFileReference section */
211EOF
212
213# Continue with the project.pbxproj file, using the APP_NAME variable appropriately
214cat >> "${TEMP_DIR}/${APP_NAME}/${APP_NAME}.xcodeproj/project.pbxproj" << EOF
215 99999999999999999999999 /* ${APP_NAME}.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "${APP_NAME}.app"; sourceTree = BUILT_PRODUCTS_DIR; };
216 22222222222222222222222 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
217 44444444444444444444444 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
218 AAAAAAAAAAAAAAAAAAAAAAA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
219 66666666666666666666666 /* llama.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = llama.xcframework; sourceTree = "<group>"; };
220/* End PBXFileReference section */
221EOF
222
223# Add the rest of the project file with fixed framework search paths
224cat >> "${TEMP_DIR}/${APP_NAME}/${APP_NAME}.xcodeproj/project.pbxproj" << 'EOF'
225/* Begin PBXFrameworksBuildPhase section */
226 BBBBBBBBBBBBBBBBBBBBBBBB /* Frameworks */ = {
227 isa = PBXFrameworksBuildPhase;
228 buildActionMask = 2147483647;
229 files = (
230 55555555555555555555555 /* llama.xcframework in Frameworks */,
231 );
232 runOnlyForDeploymentPostprocessing = 0;
233 };
234/* End PBXFrameworksBuildPhase section */
235
236/* Begin PBXGroup section */
237EOF
238
239# Continue with the project.pbxproj file, using the APP_NAME variable appropriately
240cat >> "${TEMP_DIR}/${APP_NAME}/${APP_NAME}.xcodeproj/project.pbxproj" << EOF
241 CCCCCCCCCCCCCCCCCCCCCCCC /* Products */ = {
242 isa = PBXGroup;
243 children = (
244 99999999999999999999999 /* ${APP_NAME}.app */,
245 );
246 name = Products;
247 sourceTree = "<group>";
248 };
249EOF
250
251cat >> "${TEMP_DIR}/${APP_NAME}/${APP_NAME}.xcodeproj/project.pbxproj" << 'EOF'
252 DDDDDDDDDDDDDDDDDDDDDDDD /* Frameworks */ = {
253 isa = PBXGroup;
254 children = (
255 66666666666666666666666 /* llama.xcframework */,
256 );
257 name = Frameworks;
258 sourceTree = "<group>";
259 };
260 EEEEEEEEEEEEEEEEEEEEEEEE = {
261 isa = PBXGroup;
262 children = (
263 FFFFFFFFFFFFFFFFFFFFFFFF /* TVOSLlamaTest */,
264 CCCCCCCCCCCCCCCCCCCCCCCC /* Products */,
265 DDDDDDDDDDDDDDDDDDDDDDDD /* Frameworks */,
266 );
267 sourceTree = "<group>";
268 };
269 FFFFFFFFFFFFFFFFFFFFFFFF /* TVOSLlamaTest */ = {
270 isa = PBXGroup;
271 children = (
272 1111111111111111111111AA /* Sources */,
273 AAAAAAAAAAAAAAAAAAAAAAA /* Info.plist */,
274 );
275 path = "TVOSLlamaTest";
276 sourceTree = "<group>";
277 };
278 1111111111111111111111AA /* Sources */ = {
279 isa = PBXGroup;
280 children = (
281 22222222222222222222222 /* App.swift */,
282 44444444444444444444444 /* ContentView.swift */,
283 );
284 path = Sources;
285 sourceTree = "<group>";
286 };
287/* End PBXGroup section */
288EOF
289
290# Continue with the project.pbxproj file, using the APP_NAME variable appropriately
291cat >> "${TEMP_DIR}/${APP_NAME}/${APP_NAME}.xcodeproj/project.pbxproj" << EOF
292/* Begin PBXNativeTarget section */
293 3333333333333333333333AA /* ${APP_NAME} */ = {
294 isa = PBXNativeTarget;
295 buildConfigurationList = 4444444444444444444444AA /* Build configuration list for PBXNativeTarget "${APP_NAME}" */;
296 buildPhases = (
297 5555555555555555555555AA /* Sources */,
298 BBBBBBBBBBBBBBBBBBBBBBBB /* Frameworks */,
299 6666666666666666666666AA /* Resources */,
300 88888888888888888888888 /* Embed Frameworks */,
301 );
302 buildRules = (
303 );
304 dependencies = (
305 );
306 name = "${APP_NAME}";
307 productName = "${APP_NAME}";
308 productReference = 99999999999999999999999 /* ${APP_NAME}.app */;
309 productType = "com.apple.product-type.application";
310 };
311/* End PBXNativeTarget section */
312
313/* Begin PBXProject section */
314 7777777777777777777777AA /* Project object */ = {
315 isa = PBXProject;
316 attributes = {
317 LastSwiftUpdateCheck = 1240;
318 LastUpgradeCheck = 1240;
319 TargetAttributes = {
320 3333333333333333333333AA = {
321 CreatedOnToolsVersion = 12.4;
322 };
323 };
324 };
325 buildConfigurationList = 8888888888888888888888AA /* Build configuration list for PBXProject "${APP_NAME}" */;
326 compatibilityVersion = "Xcode 12.0";
327 developmentRegion = en;
328 hasScannedForEncodings = 0;
329 knownRegions = (
330 en,
331 Base,
332 );
333 mainGroup = EEEEEEEEEEEEEEEEEEEEEEEE;
334 productRefGroup = CCCCCCCCCCCCCCCCCCCCCCCC /* Products */;
335 projectDirPath = "";
336 projectRoot = "";
337 targets = (
338 3333333333333333333333AA /* ${APP_NAME} */,
339 );
340 };
341/* End PBXProject section */
342EOF
343
344# Add the rest of the file with correct FRAMEWORK_SEARCH_PATHS and tvOS settings
345cat >> "${TEMP_DIR}/${APP_NAME}/${APP_NAME}.xcodeproj/project.pbxproj" << 'EOF'
346/* Begin PBXResourcesBuildPhase section */
347 6666666666666666666666AA /* Resources */ = {
348 isa = PBXResourcesBuildPhase;
349 buildActionMask = 2147483647;
350 files = (
351 );
352 runOnlyForDeploymentPostprocessing = 0;
353 };
354/* End PBXResourcesBuildPhase section */
355
356/* Begin PBXSourcesBuildPhase section */
357 5555555555555555555555AA /* Sources */ = {
358 isa = PBXSourcesBuildPhase;
359 buildActionMask = 2147483647;
360 files = (
361 33333333333333333333333 /* ContentView.swift in Sources */,
362 11111111111111111111111 /* App.swift in Sources */,
363 );
364 runOnlyForDeploymentPostprocessing = 0;
365 };
366/* End PBXSourcesBuildPhase section */
367
368/* Begin XCBuildConfiguration section */
369 9999999999999999999999AA /* Debug */ = {
370 isa = XCBuildConfiguration;
371 buildSettings = {
372 ALWAYS_SEARCH_USER_PATHS = NO;
373 CLANG_ANALYZER_NONNULL = YES;
374 CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
375 CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
376 CLANG_CXX_LIBRARY = "libc++";
377 CLANG_ENABLE_MODULES = YES;
378 CLANG_ENABLE_OBJC_ARC = YES;
379 CLANG_ENABLE_OBJC_WEAK = YES;
380 CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
381 CLANG_WARN_BOOL_CONVERSION = YES;
382 CLANG_WARN_COMMA = YES;
383 CLANG_WARN_CONSTANT_CONVERSION = YES;
384 CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
385 CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
386 CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
387 CLANG_WARN_EMPTY_BODY = YES;
388 CLANG_WARN_ENUM_CONVERSION = YES;
389 CLANG_WARN_INFINITE_RECURSION = YES;
390 CLANG_WARN_INT_CONVERSION = YES;
391 CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
392 CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
393 CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
394 CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
395 CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
396 CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
397 CLANG_WARN_STRICT_PROTOTYPES = YES;
398 CLANG_WARN_SUSPICIOUS_MOVE = YES;
399 CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
400 CLANG_WARN_UNREACHABLE_CODE = YES;
401 CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
402 COPY_PHASE_STRIP = NO;
403 DEBUG_INFORMATION_FORMAT = dwarf;
404 ENABLE_STRICT_OBJC_MSGSEND = YES;
405 ENABLE_TESTABILITY = YES;
406 GCC_C_LANGUAGE_STANDARD = gnu11;
407 GCC_DYNAMIC_NO_PIC = NO;
408 GCC_NO_COMMON_BLOCKS = YES;
409 GCC_OPTIMIZATION_LEVEL = 0;
410 GCC_PREPROCESSOR_DEFINITIONS = (
411 "DEBUG=1",
412 "$(inherited)",
413 );
414 GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
415 GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
416 GCC_WARN_UNDECLARED_SELECTOR = YES;
417 GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
418 GCC_WARN_UNUSED_FUNCTION = YES;
419 GCC_WARN_UNUSED_VARIABLE = YES;
420 TVOS_DEPLOYMENT_TARGET = 15.0;
421 MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
422 MTL_FAST_MATH = YES;
423 ONLY_ACTIVE_ARCH = YES;
424 SDKROOT = appletvos;
425 SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
426 SWIFT_OPTIMIZATION_LEVEL = "-Onone";
427 };
428 name = Debug;
429 };
430 AAAAAAAAAAAAAAAAAAAAABBB /* Release */ = {
431 isa = XCBuildConfiguration;
432 buildSettings = {
433 ALWAYS_SEARCH_USER_PATHS = NO;
434 CLANG_ANALYZER_NONNULL = YES;
435 CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
436 CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
437 CLANG_CXX_LIBRARY = "libc++";
438 CLANG_ENABLE_MODULES = YES;
439 CLANG_ENABLE_OBJC_ARC = YES;
440 CLANG_ENABLE_OBJC_WEAK = YES;
441 CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
442 CLANG_WARN_BOOL_CONVERSION = YES;
443 CLANG_WARN_COMMA = YES;
444 CLANG_WARN_CONSTANT_CONVERSION = YES;
445 CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
446 CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
447 CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
448 CLANG_WARN_EMPTY_BODY = YES;
449 CLANG_WARN_ENUM_CONVERSION = YES;
450 CLANG_WARN_INFINITE_RECURSION = YES;
451 CLANG_WARN_INT_CONVERSION = YES;
452 CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
453 CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
454 CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
455 CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
456 CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
457 CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
458 CLANG_WARN_STRICT_PROTOTYPES = YES;
459 CLANG_WARN_SUSPICIOUS_MOVE = YES;
460 CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
461 CLANG_WARN_UNREACHABLE_CODE = YES;
462 CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
463 COPY_PHASE_STRIP = NO;
464 DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
465 ENABLE_NS_ASSERTIONS = NO;
466 ENABLE_STRICT_OBJC_MSGSEND = YES;
467 GCC_C_LANGUAGE_STANDARD = gnu11;
468 GCC_NO_COMMON_BLOCKS = YES;
469 GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
470 GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
471 GCC_WARN_UNDECLARED_SELECTOR = YES;
472 GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
473 GCC_WARN_UNUSED_FUNCTION = YES;
474 GCC_WARN_UNUSED_VARIABLE = YES;
475 TVOS_DEPLOYMENT_TARGET = 15.0;
476 MTL_ENABLE_DEBUG_INFO = NO;
477 MTL_FAST_MATH = YES;
478 SDKROOT = appletvos;
479 SWIFT_COMPILATION_MODE = wholemodule;
480 SWIFT_OPTIMIZATION_LEVEL = "-O";
481 VALIDATE_PRODUCT = YES;
482 };
483 name = Release;
484 };
485 BBBBBBBBBBBBBBBBBBBBBBCCC /* Debug */ = {
486 isa = XCBuildConfiguration;
487 buildSettings = {
488 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
489 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
490 CODE_SIGN_STYLE = Manual;
491 DEVELOPMENT_TEAM = "";
492 ENABLE_PREVIEWS = YES;
493 FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)";
494 INFOPLIST_FILE = "TVOSLlamaTest/Info.plist";
495 LD_RUNPATH_SEARCH_PATHS = (
496 "$(inherited)",
497 "@executable_path/Frameworks",
498 );
499 PRODUCT_BUNDLE_IDENTIFIER = "org.ggml.TVOSLlamaTest";
500 PRODUCT_NAME = "$(TARGET_NAME)";
501 PROVISIONING_PROFILE_SPECIFIER = "";
502 SWIFT_VERSION = 5.0;
503 TARGETED_DEVICE_FAMILY = 3;
504 };
505 name = Debug;
506 };
507 CCCCCCCCCCCCCCCCCCCCCCDDD /* Release */ = {
508 isa = XCBuildConfiguration;
509 buildSettings = {
510 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
511 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
512 CODE_SIGN_STYLE = Manual;
513 DEVELOPMENT_TEAM = "";
514 ENABLE_PREVIEWS = YES;
515 FRAMEWORK_SEARCH_PATHS = (
516 "$(inherited)",
517 "$(PROJECT_DIR)",
518 );
519 INFOPLIST_FILE = "TVOSLlamaTest/Info.plist";
520 LD_RUNPATH_SEARCH_PATHS = (
521 "$(inherited)",
522 "@executable_path/Frameworks",
523 );
524 PRODUCT_BUNDLE_IDENTIFIER = "org.ggml.TVOSLlamaTest";
525 PRODUCT_NAME = "$(TARGET_NAME)";
526 PROVISIONING_PROFILE_SPECIFIER = "";
527 SWIFT_VERSION = 5.0;
528 TARGETED_DEVICE_FAMILY = 3;
529 };
530 name = Release;
531 };
532/* End XCBuildConfiguration section */
533EOF
534
535# Finish the project.pbxproj file
536cat >> "${TEMP_DIR}/${APP_NAME}/${APP_NAME}.xcodeproj/project.pbxproj" << EOF
537/* Begin XCConfigurationList section */
538 8888888888888888888888AA /* Build configuration list for PBXProject "${APP_NAME}" */ = {
539 isa = XCConfigurationList;
540 buildConfigurations = (
541 9999999999999999999999AA /* Debug */,
542 AAAAAAAAAAAAAAAAAAAAABBB /* Release */,
543 );
544 defaultConfigurationIsVisible = 0;
545 defaultConfigurationName = Release;
546 };
547 4444444444444444444444AA /* Build configuration list for PBXNativeTarget "${APP_NAME}" */ = {
548 isa = XCConfigurationList;
549 buildConfigurations = (
550 BBBBBBBBBBBBBBBBBBBBBBCCC /* Debug */,
551 CCCCCCCCCCCCCCCCCCCCCCDDD /* Release */,
552 );
553 defaultConfigurationIsVisible = 0;
554 defaultConfigurationName = Release;
555 };
556/* End XCConfigurationList section */
557 };
558 rootObject = 7777777777777777777777AA /* Project object */;
559}
560EOF
561
562# 2. Copy XCFramework to test project
563echo "Copying XCFramework to test project..."
564cp -R "${XCFRAMEWORK_PATH}" "${TEMP_DIR}/${APP_NAME}/"
565
566# 3. Build and archive the app
567echo "Building and archiving test app..."
568cd "${TEMP_DIR}/${APP_NAME}"
569
570# Create a simple xcscheme file to avoid xcodebuild scheme issues
571mkdir -p "${APP_NAME}.xcodeproj/xcshareddata/xcschemes"
572cat > "${APP_NAME}.xcodeproj/xcshareddata/xcschemes/${APP_NAME}.xcscheme" << EOF
573<?xml version="1.0" encoding="UTF-8"?>
574<Scheme
575 LastUpgradeVersion = "1240"
576 version = "1.3">
577 <BuildAction
578 parallelizeBuildables = "YES"
579 buildImplicitDependencies = "YES">
580 <BuildActionEntries>
581 <BuildActionEntry
582 buildForTesting = "YES"
583 buildForRunning = "YES"
584 buildForProfiling = "YES"
585 buildForArchiving = "YES"
586 buildForAnalyzing = "YES">
587 <BuildableReference
588 BuildableIdentifier = "primary"
589 BlueprintIdentifier = "3333333333333333333333AA"
590 BuildableName = "${APP_NAME}.app"
591 BlueprintName = "${APP_NAME}"
592 ReferencedContainer = "container:${APP_NAME}.xcodeproj">
593 </BuildableReference>
594 </BuildActionEntry>
595 </BuildActionEntries>
596 </BuildAction>
597 <TestAction
598 buildConfiguration = "Debug"
599 selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
600 selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
601 shouldUseLaunchSchemeArgsEnv = "YES">
602 <Testables>
603 </Testables>
604 </TestAction>
605 <LaunchAction
606 buildConfiguration = "Debug"
607 selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
608 selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
609 launchStyle = "0"
610 useCustomWorkingDirectory = "NO"
611 ignoresPersistentStateOnLaunch = "NO"
612 debugDocumentVersioning = "YES"
613 debugServiceExtension = "internal"
614 allowLocationSimulation = "YES">
615 <BuildableProductRunnable
616 runnableDebuggingMode = "0">
617 <BuildableReference
618 BuildableIdentifier = "primary"
619 BlueprintIdentifier = "3333333333333333333333AA"
620 BuildableName = "${APP_NAME}.app"
621 BlueprintName = "${APP_NAME}"
622 ReferencedContainer = "container:${APP_NAME}.xcodeproj">
623 </BuildableReference>
624 </BuildableProductRunnable>
625 </LaunchAction>
626 <ProfileAction
627 buildConfiguration = "Release"
628 shouldUseLaunchSchemeArgsEnv = "YES"
629 savedToolIdentifier = ""
630 useCustomWorkingDirectory = "NO"
631 debugDocumentVersioning = "YES">
632 <BuildableProductRunnable
633 runnableDebuggingMode = "0">
634 <BuildableReference
635 BuildableIdentifier = "primary"
636 BlueprintIdentifier = "3333333333333333333333AA"
637 BuildableName = "${APP_NAME}.app"
638 BlueprintName = "${APP_NAME}"
639 ReferencedContainer = "container:${APP_NAME}.xcodeproj">
640 </BuildableReference>
641 </BuildableProductRunnable>
642 </ProfileAction>
643 <AnalyzeAction
644 buildConfiguration = "Debug">
645 </AnalyzeAction>
646 <ArchiveAction
647 buildConfiguration = "Release"
648 revealArchiveInOrganizer = "YES">
649 </ArchiveAction>
650</Scheme>
651EOF
652
653# Now use xcodebuild with an explicitly defined product name for tvOS
654xcodebuild -project "${APP_NAME}.xcodeproj" -scheme "${APP_NAME}" -sdk appletvos -configuration Release archive -archivePath "${ARCHIVE_PATH}" CODE_SIGN_IDENTITY="-" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO PRODUCT_NAME="${APP_NAME}" SWIFT_OPTIMIZATION_LEVEL="-Onone" -quiet
655
656# 4. Create IPA from archive
657echo "Creating IPA from archive..."
658mkdir -p "${TEMP_DIR}/Payload"
659cp -R "${ARCHIVE_PATH}/Products/Applications/${APP_NAME}.app" "${TEMP_DIR}/Payload/"
660
661# Check and log app structure before zipping
662echo "App structure:"
663ls -la "${TEMP_DIR}/Payload/${APP_NAME}.app/"
664echo "Frameworks:"
665ls -la "${TEMP_DIR}/Payload/${APP_NAME}.app/Frameworks/" 2>/dev/null || echo "No Frameworks directory found"
666
667cd "${TEMP_DIR}"
668zip -r "${IPA_PATH}" Payload
669
670# Check embedded provisioning profile
671echo "Checking provisioning profile (if any)..."
672PROVISIONING_PROFILE=$(find "${ARCHIVE_PATH}/Products/Applications/${APP_NAME}.app" -name "embedded.mobileprovision" 2>/dev/null)
673if [ -n "$PROVISIONING_PROFILE" ]; then
674 echo "Found embedded provisioning profile:"
675 security cms -D -i "$PROVISIONING_PROFILE" || echo "Unable to decode provisioning profile"
676else
677 echo "No embedded provisioning profile found (expected for ad-hoc builds)"
678fi
679
680# 5. Validate the IPA
681echo "Validating IPA..."
682VALIDATION_OUTPUT="${VALIDATION_DIR}/validation_output.txt"
683
684# Check if authentication credentials are provided
685AUTH_ARGS=""
686if [ -n "$APPLE_ID" ] && [ -n "$APPLE_PASSWORD" ]; then
687 echo "Using Apple ID authentication for validation..."
688 AUTH_ARGS="--username \"$APPLE_ID\" --password \"$APPLE_PASSWORD\""
689else
690 echo "No authentication credentials provided. Will perform basic validation."
691 echo "To use your personal developer account, you can run the script with:"
692 echo " APPLE_ID='your.email@example.com' APPLE_PASSWORD='your-app-specific-password' ./validate-tvos.sh"
693 echo "Note: You need to create an app-specific password at https://appleid.apple.com/account/manage"
694fi
695
696# Run validation with detailed output
697echo "Running validation with altool..."
698if [ -n "$AUTH_ARGS" ]; then
699 # Use eval to properly handle the quoted arguments
700 eval "xcrun altool --validate-app -f \"${IPA_PATH}\" --type tvos --output-format xml $AUTH_ARGS" 2>&1 | tee "${VALIDATION_OUTPUT}"
701else
702 xcrun altool --validate-app -f "${IPA_PATH}" --type tvos --output-format xml 2>&1 | tee "${VALIDATION_OUTPUT}"
703fi
704VALIDATION_RESULT=$?
705
706# Final validation result
707FINAL_VALIDATION_RESULT=0
708
709# Check if validation failed because the app isn't in App Store Connect
710if grep -q "No suitable application records were found" "${VALIDATION_OUTPUT}"; then
711 echo "⚠️ App Store Connect Warning: The app bundle identifier is not found in App Store Connect"
712 echo "This is expected for apps that haven't been registered in App Store Connect yet."
713 echo "This doesn't indicate a problem with the build or framework."
714
715 # Perform alternative validation
716 echo "Performing alternative validation checks..."
717
718 # Check if IPA was created successfully
719 if [ -f "${IPA_PATH}" ] && [ -s "${IPA_PATH}" ]; then
720 echo "✅ IPA file created successfully"
721 else
722 echo "❌ IPA file not created or empty"
723 FINAL_VALIDATION_RESULT=1
724 fi
725
726 # Check if app binary exists and is executable
727 if [ -f "${TEMP_DIR}/Payload/${APP_NAME}.app/${APP_NAME}" ] && [ -x "${TEMP_DIR}/Payload/${APP_NAME}.app/${APP_NAME}" ]; then
728 echo "✅ App binary exists and is executable"
729 else
730 echo "❌ App binary missing or not executable"
731 FINAL_VALIDATION_RESULT=1
732 fi
733
734 # Check if framework was properly embedded
735 if [ -d "${TEMP_DIR}/Payload/${APP_NAME}.app/Frameworks/llama.framework" ]; then
736 echo "✅ llama.framework properly embedded"
737 else
738 echo "❌ llama.framework not properly embedded"
739 FINAL_VALIDATION_RESULT=1
740 fi
741
742 # Check if framework binary exists
743 if [ -f "${TEMP_DIR}/Payload/${APP_NAME}.app/Frameworks/llama.framework/llama" ]; then
744 echo "✅ Framework binary exists"
745
746 # Further validate framework by checking architecture
747 ARCHS=$(lipo -info "${TEMP_DIR}/Payload/${APP_NAME}.app/Frameworks/llama.framework/llama" 2>/dev/null | grep -o "arm64\\|x86_64" | tr '\n' ' ')
748 if [ -n "$ARCHS" ]; then
749 echo "✅ Framework architecture(s): $ARCHS"
750 else
751 echo "⚠️ Could not determine framework architecture"
752 fi
753 else
754 echo "❌ Framework binary missing"
755 FINAL_VALIDATION_RESULT=1
756 fi
757
758 if [ $FINAL_VALIDATION_RESULT -eq 0 ]; then
759 echo "✅ Alternative validation PASSED: App built successfully with embedded framework"
760 else
761 echo "❌ Alternative validation FAILED: Issues found with the app or framework"
762 fi
763elif grep -q "You must specify authentication credentials" "${VALIDATION_OUTPUT}" && [ -z "$AUTH_ARGS" ]; then
764 echo "✅ tvOS Validation PASSED: IPA successfully validated"
765 echo "Results saved to ${VALIDATION_OUTPUT}"
766else
767 echo "❌ tvOS Validation FAILED: IPA validation found issues"
768 echo "See validation output at ${VALIDATION_OUTPUT}"
769 echo ""
770 echo "==== VALIDATION ERRORS ===="
771
772 # Try to extract specific errors from the output
773 if grep -q "Error" "${VALIDATION_OUTPUT}"; then
774 grep -A 5 "Error" "${VALIDATION_OUTPUT}"
775 else
776 # If no specific error found, show the whole log
777 cat "${VALIDATION_OUTPUT}"
778 fi
779
780 # Additional debugging: check IPA contents
781 echo ""
782 echo "==== IPA CONTENTS ===="
783 mkdir -p "${TEMP_DIR}/ipa_contents"
784 unzip -q "${IPA_PATH}" -d "${TEMP_DIR}/ipa_contents"
785 ls -la "${TEMP_DIR}/ipa_contents/Payload/${APP_NAME}.app/"
786
787 # Check for code signing issues
788 echo ""
789 echo "==== CODE SIGNING INFO ===="
790 codesign -vv -d "${TEMP_DIR}/ipa_contents/Payload/${APP_NAME}.app" 2>&1 || echo "Code signing verification failed"
791
792 # Check embedded frameworks
793 echo ""
794 echo "==== FRAMEWORK INFO ===="
795 ls -la "${TEMP_DIR}/ipa_contents/Payload/${APP_NAME}.app/Frameworks/" 2>/dev/null || echo "No Frameworks directory found"
796fi
797
798# Don't clean up on error to allow inspection
799if [ $FINAL_VALIDATION_RESULT -ne 0 ]; then
800 echo ""
801 echo "Temporary files kept for inspection at: ${TEMP_DIR}"
802 echo "===== tvOS Validation Process Failed ====="
803 exit 1
804fi
805
806# Clean up temporary files but keep build artifacts
807if [ $FINAL_VALIDATION_RESULT -eq 0 ]; then
808 echo "Cleaning up temporary files..."
809 #rm -rf "${TEMP_DIR}"
810fi
811
812echo "===== tvOS Validation Process Completed ====="
813exit $FINAL_VALIDATION_RESULT