#!/bin/sh # Note: # While testing, if you double-click on the Ollama.app # some state is left on MacOS and subsequent attempts # to build again will fail with: # # hdiutil: create failed - Operation not permitted # # To work around, specify another volume name with: # # VOL_NAME="$(date)" ./scripts/build_darwin.sh # VOL_NAME=${VOL_NAME:-"Ollama"} export VERSION=${VERSION:-$(git describe --tags --first-parent --abbrev=7 --long --dirty --always | sed -e "s/^v//g")} export GOFLAGS="'-ldflags=-w -s \"-X=github.com/ollama/ollama/version.Version=${VERSION#v}\" \"-X=github.com/ollama/ollama/server.mode=release\"'" export CGO_CFLAGS="-O3 -mmacosx-version-min=14.0" export CGO_CXXFLAGS="-O3 -mmacosx-version-min=14.0" export CGO_LDFLAGS="-mmacosx-version-min=14.0" set -e status() { echo >&2 ">>> $@"; } usage() { echo "usage: $(basename $0) [build app [sign]]" exit 1 } mkdir -p dist ARCHS="arm64 amd64" while getopts "a:h" OPTION; do case $OPTION in a) ARCHS=$OPTARG ;; h) usage ;; esac done shift $(( $OPTIND - 1 )) _build_darwin() { for ARCH in $ARCHS; do status "Building darwin $ARCH" INSTALL_PREFIX=dist/darwin-$ARCH/ if [ "$ARCH" = "amd64" ]; then status "Building darwin $ARCH dynamic backends" BUILD_DIR=build/darwin-$ARCH cmake -B $BUILD_DIR \ -DCMAKE_OSX_ARCHITECTURES=x86_64 \ -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 \ -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \ -DMLX_ENGINE=ON \ -DMLX_ENABLE_X64_MAC=ON \ -DOLLAMA_RUNNER_DIR=./ cmake --build $BUILD_DIR --target ggml-cpu -j cmake --build $BUILD_DIR --target mlx mlxc -j cmake --install $BUILD_DIR --component CPU cmake --install $BUILD_DIR --component MLX # Override CGO flags to point to the amd64 build directory MLX_CGO_CFLAGS="-O3 -mmacosx-version-min=14.0" MLX_CGO_LDFLAGS="-ldl -lc++ -framework Accelerate -mmacosx-version-min=14.0" else # CPU backend (ggml-cpu, installed flat to lib/ollama/) BUILD_DIR_CPU=build/arm64-cpu status "Building arm64 CPU backend" cmake -S . -B $BUILD_DIR_CPU \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 \ -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX cmake --build $BUILD_DIR_CPU --target ggml-cpu --parallel cmake --install $BUILD_DIR_CPU --component CPU # Build MLX twice for arm64 # Metal 3.x build (backward compatible, macOS 14+) BUILD_DIR=build/metal-v3 status "Building MLX Metal v3 (macOS 14+)" cmake -S . -B $BUILD_DIR \ -DCMAKE_BUILD_TYPE=Release \ -DMLX_ENGINE=ON \ -DOLLAMA_RUNNER_DIR=mlx_metal_v3 \ -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 \ -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX cmake --build $BUILD_DIR --target mlx mlxc --parallel cmake --install $BUILD_DIR --component MLX # Metal 4.x build (NAX-enabled, macOS 26+) # Only possible with Xcode 26+ SDK; skip on older toolchains. SDK_MAJOR=$(xcrun --show-sdk-version 2>/dev/null | cut -d. -f1) if [ "${SDK_MAJOR:-0}" -ge 26 ]; then V3_DEPS=$BUILD_DIR/_deps BUILD_DIR_V4=build/metal-v4 status "Building MLX Metal v4 (macOS 26+, NAX)" cmake -S . -B $BUILD_DIR_V4 \ -DCMAKE_BUILD_TYPE=Release \ -DMLX_ENGINE=ON \ -DOLLAMA_RUNNER_DIR=mlx_metal_v4 \ -DCMAKE_OSX_DEPLOYMENT_TARGET=26.0 \ -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \ -DFETCHCONTENT_SOURCE_DIR_MLX=$V3_DEPS/mlx-src \ -DFETCHCONTENT_SOURCE_DIR_MLX-C=$V3_DEPS/mlx-c-src \ -DFETCHCONTENT_SOURCE_DIR_JSON=$V3_DEPS/json-src \ -DFETCHCONTENT_SOURCE_DIR_FMT=$V3_DEPS/fmt-src \ -DFETCHCONTENT_SOURCE_DIR_METAL_CPP=$V3_DEPS/metal_cpp-src cmake --build $BUILD_DIR_V4 --target mlx mlxc --parallel cmake --install $BUILD_DIR_V4 --component MLX else status "Skipping MLX Metal v4 (SDK $SDK_MAJOR < 26, need Xcode 26+)" fi # Use the v3 build for CGO linking (compatible with both) MLX_CGO_CFLAGS="-O3 -mmacosx-version-min=14.0" MLX_CGO_LDFLAGS="-lc++ -framework Metal -framework Foundation -framework Accelerate -mmacosx-version-min=14.0" fi GOOS=darwin GOARCH=$ARCH CGO_ENABLED=1 CGO_CFLAGS="$MLX_CGO_CFLAGS" CGO_LDFLAGS="$MLX_CGO_LDFLAGS" go build -o $INSTALL_PREFIX . # MLX libraries stay in lib/ollama/ (flat or variant subdirs). # The runtime discovery in dynamic.go searches lib/ollama/ relative # to the executable, including mlx_* subdirectories. done } _sign_darwin() { status "Creating universal binary..." mkdir -p dist/darwin lipo -create -output dist/darwin/ollama dist/darwin-*/ollama chmod +x dist/darwin/ollama if [ -n "$APPLE_IDENTITY" ]; then for F in dist/darwin/ollama dist/darwin-*/lib/ollama/* dist/darwin-*/lib/ollama/mlx_metal_v*/*; do [ -f "$F" ] && [ ! -L "$F" ] || continue codesign -f --timestamp -s "$APPLE_IDENTITY" --identifier ai.ollama.ollama --options=runtime "$F" done # create a temporary zip for notarization TEMP=$(mktemp -u).zip ditto -c -k --keepParent dist/darwin/ollama "$TEMP" xcrun notarytool submit "$TEMP" --wait --timeout 20m --apple-id $APPLE_ID --password $APPLE_PASSWORD --team-id $APPLE_TEAM_ID rm -f "$TEMP" fi status "Creating universal tarball..." tar -cf dist/ollama-darwin.tar --strip-components 2 dist/darwin/ollama tar -rf dist/ollama-darwin.tar --strip-components 4 dist/darwin-amd64/lib/ tar -rf dist/ollama-darwin.tar --strip-components 4 dist/darwin-arm64/lib/ gzip -9vc dist/ollama-darwin.tgz } _build_macapp() { if ! command -v npm &> /dev/null; then echo "npm is not installed. Please install Node.js and npm first:" echo " Visit: https://nodejs.org/" exit 1 fi if ! command -v tsc &> /dev/null; then echo "Installing TypeScript compiler..." npm install -g typescript fi echo "Installing required Go tools..." cd app/ui/app npm install npm run build cd ../../.. # Build the Ollama.app bundle rm -rf dist/Ollama.app cp -a ./app/darwin/Ollama.app dist/Ollama.app # update the modified date of the app bundle to now touch dist/Ollama.app go clean -cache GOARCH=amd64 CGO_ENABLED=1 GOOS=darwin go build -o dist/darwin-app-amd64 -ldflags="-s -w -X=github.com/ollama/ollama/app/version.Version=${VERSION}" ./app/cmd/app GOARCH=arm64 CGO_ENABLED=1 GOOS=darwin go build -o dist/darwin-app-arm64 -ldflags="-s -w -X=github.com/ollama/ollama/app/version.Version=${VERSION}" ./app/cmd/app mkdir -p dist/Ollama.app/Contents/MacOS lipo -create -output dist/Ollama.app/Contents/MacOS/Ollama dist/darwin-app-amd64 dist/darwin-app-arm64 rm -f dist/darwin-app-amd64 dist/darwin-app-arm64 # Create a mock Squirrel.framework bundle mkdir -p dist/Ollama.app/Contents/Frameworks/Squirrel.framework/Versions/A/Resources/ cp -a dist/Ollama.app/Contents/MacOS/Ollama dist/Ollama.app/Contents/Frameworks/Squirrel.framework/Versions/A/Squirrel ln -s ../Squirrel dist/Ollama.app/Contents/Frameworks/Squirrel.framework/Versions/A/Resources/ShipIt cp -a ./app/cmd/squirrel/Info.plist dist/Ollama.app/Contents/Frameworks/Squirrel.framework/Versions/A/Resources/Info.plist ln -s A dist/Ollama.app/Contents/Frameworks/Squirrel.framework/Versions/Current ln -s Versions/Current/Resources dist/Ollama.app/Contents/Frameworks/Squirrel.framework/Resources ln -s Versions/Current/Squirrel dist/Ollama.app/Contents/Frameworks/Squirrel.framework/Squirrel # Update the version in the Info.plist plutil -replace CFBundleShortVersionString -string "$VERSION" dist/Ollama.app/Contents/Info.plist plutil -replace CFBundleVersion -string "$VERSION" dist/Ollama.app/Contents/Info.plist # Setup the ollama binaries mkdir -p dist/Ollama.app/Contents/Resources if [ -d dist/darwin-amd64 ]; then lipo -create -output dist/Ollama.app/Contents/Resources/ollama dist/darwin-amd64/ollama dist/darwin-arm64/ollama # Copy .so files from both architectures (names don't collide: arm64=libggml-cpu.so, amd64=libggml-cpu-*.so) cp dist/darwin-arm64/lib/ollama/*.so dist/Ollama.app/Contents/Resources/ 2>/dev/null || true cp dist/darwin-amd64/lib/ollama/*.so dist/Ollama.app/Contents/Resources/ 2>/dev/null || true # Lipo common dylibs into universal binaries, copy amd64-only ones as-is for F in dist/darwin-amd64/lib/ollama/*.dylib; do [ -f "$F" ] && [ ! -L "$F" ] || continue BASE=$(basename "$F") if [ -f "dist/darwin-arm64/lib/ollama/$BASE" ]; then lipo -create -output "dist/Ollama.app/Contents/Resources/$BASE" "$F" "dist/darwin-arm64/lib/ollama/$BASE" else cp "$F" dist/Ollama.app/Contents/Resources/ fi done # Recreate ggml-base symlinks (cd dist/Ollama.app/Contents/Resources && ln -sf libggml-base.0.0.0.dylib libggml-base.0.dylib && ln -sf libggml-base.0.dylib libggml-base.dylib) 2>/dev/null || true # MLX Metal variant subdirs from arm64 for VARIANT in dist/darwin-arm64/lib/ollama/mlx_metal_v*/; do [ -d "$VARIANT" ] || continue VNAME=$(basename "$VARIANT") DEST=dist/Ollama.app/Contents/Resources/$VNAME mkdir -p "$DEST" if [ "$VNAME" = "mlx_metal_v3" ]; then # v3: lipo amd64 flat + arm64 v3 into universal dylibs for LIB in libmlx.dylib libmlxc.dylib; do if [ -f "dist/darwin-amd64/lib/ollama/$LIB" ] && [ -f "$VARIANT$LIB" ]; then lipo -create -output "$DEST/$LIB" "dist/darwin-amd64/lib/ollama/$LIB" "$VARIANT$LIB" elif [ -f "$VARIANT$LIB" ]; then cp "$VARIANT$LIB" "$DEST/" fi done # Copy remaining files (metallib) from arm64 v3 for F in "$VARIANT"*; do case "$(basename "$F")" in *.dylib) continue ;; esac [ -f "$F" ] && [ ! -L "$F" ] || continue cp "$F" "$DEST/" done else # v4+: arm64-only, copy all non-symlink files for F in "$VARIANT"*; do [ -f "$F" ] && [ ! -L "$F" ] || continue cp "$F" "$DEST/" done fi done else cp -a dist/darwin/ollama dist/Ollama.app/Contents/Resources/ollama # arm64-only build: copy variant subdirs directly for VARIANT in dist/darwin-arm64/lib/ollama/mlx_metal_v*/; do [ -d "$VARIANT" ] || continue VNAME=$(basename "$VARIANT") mkdir -p dist/Ollama.app/Contents/Resources/$VNAME cp "$VARIANT"* dist/Ollama.app/Contents/Resources/$VNAME/ 2>/dev/null || true done # CPU backend libs (ggml-base, ggml-cpu) are flat in lib/ollama/ cp dist/darwin-arm64/lib/ollama/*.so dist/Ollama.app/Contents/Resources/ 2>/dev/null || true for F in dist/darwin-arm64/lib/ollama/*.dylib; do [ -f "$F" ] && [ ! -L "$F" ] || continue cp "$F" dist/Ollama.app/Contents/Resources/ done (cd dist/Ollama.app/Contents/Resources && ln -sf libggml-base.0.0.0.dylib libggml-base.0.dylib && ln -sf libggml-base.0.dylib libggml-base.dylib) 2>/dev/null || true fi chmod a+x dist/Ollama.app/Contents/Resources/ollama # Sign if [ -n "$APPLE_IDENTITY" ]; then codesign -f --timestamp -s "$APPLE_IDENTITY" --identifier ai.ollama.ollama --options=runtime dist/Ollama.app/Contents/Resources/ollama for lib in dist/Ollama.app/Contents/Resources/*.so dist/Ollama.app/Contents/Resources/*.dylib dist/Ollama.app/Contents/Resources/*.metallib dist/Ollama.app/Contents/Resources/mlx_metal_v*/*.dylib dist/Ollama.app/Contents/Resources/mlx_metal_v*/*.metallib dist/Ollama.app/Contents/Resources/mlx_metal_v*/*.so; do [ -f "$lib" ] || continue codesign -f --timestamp -s "$APPLE_IDENTITY" --identifier ai.ollama.ollama --options=runtime "$lib" done codesign -f --timestamp -s "$APPLE_IDENTITY" --identifier com.electron.ollama --deep --options=runtime dist/Ollama.app fi rm -f dist/Ollama-darwin.zip ditto -c -k --norsrc --keepParent dist/Ollama.app dist/Ollama-darwin.zip (cd dist/Ollama.app/Contents/Resources/; tar -cf - ollama *.so *.dylib *.metallib mlx_metal_v*/ 2>/dev/null) | gzip -9vc > dist/ollama-darwin.tgz # Notarize and Staple if [ -n "$APPLE_IDENTITY" ]; then $(xcrun -f notarytool) submit dist/Ollama-darwin.zip --wait --timeout 20m --apple-id "$APPLE_ID" --password "$APPLE_PASSWORD" --team-id "$APPLE_TEAM_ID" rm -f dist/Ollama-darwin.zip $(xcrun -f stapler) staple dist/Ollama.app ditto -c -k --norsrc --keepParent dist/Ollama.app dist/Ollama-darwin.zip rm -f dist/Ollama.dmg (cd dist && ../scripts/create-dmg.sh \ --volname "${VOL_NAME}" \ --volicon ../app/darwin/Ollama.app/Contents/Resources/icon.icns \ --background ../app/assets/background.png \ --window-pos 200 120 \ --window-size 800 400 \ --icon-size 128 \ --icon "Ollama.app" 200 190 \ --hide-extension "Ollama.app" \ --app-drop-link 600 190 \ --text-size 12 \ "Ollama.dmg" \ "Ollama.app" \ ; ) rm -f dist/rw*.dmg codesign -f --timestamp -s "$APPLE_IDENTITY" --identifier ai.ollama.ollama --options=runtime dist/Ollama.dmg $(xcrun -f notarytool) submit dist/Ollama.dmg --wait --timeout 20m --apple-id "$APPLE_ID" --password "$APPLE_PASSWORD" --team-id "$APPLE_TEAM_ID" $(xcrun -f stapler) staple dist/Ollama.dmg else echo "WARNING: Code signing disabled, this bundle will not work for upgrade testing" fi } if [ "$#" -eq 0 ]; then _build_darwin _sign_darwin _build_macapp exit 0 fi for CMD in "$@"; do case $CMD in build) _build_darwin ;; sign) _sign_darwin ;; app) _build_macapp ;; *) usage ;; esac done