Skip to content

Commit d5f66df

Browse files
authored
feat: bundle Mieru native binary (no external plugin required) (#12)
* feat: bundle Mieru native binary (no external plugin required) Build the Mieru client from source for all 4 Android ABIs and bundle it as app/executableSo/<abi>/libmieru.so, so Mieru profiles work without installing the separate moe.matsuri.exe.mieru plugin. - buildScript/lib/mieru.sh: cross-compiles enfein/mieru v3.34.0 via the NDK for arm64-v8a, armeabi-v7a, x86, x86_64 (./run lib mieru). - PluginManager.initNativeInternal: resolve mieru-plugin -> libmieru.so from nativeLibraryDir, mirroring the bundled-Hysteria mechanism. Falls back to the external APK plugin when the bundled binary is absent. - Executable.EXECUTABLES: add libmieru.so so orphaned processes are cleaned up. - ci.yml: build + cache the Mieru binaries (Go+NDK) and restore before Gradle. Reuses the existing MieruBean / MieruFmt buildMieruConfig / BoxInstance run contract (config via MIERU_CONFIG_JSON_FILE, socket protection via MIERU_PROTECT_PATH) unchanged; upstream mieru v3 honors both. * chore: mark buildScript/lib/mieru.sh executable * ci: harden Mieru build wiring across all workflows Address review feedback and ensure shipping APKs bundle Mieru: - Add the Mieru native-build job + cache restore + artifact verification to build.yml, preview.yml, release.yml (previously only ci.yml had it), so release/preview APKs also bundle libmieru.so. - Include buildScript/init/env.sh in the Mieru cache-status hash (mieru.sh sources it). - Pin MIERU_VERSION as a workflow env and include it in the cache key so a version override invalidates stale binaries. - Add a 'Verify Mieru Artifacts' step in each build job that fails fast if any ABI's libmieru.so is missing (guards against a silent cache miss shipping an APK without the bundled binary). - mieru.sh: validate ANDROID_NDK_HOME is set before use. * ci: use -f not -x to verify Mieru artifacts GitHub Actions cache restore does not reliably preserve the execute bit, so testing with -x could falsely fail on a cache hit. The runtime execute permission is applied by Android when extracting from the APK; here we only need to confirm the binary exists. * ci: validate resolved NDK toolchain dir in mieru.sh Fail fast with a clear message if neither the linux-x86_64 nor darwin-x86_64 NDK LLVM toolchain dir exists, instead of a cryptic CC-not-found later. * ci: re-clone Mieru source if existing checkout is unhealthy Guard the incremental-fetch path: if fetch/checkout fail (e.g. a corrupted .mieru-build from a prior failed run), remove and clone fresh instead of erroring out. * ci: support darwin-arm64 NDK host in mieru.sh Add a darwin-arm64 fallback for the NDK LLVM toolchain so local Mieru builds work natively on Apple Silicon Macs (newer NDKs ship darwin-arm64). * ci: address review - env_ndk.sh in cache hash, consolidate MIERU_VERSION, pipefail - Include buildScript/init/env_ndk.sh (NDK version pin) in the Mieru cache-key hash so bumping the NDK invalidates stale binaries. - Hoist MIERU_VERSION to a single workflow-level env block per workflow (was duplicated in the mieru and build jobs). - Add 'set -o pipefail' to mieru.sh as a defensive guard.
1 parent f685fc6 commit d5f66df

7 files changed

Lines changed: 249 additions & 1 deletion

File tree

.github/workflows/build.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ on:
66
- main
77
paths:
88
- 'nb4a.properties'
9+
env:
10+
MIERU_VERSION: v3.34.0
911
jobs:
1012
libcore:
1113
name: Native Build (LibCore)
@@ -32,11 +34,35 @@ jobs:
3234
- name: Native Build
3335
if: steps.cache.outputs.cache-hit != 'true'
3436
run: ./run lib core
37+
mieru:
38+
name: Native Build (Mieru)
39+
runs-on: ubuntu-latest
40+
steps:
41+
- name: Checkout
42+
uses: actions/checkout@v5
43+
- name: Mieru Status
44+
run: cat buildScript/lib/mieru.sh buildScript/init/env.sh buildScript/init/env_ndk.sh | sha1sum > mieru_status
45+
- name: Mieru Cache
46+
id: cache
47+
uses: actions/cache@v5
48+
with:
49+
path: |
50+
app/executableSo
51+
key: ${{ hashFiles('.github/workflows/*', 'mieru_status') }}-${{ env.MIERU_VERSION }}-mieru
52+
- name: Install Golang
53+
if: steps.cache.outputs.cache-hit != 'true'
54+
uses: actions/setup-go@v6
55+
with:
56+
go-version: '1.24.9'
57+
- name: Mieru Build
58+
if: steps.cache.outputs.cache-hit != 'true'
59+
run: ./run lib mieru
3560
build:
3661
name: Build OSS APK
3762
runs-on: ubuntu-latest
3863
needs:
3964
- libcore
65+
- mieru
4066
steps:
4167
- name: Checkout
4268
uses: actions/checkout@v5
@@ -50,6 +76,23 @@ jobs:
5076
path: |
5177
app/libs/libcore.aar
5278
key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }}
79+
- name: Mieru Status
80+
run: cat buildScript/lib/mieru.sh buildScript/init/env.sh buildScript/init/env_ndk.sh | sha1sum > mieru_status
81+
- name: Mieru Cache
82+
id: mieru-cache
83+
uses: actions/cache@v5
84+
with:
85+
path: |
86+
app/executableSo
87+
key: ${{ hashFiles('.github/workflows/*', 'mieru_status') }}-${{ env.MIERU_VERSION }}-mieru
88+
- name: Verify Mieru Artifacts
89+
run: |
90+
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
91+
if [ ! -f "app/executableSo/$abi/libmieru.so" ]; then
92+
echo "Error: missing app/executableSo/$abi/libmieru.so (Mieru cache miss)" >&2
93+
exit 1
94+
fi
95+
done
5396
- name: Gradle cache
5497
uses: actions/cache@v5
5598
with:

