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