Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copy to `.env` (gitignored) and fill in. `config/settings.py` reads this file as well as the
# process environment, so anything set here reaches the CLI without exporting it in your shell.
#
# Nothing here is needed to run `make check`, `make test-cpp` or the Android JVM suites. These are
# only for the operations that talk to the Hugging Face Hub.

# Personal Hub token — read access for pulling gated/private BASE models during an export, and write
# access to your own namespace.
#
# Needed by: mobiletransformers export --model <gated-id>
# mobiletransformers pull / push (personal repos)
# make device-hub-test REPO=<you>/<name>
# the sample app's Install button, baked in at build time as BuildConfig.HF_TOKEN
# (see MobileTransformersApp/build.gradle.kts — `make android-build` does NOT source
# this file, so export it yourself: `set -a && . ./.env && set +a`)
HF_TOKEN=
118 changes: 118 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# The badge workflow: the checks that are cheap, deterministic and meaningful on a bare runner.
#
# Separate from `ci.yml` on purpose. That workflow stages everything including `export-smoke` (which
# installs torch + optimum) and `android-assemble` (which SELF-SKIPS without the vendored native
# deps). Neither belongs behind a status badge: one is slow enough to discourage pushing, and the
# other would report green by skipping — a badge that is green when nothing ran is worse than no
# badge, because it is trusted.
#
# So this file runs only what a hosted runner can do honestly, in a couple of minutes, with no model
# downloads, no NDK, no vendored binaries and no credentials:
#
# python -> lint + typecheck + enum parity + guards + docs gate + the core unit suite
# cpp -> host googletests over the ORT-free headers
# kotlin -> SDK + app JVM unit tests (Kotlin compiler + Android SDK stubs; no native libs)
#
# Every job here was verified against a SIMULATED FRESH CLONE on 2026-08-17 — the tracked files only,
# with no `third_party/wheels/` wheel, no `jniLibs`/`aarLibs`, no `.env` and no device. That run is
# what caught `make check` failing on a bare machine: `[tool.uv.sources]` points
# `onnxruntime-training` at a local path, and a bare `uv run` validates it before executing, so
# `make lint` died on a missing 632 MB training wheel. The Makefile now uses `uv run --frozen`.
# Without that fix this workflow would have gone red on its first run.
#
# `ci.yml` stays `workflow_dispatch`-only and keeps the heavier stages. Anything needing the native
# dependencies, a device or a token belongs there, not here.
name: checks

on:
push:
branches: [main]
tags: ['v*']
# Unqualified on purpose: a pull request from ANY branch runs these checks, which is what makes
# dropping work branches from the `push` list above safe — the badge tracks `main`, and everything
# merging into it is still gated.
pull_request: {}
workflow_dispatch: {}

# A new push supersedes an in-flight run of the same ref: the badge should reflect the tip, and
# queued runs of superseded commits cost minutes for an answer nobody reads.
concurrency:
group: checks-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
python:
name: python (lint, typecheck, parity, guards, tests)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
# The core/dev profile: no onnxruntime provider, no torch, no model downloads. `--frozen` so a
# drifted lock fails here rather than silently resolving something else.
- name: Sync core + dev (Python 3.10)
run: uv sync --frozen --group dev --python 3.10
- name: Lint
run: make lint
- name: Typecheck
run: make typecheck
- name: Enum/schema parity (Python source of truth vs Kotlin + C++ mirrors)
run: make parity
- name: Guards (secrets, registry dispatch, plan identifiers, machine paths)
run: make guard
- name: Docs gate (markdown links, CLI table, Kotlin facade symbols)
# `--frozen` for the reason spelled out at the top of this file: a bare `uv run` validates
# the local-path `onnxruntime-training` source before running anything, and that wheel is
# git-ignored. Every `uv run` in a workflow carries it — see test_guards.py.
run: uv run --frozen pytest tests/unit/test_docs.py -q
- name: Unit tests
run: make test

cpp:
name: cpp (host googletest)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
# Host toolchain only. The shipping library needs the NDK and ONNX Runtime, but the headers
# carrying the fail-closed logic are ORT-free and testable anywhere.
- name: C++ host unit tests
run: make test-cpp

kotlin:
name: kotlin (SDK JVM unit tests)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('android/MobileTransformers/**/*.gradle.kts', 'android/MobileTransformers/gradle/libs.versions.toml') }}
# JVM-only: no NDK, no vendored native libs — both modules were confirmed to build and test
# against a tree with an empty `jniLibs`/`aarLibs`. The app module is included because it holds
# the navigation, download-state and PEFT-label suites, which are exactly the logic that has
# broken from under the UI before.
- name: JVM unit tests (SDK + sample app)
working-directory: android/MobileTransformers
run: ./gradlew :MobileTransformers:testDebugUnitTest :MobileTransformersApp:testDebugUnitTest
- name: Upload test report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: kotlin-test-report
path: |
android/MobileTransformers/MobileTransformers/build/reports/tests/testDebugUnitTest
android/MobileTransformers/MobileTransformersApp/build/reports/tests/testDebugUnitTest
171 changes: 171 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Staged CI. Cheapest-first so failures surface fast:
#
# fast -> lint + typecheck + parity + guards (F2) + core unit tests (every PR, minutes)
# kotlin-test -> :MobileTransformers JVM unit tests (every PR, no NDK)
# cpp-test -> host googletest over the ORT-free C++ headers (every PR, no NDK)
# export-smoke -> export profile installed; export/package/manifest wiring (every PR, < ~10 min)
# android-* -> Gradle assembleDebug for both modules (every PR, self-skips*)
#
# The Kotlin JVM suite needs only the Kotlin compiler + Android SDK stubs — NOT the vendored native
# libraries — so it runs on every PR.
#
# * The Android ASSEMBLE job needs the git-ignored vendored native deps. On a bare hosted runner those
# are absent, so the job SELF-SKIPS with a warning rather than failing spuriously (mirrors
# .github/workflows/ort-training-smoke.yml). How the native deps reach CI — rebuild vs. cached
# artifact vs. private storage — is still an open decision. See docs/RELEASE_CHECKLIST.md.
#
# No PR job downloads a large model (assert via logs): fast/export-smoke use only tiny fixtures; the
# generate_artifacts + 1-token desktop leg + device train->merge->generate->RAG run in device.yml
# (manual + nightly), never on a PR.

name: ci

# DISABLED (2026-08-08): automatic triggers removed — these workflows are not in use yet, and the
# native-dependency provisioning question they depend on is unresolved, so every run either self-skips
# or fails for reasons unrelated to the change under test. A red badge nobody acts on is worse than no
# badge: it trains everyone to ignore CI.
#
# They remain fully intact and MANUALLY runnable (Actions -> select workflow -> "Run workflow").
# To re-enable, restore the original `on:` block recorded directly below.
#
# ORIGINAL:
# on:
# push:
# branches: [main]
# tags: ['v*']
# pull_request: {}

on:
workflow_dispatch: {}

jobs:
fast:
name: fast (lint + typecheck + parity + tests)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Sync core + dev (Python 3.10, no onnxruntime provider)
run: uv sync --frozen --group dev --python 3.10
- name: Lint
run: make lint
- name: Typecheck
run: make typecheck
- name: Parity gate (F2 — enums/schemas vs. Kotlin/C++ mirrors)
run: make parity
- name: Guards (secret reads, registry dispatch, plan identifiers, machine paths)
run: make guard
- name: Docs gate (markdown links, CLI table, Kotlin facade symbols)
# `--frozen` is load-bearing, not a nicety: a bare `uv run` validates every source in the
# lock before executing, and `onnxruntime-training` resolves to a git-ignored 662 MB wheel
# that no runner has. Without it this step dies on a missing training wheel it never needed.
run: uv run --frozen pytest tests/unit/test_docs.py -q
- name: Unit tests
run: make test

kotlin-test:
name: kotlin-test (SDK JVM unit tests)
needs: fast
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('android/MobileTransformers/**/*.gradle.kts', 'android/MobileTransformers/gradle/libs.versions.toml') }}
# JVM-only: no NDK, no vendored native libs. `testDebugUnitTest` compiles Kotlin and runs the
# facade/hub/packages/rag/runtime/training suites.
- name: SDK JVM unit tests
working-directory: android/MobileTransformers
run: ./gradlew :MobileTransformers:testDebugUnitTest
- name: Upload test report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: kotlin-test-report
path: android/MobileTransformers/MobileTransformers/build/reports/tests/testDebugUnitTest

cpp-test:
name: cpp-test (host googletest, ORT-free headers)
needs: fast
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
# Host toolchain only — the shipping library needs the NDK + ONNX Runtime, but handoff_io.h,
# constants/merger_variant.h and mem_probe.h are ORT-free and carry the fail-closed logic.
- name: C++ host unit tests
run: make test-cpp

export-smoke:
name: export-smoke (export/package/manifest wiring)
needs: fast
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
# Export profile needs Python >= 3.11 (optimum-onnx pulls onnxruntime >= 1.24). Runs the
# export-profile tests that self-skip in the fast job's core env.
- name: Sync export profile + dev (Python 3.12)
run: uv sync --extra export --group dev --python 3.12
- name: Export/package/manifest smoke
run: make test-smoke

android-assemble:
name: android-assemble (self-skips without vendored native deps)
needs: fast
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Check for the vendored native deps
id: nativedeps
working-directory: android/MobileTransformers
run: |
# jniLibs/ is the only vendored input the native build still needs. aarLibs/ was dropped as a
# Gradle dependency (build.gradle.kts documents the .so-from-jniLibs decision) and the
# protobuf headers under cpp/includes/ went with weight_serializer.cpp — the flat
# per-tensor .bin files are raw external data, so nothing on device parses ONNX protobuf.
if [ -d MobileTransformers/src/main/jniLibs ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
echo "::warning::Vendored native libs (src/main/jniLibs) absent; skipping Android assemble. Run scripts/fetch_native_deps.sh, or see docs/ARCHITECTURE.md."
fi
- name: Assemble SDK + sample app (debug)
if: steps.nativedeps.outputs.present == 'true'
working-directory: android/MobileTransformers
run: ./gradlew :MobileTransformers:assembleDebug :MobileTransformersApp:assembleDebug

# On a tag, also produce the release AAR. scripts/android_build_aar.sh verifies that every ABI
# directory the AAR ships actually contains libmobiletransformers.so — an AAR with an ABI dir
# but no project library fails at System.loadLibrary on the consumer's device.
- name: Build the release AAR (tags only)
if: steps.nativedeps.outputs.present == 'true' && startsWith(github.ref, 'refs/tags/v')
run: scripts/android_build_aar.sh
- name: Upload the release AAR (tags only)
if: steps.nativedeps.outputs.present == 'true' && startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v4
with:
name: mobiletransformers-android-aar
path: android/MobileTransformers/MobileTransformers/build/outputs/aar/*-release.aar
Loading
Loading