.github/workflows/ci.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ on:
66
branches:
77
- '*'
88
pull_request:
9+
env:
10+
MIERU_VERSION: v3.34.0
911
jobs:
1012
libcore:
1113
name: Native Build (LibCore)
@@ -37,11 +39,35 @@ jobs:
3739
- name: Native Build
3840
if: steps.cache.outputs.cache-hit != 'true'
3941
run: ./run lib core
42+
mieru:
43+
name: Native Build (Mieru)
44+
runs-on: ubuntu-latest
45+
steps:
46+
- name: Checkout
47+
uses: actions/checkout@v5
48+
- name: Mieru Status
49+
run: cat buildScript/lib/mieru.sh buildScript/init/env.sh buildScript/init/env_ndk.sh | sha1sum > mieru_status
50+
- name: Mieru Cache
51+
id: cache
52+
uses: actions/cache@v5
53+
with:
54+
path: |
55+
app/executableSo
56+
key: ${{ hashFiles('.github/workflows/*', 'mieru_status') }}-${{ env.MIERU_VERSION }}-mieru-ci
57+
- name: Install Golang
58+
if: steps.cache.outputs.cache-hit != 'true'
59+
uses: actions/setup-go@v6
60+
with:
61+
go-version: '1.24.9'
62+
- name: Mieru Build
63+
if: steps.cache.outputs.cache-hit != 'true'
64+
run: ./run lib mieru
4065
build:
4166
name: Build OSS APK
4267
runs-on: ubuntu-latest
4368
needs:
4469
- libcore
70+
- mieru
4571
steps:
4672
- name: Checkout
4773
uses: actions/checkout@v5
@@ -60,6 +86,23 @@ jobs:
6086
path: |
6187
app/libs/libcore.aar
6288
key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }}-ci
89+
- name: Mieru Status
90+
run: cat buildScript/lib/mieru.sh buildScript/init/env.sh buildScript/init/env_ndk.sh | sha1sum > mieru_status
91+
- name: Mieru Cache
92+
id: mieru-cache
93+
uses: actions/cache@v5
94+
with:
95+
path: |
96+
app/executableSo
97+
key: ${{ hashFiles('.github/workflows/*', 'mieru_status') }}-${{ env.MIERU_VERSION }}-mieru-ci
98+
- name: Verify Mieru Artifacts
99+
run: |
100+
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
101+
if [ ! -f "app/executableSo/$abi/libmieru.so" ]; then
102+
echo "Error: missing app/executableSo/$abi/libmieru.so (Mieru cache miss)" >&2
103+
exit 1
104+
fi
105+
done
63106
- name: Gradle cache
64107
uses: actions/cache@v5
65108
with:

