Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/workflows/mobile-sdk-artifacts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Mobile SDK Artifacts

on:
workflow_dispatch:
release:
types: [published]

jobs:
package-mobile-sdk:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Build and package mobile SDK artifact
run: ./scripts/package-mobile-sdk.sh
- name: Upload mobile SDK artifact
uses: actions/upload-artifact@v4
with:
name: mobile-sdk-${{ matrix.os }}
path: |
dist/mobile-sdk/*.tar.gz
dist/mobile-sdk/*.tar.gz.sha256

package-rust-ffi:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Build and package Rust FFI artifact
run: ./scripts/package-rust-ffi-dylib.sh
- name: Upload Rust FFI artifact
uses: actions/upload-artifact@v4
with:
name: rust-ffi-${{ matrix.os }}
path: |
dist/rust-ffi/*.tar.gz
dist/rust-ffi/*.tar.gz.sha256

publish-release-assets:
if: github.event_name == 'release'
needs: [package-mobile-sdk, package-rust-ffi]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download mobile SDK artifacts
uses: actions/download-artifact@v4
with:
pattern: mobile-sdk-*
merge-multiple: true
path: dist/mobile-sdk

- name: Download Rust FFI artifacts
uses: actions/download-artifact@v4
with:
pattern: rust-ffi-*
merge-multiple: true
path: dist/rust-ffi

- name: Publish artifacts to release
uses: softprops/action-gh-release@v2
with:
files: |
dist/mobile-sdk/*.tar.gz
dist/mobile-sdk/*.tar.gz.sha256
dist/rust-ffi/*.tar.gz
dist/rust-ffi/*.tar.gz.sha256
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,19 @@ cargo check --workspace
cargo test --workspace
```

### Mobile SDK Bindings (`sdk/`) — landed on `develop` (Slice 3)

UniFFI-generated iOS (Swift) and Android (Kotlin) bindings over `core/mobile-sdk`, plus sample consumers and packaging scripts. The `sdk/*/generated/**` files are machine-generated — **regenerate, never hand-edit** (`scripts/generate-mobile-sdk-bindings.sh`; output is byte-reproducible from the crate).

| File | Purpose |
|------|---------|
| `sdk/ios/generated/*` | UniFFI Swift bindings + FFI header/modulemap for iOS consumers. |
| `sdk/android/generated/.../skilly_core_mobile_sdk.kt` | UniFFI Kotlin bindings for Android consumers. |
| `sdk/{ios,android}/sample/*` | Sample apps showing policy gating + realtime replay against the bindings. |
| `scripts/generate-mobile-sdk-bindings.sh` | Builds the crate + regenerates Swift/Kotlin bindings into `sdk/`. |
| `scripts/package-mobile-sdk.sh` / `validate-mobile-sdk-consumers.sh` | Package and end-to-end validate generated SDK consumers. |
| `.github/workflows/mobile-sdk-artifacts.yml` | Release-triggered packaging/publishing of mobile SDK + FFI tarballs. |

### Skill Files

The repo ships 5 bundled skills under `skills/`, also copied into the app bundle under `Resources/skills/` so new users get them without downloading anything.
Expand Down
44 changes: 44 additions & 0 deletions docs/architecture/mobile-sdk-phase-validation-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Mobile SDK Phase Validation Report

Date: 2026-04-18
Branch: `feature/skills-bridge-swift`

## Scope
Validation for roadmap Phase 6 (Mobile SDK Surface) artifacts:
1. UniFFI-exported mobile Rust crate.
2. Workspace compilation and tests including mobile SDK crate.
3. Swift/Kotlin binding generation from compiled library.
4. Sample integration snippets aligned with generated API.
5. Runtime validation for generated Swift/Kotlin sample consumers.
6. Package/distribution automation for SDK artifacts.

## Commands Executed
```bash
cargo check --workspace
cargo test --workspace
./scripts/generate-mobile-sdk-bindings.sh
./scripts/validate-mobile-sdk-consumers.sh
./scripts/package-mobile-sdk.sh
```

## Results
- `cargo check --workspace`: pass
- `cargo test --workspace`: pass
- includes mobile SDK crate tests (`skilly-core-mobile-sdk`): 3 passed
- `./scripts/generate-mobile-sdk-bindings.sh`: pass
- generated `sdk/ios/generated/skilly_core_mobile_sdk.swift`
- generated `sdk/android/generated/uniffi/skilly_core_mobile_sdk/skilly_core_mobile_sdk.kt`
- `./scripts/validate-mobile-sdk-consumers.sh`: pass
- Kotlin/JVM sample runtime executed against generated Kotlin bindings
- Swift sample runtime executed against generated Swift bindings (macOS host)
- `./scripts/package-mobile-sdk.sh`: pass
- generated distributable artifact in `dist/mobile-sdk/` with checksum

## Key Outcomes
1. Phase 6 core requirement is implemented via `core/mobile-sdk` UniFFI API surface.
2. Swift and Kotlin bindings are generated from source with a single script command.
3. iOS and Android sample usage snippets are present under `sdk/ios/sample` and `sdk/android/sample`.
4. Release automation is in place via `.github/workflows/mobile-sdk-artifacts.yml` for packaging and publishing SDK artifacts.

## Remaining Work
1. Full runtime validation inside real iOS and Android host apps (simulator/device lanes).
50 changes: 50 additions & 0 deletions scripts/generate-mobile-sdk-bindings.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"

LIB_NAME="skilly_core_mobile_sdk"
BUILD_PROFILE="debug"
IOS_OUTPUT_DIR="$REPO_ROOT/sdk/ios/generated"
ANDROID_OUTPUT_DIR="$REPO_ROOT/sdk/android/generated"

cargo build -p skilly-core-mobile-sdk

case "$(uname -s)" in
Darwin)
LIB_PATH="$REPO_ROOT/target/$BUILD_PROFILE/lib${LIB_NAME}.dylib"
;;
Linux)
LIB_PATH="$REPO_ROOT/target/$BUILD_PROFILE/lib${LIB_NAME}.so"
;;
MINGW*|MSYS*|CYGWIN*)
LIB_PATH="$REPO_ROOT/target/$BUILD_PROFILE/${LIB_NAME}.dll"
;;
*)
echo "Unsupported host OS for binding generation." >&2
exit 1
;;
esac

if [[ ! -f "$LIB_PATH" ]]; then
echo "Expected compiled library at $LIB_PATH" >&2
exit 1
fi

mkdir -p "$IOS_OUTPUT_DIR" "$ANDROID_OUTPUT_DIR"

cargo run -p skilly-core-mobile-sdk --bin uniffi-bindgen -- \
generate \
--library "$LIB_PATH" \
--language swift \
--out-dir "$IOS_OUTPUT_DIR"

cargo run -p skilly-core-mobile-sdk --bin uniffi-bindgen -- \
generate \
--library "$LIB_PATH" \
--language kotlin \
--out-dir "$ANDROID_OUTPUT_DIR"

echo "Generated Swift bindings in $IOS_OUTPUT_DIR"
echo "Generated Kotlin bindings in $ANDROID_OUTPUT_DIR"
78 changes: 78 additions & 0 deletions scripts/package-mobile-sdk.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"

MOBILE_SDK_CRATE_PATH="$REPO_ROOT/core/mobile-sdk/Cargo.toml"
MOBILE_SDK_VERSION="$(sed -n 's/^version = "\([0-9.]*\)"/\1/p' "$MOBILE_SDK_CRATE_PATH" | head -1)"
if [[ -z "$MOBILE_SDK_VERSION" ]]; then
echo "Could not determine mobile SDK version from $MOBILE_SDK_CRATE_PATH" >&2
exit 1
fi

RELEASE_LIBRARY_DIR="$REPO_ROOT/target/release"
DIST_ROOT="$REPO_ROOT/dist/mobile-sdk"
DIST_VERSION_DIR="$DIST_ROOT/v${MOBILE_SDK_VERSION}"
DIST_PLATFORM_NAME="$(uname -s | tr '[:upper:]' '[:lower:]')"
DIST_OUTPUT_FILE="$DIST_ROOT/skilly-mobile-sdk-v${MOBILE_SDK_VERSION}-${DIST_PLATFORM_NAME}.tar.gz"

rm -rf "$DIST_VERSION_DIR"
mkdir -p \
"$DIST_VERSION_DIR/ios/generated" \
"$DIST_VERSION_DIR/ios/sample" \
"$DIST_VERSION_DIR/android/generated" \
"$DIST_VERSION_DIR/android/sample" \
"$DIST_VERSION_DIR/native"

./scripts/generate-mobile-sdk-bindings.sh
cargo build -p skilly-core-mobile-sdk --release

case "$(uname -s)" in
Darwin)
RELEASE_LIBRARY_PATH="$RELEASE_LIBRARY_DIR/libskilly_core_mobile_sdk.dylib"
;;
Linux)
RELEASE_LIBRARY_PATH="$RELEASE_LIBRARY_DIR/libskilly_core_mobile_sdk.so"
;;
MINGW*|MSYS*|CYGWIN*)
RELEASE_LIBRARY_PATH="$RELEASE_LIBRARY_DIR/skilly_core_mobile_sdk.dll"
;;
*)
echo "Unsupported host OS for packaging." >&2
exit 1
;;
esac

if [[ ! -f "$RELEASE_LIBRARY_PATH" ]]; then
echo "Expected release library at $RELEASE_LIBRARY_PATH" >&2
exit 1
fi

cp -R sdk/ios/generated/. "$DIST_VERSION_DIR/ios/generated/"
cp -R sdk/ios/sample/. "$DIST_VERSION_DIR/ios/sample/"
cp -R sdk/android/generated/. "$DIST_VERSION_DIR/android/generated/"
cp -R sdk/android/sample/. "$DIST_VERSION_DIR/android/sample/"
cp "$RELEASE_LIBRARY_PATH" "$DIST_VERSION_DIR/native/"
cp sdk/README.md "$DIST_VERSION_DIR/README.md"

cat > "$DIST_VERSION_DIR/MANIFEST.txt" <<MANIFEST
skilly-core-mobile-sdk version: ${MOBILE_SDK_VERSION}
packaged-on-host: ${DIST_PLATFORM_NAME}
release-library: $(basename "$RELEASE_LIBRARY_PATH")
MANIFEST

mkdir -p "$DIST_ROOT"
rm -f "$DIST_OUTPUT_FILE"
tar -czf "$DIST_OUTPUT_FILE" -C "$DIST_ROOT" "v${MOBILE_SDK_VERSION}"

if command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$DIST_OUTPUT_FILE" > "${DIST_OUTPUT_FILE}.sha256"
elif command -v sha256sum >/dev/null 2>&1; then
sha256sum "$DIST_OUTPUT_FILE" > "${DIST_OUTPUT_FILE}.sha256"
fi

echo "Packaged mobile SDK artifact: $DIST_OUTPUT_FILE"
if [[ -f "${DIST_OUTPUT_FILE}.sha256" ]]; then
echo "Checksum file: ${DIST_OUTPUT_FILE}.sha256"
fi
92 changes: 92 additions & 0 deletions scripts/validate-mobile-sdk-consumers.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"

BUILD_DIR="$REPO_ROOT/build/mobile-sdk-validation"
KOTLIN_VERSION="2.0.21"
KOTLIN_DISTRIBUTION_URL="https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip"
JNA_VERSION="5.17.0"
JNA_JAR_PATH="$BUILD_DIR/jna-${JNA_VERSION}.jar"
KOTLIN_MAIN_PATH="$BUILD_DIR/SkillyMobileSdkValidationMain.kt"
KOTLIN_OUTPUT_JAR="$BUILD_DIR/skilly-mobile-sdk-validation.jar"
SWIFT_MAIN_PATH="$BUILD_DIR/SkillyMobileSdkValidationMain.swift"
SWIFT_BINARY_PATH="$BUILD_DIR/skilly-mobile-sdk-validation"
KOTLIN_COMPILER_ROOT="$BUILD_DIR/kotlin-compiler"

mkdir -p "$BUILD_DIR"

./scripts/generate-mobile-sdk-bindings.sh

cargo build -p skilly-core-mobile-sdk

if [[ ! -f "$JNA_JAR_PATH" ]]; then
curl -L -o "$JNA_JAR_PATH" "https://repo1.maven.org/maven2/net/java/dev/jna/jna/${JNA_VERSION}/jna-${JNA_VERSION}.jar"
fi

KOTLIN_COMPILER_BIN="$(command -v kotlinc || true)"
if [[ -z "$KOTLIN_COMPILER_BIN" ]]; then
KOTLIN_COMPILER_BIN="$KOTLIN_COMPILER_ROOT/kotlinc/bin/kotlinc"
if [[ ! -x "$KOTLIN_COMPILER_BIN" ]]; then
KOTLIN_ZIP_PATH="$BUILD_DIR/kotlin-compiler-${KOTLIN_VERSION}.zip"
if [[ ! -f "$KOTLIN_ZIP_PATH" ]]; then
curl -L -o "$KOTLIN_ZIP_PATH" "$KOTLIN_DISTRIBUTION_URL"
fi

rm -rf "$KOTLIN_COMPILER_ROOT"
mkdir -p "$KOTLIN_COMPILER_ROOT"
unzip -q "$KOTLIN_ZIP_PATH" -d "$KOTLIN_COMPILER_ROOT"
fi
fi

cat > "$KOTLIN_MAIN_PATH" <<'KOTLIN'
import app.tryskilly.sdk.PolicyAndRealtimeExample

fun main() {
PolicyAndRealtimeExample.runDemo()
}
KOTLIN

"$KOTLIN_COMPILER_BIN" \
sdk/android/generated/uniffi/skilly_core_mobile_sdk/skilly_core_mobile_sdk.kt \
sdk/android/sample/src/main/kotlin/app/tryskilly/sdk/PolicyAndRealtimeExample.kt \
"$KOTLIN_MAIN_PATH" \
-cp "$JNA_JAR_PATH" \
-include-runtime \
-d "$KOTLIN_OUTPUT_JAR"

java \
-Djna.library.path="$REPO_ROOT/target/debug" \
-Djava.library.path="$REPO_ROOT/target/debug" \
-cp "$KOTLIN_OUTPUT_JAR:$JNA_JAR_PATH" \
SkillyMobileSdkValidationMainKt

if [[ "$(uname -s)" == "Darwin" ]]; then
cat > "$SWIFT_MAIN_PATH" <<'SWIFT'
import Foundation

@main
struct Main {
static func main() {
runPolicyAndRealtimeDemo()
}
}
SWIFT

swiftc \
sdk/ios/generated/skilly_core_mobile_sdk.swift \
sdk/ios/sample/PolicyAndRealtimeExample.swift \
"$SWIFT_MAIN_PATH" \
-I sdk/ios/generated \
-Xcc -fmodule-map-file="$REPO_ROOT/sdk/ios/generated/skilly_core_mobile_sdkFFI.modulemap" \
-L "$REPO_ROOT/target/debug" \
-lskilly_core_mobile_sdk \
-o "$SWIFT_BINARY_PATH"

DYLD_LIBRARY_PATH="$REPO_ROOT/target/debug" "$SWIFT_BINARY_PATH"
else
echo "Skipping Swift mobile SDK consumer runtime validation on non-macOS host."
fi

echo "Mobile SDK consumer validation completed successfully."
Loading
Loading