b23377acde
排查 Android 打包"libbox.aar not found / Unresolved reference nekohasekai"踩坑:
- scripts/build-libbox.sh:
· 构建后自动部署产物到工程引用位置(android→app/kernel/dist/android/libbox.aar;
apple→client/{macos,ios}/Frameworks/Libbox.xcframework),不再留 /tmp 让人手动拷
· android 自动挑 NDK ≥28(未设 ANDROID_NDK_HOME 时):官方 build_libbox 拉 cronet-go,
其 libcronet.a 在 NDK 27 lld 链接报 unknown relocation(315)失败
· 头注释补全前置(NDK≥28 / JDK17 / gomobile 入 PATH)与产物路径
- app/kernel/build-android.sh:补 -javapkg io.nekohasekai —— 缺它产出默认包名 `libbox`,
与 Kotlin 的 io.nekohasekai.libbox 对不上、APK Kotlin 编译失败
- build.gradle:注释指明两条构建途径 + 包名必须 io.nekohasekai.libbox
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
173 lines
8.4 KiB
Bash
173 lines
8.4 KiB
Bash
#!/usr/bin/env bash
|
||
# build-android.sh — gomobile bind sing-box libbox → dist/android/libbox.aar
|
||
#
|
||
# 与 scripts/build-libbox.sh android 的关系(两条都能产 app/kernel/dist/android/libbox.aar):
|
||
# - 本脚本:裸 gomobile bind 本地 .build/singbox 源,tag 精简(无 tailscale/cronet)→
|
||
# 体积小、NDK 27 也能链接。**必须带 -javapkg io.nekohasekai**(见下方 bind),否则
|
||
# 产出包名是默认 `libbox`,与 Kotlin 的 io.nekohasekai.libbox 对不上、APK 编不过。
|
||
# - scripts/build-libbox.sh:走上游官方 build_libbox,带 tailscale/cronet → 体积大、需 NDK ≥ 28。
|
||
# 两者产出包名一致(io.nekohasekai.libbox),择一即可;product 是 gitignore 的,清掉就得重建。
|
||
#
|
||
# 用法: ./build-android.sh [--force]
|
||
# --force 忽略已有产物,强制重新构建
|
||
#
|
||
# 依赖:
|
||
# - Go : VERSION 中 GO_VERSION 指定的版本($GOROOT 或 PATH 中的 go)
|
||
# - gomobile: 脚本自动安装至 $GOPATH/bin,依赖 GOMOBILE_VERSION
|
||
# - Android NDK: 需设置 ANDROID_NDK_HOME 或 ANDROID_HOME(含 ndk-bundle/ndk/<ver>)
|
||
#
|
||
# 产物: dist/android/libbox.aar
|
||
# 包含三个 ABI: arm64-v8a / armeabi-v7a / x86_64
|
||
#
|
||
# Build tags 说明(对齐官方 SFA — sing-box for Android):
|
||
# with_quic — QUIC/HTTP3 协议支持(Hysteria2 必须)
|
||
# with_utls — uTLS 指纹模拟(TLS 防检测)
|
||
# with_clash_api — Clash 兼容 API(面板控制必须)
|
||
# with_gvisor — gVisor TUN 网络栈(高性能 TUN 回退)
|
||
#
|
||
# 裁剪开关(按需取消注释):
|
||
# with_grpc — gRPC 入站(约 +4 MB so 体积)
|
||
# with_ech — TLS ECH 扩展(约 +0.5 MB)
|
||
# with_wireguard — WireGuard 出站(约 +2 MB)
|
||
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
||
# ── 加载版本锚点 ─────────────────────────────────────────────────────────────
|
||
# shellcheck source=VERSION
|
||
. "${SCRIPT_DIR}/VERSION"
|
||
|
||
# ── 参数解析 ─────────────────────────────────────────────────────────────────
|
||
FORCE=false
|
||
for arg in "$@"; do
|
||
case "${arg}" in
|
||
--force) FORCE=true ;;
|
||
*) printf 'Unknown argument: %s\n' "${arg}" >&2; exit 1 ;;
|
||
esac
|
||
done
|
||
|
||
# ── 路径配置 ─────────────────────────────────────────────────────────────────
|
||
DIST_DIR="${SCRIPT_DIR}/dist/android"
|
||
OUT_AAR="${DIST_DIR}/libbox.aar"
|
||
BUILD_SRC="${SCRIPT_DIR}/.build/singbox"
|
||
|
||
# ── Build tags ────────────────────────────────────────────────────────────────
|
||
# 功能 tags(最小集:REALITY/VLESS + Hysteria2(quic) + uTLS + Clash API + gVisor)。
|
||
# badlinkname,tfogo_checklinkname0 = Go 1.23+ 的 //go:linkname 限制放行
|
||
# (对齐 sing-box cmd/internal/build_libbox 的 sharedTags,缺则 Go 1.24 编译失败)。
|
||
BUILD_TAGS="with_quic,with_utls,with_clash_api,with_gvisor,badlinkname,tfogo_checklinkname0"
|
||
# 取消注释以增加功能(会增大 .so 体积):
|
||
# BUILD_TAGS="${BUILD_TAGS},with_grpc"
|
||
# BUILD_TAGS="${BUILD_TAGS},with_ech"
|
||
# BUILD_TAGS="${BUILD_TAGS},with_wireguard"
|
||
|
||
# ── 幂等检查 ─────────────────────────────────────────────────────────────────
|
||
if [[ "${FORCE}" == false && -f "${OUT_AAR}" ]]; then
|
||
printf '✓ %s 已存在,跳过构建(传 --force 强制重建)\n' "${OUT_AAR}"
|
||
exit 0
|
||
fi
|
||
|
||
# ── 前置检查 ─────────────────────────────────────────────────────────────────
|
||
printf '==> 检查前置依赖…\n'
|
||
|
||
if ! command -v go >/dev/null 2>&1; then
|
||
printf '✗ go 未找到,请安装 Go %s\n' "${GO_VERSION}" >&2
|
||
exit 1
|
||
fi
|
||
if ! command -v git >/dev/null 2>&1; then
|
||
printf '✗ git 未找到\n' >&2
|
||
exit 1
|
||
fi
|
||
|
||
ACTUAL_GO="$(go version | awk '{print $3}' | sed 's/go//')"
|
||
printf ' Go 版本: %s(期望 %s)\n' "${ACTUAL_GO}" "${GO_VERSION}"
|
||
|
||
if [[ -n "${ANDROID_NDK_HOME:-}" ]]; then
|
||
printf ' NDK: %s\n' "${ANDROID_NDK_HOME}"
|
||
elif [[ -n "${ANDROID_HOME:-}" ]]; then
|
||
printf ' Android SDK: %s(gomobile 将自动定位 NDK)\n' "${ANDROID_HOME}"
|
||
else
|
||
printf '⚠ 未设置 ANDROID_NDK_HOME / ANDROID_HOME,gomobile init 可能失败\n' >&2
|
||
fi
|
||
|
||
# ── JDK(gomobile bind 末段要 javac/jar 编译 Java 绑定)─────────────────────────
|
||
# macOS 的 /usr/bin/javac 仅为桩(无系统 JDK 时报 "Unable to locate a Java Runtime")。
|
||
# 若未显式设 JAVA_HOME,优先用 Android Studio 自带 JBR(OpenJDK 21)。
|
||
if [[ -z "${JAVA_HOME:-}" ]]; then
|
||
AS_JBR="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
|
||
if [[ -x "${AS_JBR}/bin/javac" ]]; then
|
||
export JAVA_HOME="${AS_JBR}"
|
||
export PATH="${JAVA_HOME}/bin:${PATH}"
|
||
printf ' JDK: %s(Android Studio JBR)\n' "${JAVA_HOME}"
|
||
else
|
||
printf '⚠ 未设 JAVA_HOME 且未找到 Android Studio JBR,gomobile bind 末段 javac 可能失败\n' >&2
|
||
fi
|
||
else
|
||
printf ' JDK: %s(JAVA_HOME)\n' "${JAVA_HOME}"
|
||
fi
|
||
|
||
# ── 安装 gomobile(SagerNet fork,见 VERSION 注释)────────────────────────────
|
||
printf '==> 安装 %s/cmd/gomobile@%s…\n' "${GOMOBILE_MODULE}" "${GOMOBILE_VERSION}"
|
||
go install "${GOMOBILE_MODULE}/cmd/gomobile@${GOMOBILE_VERSION}"
|
||
go install "${GOMOBILE_MODULE}/cmd/gobind@${GOMOBILE_VERSION}"
|
||
|
||
printf '==> gomobile init…\n'
|
||
gomobile init
|
||
|
||
# ── 准备 sing-box 源码 ────────────────────────────────────────────────────────
|
||
printf '==> 准备 sing-box %s 源码…\n' "${SINGBOX_VERSION}"
|
||
|
||
EXISTING_TAG=""
|
||
if [[ -d "${BUILD_SRC}/.git" ]]; then
|
||
EXISTING_TAG="$(git -C "${BUILD_SRC}" describe --tags --exact-match 2>/dev/null)" || EXISTING_TAG="unknown"
|
||
fi
|
||
|
||
if [[ "${EXISTING_TAG}" == "${SINGBOX_VERSION}" ]]; then
|
||
printf ' 源码已是 %s,跳过克隆\n' "${SINGBOX_VERSION}"
|
||
else
|
||
if [[ -d "${BUILD_SRC}" ]]; then
|
||
printf ' 已有源码版本 %s ≠ %s,清理后重新克隆…\n' "${EXISTING_TAG}" "${SINGBOX_VERSION}"
|
||
rm -rf "${BUILD_SRC}"
|
||
fi
|
||
mkdir -p "${BUILD_SRC%/*}"
|
||
git clone --depth 1 --branch "${SINGBOX_VERSION}" \
|
||
https://github.com/SagerNet/sing-box.git "${BUILD_SRC}"
|
||
fi
|
||
|
||
# ── 构建 AAR ──────────────────────────────────────────────────────────────────
|
||
printf '==> 构建 libbox.aar\n'
|
||
printf ' target : android/arm64,android/arm,android/amd64\n'
|
||
printf ' api : 21\n'
|
||
printf ' tags : %s\n' "${BUILD_TAGS}"
|
||
|
||
mkdir -p "${DIST_DIR}"
|
||
|
||
# ldflags 对齐 sing-box cmd/internal/build_libbox 的 sharedFlags:
|
||
# -checklinkname=0 放行 badlinkname 的 //go:linkname(Go 1.23+ 默认 checklinkname=1
|
||
# 会拒 os.checkPidfdOnce 等内部符号引用,缺则链接失败)
|
||
# defaultGODEBUG=multipathtcp=0 关 MPTCP(与官方一致)
|
||
# -X ...constant.Version 版本戳
|
||
# -s -w -buildid= 裁剪符号表,产物更小
|
||
LDFLAGS="-X github.com/sagernet/sing-box/constant.Version=${SINGBOX_VERSION} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0"
|
||
|
||
(
|
||
cd "${BUILD_SRC}"
|
||
go mod download
|
||
gomobile bind \
|
||
-v \
|
||
-trimpath \
|
||
-buildvcs=false \
|
||
-ldflags "${LDFLAGS}" \
|
||
-target android/arm64,android/arm,android/amd64 \
|
||
-androidapi 21 \
|
||
-tags "${BUILD_TAGS}" \
|
||
-javapkg io.nekohasekai \
|
||
-o "${OUT_AAR}" \
|
||
./experimental/libbox
|
||
)
|
||
|
||
printf '\n✓ 构建完成: %s\n' "${OUT_AAR}"
|
||
printf ' ABI 验证(.so 文件列表):\n'
|
||
unzip -l "${OUT_AAR}" | grep -E '\.so$' | awk '{print " " $NF}' || true
|