.github/workflows/preview.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ name: Preview Build
22
on:
33
workflow_dispatch:
44
inputs:
5+
env:
6+
MIERU_VERSION: v3.34.0
57
jobs:
68
libcore:
79
name: Native Build (LibCore)
@@ -28,11 +30,35 @@ jobs:
2830
- name: Native Build
2931
if: steps.cache.outputs.cache-hit != 'true'
3032
run: ./run lib core
33+
mieru:
34+
name: Native Build (Mieru)
35+
runs-on: ubuntu-latest
36+
steps:
37+
- name: Checkout
38+
uses: actions/checkout@v5
39+
- name: Mieru Status
40+
run: cat buildScript/lib/mieru.sh buildScript/init/env.sh buildScript/init/env_ndk.sh | sha1sum > mieru_status
41+
- name: Mieru Cache
42+
id: cache
43+
uses: actions/cache@v5
44+
with:
45+
path: |
46+
app/executableSo
47+
key: ${{ hashFiles('.github/workflows/*', 'mieru_status') }}-${{ env.MIERU_VERSION }}-mieru
48+
- name: Install Golang
49+
if: steps.cache.outputs.cache-hit != 'true'
50+
uses: actions/setup-go@v6
51+
with:
52+
go-version: '1.24.9'
53+
- name: Mieru Build
54+
if: steps.cache.outputs.cache-hit != 'true'
55+
run: ./run lib mieru
3156
build:
3257
name: Build OSS APK
3358
runs-on: ubuntu-latest
3459
needs:
3560
- libcore
61+
- mieru
3662
steps:
3763
- name: Checkout
3864
uses: actions/checkout@v5
@@ -46,6 +72,23 @@ jobs:
4672
path: |
4773
app/libs/libcore.aar
4874
key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }}
75+
- name: Mieru Status
76+
run: cat buildScript/lib/mieru.sh buildScript/init/env.sh buildScript/init/env_ndk.sh | sha1sum > mieru_status
77+
- name: Mieru Cache
78+
id: mieru-cache
79+
uses: actions/cache@v5
80+
with:
81+
path: |
82+
app/executableSo
83+
key: ${{ hashFiles('.github/workflows/*', 'mieru_status') }}-${{ env.MIERU_VERSION }}-mieru
84+
- name: Verify Mieru Artifacts
85+
run: |
86+
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
87+
if [ ! -f "app/executableSo/$abi/libmieru.so" ]; then
88+
echo "Error: missing app/executableSo/$abi/libmieru.so (Mieru cache miss)" >&2
89+
exit 1
90+
fi
91+
done
4992
- name: Gradle cache
5093
uses: actions/cache@v5
5194
with:

.github/workflows/release.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ on:
88
publish:
99
description: "Publish: If want ignore"
1010
required: false
11+
env:
12+
MIERU_VERSION: v3.34.0
1113
jobs:
1214
libcore:
1315
name: Native Build (LibCore)
@@ -34,11 +36,35 @@ jobs:
3436
- name: Native Build
3537
if: steps.cache.outputs.cache-hit != 'true'
3638
run: ./run lib core
39+
mieru:
40+
name: Native Build (Mieru)
41+
runs-on: ubuntu-latest
42+
steps:
43+
- name: Checkout
44+
uses: actions/checkout@v5
45+
- name: Mieru Status
46+
run: cat buildScript/lib/mieru.sh buildScript/init/env.sh buildScript/init/env_ndk.sh | sha1sum > mieru_status
47+
- name: Mieru Cache
48+
id: cache
49+
uses: actions/cache@v5
50+
with:
51+
path: |
52+
app/executableSo
53+
key: ${{ hashFiles('.github/workflows/*', 'mieru_status') }}-${{ env.MIERU_VERSION }}-mieru
54+
- name: Install Golang
55+
if: steps.cache.outputs.cache-hit != 'true'
56+
uses: actions/setup-go@v6
57+
with:
58+
go-version: '1.24.9'
59+
- name: Mieru Build
60+
if: steps.cache.outputs.cache-hit != 'true'
61+
run: ./run lib mieru
3762
build:
3863
name: Build OSS APK
3964
runs-on: ubuntu-latest
4065
needs:
4166
- libcore
67+
- mieru
4268
steps:
4369
- name: Checkout
4470
uses: actions/checkout@v5
@@ -52,6 +78,23 @@ jobs:
5278
path: |
5379
app/libs/libcore.aar
5480
key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }}
81+
- name: Mieru Status
82+
run: cat buildScript/lib/mieru.sh buildScript/init/env.sh buildScript/init/env_ndk.sh | sha1sum > mieru_status
83+
- name: Mieru Cache
84+
id: mieru-cache
85+
uses: actions/cache@v5
86+
with:
87+
path: |
88+
app/executableSo
89+
key: ${{ hashFiles('.github/workflows/*', 'mieru_status') }}-${{ env.MIERU_VERSION }}-mieru
90+
- name: Verify Mieru Artifacts
91+
run: |
92+
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
93+
if [ ! -f "app/executableSo/$abi/libmieru.so" ]; then
94+
echo "Error: missing app/executableSo/$abi/libmieru.so (Mieru cache miss)" >&2
95+
exit 1
96+
fi
97+
done
5598
- name: Gradle cache
5699
uses: actions/cache@v5
57100
with:

