mirror of
https://github.com/Helldez/BigMoeOnEdge.git
synced 2026-10-03 03:25:42 +00:00
nttld/setup-ndk and softprops/action-gh-release ran from mutable major tags inside the APK job, which holds a contents:write token. Whoever controls those tags upstream could have pointed them at unreviewed code with permission to rewrite this repo's release assets. Both now resolve to a fixed commit, with the human-readable version in a trailing comment. GitHub-owned actions stay on major tags: pinning them would cost an SHA bump on every upstream release for a materially smaller risk. The signing keystore was never reachable this way. It lives only in release-apk.yml, which runs no third-party action and triggers only on events that already require write access.
177 lines
7.7 KiB
YAML
177 lines
7.7 KiB
YAML
name: ci
|
|
|
|
on:
|
|
push:
|
|
branches: [ main ]
|
|
tags: [ 'v*' ]
|
|
pull_request:
|
|
branches: [ main ]
|
|
paths-ignore: [ '**/*.md', 'docs/**', 'LICENSE', '.gitignore' ]
|
|
workflow_dispatch: {}
|
|
|
|
# A newer push to the same ref cancels the older, still-running CI — so a burst of commits
|
|
# spends minutes only on the last one instead of building every intermediate commit.
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# Does this push carry anything a build could break?
|
|
#
|
|
# `paths-ignore` handles that for pull requests, but it cannot be put on the push trigger: the
|
|
# same trigger carries the release tags, and a tag push has no commit range for a path filter to
|
|
# read, so filtering here would silence the tag build that attaches the APK. Hence the decision
|
|
# is made once, in ten seconds, and each build job is gated on it.
|
|
#
|
|
# Anything not a push (pull_request, workflow_dispatch) and every tag build answers "yes"
|
|
# unconditionally — this is a saver for docs merges, not a second opinion on what to test.
|
|
changes:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
code: ${{ steps.decide.outputs.code }}
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
with:
|
|
fetch-depth: 0
|
|
- id: decide
|
|
env:
|
|
EVENT: ${{ github.event_name }}
|
|
REF: ${{ github.ref }}
|
|
BEFORE: ${{ github.event.before }}
|
|
SHA: ${{ github.sha }}
|
|
run: |
|
|
say() { echo "code=$1" >> "$GITHUB_OUTPUT"; echo "code=$1"; }
|
|
case "$EVENT:$REF" in
|
|
push:refs/tags/*) say true; exit 0 ;;
|
|
push:*) ;;
|
|
*) say true; exit 0 ;;
|
|
esac
|
|
# A branch created by this push, or a force-push past the old head, leaves nothing to
|
|
# diff against; build rather than guess.
|
|
if [ -z "$BEFORE" ] || [ "$BEFORE" = "0000000000000000000000000000000000000000" ] \
|
|
|| ! git cat-file -e "$BEFORE^{commit}" 2>/dev/null; then
|
|
say true; exit 0
|
|
fi
|
|
files=$(git diff --name-only "$BEFORE" "$SHA")
|
|
echo "changed:"; echo "$files"
|
|
if echo "$files" | grep -qvE '(\.md$|^docs/|^LICENSE$|^\.gitignore$)'; then
|
|
say true
|
|
else
|
|
say false
|
|
fi
|
|
|
|
format:
|
|
needs: changes
|
|
if: needs.changes.outputs.code == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
- name: clang-format (our sources only)
|
|
# Pinned to 18 because AGENTS.md tells contributors to match that version locally. Plain
|
|
# `clang-format` is whatever the runner image ships, which happens to be 18 today: an image
|
|
# bump would silently start failing every PR against a version nobody was told about.
|
|
run: |
|
|
sudo apt-get update && sudo apt-get install -y clang-format-18
|
|
find core cli tests -type f \( -name '*.cpp' -o -name '*.h' \) \
|
|
-print0 | xargs -0 clang-format-18 --dry-run --Werror
|
|
|
|
host-linux:
|
|
needs: changes
|
|
if: needs.changes.outputs.code == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
with:
|
|
submodules: recursive
|
|
- name: Install deps
|
|
run: |
|
|
sudo apt-get update && sudo apt-get install -y cmake ninja-build
|
|
python3 -m pip install --upgrade pip
|
|
python3 -m pip install gguf numpy
|
|
- name: Configure & build
|
|
run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build -j
|
|
- name: Byte-identity gates
|
|
run: cd build && ctest --output-on-failure
|
|
|
|
host-windows:
|
|
# Windows minutes bill at 2x. It only compile-checks the Win32 I/O paths, so run it where it
|
|
# matters — PRs (before merge) and release tags — not on every main push (already tested on PR).
|
|
if: github.event_name != 'push' || startsWith(github.ref, 'refs/tags/v')
|
|
runs-on: windows-latest
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
with:
|
|
submodules: recursive
|
|
- name: Configure & build (compile-check the Win32 I/O paths)
|
|
run: |
|
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBMOE_BUILD_TESTS=OFF
|
|
cmake --build build --config Release -j
|
|
|
|
# Cross-compiles the arm64 engine (the native compile gate) and packages the example
|
|
# APK around it, uploading the APK as an artifact — and attaching it to the release on a
|
|
# tag push. Debug-signed: for on-device validation, not Play distribution.
|
|
android-apk:
|
|
# The long job (NDK + cross-compile + gradle). Build it on PRs (catch APK breakage before merge)
|
|
# and on release tags (attach the APK), but not on every main push. Trigger a one-off manually
|
|
# with the Run workflow button (workflow_dispatch) if you need a fresh main APK between releases.
|
|
if: github.event_name != 'push' || startsWith(github.ref, 'refs/tags/v')
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write # only used by the tag-triggered release attach step
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
with:
|
|
submodules: recursive
|
|
- name: Set up JDK 17
|
|
uses: actions/setup-java@v4
|
|
with:
|
|
distribution: temurin
|
|
java-version: '17'
|
|
- name: Set up NDK
|
|
# Third-party action, pinned by commit SHA: a mutable tag here would run whatever the
|
|
# upstream repo points it at, inside a job that holds a contents:write token.
|
|
uses: nttld/setup-ndk@ed92fe6cadad69be94a966a7ee3271275e62f779 # v1.6.0
|
|
id: ndk
|
|
with:
|
|
# r27c == 27.2.12479018, the NDK release-apk.yml builds published APKs with. These two
|
|
# must not drift, or CI validates a build nobody installs.
|
|
ndk-version: r27c
|
|
- name: Cross-compile bmoe-cli (arm64)
|
|
env:
|
|
ANDROID_NDK_HOME: ${{ steps.ndk.outputs.ndk-path }}
|
|
run: |
|
|
cmake -S . -B build-android -G Ninja \
|
|
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \
|
|
-DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-29 \
|
|
-DCMAKE_BUILD_TYPE=Release -DBMOE_BUILD_TESTS=OFF \
|
|
-DGGML_NATIVE=OFF -DGGML_OPENMP=OFF -DGGML_OPENCL=OFF \
|
|
-DGGML_CPU_ARM_ARCH="armv8.2-a+dotprod+fp16" \
|
|
-DLLAMA_CURL=OFF
|
|
cmake --build build-android -j
|
|
- name: Stage engine binaries into jniLibs
|
|
env:
|
|
ANDROID_NDK_HOME: ${{ steps.ndk.outputs.ndk-path }}
|
|
run: |
|
|
jni=examples/android/app/src/main/jniLibs/arm64-v8a
|
|
mkdir -p "$jni"
|
|
cp build-android/cli/bmoe-cli "$jni/libbmoe-cli.so"
|
|
find build-android \( -name 'libggml*.so' -o -name 'libllama*.so' \) -exec cp {} "$jni/" \;
|
|
# bmoe-cli links the c++_shared STL; ship its runtime from the NDK sysroot.
|
|
cp "$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so" "$jni/"
|
|
ls -la "$jni"
|
|
- name: Build debug APK (dev flavor — sideloaded, all-files access)
|
|
working-directory: examples/android
|
|
run: ./gradlew assembleDevDebug --no-daemon
|
|
- name: Upload APK artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: bmoe-example-dev-debug-apk
|
|
path: examples/android/app/build/outputs/apk/dev/debug/app-dev-debug.apk
|
|
if-no-files-found: error
|
|
- name: Attach APK to release
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
# Pinned by commit SHA for the same reason. This step only runs on a tag push, so a
|
|
# wrong pin would not surface until a release: resolve the SHA, do not hand-edit it.
|
|
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
|
|
with:
|
|
files: examples/android/app/build/outputs/apk/dev/debug/app-dev-debug.apk
|