diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4f63f35..f8f7130 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,13 +9,14 @@ permissions: jobs: build: runs-on: ${{ matrix.os }} - name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'isim' && matrix.name != 'ios' && ' + test' || ''}} + container: ${{ matrix.container && matrix.container || '' }} + name: ${{ matrix.name }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} build${{ matrix.arch != 'arm64-v8a' && matrix.name != 'ios-sim' && matrix.name != 'ios' && matrix.name != 'apple-xcframework' && ' + test' || ''}} timeout-minutes: 120 strategy: fail-fast: false matrix: include: - - os: macos-latest + - os: macos-15 name: macos make: LLAMA="-DGGML_NATIVE=OFF -DGGML_METAL=ON -DGGML_ACCELERATE=ON -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=Apple" WHISPER="-DWHISPER_COREML=ON -DWHISPER_COREML_ALLOW_FALLBACK=ON" - os: ubuntu-22.04 @@ -26,14 +27,32 @@ jobs: arch: x86_64 name: linux-gpu make: LLAMA="-DGGML_NATIVE=OFF -DGGML_CPU=ON -DGGML_VULKAN=ON -DGGML_OPENCL=ON" - - os: LinuxARM64 + - os: ubuntu-22.04-arm arch: arm64 name: linux-cpu make: LLAMA="-DGGML_NATIVE=OFF -DGGML_CPU=ON -DGGML_CPU_ARM_ARCH=armv8.2-a" - - os: LinuxARM64 + - os: ubuntu-22.04-arm arch: arm64 name: linux-gpu make: LLAMA="-DGGML_NATIVE=OFF -DGGML_CPU=ON -DGGML_VULKAN=ON -DGGML_OPENCL=ON" + - os: ubuntu-22.04 + arch: x86_64 + name: linux-musl-cpu + container: alpine:latest + make: LLAMA="-DGGML_NATIVE=OFF -DGGML_CPU=ON -DGGML_AVX2=ON" + - os: ubuntu-22.04 + arch: x86_64 + name: linux-musl-gpu + container: alpine:latest + make: LLAMA="-DGGML_NATIVE=OFF -DGGML_CPU=ON -DGGML_OPENCL=ON" + - os: ubuntu-22.04-arm + arch: arm64 + name: linux-musl-cpu + make: LLAMA="-DGGML_NATIVE=OFF -DGGML_CPU=ON -DGGML_CPU_ARM_ARCH=armv8.2-a" + - os: ubuntu-22.04-arm + arch: arm64 + name: linux-musl-gpu + make: LLAMA="-DGGML_NATIVE=OFF -DGGML_CPU=ON -DGGML_OPENCL=ON" - os: windows-2022 arch: x86_64 name: windows-cpu @@ -42,28 +61,35 @@ jobs: arch: x86_64 name: windows-gpu make: LLAMA="-DGGML_NATIVE=OFF -DGGML_CPU=ON -DGGML_VULKAN=ON -DGGML_OPENCL=ON" - - os: ubuntu-latest + - os: ubuntu-22.04 arch: x86_64 name: android make: PLATFORM=android ARCH=x86_64 sqlite-amalgamation-zip: https://sqlite.org/2025/sqlite-amalgamation-3490100.zip - - os: ubuntu-latest + - os: ubuntu-22.04 arch: arm64-v8a name: android make: PLATFORM=android ARCH=arm64-v8a LLAMA="-DGGML_CPU_ARM_ARCH=armv8.2-a+dotprod" - - os: macos-latest + - os: macos-15 name: ios make: PLATFORM=ios LLAMA="-DGGML_NATIVE=OFF -DGGML_METAL=ON -DGGML_ACCELERATE=ON -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=Apple" WHISPER="-DWHISPER_COREML=ON -DWHISPER_COREML_ALLOW_FALLBACK=ON" - - os: macos-latest - name: isim - make: PLATFORM=isim LLAMA="-DGGML_NATIVE=OFF -DGGML_METAL=ON -DGGML_ACCELERATE=ON -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=Apple" WHISPER="-DWHISPER_COREML=ON -DWHISPER_COREML_ALLOW_FALLBACK=ON" + - os: macos-15 + name: ios-sim + make: PLATFORM=ios-sim LLAMA="-DGGML_NATIVE=OFF -DGGML_METAL=ON -DGGML_ACCELERATE=ON -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=Apple" WHISPER="-DWHISPER_COREML=ON -DWHISPER_COREML_ALLOW_FALLBACK=ON" + - os: macos-15 + name: apple-xcframework + make: xcframework LLAMA="-DGGML_NATIVE=OFF -DGGML_METAL=ON -DGGML_ACCELERATE=ON -DGGML_BLAS=ON -DGGML_BLAS_VENDOR=Apple" WHISPER="-DWHISPER_COREML=ON -DWHISPER_COREML_ALLOW_FALLBACK=ON" defaults: run: - shell: bash + shell: ${{ matrix.container && 'sh' || 'bash' }} steps: + - name: linux-musl x86_64 install dependencies + if: contains(matrix.name, 'linux-musl') && matrix.arch == 'x86_64' + run: apk update && apk add --no-cache git gcc g++ make cmake sqlite musl-dev linux-headers python3 + - uses: actions/checkout@v4.2.2 with: submodules: true @@ -116,10 +142,29 @@ jobs: ${{ matrix.name == 'windows-gpu' && 'mingw-w64-x86_64-opencl-headers' || '' }} ${{ matrix.name == 'windows-gpu' && 'mingw-w64-x86_64-opencl-icd' || '' }} + - name: macos install dependencies + if: matrix.name == 'macos' + run: brew link sqlite --force + + - name: linux-musl arm64 setup container + if: contains(matrix.name, 'linux-musl') && matrix.arch == 'arm64' + run: | + docker run -d --name alpine \ + --platform linux/arm64 \ + -v ${{ github.workspace }}:/workspace \ + -w /workspace \ + alpine:latest \ + tail -f /dev/null + docker exec alpine sh -c "apk update && apk add --no-cache gcc g++ make cmake sqlite musl-dev linux-headers python3" + - name: linux install opencl if: matrix.name == 'linux-gpu' run: sudo apt-get install -y opencl-headers ocl-icd-opencl-dev + - name: linux-musl install opencl + if: matrix.name == 'linux-musl-gpu' + run: ${{ matrix.arch == 'arm64' && 'docker exec alpine' || '' }} apk add --no-cache opencl-headers opencl-icd-loader-dev + - name: linux-x86_64 install vulkan if: matrix.name == 'linux-gpu' && matrix.arch == 'x86_64' run: | @@ -190,7 +235,7 @@ jobs: - name: unix build llama.cpp if: matrix.os != 'windows-2022' && steps.cache-llama.outputs.cache-hit != 'true' - run: make build/llama.cpp.stamp ${{ matrix.make && matrix.make || ''}} + run: ${{ contains(matrix.name, 'linux-musl') && matrix.arch == 'arm64' && 'docker exec alpine' || '' }} make build/llama.cpp.stamp ${{ matrix.make && matrix.make || ''}} - name: windows build whisper.cpp if: matrix.os == 'windows-2022' && steps.cache-whisper.outputs.cache-hit != 'true' @@ -201,7 +246,7 @@ jobs: - name: unix build whisper.cpp if: matrix.os != 'windows-2022' && steps.cache-whisper.outputs.cache-hit != 'true' - run: make build/whisper.cpp.stamp ${{ matrix.make && matrix.make || ''}} + run: ${{ contains(matrix.name, 'linux-musl') && matrix.arch == 'arm64' && 'docker exec alpine' || '' }} make build/whisper.cpp.stamp ${{ matrix.make && matrix.make || ''}} - name: windows build miniaudio if: matrix.os == 'windows-2022' && steps.cache-miniaudio.outputs.cache-hit != 'true' @@ -210,7 +255,7 @@ jobs: - name: unix build miniaudio if: matrix.os != 'windows-2022' && steps.cache-miniaudio.outputs.cache-hit != 'true' - run: make build/miniaudio.stamp ${{ matrix.make && matrix.make || ''}} + run: ${{ contains(matrix.name, 'linux-musl') && matrix.arch == 'arm64' && 'docker exec alpine' || '' }} make build/miniaudio.stamp ${{ matrix.make && matrix.make || ''}} - name: windows build sqlite-ai if: matrix.os == 'windows-2022' @@ -221,11 +266,36 @@ jobs: - name: unix build sqlite-ai if: matrix.os != 'windows-2022' - run: make extension ${{ matrix.make && matrix.make || ''}} + run: ${{ contains(matrix.name, 'linux-musl') && matrix.arch == 'arm64' && 'docker exec alpine' || '' }} make extension ${{ matrix.make && matrix.make || ''}} - - name: macos install sqlite3 without SQLITE_OMIT_LOAD_EXTENSION - if: matrix.name == 'macos' - run: brew link sqlite --force + - name: create keychain for codesign + if: matrix.os == 'macos-15' + run: | + echo "${{ secrets.APPLE_CERTIFICATE }}" | base64 --decode > certificate.p12 + security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain + security import certificate.p12 -k build.keychain -P "${{ secrets.CERTIFICATE_PASSWORD }}" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "${{ secrets.KEYCHAIN_PASSWORD }}" build.keychain + + - name: codesign dylib + if: matrix.os == 'macos-15' && matrix.name != 'apple-xcframework' + run: codesign --sign "${{ secrets.APPLE_TEAM_ID }}" --timestamp --options runtime dist/ai.dylib + + - name: codesign and notarize xcframework + if: matrix.name == 'apple-xcframework' + run: | + find dist/ai.xcframework -name "*.framework" -exec echo "Signing: {}" \; -exec codesign --sign "${{ secrets.APPLE_TEAM_ID }}" --timestamp --options runtime {} \; # Sign each individual framework FIRST + codesign --sign "${{ secrets.APPLE_TEAM_ID }}" --timestamp --options runtime dist/ai.xcframework # Then sign the xcframework wrapper + ditto -c -k --keepParent dist/ai.xcframework dist/ai.xcframework.zip + xcrun notarytool submit dist/ai.xcframework.zip --apple-id "${{ secrets.APPLE_ID }}" --password "${{ secrets.APPLE_PASSWORD }}" --team-id "${{ secrets.APPLE_TEAM_ID }}" --wait + rm dist/ai.xcframework.zip + + - name: cleanup keychain for codesign + if: matrix.os == 'macos-15' + run: | + rm certificate.p12 + security delete-keychain build.keychain - name: android setup test environment if: matrix.name == 'android' && matrix.arch != 'arm64-v8a' @@ -280,7 +350,7 @@ jobs: - name: unix test sqlite-ai if: contains(matrix.name, 'linux') || matrix.name == 'macos' - run: make test ${{ matrix.make && matrix.make || ''}} + run: ${{ contains(matrix.name, 'linux-musl') && matrix.arch == 'arm64' && 'docker exec alpine' || '' }} make test ${{ matrix.make && matrix.make || ''}} - uses: actions/upload-artifact@v4.6.2 if: always() @@ -290,7 +360,7 @@ jobs: if-no-files-found: error release: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 name: release needs: build if: github.ref == 'refs/heads/main' @@ -324,9 +394,10 @@ jobs: for folder in "artifacts"/*; do if [ -d "$folder" ]; then name=$(basename "$folder") - zip -jq "${name}-${{ steps.tag.outputs.version }}.zip" "$folder"/* - tar -cJf "${name}-${{ steps.tag.outputs.version }}.tar.xz" -C "$folder" . - tar -czf "${name}-${{ steps.tag.outputs.version }}.tar.gz" -C "$folder" . + if [[ "$name" != "ai-apple-xcframework" ]]; then + tar -czf "${name}-${{ steps.tag.outputs.version }}.tar.gz" -C "$folder" . + fi + (cd "$folder" && zip -rq "../../${name}-${{ steps.tag.outputs.version }}.zip" .) fi done @@ -337,6 +408,5 @@ jobs: tag_name: ${{ steps.tag.outputs.version }} files: | ai-*-${{ steps.tag.outputs.version }}.zip - ai-*-${{ steps.tag.outputs.version }}.tar.xz ai-*-${{ steps.tag.outputs.version }}.tar.gz make_latest: true diff --git a/Makefile b/Makefile index 11e22a6..962b835 100644 --- a/Makefile +++ b/Makefile @@ -79,23 +79,17 @@ else ifeq ($(PLATFORM),macos) MINIAUDIO_OPTIONS += -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" STRIP = strip -x -S $@ else ifeq ($(PLATFORM),android) - # Set ARCH to find Android NDK's Clang compiler, the user should set the ARCH - ifeq ($(filter %,$(ARCH)),) + ifndef ARCH # Set ARCH to find Android NDK's Clang compiler, the user should set the ARCH $(error "Android ARCH must be set to ARCH=x86_64 or ARCH=arm64-v8a") endif - # Set ANDROID_NDK path to find android build tools - # e.g. on MacOS: export ANDROID_NDK=/Users/username/Library/Android/sdk/ndk/25.2.9519653 - ifeq ($(filter %,$(ANDROID_NDK)),) + ifndef ANDROID_NDK # Set ANDROID_NDK path to find android build tools; e.g. on MacOS: export ANDROID_NDK=/Users/username/Library/Android/sdk/ndk/25.2.9519653 $(error "Android NDK must be set") endif - BIN = $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(HOST)-x86_64/bin PATH := $(BIN):$(PATH) - ifneq (,$(filter $(ARCH),arm64 arm64-v8a)) override ARCH := aarch64 endif - CC = $(BIN)/$(ARCH)-linux-android26-clang CXX = $(CC)++ TARGET := $(DIST_DIR)/ai.so @@ -116,7 +110,7 @@ else ifeq ($(PLATFORM),ios) WHISPER_OPTIONS += -DGGML_OPENMP=OFF -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DWHISPER_COREML=ON MINIAUDIO_OPTIONS += -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCMAKE_C_FLAGS="-x objective-c" STRIP = strip -x -S $@ -else ifeq ($(PLATFORM),isim) +else ifeq ($(PLATFORM),ios-sim) TARGET := $(DIST_DIR)/ai.dylib SDK := -isysroot $(shell xcrun --sdk iphonesimulator --show-sdk-path) -miphonesimulator-version-min=14.0 LLAMA_LIBS += $(BUILD_GGML)/lib/libggml-metal.a @@ -182,7 +176,7 @@ $(DEF_FILE): ifeq ($(PLATFORM),windows) @echo "LIBRARY ai.dll" > $@ @echo "EXPORTS" >> $@ - @echo " sqlite3_ai_init" >> $@ + @echo " sqlite3_ai_init" >> $@ endif # Make sure the build and dist directories exist @@ -243,6 +237,67 @@ version: clean: rm -rf $(BUILD_DIR)/* $(DIST_DIR)/* *.gcda *.gcno *.gcov *.sqlite +.NOTPARALLEL: %.dylib +%.dylib: + rm -rf $(BUILD_DIR) && $(MAKE) PLATFORM=$* + mv $(DIST_DIR)/ai.dylib $(DIST_DIR)/$@ + +define PLIST +\ +\ +\ +\ +CFBundleDevelopmentRegion\ +en\ +CFBundleExecutable\ +ai\ +CFBundleIdentifier\ +ai.sqlite.ai\ +CFBundleInfoDictionaryVersion\ +6.0\ +CFBundlePackageType\ +FMWK\ +CFBundleSignature\ +????\ +CFBundleVersion\ +$(shell make version)\ +CFBundleShortVersionString\ +$(shell make version)\ +MinimumOSVersion\ +11.0\ +\ + +endef + +define MODULEMAP +framework module ai {\ + umbrella header \"sqlite-ai.h\"\ + export *\ +} +endef + +LIB_NAMES = ios.dylib ios-sim.dylib macos.dylib +FMWK_NAMES = ios-arm64 ios-arm64_x86_64-simulator macos-arm64_x86_64 +$(DIST_DIR)/%.xcframework: $(LIB_NAMES) + @$(foreach i,1 2 3,\ + lib=$(word $(i),$(LIB_NAMES)); \ + fmwk=$(word $(i),$(FMWK_NAMES)); \ + mkdir -p $(DIST_DIR)/$$fmwk/ai.framework/Headers; \ + mkdir -p $(DIST_DIR)/$$fmwk/ai.framework/Modules; \ + cp src/sqlite-ai.h $(DIST_DIR)/$$fmwk/ai.framework/Headers; \ + printf "$(PLIST)" > $(DIST_DIR)/$$fmwk/ai.framework/Info.plist; \ + printf "$(MODULEMAP)" > $(DIST_DIR)/$$fmwk/ai.framework/Modules/module.modulemap; \ + mv $(DIST_DIR)/$$lib $(DIST_DIR)/$$fmwk/ai.framework/ai; \ + install_name_tool -id "@rpath/ai.framework/ai" $(DIST_DIR)/$$fmwk/ai.framework/ai; \ + ) + xcodebuild -create-xcframework $(foreach fmwk,$(FMWK_NAMES),-framework $(DIST_DIR)/$(fmwk)/ai.framework) -output $@ + rm -rf $(foreach fmwk,$(FMWK_NAMES),$(DIST_DIR)/$(fmwk)) + +xcframework: $(DIST_DIR)/ai.xcframework + +version: + @echo $(shell sed -n 's/^#define SQLITE_AI_VERSION[[:space:]]*"\([^"]*\)".*/\1/p' src/sqlite-ai.h) + # Help message help: @echo "SQLite AI Extension Makefile" @@ -255,12 +310,13 @@ help: @echo " windows (default on Windows)" @echo " android (needs ARCH to be set to x86_64 or arm64-v8a and ANDROID_NDK to be set)" @echo " ios (only on macOS)" - @echo " isim (only on macOS)" + @echo " ios-sim (only on macOS)" @echo "" @echo "Targets:" - @echo " all - Build the extension (default)" - @echo " clean - Remove built files" - @echo " test - Test the extension" - @echo " help - Display this help message" + @echo " all - Build the extension (default)" + @echo " clean - Remove built files" + @echo " test - Test the extension" + @echo " help - Display this help message" + @echo " xcframework - Build the Apple XCFramework" -.PHONY: all clean test extension help \ No newline at end of file +.PHONY: all clean test extension help version xcframework \ No newline at end of file diff --git a/src/sqlite-ai.h b/src/sqlite-ai.h index 43c5bdb..d88f6ea 100644 --- a/src/sqlite-ai.h +++ b/src/sqlite-ai.h @@ -24,7 +24,7 @@ extern "C" { #endif -#define SQLITE_AI_VERSION "0.6.0" +#define SQLITE_AI_VERSION "0.6.1" SQLITE_AI_API int sqlite3_ai_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi);