Skip to content

Commit 621fbcb

Browse files
committed
feat(android): add AAR package build, release and documentation
1 parent 8bc480d commit 621fbcb

File tree

12 files changed

+689
-37
lines changed

12 files changed

+689
-37
lines changed

.github/workflows/main.yml

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
build:
1111
runs-on: ${{ matrix.os }}
1212
container: ${{ matrix.container && matrix.container || '' }}
13-
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' || ''}}
13+
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' && matrix.name != 'android-aar' && ' + test' || ''}}
1414
timeout-minutes: 120
1515
strategy:
1616
fail-fast: false
@@ -69,7 +69,7 @@ jobs:
6969
- os: ubuntu-22.04
7070
arch: arm64-v8a
7171
name: android
72-
make: PLATFORM=android ARCH=arm64-v8a LLAMA="-DGGML_CPU_ARM_ARCH=armv8.2-a+dotprod"
72+
make: PLATFORM=android ARCH=arm64-v8a
7373
- os: macos-15
7474
name: ios
7575
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"
@@ -79,6 +79,9 @@ jobs:
7979
- os: macos-15
8080
name: apple-xcframework
8181
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"
82+
- os: ubuntu-22.04
83+
name: android-aar
84+
make: aar
8285

8386
defaults:
8487
run:
@@ -94,6 +97,13 @@ jobs:
9497
with:
9598
submodules: true
9699

100+
- name: android setup java
101+
if: matrix.name == 'android-aar'
102+
uses: actions/setup-java@v4
103+
with:
104+
distribution: 'temurin'
105+
java-version: '17'
106+
97107
- name: calculate cache version hashes for modules and dependencies
98108
id: submodule-hashes
99109
env:
@@ -377,12 +387,64 @@ jobs:
377387
with:
378388
path: artifacts
379389

390+
- name: zip artifacts
391+
run: |
392+
VERSION=$(make version)
393+
for folder in "artifacts"/*; do
394+
if [ -d "$folder" ]; then
395+
name=$(basename "$folder")
396+
if [[ "$name" != "ai-apple-xcframework" && "$name" != "ai-android-aar" ]]; then
397+
tar -czf "${name}-${VERSION}.tar.gz" -C "$folder" .
398+
fi
399+
if [[ "$name" != "ai-android-aar" ]]; then
400+
(cd "$folder" && zip -rq "../../${name}-${VERSION}.zip" .)
401+
else
402+
cp "$folder"/*.aar "${name}-${VERSION}.aar"
403+
fi
404+
fi
405+
done
406+
380407
- name: release tag version from sqlite-ai.h
381408
id: tag
382409
run: |
383410
VERSION=$(make version)
384411
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
385-
LATEST=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.name')
412+
LATEST_RELEASE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/releases/latest)
413+
LATEST=$(echo "$LATEST_RELEASE" | jq -r '.name')
414+
# Check artifact sizes against previous release
415+
if [ -n "$LATEST" ] && [ "$LATEST" != "null" ]; then
416+
echo "Checking artifact sizes against previous release: $LATEST"
417+
FAILED=0
418+
for artifact in ai-*-${VERSION}.*; do
419+
if [ ! -f "$artifact" ]; then
420+
continue
421+
fi
422+
# Get current artifact size
423+
NEW_SIZE=$(stat -c%s "$artifact" 2>/dev/null || stat -f%z "$artifact")
424+
# Get artifact name for previous release
425+
ARTIFACT_NAME=$(echo "$artifact" | sed "s/${VERSION}/${LATEST}/")
426+
# Get previous artifact size from GitHub API
427+
OLD_SIZE=$(echo "$LATEST_RELEASE" | jq -r ".assets[] | select(.name == \"$(basename "$ARTIFACT_NAME")\") | .size")
428+
if [ -z "$OLD_SIZE" ] || [ "$OLD_SIZE" = "null" ]; then
429+
echo "⚠️ Previous artifact not found: $(basename "$ARTIFACT_NAME"), skipping comparison"
430+
continue
431+
fi
432+
# Calculate percentage increase
433+
INCREASE=$(awk "BEGIN {printf \"%.2f\", (($NEW_SIZE - $OLD_SIZE) / $OLD_SIZE) * 100}")
434+
echo "📦 $artifact: $OLD_SIZE → $NEW_SIZE bytes (${INCREASE}% change)"
435+
# Check if increase is more than 5%
436+
if (( $(echo "$INCREASE > 5" | bc -l) )); then
437+
echo "❌ ERROR: $artifact size increased by ${INCREASE}% (limit: 5%)"
438+
FAILED=1
439+
fi
440+
done
441+
if [ $FAILED -eq 1 ]; then
442+
echo ""
443+
echo "❌ One or more artifacts exceeded the 5% size increase limit"
444+
exit 1
445+
fi
446+
echo "✅ All artifacts within 5% size increase limit"
447+
fi
386448
if [[ "$VERSION" != "$LATEST" || "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then
387449
echo "version=$VERSION" >> $GITHUB_OUTPUT
388450
else
@@ -393,24 +455,20 @@ jobs:
393455
echo "❌ SQLITE_AI_VERSION not found in sqlite-ai.h"
394456
exit 1
395457
396-
- name: zip artifacts
397-
run: |
398-
for folder in "artifacts"/*; do
399-
if [ -d "$folder" ]; then
400-
name=$(basename "$folder")
401-
if [[ "$name" != "ai-apple-xcframework" ]]; then
402-
tar -czf "${name}-${{ steps.tag.outputs.version }}.tar.gz" -C "$folder" .
403-
fi
404-
(cd "$folder" && zip -rq "../../${name}-${{ steps.tag.outputs.version }}.zip" .)
405-
fi
406-
done
458+
- uses: actions/setup-java@v4
459+
if: steps.tag.outputs.version != ''
460+
with:
461+
distribution: 'temurin'
462+
java-version: '17'
463+
464+
- name: release android aar to maven central
465+
if: steps.tag.outputs.version != ''
466+
run: make aar && cd packages/android && ./gradlew publishAggregationToCentralPortal -PSIGNING_KEY="${{ secrets.SIGNING_KEY }}" -PSIGNING_PASSWORD="${{ secrets.SIGNING_PASSWORD }}" -PSONATYPE_USERNAME="${{ secrets.MAVEN_CENTRAL_USERNAME }}" -PSONATYPE_PASSWORD="${{ secrets.MAVEN_CENTRAL_TOKEN }}" -PVERSION="${{ steps.tag.outputs.version }}"
407467

408468
- uses: softprops/[email protected]
409469
if: steps.tag.outputs.version != ''
410470
with:
411471
generate_release_notes: true
412472
tag_name: ${{ steps.tag.outputs.version }}
413-
files: |
414-
ai-*-${{ steps.tag.outputs.version }}.zip
415-
ai-*-${{ steps.tag.outputs.version }}.tar.gz
473+
files: ai-*-${{ steps.tag.outputs.version }}.*
416474
make_latest: true

.gitignore

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,30 @@
1-
.DS_Store
1+
# Build artifacts
2+
build/
3+
/dist
4+
.build
5+
*.a
6+
*.sqlite
27
/modules/libs
8+
9+
# iOS/macOS
310
*.xcworkspacedata
411
*.xcuserstate
512
*.xcbkptlist
613
*.plist
7-
/build
8-
/dist
9-
*.sqlite
10-
*.a
14+
15+
# Android
16+
.gradle/
17+
*.aar
18+
local.properties
19+
jniLibs/
20+
*.apk
21+
*.ap_
22+
*.dex
23+
24+
# IDE
1125
.vscode
12-
.build
26+
.idea/
27+
*.iml
28+
29+
# System
30+
.DS_Store

Makefile

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,12 @@ else ifeq ($(PLATFORM),android)
9494
CXX = $(CC)++
9595
TARGET := $(DIST_DIR)/ai.so
9696
LDFLAGS += -static-libstdc++ -shared
97-
ANDROID_OPTIONS = -DCMAKE_TOOLCHAIN_FILE=$(ANDROID_NDK)/build/cmake/android.toolchain.cmake -DANDROID_ABI=$(if $(filter aarch64,$(ARCH)),arm64-v8a,$(ARCH)) -DANDROID_PLATFORM=android-26 -DCMAKE_C_FLAGS="-march=$(if $(filter aarch64,$(ARCH)),armv8.7a,x86-64)" -DCMAKE_CXX_FLAGS="-march=$(if $(filter aarch64,$(ARCH)),armv8.7a,x86-64)" -DCMAKE_POSITION_INDEPENDENT_CODE=ON
98-
LLAMA_OPTIONS += $(ANDROID_OPTIONS) -DGGML_OPENMP=OFF -DGGML_LLAMAFILE=OFF
99-
WHISPER_OPTIONS += $(ANDROID_OPTIONS) -DGGML_OPENMP=OFF -DGGML_LLAMAFILE=OFF
97+
ANDROID_OPTIONS = -DCMAKE_TOOLCHAIN_FILE=$(ANDROID_NDK)/build/cmake/android.toolchain.cmake -DANDROID_ABI=$(if $(filter aarch64,$(ARCH)),arm64-v8a,$(ARCH)) -DANDROID_PLATFORM=android-26 -DCMAKE_C_FLAGS="-march=$(if $(filter aarch64,$(ARCH)),armv8.7a,x86-64)" -DCMAKE_CXX_FLAGS="-march=$(if $(filter aarch64,$(ARCH)),armv8.7a,x86-64)" -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DGGML_OPENMP=OFF -DGGML_LLAMAFILE=OFF
98+
ifneq (,$(filter $(ARCH),aarch64))
99+
ANDROID_OPTIONS += -DGGML_CPU_ARM_ARCH=armv8.2-a+dotprod
100+
endif
101+
LLAMA_OPTIONS += $(ANDROID_OPTIONS)
102+
WHISPER_OPTIONS += $(ANDROID_OPTIONS)
100103
MINIAUDIO_OPTIONS += $(ANDROID_OPTIONS)
101104
STRIP = $(BIN)/llvm-strip --strip-unneeded $@
102105
else ifeq ($(PLATFORM),ios)
@@ -303,6 +306,17 @@ $(DIST_DIR)/%.xcframework: $(LIB_NAMES)
303306

304307
xcframework: $(DIST_DIR)/ai.xcframework
305308

309+
AAR_ARM = packages/android/src/main/jniLibs/arm64-v8a/
310+
AAR_X86 = packages/android/src/main/jniLibs/x86_64/
311+
aar:
312+
mkdir -p $(AAR_ARM) $(AAR_X86)
313+
$(MAKE) clean && $(MAKE) PLATFORM=android ARCH=arm64-v8a
314+
mv $(DIST_DIR)/ai.so $(AAR_ARM)
315+
$(MAKE) clean && $(MAKE) PLATFORM=android ARCH=x86_64
316+
mv $(DIST_DIR)/ai.so $(AAR_X86)
317+
cd packages/android && ./gradlew clean assembleRelease
318+
cp packages/android/build/outputs/aar/android-release.aar $(DIST_DIR)/ai.aar
319+
306320
# Help message
307321
help:
308322
@echo "SQLite AI Extension Makefile"
@@ -323,5 +337,6 @@ help:
323337
@echo " test - Test the extension"
324338
@echo " help - Display this help message"
325339
@echo " xcframework - Build the Apple XCFramework"
340+
@echo " aar - Build the Android AAR package"
326341

327-
.PHONY: all clean test extension help version xcframework
342+
.PHONY: all clean test extension help version xcframework aar

README.md

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ Download the appropriate pre-built binary for your platform from the official [R
3030
- Android
3131
- iOS
3232

33+
### Loading the Extension
34+
35+
```sql
36+
-- In SQLite CLI
37+
.load ./ai
38+
39+
-- In SQL
40+
SELECT load_extension('./ai');
41+
```
42+
3343
### Swift Package
3444

3545
You can [add this repository as a package dependency to your Swift project](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app#Add-a-package-dependency). After adding the package, you'll need to set up SQLite with extension loading by following steps 4 and 5 of [this guide](https://github.com/sqliteai/sqlite-extensions-guide/blob/main/platforms/ios.md#4-set-up-sqlite-with-extension-loading).
@@ -53,6 +63,86 @@ log("ai_version(): \(String(cString: sqlite3_column_text(stmt, 0)))")
5363
sqlite3_close(db)
5464
```
5565

66+
### Android Package
67+
68+
You can [add this project as a dependency to your Android project](https://central.sonatype.com/artifact/ai.sqlite/ai).
69+
70+
**Groovy:**
71+
```gradle
72+
repositories {
73+
google()
74+
mavenCentral()
75+
maven { url 'https://jitpack.io' }
76+
}
77+
dependencies {
78+
// ...
79+
// Use requery's SQLite instead of Android's built-in SQLite to support loading custom extensions
80+
implementation 'com.github.requery:sqlite-android:3.49.0'
81+
// Both packages below are identical - use either one
82+
implementation 'ai.sqlite:ai:0.7.55' // Maven Central
83+
// implementation 'com.github.sqliteai:sqlite-ai:0.7.55' // JitPack (alternative)
84+
}
85+
```
86+
87+
**Kotlin:**
88+
```kotlin
89+
repositories {
90+
google()
91+
mavenCentral()
92+
maven(url = "https://jitpack.io")
93+
}
94+
95+
dependencies {
96+
// ...
97+
98+
// Use requery's SQLite instead of Android's built-in SQLite to support loading custom extensions
99+
implementation("com.github.requery:sqlite-android:3.49.0")
100+
// Both packages below are identical - use either one
101+
implementation("ai.sqlite:ai:0.7.55") // Maven Central
102+
// implementation("com.github.sqliteai:sqlite-ai:0.7.55") // JitPack (alternative)
103+
}
104+
```
105+
106+
After adding the package, you'll need to [enable extractNativeLibs](https://github.com/sqliteai/sqlite-extensions-guide/blob/18acfc56d6af8791928f3ac8df7dc0e6a9741dd4/examples/android/src/main/AndroidManifest.xml#L6).
107+
108+
Here's an example of how to use the package:
109+
```java
110+
import android.database.Cursor;
111+
import android.util.Log;
112+
import io.requery.android.database.sqlite.SQLiteCustomExtension;
113+
import io.requery.android.database.sqlite.SQLiteDatabase;
114+
import io.requery.android.database.sqlite.SQLiteDatabaseConfiguration;
115+
import java.util.Collections;
116+
117+
...
118+
119+
private void aiExtension() {
120+
try {
121+
SQLiteCustomExtension aiExtension = new SQLiteCustomExtension(getApplicationInfo().nativeLibraryDir + "/ai", null);
122+
SQLiteDatabaseConfiguration config = new SQLiteDatabaseConfiguration(
123+
getCacheDir().getPath() + "/ai_test.db",
124+
SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.OPEN_READWRITE,
125+
Collections.emptyList(),
126+
Collections.emptyList(),
127+
Collections.singletonList(aiExtension)
128+
);
129+
130+
SQLiteDatabase db = SQLiteDatabase.openDatabase(config, null, null);
131+
132+
Cursor cursor = db.rawQuery("SELECT ai_version()", null);
133+
if (cursor.moveToFirst()) {
134+
String version = cursor.getString(0);
135+
Log.i("sqlite-ai", "ai_version(): " + version);
136+
}
137+
cursor.close();
138+
db.close();
139+
140+
} catch (Exception e) {
141+
Log.e("sqlite-ai", "Error: " + e.getMessage());
142+
}
143+
}
144+
```
145+
56146
### Python Package
57147

58148
Python developers can quickly get started using the ready-to-use `sqlite-ai` package available on PyPI:
@@ -63,16 +153,6 @@ pip install sqlite-ai
63153

64154
For usage details and examples, see the [Python package documentation](./packages/python/README.md).
65155

66-
### Loading the Extension
67-
68-
```sql
69-
-- In SQLite CLI
70-
.load ./ai
71-
72-
-- In SQL
73-
SELECT load_extension('./ai');
74-
```
75-
76156
## Getting Started
77157

78158
Here's a quick example to get started with SQLite Sync:

jitpack.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
jdk:
2+
- openjdk17
3+
install:
4+
- make aar ANDROID_NDK=$ANDROID_HOME/ndk-bundle
5+
- export VERSION=$(make version 2>/dev/null | tail -1)
6+
- cd packages/android && ./gradlew publishToMavenLocal -PVERSION="$VERSION"

0 commit comments

Comments
 (0)