Skip to content

Commit 17519c8

Browse files
committed
feat: add UniFFI bindings for iOS and Android
Add FFI bindings using UniFFI derives to enable using this library from iOS (Swift) and Android (Kotlin) applications. Changes: - Add uniffi feature flag and dependencies to Cargo.toml - Create uniffi_bindings.rs with FFI-compatible wrapper types - Add build scripts for iOS (XCFramework) and Android (JNI libs) - Add Makefile for build orchestration - Add GitHub Actions release workflow for mobile artifacts The FFI API exposes: - import_from_url: Import recipe from URL - convert_text_to_cooklang: Convert plain text to Cooklang - convert_image_to_cooklang: OCR image and convert to Cooklang - extract_recipe_from_url: Extract recipe without conversion - generate_frontmatter: Create Cooklang frontmatter - simple_import: Convenience function for basic imports - get_version: Get library version - is_provider_available: Check if LLM provider is configured
1 parent a2e31ac commit 17519c8

10 files changed

Lines changed: 2027 additions & 5 deletions

File tree

.github/workflows/release.yml

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
inputs:
9+
dry_run:
10+
description: 'Dry run (skip release creation)'
11+
required: false
12+
default: 'false'
13+
type: boolean
14+
15+
env:
16+
CARGO_TERM_COLOR: always
17+
18+
jobs:
19+
# Build iOS artifacts on macOS
20+
build-ios:
21+
name: Build iOS
22+
runs-on: macos-latest
23+
steps:
24+
- name: Checkout sources
25+
uses: actions/checkout@v4
26+
27+
- name: Install Rust toolchain
28+
uses: dtolnay/rust-toolchain@stable
29+
with:
30+
targets: aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios
31+
32+
- name: Cache Rust dependencies
33+
uses: actions/cache@v4
34+
with:
35+
path: |
36+
~/.cargo/bin/
37+
~/.cargo/registry/index/
38+
~/.cargo/registry/cache/
39+
~/.cargo/git/db/
40+
target/
41+
key: ${{ runner.os }}-ios-cargo-${{ hashFiles('**/Cargo.lock') }}
42+
restore-keys: ${{ runner.os }}-ios-cargo-
43+
44+
- name: Install uniffi-bindgen
45+
run: |
46+
if ! command -v uniffi-bindgen &> /dev/null; then
47+
cargo install uniffi-bindgen-cli --version 0.28
48+
fi
49+
50+
- name: Build iOS targets
51+
run: |
52+
cargo build --release --target aarch64-apple-ios --features uniffi
53+
cargo build --release --target aarch64-apple-ios-sim --features uniffi
54+
cargo build --release --target x86_64-apple-ios --features uniffi
55+
56+
- name: Generate Swift bindings
57+
run: |
58+
mkdir -p target/ios/swift
59+
uniffi-bindgen generate \
60+
--library target/aarch64-apple-ios/release/libcooklang_import.a \
61+
--language swift \
62+
--out-dir target/ios/swift
63+
64+
- name: Create XCFramework
65+
run: |
66+
mkdir -p target/ios/ios-device target/ios/ios-simulator
67+
68+
# Copy device library
69+
cp target/aarch64-apple-ios/release/libcooklang_import.a target/ios/ios-device/
70+
71+
# Create fat library for simulator
72+
lipo -create \
73+
target/aarch64-apple-ios-sim/release/libcooklang_import.a \
74+
target/x86_64-apple-ios/release/libcooklang_import.a \
75+
-output target/ios/ios-simulator/libcooklang_import.a
76+
77+
# Create headers
78+
mkdir -p target/ios/ios-device/Headers target/ios/ios-simulator/Headers
79+
cp target/ios/swift/cooklang_importFFI.h target/ios/ios-device/Headers/
80+
cp target/ios/swift/cooklang_importFFI.h target/ios/ios-simulator/Headers/
81+
82+
# Create module maps
83+
mkdir -p target/ios/ios-device/Modules target/ios/ios-simulator/Modules
84+
echo 'module CooklangImportFFI { header "cooklang_importFFI.h" export * }' > target/ios/ios-device/Modules/module.modulemap
85+
echo 'module CooklangImportFFI { header "cooklang_importFFI.h" export * }' > target/ios/ios-simulator/Modules/module.modulemap
86+
87+
# Create XCFramework
88+
xcodebuild -create-xcframework \
89+
-library target/ios/ios-device/libcooklang_import.a \
90+
-headers target/ios/ios-device/Headers \
91+
-library target/ios/ios-simulator/libcooklang_import.a \
92+
-headers target/ios/ios-simulator/Headers \
93+
-output target/ios/CooklangImportFFI.xcframework
94+
95+
- name: Create Swift Package
96+
run: |
97+
mkdir -p target/ios/CooklangImport/Sources/CooklangImport
98+
cp target/ios/swift/cooklang_import.swift target/ios/CooklangImport/Sources/CooklangImport/
99+
cp -R target/ios/CooklangImportFFI.xcframework target/ios/CooklangImport/
100+
101+
cat > target/ios/CooklangImport/Package.swift << 'EOF'
102+
// swift-tools-version:5.5
103+
import PackageDescription
104+
105+
let package = Package(
106+
name: "CooklangImport",
107+
platforms: [
108+
.iOS(.v13),
109+
.macOS(.v10_15)
110+
],
111+
products: [
112+
.library(
113+
name: "CooklangImport",
114+
targets: ["CooklangImport", "CooklangImportFFI"]
115+
),
116+
],
117+
targets: [
118+
.target(
119+
name: "CooklangImport",
120+
dependencies: ["CooklangImportFFI"]
121+
),
122+
.binaryTarget(
123+
name: "CooklangImportFFI",
124+
path: "CooklangImportFFI.xcframework"
125+
),
126+
]
127+
)
128+
EOF
129+
130+
- name: Package iOS artifacts
131+
run: |
132+
cd target/ios
133+
zip -r ../../cooklang-import-ios.zip CooklangImport CooklangImportFFI.xcframework swift
134+
135+
- name: Upload iOS artifacts
136+
uses: actions/upload-artifact@v4
137+
with:
138+
name: ios-artifacts
139+
path: cooklang-import-ios.zip
140+
141+
# Build Android artifacts on Linux
142+
build-android:
143+
name: Build Android
144+
runs-on: ubuntu-latest
145+
steps:
146+
- name: Checkout sources
147+
uses: actions/checkout@v4
148+
149+
- name: Install Rust toolchain
150+
uses: dtolnay/rust-toolchain@stable
151+
with:
152+
targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android,i686-linux-android
153+
154+
- name: Cache Rust dependencies
155+
uses: actions/cache@v4
156+
with:
157+
path: |
158+
~/.cargo/bin/
159+
~/.cargo/registry/index/
160+
~/.cargo/registry/cache/
161+
~/.cargo/git/db/
162+
target/
163+
key: ${{ runner.os }}-android-cargo-${{ hashFiles('**/Cargo.lock') }}
164+
restore-keys: ${{ runner.os }}-android-cargo-
165+
166+
- name: Setup Android NDK
167+
uses: android-actions/setup-android@v3
168+
169+
- name: Install NDK
170+
run: |
171+
sdkmanager --install "ndk;26.1.10909125"
172+
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125" >> $GITHUB_ENV
173+
174+
- name: Install cargo-ndk and uniffi-bindgen
175+
run: |
176+
cargo install cargo-ndk || true
177+
cargo install uniffi-bindgen-cli --version 0.28 || true
178+
179+
- name: Build Android targets
180+
run: |
181+
cargo ndk --target aarch64-linux-android --platform 21 build --release --features uniffi
182+
cargo ndk --target armv7-linux-androideabi --platform 21 build --release --features uniffi
183+
cargo ndk --target x86_64-linux-android --platform 21 build --release --features uniffi
184+
cargo ndk --target i686-linux-android --platform 21 build --release --features uniffi
185+
186+
- name: Generate Kotlin bindings
187+
run: |
188+
mkdir -p target/android/kotlin
189+
uniffi-bindgen generate \
190+
--library target/aarch64-linux-android/release/libcooklang_import.so \
191+
--language kotlin \
192+
--out-dir target/android/kotlin
193+
194+
- name: Organize JNI libraries
195+
run: |
196+
mkdir -p target/android/jniLibs/{arm64-v8a,armeabi-v7a,x86_64,x86}
197+
cp target/aarch64-linux-android/release/libcooklang_import.so target/android/jniLibs/arm64-v8a/
198+
cp target/armv7-linux-androideabi/release/libcooklang_import.so target/android/jniLibs/armeabi-v7a/
199+
cp target/x86_64-linux-android/release/libcooklang_import.so target/android/jniLibs/x86_64/
200+
cp target/i686-linux-android/release/libcooklang_import.so target/android/jniLibs/x86/
201+
202+
- name: Create Android library module
203+
run: |
204+
mkdir -p target/android/cooklang-import-android/src/main/kotlin
205+
mkdir -p target/android/cooklang-import-android/src/main/jniLibs
206+
207+
# Copy JNI libs
208+
cp -R target/android/jniLibs/* target/android/cooklang-import-android/src/main/jniLibs/
209+
210+
# Copy Kotlin bindings
211+
cp target/android/kotlin/*.kt target/android/cooklang-import-android/src/main/kotlin/
212+
213+
# Create build.gradle.kts
214+
cat > target/android/cooklang-import-android/build.gradle.kts << 'EOF'
215+
plugins {
216+
id("com.android.library")
217+
id("org.jetbrains.kotlin.android")
218+
}
219+
220+
android {
221+
namespace = "com.cooklang.import_lib"
222+
compileSdk = 34
223+
224+
defaultConfig {
225+
minSdk = 21
226+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
227+
consumerProguardFiles("consumer-rules.pro")
228+
}
229+
230+
buildTypes {
231+
release {
232+
isMinifyEnabled = false
233+
proguardFiles(
234+
getDefaultProguardFile("proguard-android-optimize.txt"),
235+
"proguard-rules.pro"
236+
)
237+
}
238+
}
239+
compileOptions {
240+
sourceCompatibility = JavaVersion.VERSION_1_8
241+
targetCompatibility = JavaVersion.VERSION_1_8
242+
}
243+
kotlinOptions {
244+
jvmTarget = "1.8"
245+
}
246+
sourceSets {
247+
getByName("main") {
248+
jniLibs.srcDirs("src/main/jniLibs")
249+
}
250+
}
251+
}
252+
253+
dependencies {
254+
implementation("net.java.dev.jna:jna:5.14.0@aar")
255+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
256+
}
257+
EOF
258+
259+
# Create AndroidManifest.xml
260+
mkdir -p target/android/cooklang-import-android/src/main
261+
cat > target/android/cooklang-import-android/src/main/AndroidManifest.xml << 'EOF'
262+
<?xml version="1.0" encoding="utf-8"?>
263+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
264+
<uses-permission android:name="android.permission.INTERNET" />
265+
</manifest>
266+
EOF
267+
268+
# Create proguard rules
269+
cat > target/android/cooklang-import-android/proguard-rules.pro << 'EOF'
270+
-keep class uniffi.** { *; }
271+
-keep class com.cooklang.** { *; }
272+
-keep class com.sun.jna.** { *; }
273+
-keepclassmembers class * extends com.sun.jna.** { public *; }
274+
EOF
275+
276+
cat > target/android/cooklang-import-android/consumer-rules.pro << 'EOF'
277+
-keep class uniffi.** { *; }
278+
-keep class com.cooklang.** { *; }
279+
EOF
280+
281+
- name: Package Android artifacts
282+
run: |
283+
cd target/android
284+
zip -r ../../cooklang-import-android.zip cooklang-import-android jniLibs kotlin
285+
286+
- name: Upload Android artifacts
287+
uses: actions/upload-artifact@v4
288+
with:
289+
name: android-artifacts
290+
path: cooklang-import-android.zip
291+
292+
# Create GitHub Release
293+
release:
294+
name: Create Release
295+
needs: [build-ios, build-android]
296+
runs-on: ubuntu-latest
297+
if: startsWith(github.ref, 'refs/tags/') && github.event.inputs.dry_run != 'true'
298+
permissions:
299+
contents: write
300+
steps:
301+
- name: Checkout sources
302+
uses: actions/checkout@v4
303+
304+
- name: Download iOS artifacts
305+
uses: actions/download-artifact@v4
306+
with:
307+
name: ios-artifacts
308+
309+
- name: Download Android artifacts
310+
uses: actions/download-artifact@v4
311+
with:
312+
name: android-artifacts
313+
314+
- name: Get version from tag
315+
id: version
316+
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
317+
318+
- name: Create Release
319+
uses: softprops/action-gh-release@v1
320+
with:
321+
name: Release ${{ steps.version.outputs.VERSION }}
322+
draft: false
323+
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
324+
generate_release_notes: true
325+
files: |
326+
cooklang-import-ios.zip
327+
cooklang-import-android.zip
328+
body: |
329+
## Mobile SDK Release ${{ steps.version.outputs.VERSION }}
330+
331+
### iOS
332+
Download `cooklang-import-ios.zip` which contains:
333+
- `CooklangImport/` - Swift Package ready to use
334+
- `CooklangImportFFI.xcframework` - XCFramework for manual integration
335+
- `swift/` - Swift bindings source files
336+
337+
### Android
338+
Download `cooklang-import-android.zip` which contains:
339+
- `cooklang-import-android/` - Android library module
340+
- `jniLibs/` - Native libraries for all architectures
341+
- `kotlin/` - Kotlin bindings source files
342+
343+
### Supported Architectures
344+
345+
**iOS:**
346+
- arm64 (devices)
347+
- arm64 (simulator, Apple Silicon)
348+
- x86_64 (simulator, Intel)
349+
350+
**Android:**
351+
- arm64-v8a
352+
- armeabi-v7a
353+
- x86_64
354+
- x86
355+
356+
See the README for integration instructions.

.github/workflows/test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ jobs:
2828
- name: Run cargo check
2929
run: cargo check
3030

31+
- name: Run cargo check (with uniffi)
32+
run: cargo check --features uniffi
33+
3134
test:
3235
name: Test Suite
3336
runs-on: ubuntu-latest
@@ -53,6 +56,9 @@ jobs:
5356
- name: Run cargo test
5457
run: cargo test
5558

59+
- name: Run cargo test (with uniffi)
60+
run: cargo test --features uniffi
61+
5662
lints:
5763
name: Lints
5864
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)