char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Jhen-Jie Hongmetal : add metallib build support for xcframework (#28163)b96806d96

char/aliceai-t5-moe
24.3 KiB644 linesraw
1#!/usr/bin/env bash
2#
3# usage: ./build-xcframework.sh [BUILD ...] (default: all builds)
4# builds: ios-sim ios-device macos visionos visionos-sim tvos-sim tvos-device
5#
6# Options
7IOS_MIN_OS_VERSION=16.4
8MACOS_MIN_OS_VERSION=13.3
9VISIONOS_MIN_OS_VERSION=1.0
10TVOS_MIN_OS_VERSION=16.4
11
12BUILD_SHARED_LIBS=OFF
13LLAMA_BUILD_APP=OFF
14LLAMA_BUILD_COMMON=OFF
15LLAMA_BUILD_EXAMPLES=OFF
16LLAMA_BUILD_TOOLS=OFF
17LLAMA_BUILD_TESTS=OFF
18LLAMA_BUILD_SERVER=OFF
19LLAMA_BUILD_MTMD=ON
20GGML_METAL=ON
21GGML_METAL_EMBED_LIBRARY=${GGML_METAL_EMBED_LIBRARY:-ON}
22GGML_BLAS_DEFAULT=ON
23GGML_OPENMP=OFF
24
25# Max number of concurrent platform builds
26MAX_PARALLEL_BUILDS=1
27
28# Split the available cores between the concurrent builds (min 1)
29JOBS_PER_BUILD=$(( $(sysctl -n hw.logicalcpu) / MAX_PARALLEL_BUILDS ))
30if [[ "$JOBS_PER_BUILD" -lt 1 ]]; then
31    JOBS_PER_BUILD=1
32fi
33
34# echo "build_fn build_dir release_dir platform is_simulator min_os" for a build name
35build_spec() {
36    case "$1" in
37        ios-sim)      echo "build_ios_sim build-ios-sim Release-iphonesimulator ios true ${IOS_MIN_OS_VERSION}" ;;
38        ios-device)   echo "build_ios_device build-ios-device Release-iphoneos ios false ${IOS_MIN_OS_VERSION}" ;;
39        macos)        echo "build_macos build-macos Release macos false ${MACOS_MIN_OS_VERSION}" ;;
40        visionos)     echo "build_visionos build-visionos Release-xros visionos false ${VISIONOS_MIN_OS_VERSION}" ;;
41        visionos-sim) echo "build_visionos_sim build-visionos-sim Release-xrsimulator visionos true ${VISIONOS_MIN_OS_VERSION}" ;;
42        tvos-sim)     echo "build_tvos_sim build-tvos-sim Release-appletvsimulator tvos true ${TVOS_MIN_OS_VERSION}" ;;
43        tvos-device)  echo "build_tvos_device build-tvos-device Release-appletvos tvos false ${TVOS_MIN_OS_VERSION}" ;;
44        *)            return 1 ;;
45    esac
46}
47
48# Default: build everything
49if [[ $# -eq 0 ]]; then
50    BUILDS=(ios-sim ios-device macos visionos visionos-sim tvos-sim tvos-device)
51else
52    BUILDS=("$@")
53fi
54for b in "${BUILDS[@]}"; do
55    if ! build_spec "$b" >/dev/null; then
56        echo "Error: unknown build '$b'" >&2
57        echo "Valid builds: ios-sim ios-device macos visionos visionos-sim tvos-sim tvos-device" >&2
58        exit 1
59    fi
60done
61
62COMMON_C_FLAGS="-Wno-macro-redefined -Wno-shorten-64-to-32 -Wno-unused-command-line-argument -g"
63COMMON_CXX_FLAGS="-Wno-macro-redefined -Wno-shorten-64-to-32 -Wno-unused-command-line-argument -g"
64
65# Common options for all builds
66COMMON_CMAKE_ARGS=(
67    -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=NO
68    -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY=""
69    -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO
70    -DCMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT="dwarf-with-dsym"
71    -DCMAKE_XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS=YES
72    -DCMAKE_XCODE_ATTRIBUTE_COPY_PHASE_STRIP=NO
73    -DCMAKE_XCODE_ATTRIBUTE_STRIP_INSTALLED_PRODUCT=NO
74    -DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM=ggml
75    -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS}
76    -DLLAMA_BUILD_APP=${LLAMA_BUILD_APP}
77    -DLLAMA_BUILD_COMMON=${LLAMA_BUILD_COMMON}
78    -DLLAMA_BUILD_EXAMPLES=${LLAMA_BUILD_EXAMPLES}
79    -DLLAMA_BUILD_TOOLS=${LLAMA_BUILD_TOOLS}
80    -DLLAMA_BUILD_TESTS=${LLAMA_BUILD_TESTS}
81    -DLLAMA_BUILD_SERVER=${LLAMA_BUILD_SERVER}
82    -DLLAMA_BUILD_MTMD=${LLAMA_BUILD_MTMD}
83    -DGGML_METAL_EMBED_LIBRARY=${GGML_METAL_EMBED_LIBRARY}
84    -DGGML_BLAS_DEFAULT=${GGML_BLAS_DEFAULT}
85    -DGGML_METAL=${GGML_METAL}
86    -DGGML_NATIVE=OFF
87    -DGGML_OPENMP=${GGML_OPENMP}
88)
89
90check_required_tool() {
91    local tool=$1
92    local install_message=$2
93
94    if ! command -v $tool &> /dev/null; then
95        echo "Error: $tool is required but not found."
96        echo "$install_message"
97        exit 1
98    fi
99}
100echo "Checking for required tools..."
101check_required_tool "cmake" "Please install CMake 3.28.0 or later (brew install cmake)"
102check_required_tool "xcrun" "Please install Xcode and Xcode Command Line Tools (xcode-select --install)"
103
104XCODE_VERSION=$(xcrun xcodebuild -version 2>/dev/null | head -n1 | awk '{ print $2 }')
105MAJOR_VERSION=$(echo $XCODE_VERSION | cut -d. -f1)
106MINOR_VERSION=$(echo $XCODE_VERSION | cut -d. -f2)
107echo "Detected Xcode version: $XCODE_VERSION"
108
109set -e
110
111## Clean up previous builds
112rm -rf build-apple
113rm -rf build-ios-sim
114rm -rf build-ios-device
115rm -rf build-macos
116rm -rf build-visionos
117rm -rf build-visionos-sim
118rm -rf build-tvos-sim
119rm -rf build-tvos-device
120
121# Setup the xcframework build directory structure
122setup_framework_structure() {
123    local build_dir=$1
124    local min_os_version=$2
125    local platform=$3  # "ios", "macos", "visionos", or "tvos"
126    local framework_name="llama"
127
128    echo "Creating ${platform}-style framework structure for ${build_dir}"
129
130    if [[ "$platform" == "macos" ]]; then
131        # macOS versioned structure uses versioned directories
132        mkdir -p ${build_dir}/framework/${framework_name}.framework/Versions/A/Headers
133        mkdir -p ${build_dir}/framework/${framework_name}.framework/Versions/A/Modules
134        mkdir -p ${build_dir}/framework/${framework_name}.framework/Versions/A/Resources
135
136        # Create symbolic links
137        ln -sf A ${build_dir}/framework/${framework_name}.framework/Versions/Current
138        ln -sf Versions/Current/Headers ${build_dir}/framework/${framework_name}.framework/Headers
139        ln -sf Versions/Current/Modules ${build_dir}/framework/${framework_name}.framework/Modules
140        ln -sf Versions/Current/Resources ${build_dir}/framework/${framework_name}.framework/Resources
141        ln -sf Versions/Current/${framework_name} ${build_dir}/framework/${framework_name}.framework/${framework_name}
142
143        # Set header and module paths
144        local header_path=${build_dir}/framework/${framework_name}.framework/Versions/A/Headers/
145        local module_path=${build_dir}/framework/${framework_name}.framework/Versions/A/Modules/
146    else
147        # iOS/VisionOS/tvOS use a flat structure
148        mkdir -p ${build_dir}/framework/${framework_name}.framework/Headers
149        mkdir -p ${build_dir}/framework/${framework_name}.framework/Modules
150
151        # Remove any existing structure to ensure clean build
152        rm -rf ${build_dir}/framework/${framework_name}.framework/Versions
153
154        # Set header and module paths
155        local header_path=${build_dir}/framework/${framework_name}.framework/Headers/
156        local module_path=${build_dir}/framework/${framework_name}.framework/Modules/
157    fi
158
159    # Copy all required headers (common for all platforms)
160    cp include/llama.h             ${header_path}
161    cp ggml/include/ggml.h         ${header_path}
162    cp ggml/include/ggml-opt.h     ${header_path}
163    cp ggml/include/ggml-alloc.h   ${header_path}
164    cp ggml/include/ggml-backend.h ${header_path}
165    cp ggml/include/ggml-metal.h   ${header_path}
166    cp ggml/include/ggml-cpu.h     ${header_path}
167    cp ggml/include/ggml-blas.h    ${header_path}
168    cp ggml/include/gguf.h         ${header_path}
169    cp tools/mtmd/mtmd.h           ${header_path}
170    cp tools/mtmd/mtmd-helper.h    ${header_path}
171
172    if [[ "$GGML_METAL_EMBED_LIBRARY" == "OFF" ]]; then
173        if [[ "$platform" == "macos" ]]; then
174            cp ${build_dir}/bin/*.metallib ${build_dir}/framework/${framework_name}.framework/Versions/A/Resources/
175        else
176            cp ${build_dir}/bin/*.metallib ${build_dir}/framework/${framework_name}.framework/
177        fi
178    fi
179
180    # Create module map (common for all platforms)
181    cat > ${module_path}module.modulemap << EOF
182framework module llama {
183    umbrella "Headers"
184
185    link "c++"
186    link framework "Accelerate"
187    link framework "Metal"
188    link framework "Foundation"
189
190    export *
191}
192EOF
193
194    # Platform-specific settings for Info.plist
195    local platform_name=""
196    local sdk_name=""
197    local supported_platform=""
198
199    case "$platform" in
200        "ios")
201            platform_name="iphoneos"
202            sdk_name="iphoneos${min_os_version}"
203            supported_platform="iPhoneOS"
204            local plist_path="${build_dir}/framework/${framework_name}.framework/Info.plist"
205            local device_family='    <key>UIDeviceFamily</key>
206    <array>
207        <integer>1</integer>
208        <integer>2</integer>
209    </array>'
210            ;;
211        "macos")
212            platform_name="macosx"
213            sdk_name="macosx${min_os_version}"
214            supported_platform="MacOSX"
215            local plist_path="${build_dir}/framework/${framework_name}.framework/Versions/A/Resources/Info.plist"
216            local device_family=""
217            ;;
218        "visionos")
219            platform_name="xros"
220            sdk_name="xros${min_os_version}"
221            supported_platform="XRPlatform"
222            local plist_path="${build_dir}/framework/${framework_name}.framework/Info.plist"
223            local device_family=""
224            ;;
225        "tvos")
226            platform_name="appletvos"
227            sdk_name="appletvos${min_os_version}"
228            supported_platform="AppleTVOS"
229            local plist_path="${build_dir}/framework/${framework_name}.framework/Info.plist"
230            local device_family='    <key>UIDeviceFamily</key>
231    <array>
232        <integer>3</integer>
233    </array>'
234            ;;
235    esac
236
237    # Create Info.plist
238    cat > ${plist_path} << EOF
239<?xml version="1.0" encoding="UTF-8"?>
240<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
241<plist version="1.0">
242<dict>
243    <key>CFBundleDevelopmentRegion</key>
244    <string>en</string>
245    <key>CFBundleExecutable</key>
246    <string>llama</string>
247    <key>CFBundleIdentifier</key>
248    <string>org.ggml.llama</string>
249    <key>CFBundleInfoDictionaryVersion</key>
250    <string>6.0</string>
251    <key>CFBundleName</key>
252    <string>llama</string>
253    <key>CFBundlePackageType</key>
254    <string>FMWK</string>
255    <key>CFBundleShortVersionString</key>
256    <string>1.0</string>
257    <key>CFBundleVersion</key>
258    <string>1</string>
259    <key>MinimumOSVersion</key>
260    <string>${min_os_version}</string>
261    <key>CFBundleSupportedPlatforms</key>
262    <array>
263        <string>${supported_platform}</string>
264    </array>${device_family}
265    <key>DTPlatformName</key>
266    <string>${platform_name}</string>
267    <key>DTSDKName</key>
268    <string>${sdk_name}</string>
269</dict>
270</plist>
271EOF
272}
273
274# Create dynamic libraries from static libraries.
275combine_static_libraries() {
276    local build_dir="$1"
277    local release_dir="$2"
278    local platform="$3"  # "ios", "macos", "visionos", or "tvos"
279    local is_simulator="$4"
280    local base_dir="$(pwd)"
281    local framework_name="llama"
282
283    # Determine output path based on platform
284    local output_lib=""
285    if [[ "$platform" == "macos" ]]; then
286        # macOS uses versioned structure
287        output_lib="${build_dir}/framework/${framework_name}.framework/Versions/A/${framework_name}"
288    else
289        # iOS, visionOS, and tvOS use a directory flat structure
290        output_lib="${build_dir}/framework/${framework_name}.framework/${framework_name}"
291    fi
292
293    local libs=(
294        "${base_dir}/${build_dir}/src/${release_dir}/libllama.a"
295        "${base_dir}/${build_dir}/ggml/src/${release_dir}/libggml.a"
296        "${base_dir}/${build_dir}/ggml/src/${release_dir}/libggml-base.a"
297        "${base_dir}/${build_dir}/ggml/src/${release_dir}/libggml-cpu.a"
298        "${base_dir}/${build_dir}/ggml/src/ggml-metal/${release_dir}/libggml-metal.a"
299        "${base_dir}/${build_dir}/ggml/src/ggml-blas/${release_dir}/libggml-blas.a"
300        "${base_dir}/${build_dir}/tools/mtmd/${release_dir}/libmtmd.a"
301        "${base_dir}/${build_dir}/vendor/hash/${release_dir}/libvendor-hash.a"
302    )
303
304    # Create temporary directory for processing
305    local temp_dir="${base_dir}/${build_dir}/temp"
306    mkdir -p "${temp_dir}"
307
308    # Since we have multiple architectures libtool will find object files that do not
309    # match the target architecture. We suppress these warnings.
310    xcrun libtool -static -o "${temp_dir}/combined.a" "${libs[@]}" 2> /dev/null
311
312    # Determine SDK, architectures, and install_name based on platform and simulator flag.
313    local sdk=""
314    local archs=""
315    local min_version_flag=""
316    local install_name=""
317
318    case "$platform" in
319        "ios")
320            if [[ "$is_simulator" == "true" ]]; then
321                sdk="iphonesimulator"
322                archs="arm64 x86_64"
323                min_version_flag="-mios-simulator-version-min=${IOS_MIN_OS_VERSION}"
324            else
325                sdk="iphoneos"
326                archs="arm64"
327                min_version_flag="-mios-version-min=${IOS_MIN_OS_VERSION}"
328            fi
329            install_name="@rpath/llama.framework/llama"
330            ;;
331        "macos")
332            sdk="macosx"
333            archs="arm64 x86_64"
334            min_version_flag="-mmacosx-version-min=${MACOS_MIN_OS_VERSION}"
335            install_name="@rpath/llama.framework/Versions/Current/llama"
336            ;;
337        "visionos")
338            if [[ "$is_simulator" == "true" ]]; then
339                sdk="xrsimulator"
340                archs="arm64 x86_64"
341                min_version_flag="-mtargetos=xros${VISIONOS_MIN_OS_VERSION}-simulator"
342            else
343                sdk="xros"
344                archs="arm64"
345                min_version_flag="-mtargetos=xros${VISIONOS_MIN_OS_VERSION}"
346            fi
347            # Use flat structure for visionOS, same as iOS
348            install_name="@rpath/llama.framework/llama"
349            ;;
350        "tvos")
351            if [[ "$is_simulator" == "true" ]]; then
352                sdk="appletvsimulator"
353                archs="arm64 x86_64"
354                min_version_flag="-mtvos-simulator-version-min=${TVOS_MIN_OS_VERSION}"
355            else
356                sdk="appletvos"
357                archs="arm64"
358                min_version_flag="-mtvos-version-min=${TVOS_MIN_OS_VERSION}"
359            fi
360            install_name="@rpath/llama.framework/llama"
361            ;;
362    esac
363
364    # Build architecture flags
365    local arch_flags=""
366    for arch in $archs; do
367        arch_flags+=" -arch $arch"
368    done
369
370    # Create dynamic library
371    echo "Creating dynamic library for ${platform}."
372    xcrun -sdk $sdk clang++ -dynamiclib \
373        -isysroot $(xcrun --sdk $sdk --show-sdk-path) \
374        $arch_flags \
375        $min_version_flag \
376        -Wl,-force_load,"${temp_dir}/combined.a" \
377        -framework Foundation -framework Metal -framework Accelerate \
378        -install_name "$install_name" \
379        -o "${base_dir}/${output_lib}"
380
381    # Platform-specific post-processing for device builds
382    if [[ "$is_simulator" == "false" ]]; then
383        if xcrun -f vtool &>/dev/null; then
384            case "$platform" in
385                "ios")
386                    echo "Marking binary as a framework binary for iOS..."
387                    xcrun vtool -set-build-version ios ${IOS_MIN_OS_VERSION} ${IOS_MIN_OS_VERSION} -replace \
388                        -output "${base_dir}/${output_lib}" "${base_dir}/${output_lib}"
389                    ;;
390                "visionos")
391                    echo "Marking binary as a framework binary for visionOS..."
392                    if [[ "$MAJOR_VERSION" -gt 16 ]] || [[ "$MAJOR_VERSION" -eq 16 && "$MINOR_VERSION" -gt 2 ]]; then
393                        echo "Xcode version greater than 16.2, using visionOS."
394                        VISION_OS_BUILD_VERSION="visionos"
395                    else
396                        echo "Xcode version less than or equal to 16.2, using xros."
397                        VISION_OS_BUILD_VERSION="xros"
398                    fi
399                    xcrun vtool -set-build-version ${VISION_OS_BUILD_VERSION} ${VISIONOS_MIN_OS_VERSION} ${VISIONOS_MIN_OS_VERSION} -replace \
400                        -output "${base_dir}/${output_lib}" "${base_dir}/${output_lib}"
401                    ;;
402                "tvos")
403                    echo "Marking binary as a framework binary for tvOS..."
404                    xcrun vtool -set-build-version tvos ${TVOS_MIN_OS_VERSION} ${TVOS_MIN_OS_VERSION} -replace \
405                        -output "${base_dir}/${output_lib}" "${base_dir}/${output_lib}"
406                    ;;
407            esac
408        else
409            echo "Warning: vtool not found. Binary may not pass App Store validation."
410        fi
411    fi
412
413    echo "Creating properly formatted dSYM..."
414    # Create a separate directory for dSYMs for all platforms
415    mkdir -p "${base_dir}/${build_dir}/dSYMs"
416
417    # iOS and visionOS style dSYM (flat structure)
418    if [[ "$platform" == "ios" || "$platform" == "visionos" || "$platform" == "tvos" ]]; then
419        # Generate dSYM in the dSYMs directory
420        xcrun dsymutil "${base_dir}/${output_lib}" -o "${base_dir}/${build_dir}/dSYMs/llama.dSYM"
421
422        # Create a copy of the binary that will be stripped
423        cp "${base_dir}/${output_lib}" "${temp_dir}/binary_to_strip"
424
425        # Strip debug symbols from the copy
426        xcrun strip -S "${temp_dir}/binary_to_strip" -o "${temp_dir}/stripped_lib"
427
428        # Replace the original with the stripped version
429        mv "${temp_dir}/stripped_lib" "${base_dir}/${output_lib}"
430    else
431        # macOS style dSYM
432        # First strip debug info to a separate file
433        xcrun strip -S "${base_dir}/${output_lib}" -o "${temp_dir}/stripped_lib"
434
435        # Generate dSYM in the dSYMs directory
436        xcrun dsymutil "${base_dir}/${output_lib}" -o "${base_dir}/${build_dir}/dSYMs/llama.dSYM"
437
438        # Replace original binary with stripped version
439        mv "${temp_dir}/stripped_lib" "${base_dir}/${output_lib}"
440    fi
441
442    # Remove any automatically generated dSYM files in the framework structure as they will
443    # otherwise case Invalid Bundle Structure validation errors.
444    if [ -d "${base_dir}/${output_lib}.dSYM" ]; then
445        echo "Removing generated dSYM file in framework structure: ${base_dir}/${output_lib}.dSYM"
446        rm -rf "${base_dir}/${output_lib}.dSYM"
447    fi
448
449    # Clean up
450    rm -rf "${temp_dir}"
451}
452
453build_ios_sim() {
454    echo "Building for iOS simulator..."
455    cmake -B build-ios-sim -G Xcode \
456        "${COMMON_CMAKE_ARGS[@]}" \
457        -DCMAKE_OSX_DEPLOYMENT_TARGET=${IOS_MIN_OS_VERSION} \
458        -DIOS=ON \
459        -DCMAKE_SYSTEM_NAME=iOS \
460        -DCMAKE_OSX_SYSROOT=iphonesimulator \
461        -DGGML_METAL_TARGET_OS=ios \
462        -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
463        -DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=iphonesimulator \
464        -DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
465        -DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
466        -DLLAMA_OPENSSL=OFF \
467        -DMTMD_VIDEO=OFF \
468        -S .
469    cmake --build build-ios-sim --config Release -j "${JOBS_PER_BUILD}" -- -quiet
470}
471
472build_ios_device() {
473    echo "Building for iOS devices..."
474    cmake -B build-ios-device -G Xcode \
475        "${COMMON_CMAKE_ARGS[@]}" \
476        -DCMAKE_OSX_DEPLOYMENT_TARGET=${IOS_MIN_OS_VERSION} \
477        -DCMAKE_SYSTEM_NAME=iOS \
478        -DCMAKE_OSX_SYSROOT=iphoneos \
479        -DGGML_METAL_TARGET_OS=ios \
480        -DCMAKE_OSX_ARCHITECTURES="arm64" \
481        -DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=iphoneos \
482        -DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
483        -DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
484        -DLLAMA_OPENSSL=OFF \
485        -DMTMD_VIDEO=OFF \
486        -S .
487    cmake --build build-ios-device --config Release -j "${JOBS_PER_BUILD}" -- -quiet
488}
489
490build_macos() {
491    echo "Building for macOS..."
492    cmake -B build-macos -G Xcode \
493        "${COMMON_CMAKE_ARGS[@]}" \
494        -DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOS_MIN_OS_VERSION} \
495        -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
496        -DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
497        -DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
498        -DLLAMA_OPENSSL=OFF \
499        -S .
500    cmake --build build-macos --config Release -j "${JOBS_PER_BUILD}" -- -quiet
501}
502
503build_visionos() {
504    echo "Building for visionOS..."
505    cmake -B build-visionos -G Xcode \
506        "${COMMON_CMAKE_ARGS[@]}" \
507        -DCMAKE_OSX_DEPLOYMENT_TARGET=${VISIONOS_MIN_OS_VERSION} \
508        -DCMAKE_OSX_ARCHITECTURES="arm64" \
509        -DCMAKE_SYSTEM_NAME=visionOS \
510        -DCMAKE_OSX_SYSROOT=xros \
511        -DGGML_METAL_TARGET_OS=xros \
512        -DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=xros \
513        -DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
514        -DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
515        -DLLAMA_OPENSSL=OFF \
516        -DLLAMA_BUILD_SERVER=OFF \
517        -DMTMD_VIDEO=OFF \
518        -S .
519    cmake --build build-visionos --config Release -j "${JOBS_PER_BUILD}" -- -quiet
520}
521
522build_visionos_sim() {
523    echo "Building for visionOS simulator..."
524    cmake -B build-visionos-sim -G Xcode \
525        "${COMMON_CMAKE_ARGS[@]}" \
526        -DCMAKE_OSX_DEPLOYMENT_TARGET=${VISIONOS_MIN_OS_VERSION} \
527        -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
528        -DCMAKE_SYSTEM_NAME=visionOS \
529        -DCMAKE_OSX_SYSROOT=xrsimulator \
530        -DGGML_METAL_TARGET_OS=xros \
531        -DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=xrsimulator \
532        -DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
533        -DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
534        -DLLAMA_OPENSSL=OFF \
535        -DLLAMA_BUILD_SERVER=OFF \
536        -DMTMD_VIDEO=OFF \
537        -S .
538    cmake --build build-visionos-sim --config Release -j "${JOBS_PER_BUILD}" -- -quiet
539}
540
541# Add tvOS builds (might need the same u_int definitions as watchOS and visionOS)
542build_tvos_sim() {
543    echo "Building for tvOS simulator..."
544    cmake -B build-tvos-sim -G Xcode \
545        "${COMMON_CMAKE_ARGS[@]}" \
546        -DCMAKE_OSX_DEPLOYMENT_TARGET=${TVOS_MIN_OS_VERSION} \
547        -DCMAKE_SYSTEM_NAME=tvOS \
548        -DCMAKE_OSX_SYSROOT=appletvsimulator \
549        -DGGML_METAL_TARGET_OS=tvos \
550        -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
551        -DGGML_METAL=ON \
552        -DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=appletvsimulator \
553        -DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
554        -DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
555        -DLLAMA_OPENSSL=OFF \
556        -DMTMD_VIDEO=OFF \
557        -S .
558    cmake --build build-tvos-sim --config Release -j "${JOBS_PER_BUILD}" -- -quiet
559}
560
561build_tvos_device() {
562    echo "Building for tvOS devices..."
563    cmake -B build-tvos-device -G Xcode \
564        "${COMMON_CMAKE_ARGS[@]}" \
565        -DCMAKE_OSX_DEPLOYMENT_TARGET=${TVOS_MIN_OS_VERSION} \
566        -DCMAKE_SYSTEM_NAME=tvOS \
567        -DCMAKE_OSX_SYSROOT=appletvos \
568        -DGGML_METAL_TARGET_OS=tvos \
569        -DCMAKE_OSX_ARCHITECTURES="arm64" \
570        -DGGML_METAL=ON \
571        -DCMAKE_XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS=appletvos \
572        -DCMAKE_C_FLAGS="${COMMON_C_FLAGS}" \
573        -DCMAKE_CXX_FLAGS="${COMMON_CXX_FLAGS}" \
574        -DLLAMA_OPENSSL=OFF \
575        -DMTMD_VIDEO=OFF \
576        -S .
577    cmake --build build-tvos-device --config Release -j "${JOBS_PER_BUILD}" -- -quiet
578}
579
580run_builds_parallel() {
581    local -a pids=()
582    local -a names=()
583    local name i
584    for name in "$@"; do
585        # Wait for the oldest running build to free a slot
586        if [[ "${#pids[@]}" -ge "$MAX_PARALLEL_BUILDS" ]]; then
587            if ! wait "${pids[0]}"; then
588                echo "ERROR: build '${names[0]}' failed, log follows (${names[0]}.log):" >&2
589                kill "${pids[@]}" 2>/dev/null || true
590                cat "${names[0]}.log" >&2
591                exit 1
592            fi
593            pids=("${pids[@]:1}")
594            names=("${names[@]:1}")
595        fi
596        echo "Starting build: $name (log: ${name}.log, -j ${JOBS_PER_BUILD})"
597        "$name" > "${name}.log" 2>&1 &
598        pids+=("$!")
599        names+=("$name")
600    done
601    # Wait for the remaining builds
602    for i in "${!pids[@]}"; do
603        if ! wait "${pids[$i]}"; then
604            echo "ERROR: build '${names[$i]}' failed, log follows (${names[$i]}.log):" >&2
605            kill "${pids[@]}" 2>/dev/null || true
606            cat "${names[$i]}.log" >&2
607            exit 1
608        fi
609    done
610}
611
612BUILD_FNS=()
613for b in "${BUILDS[@]}"; do
614    read -r fn _ < <(build_spec "$b")
615    BUILD_FNS+=("$fn")
616done
617echo "Building: ${BUILDS[*]} (max ${MAX_PARALLEL_BUILDS} at a time, -j ${JOBS_PER_BUILD} each)..."
618run_builds_parallel "${BUILD_FNS[@]}"
619
620# Setup frameworks and copy binaries and headers
621echo "Setting up framework structures..."
622for b in "${BUILDS[@]}"; do
623    read -r _ bdir _ platform _ min_os < <(build_spec "$b")
624    setup_framework_structure "$bdir" "$min_os" "$platform"
625done
626
627# Create dynamic libraries from static libraries
628echo "Creating dynamic libraries from static libraries..."
629for b in "${BUILDS[@]}"; do
630    read -r _ bdir rdir platform is_sim _ < <(build_spec "$b")
631    combine_static_libraries "$bdir" "$rdir" "$platform" "$is_sim"
632done
633
634# Create XCFramework with correct debug symbols paths
635echo "Creating XCFramework..."
636XCFW_ARGS=()
637for b in "${BUILDS[@]}"; do
638    read -r _ bdir _ _ _ _ < <(build_spec "$b")
639    XCFW_ARGS+=(-framework "$(pwd)/${bdir}/framework/llama.framework")
640    XCFW_ARGS+=(-debug-symbols "$(pwd)/${bdir}/dSYMs/llama.dSYM")
641done
642xcrun xcodebuild -create-xcframework \
643    "${XCFW_ARGS[@]}" \
644    -output "$(pwd)/build-apple/llama.xcframework"