app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import androidx.core.text.isDigitsOnly
1010

1111
object Executable {
1212
private val EXECUTABLES = setOf(
13-
"libtrojan.so", "libtrojan-go.so", "libnaive.so", "libtuic.so", "libhysteria.so"
13+
"libtrojan.so", "libtrojan-go.so", "libnaive.so", "libtuic.so", "libhysteria.so", "libmieru.so"
1414
)
1515

1616
fun killAll(alsoKillBg: Boolean = false) {

app/src/main/java/io/nekohasekai/sagernet/plugin/PluginManager.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ object PluginManager {
7373
return when (pluginId) {
7474
"hysteria-plugin" -> soIfExist("libhysteria.so")
7575
"hysteria2-plugin" -> soIfExist("libhysteria2.so")
76+
"mieru-plugin" -> soIfExist("libmieru.so")
7677
else -> null
7778
}
7879
}

buildScript/lib/mieru.sh

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/bin/bash
2+
# Build the Mieru client binary for all Android ABIs and install them as
3+
# bundled native executables (app/executableSo/<abi>/libmieru.so).
4+
#
5+
# This lets NekoBox run Mieru profiles without the separate external plugin
6+
# (moe.matsuri.exe.mieru). PluginManager.initNativeInternal resolves
7+
# "mieru-plugin" -> libmieru.so from nativeLibraryDir.
8+
#
9+
# Usage: ./run lib mieru
10+
set -e
11+
set -o pipefail
12+
13+
source "buildScript/init/env.sh"
14+
15+
if [ -z "$ANDROID_NDK_HOME" ]; then
16+
echo "Error: ANDROID_NDK_HOME is not set (NDK required to cross-compile Mieru)." >&2
17+
exit 1
18+
fi
19+
20+
# Mieru release tag to build from source.
21+
MIERU_VERSION="${MIERU_VERSION:-v3.34.0}"
22+
23+
DEPS="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
24+
# macOS NDK host dirs are darwin-x86_64 / darwin-arm64; fall back if linux is absent.
25+
if [ ! -d "$DEPS" ]; then
26+
DEPS="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin"
27+
fi
28+
if [ ! -d "$DEPS" ]; then
29+
DEPS="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-arm64/bin"
30+
fi
31+
if [ ! -d "$DEPS" ]; then
32+
echo "Error: NDK LLVM toolchain not found under $ANDROID_NDK_HOME (unsupported host or NDK layout)." >&2
33+
exit 1
34+
fi
35+
36+
WORK="$(pwd)/.mieru-build"
37+
OUT="$(pwd)/app/executableSo"
38+
39+
# Fetch source (shallow clone at the pinned tag). Re-clone fresh if the existing
40+
# checkout is missing or its git operations fail (e.g. corrupted by a prior build).
41+
need_clone=1
42+
if [ -d "$WORK/.git" ]; then
43+
if git -C "$WORK" fetch --depth 1 origin "refs/tags/$MIERU_VERSION" \
44+
&& git -C "$WORK" checkout -q FETCH_HEAD; then
45+
need_clone=0
46+
else
47+
echo ">> existing $WORK is unusable; re-cloning"
48+
fi
49+
fi
50+
if [ "$need_clone" -eq 1 ]; then
51+
rm -rf "$WORK"
52+
git clone --depth 1 --branch "$MIERU_VERSION" https://github.com/enfein/mieru.git "$WORK"
53+
fi
54+
55+
pushd "$WORK" >/dev/null
56+
57+
build_abi() {
58+
local abi="$1" goarch="$2" cc="$3" goarm="$4"
59+
echo ">> building libmieru.so for $abi"
60+
mkdir -p "$OUT/$abi"
61+
env GOOS=android GOARCH="$goarch" ${goarm:+GOARM=$goarm} CGO_ENABLED=1 \
62+
CC="$DEPS/$cc" \
63+
go build -trimpath -ldflags='-s -w' \
64+
-o "$OUT/$abi/libmieru.so" ./cmd/mieru
65+
}
66+
67+
build_abi "arm64-v8a" "arm64" "aarch64-linux-android21-clang"
68+
build_abi "armeabi-v7a" "arm" "armv7a-linux-androideabi21-clang" "7"
69+
build_abi "x86" "386" "i686-linux-android21-clang"
70+
build_abi "x86_64" "amd64" "x86_64-linux-android21-clang"
71+
72+
popd >/dev/null
73+
74+
echo ">> installed Mieru binaries:"
75+
ls -la "$OUT"/*/libmieru.so

0 commit comments

Comments
 (0)