ci: 完善构建流程和中国网络环境加速

This commit is contained in:
mofeng-git
2026-06-10 09:48:42 +08:00
parent 921c00c472
commit 4b65eebd5d
29 changed files with 7166 additions and 367 deletions

View File

@@ -5,13 +5,12 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
SOURCE_DIR=""
OUTPUT_DIR="${PROJECT_ROOT}/dist/android-alsa"
ANDROID_API="${ANDROID_API:-21}"
NDK_ROOT="${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}"
BUILD_ABIS="arm64-v8a armeabi-v7a"
JOBS="${JOBS:-$(nproc 2>/dev/null || echo 4)}"
ALSA_REPO="${ALSA_REPO:-https://github.com/alsa-project/alsa-lib.git}"
ALSA_VERSION="${ALSA_VERSION:-1.2.15}"
usage() {
cat <<'EOF'
@@ -19,8 +18,6 @@ Usage:
scripts/build-android-alsa.sh [options]
Options:
--source <dir> Existing alsa-lib source checkout. If omitted, the
script clones it into .tmp/android-alsa-src.
--output <dir> Output root. Default: dist/android-alsa
--ndk <dir> Android NDK root. Defaults to ANDROID_NDK_HOME or ANDROID_NDK_ROOT.
--api <level> Android API level. Default: 21.
@@ -44,10 +41,6 @@ fail() {
while [[ $# -gt 0 ]]; do
case "$1" in
--source)
SOURCE_DIR="${2:-}"
shift 2
;;
--output)
OUTPUT_DIR="${2:-}"
shift 2
@@ -77,16 +70,15 @@ done
[[ -n "$NDK_ROOT" ]] || fail "--ndk or ANDROID_NDK_HOME/ANDROID_NDK_ROOT is required"
[[ -d "$NDK_ROOT/toolchains/llvm/prebuilt" ]] || fail "Invalid NDK root: $NDK_ROOT"
if [[ -z "$SOURCE_DIR" ]]; then
SOURCE_DIR="${PROJECT_ROOT}/.tmp/android-alsa-src"
if [[ ! -d "$SOURCE_DIR/.git" ]]; then
rm -rf "$SOURCE_DIR"
git clone --depth 1 "$ALSA_REPO" "$SOURCE_DIR"
fi
fi
[[ -d "$SOURCE_DIR" ]] || fail "alsa-lib source not found: $SOURCE_DIR"
[[ -f "$SOURCE_DIR/configure.ac" || -f "$SOURCE_DIR/configure" ]] || fail "alsa-lib source layout not recognized under: $SOURCE_DIR"
SOURCE_DIR="${PROJECT_ROOT}/.tmp/android-alsa-src"
rm -rf "$SOURCE_DIR"
mkdir -p "${PROJECT_ROOT}/.tmp"
archive="${PROJECT_ROOT}/.tmp/alsa-lib-${ALSA_VERSION}.tar.bz2"
url="https://www.alsa-project.org/files/pub/lib/alsa-lib-${ALSA_VERSION}.tar.bz2"
echo "Downloading ALSA ${ALSA_VERSION}: $url"
curl -fL "$url" -o "$archive"
tar -xjf "$archive" -C "${PROJECT_ROOT}/.tmp"
mv "${PROJECT_ROOT}/.tmp/alsa-lib-${ALSA_VERSION}" "$SOURCE_DIR"
SOURCE_DIR="$(cd "$SOURCE_DIR" && pwd)"
mkdir -p "$OUTPUT_DIR"
@@ -94,11 +86,6 @@ OUTPUT_DIR="$(cd "$OUTPUT_DIR" && pwd)"
HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-x86_64"
TOOLCHAIN="${NDK_ROOT}/toolchains/llvm/prebuilt/${HOST_TAG}"
ANDROID_TOOLCHAIN_FILE="${NDK_ROOT}/build/cmake/android.toolchain.cmake"
[[ -d "$TOOLCHAIN/bin" ]] || fail "NDK LLVM toolchain not found: $TOOLCHAIN"
[[ -f "$ANDROID_TOOLCHAIN_FILE" ]] || fail "NDK CMake toolchain not found: $ANDROID_TOOLCHAIN_FILE"
command -v cmake >/dev/null 2>&1 || fail "cmake is required"
command -v autoreconf >/dev/null 2>&1 || fail "autoreconf is required"
normalize_abis() {
printf '%s\n' "$BUILD_ABIS" | tr ',' ' '
@@ -147,14 +134,6 @@ build_one() {
clean_generated_source_headers
if [[ -f "$SOURCE_DIR/config.status" || -f "$SOURCE_DIR/Makefile" ]]; then
(
cd "$SOURCE_DIR"
make distclean >/dev/null 2>&1 || true
)
clean_generated_source_headers
fi
if [[ ! -x "$SOURCE_DIR/configure" ]]; then
(
cd "$SOURCE_DIR"
@@ -178,6 +157,8 @@ build_one() {
--disable-doc \
--disable-oss \
--disable-seq \
--disable-ucm \
--disable-topology \
--disable-rawmidi \
--disable-hwdep \
--disable-usb \

View File

@@ -5,21 +5,17 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
SOURCE_DIR=""
OUTPUT_DIR="${PROJECT_ROOT}/dist/android-ffmpeg-mediacodec"
ANDROID_API="${ANDROID_API:-21}"
NDK_ROOT="${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}"
BUILD_ABIS="arm64-v8a armeabi-v7a"
JOBS="${JOBS:-$(nproc 2>/dev/null || echo 4)}"
FFMPEG_ROCKCHIP_REV="${FFMPEG_ROCKCHIP_REV:-40c412daccf08164493da0de990eb99a8948116b}"
usage() {
cat <<'EOF'
Usage:
scripts/build-android-ffmpeg-mediacodec.sh --source <ffmpeg-source-dir> [options]
Required:
--source <dir> FFmpeg source directory. For the downloaded package,
use the extracted ffmpeg-rockchip directory.
scripts/build-android-ffmpeg-mediacodec.sh [options]
Options:
--output <dir> Output root. Default: dist/android-ffmpeg-mediacodec
@@ -35,9 +31,7 @@ The output layout is compatible with ONE_KVM_ANDROID_FFMPEG_ROOT:
<output>/armeabi-v7a/lib
Example:
scripts/build-android-ffmpeg-mediacodec.sh \
--source .tmp/android-ffmpeg-check/src/ffmpeg-rockchip \
--output /opt/one-kvm/android-ffmpeg
scripts/build-android-ffmpeg-mediacodec.sh --output /opt/one-kvm/android-ffmpeg
export ONE_KVM_ANDROID_FFMPEG_ROOT=/opt/one-kvm/android-ffmpeg
cd android && ./gradlew :app:assembleDebug
@@ -51,10 +45,6 @@ fail() {
while [[ $# -gt 0 ]]; do
case "$1" in
--source)
SOURCE_DIR="${2:-}"
shift 2
;;
--output)
OUTPUT_DIR="${2:-}"
shift 2
@@ -81,11 +71,24 @@ while [[ $# -gt 0 ]]; do
esac
done
[[ -n "$SOURCE_DIR" ]] || fail "--source is required"
[[ -d "$SOURCE_DIR" ]] || fail "FFmpeg source not found: $SOURCE_DIR"
[[ -x "$SOURCE_DIR/configure" ]] || fail "FFmpeg configure script not found under: $SOURCE_DIR"
SOURCE_DIR="${PROJECT_ROOT}/.tmp/android-ffmpeg-check/src/ffmpeg-rockchip"
rm -rf "$SOURCE_DIR"
mkdir -p "$(dirname "$SOURCE_DIR")"
repo_url="https://github.com/nyanmisaka/ffmpeg-rockchip.git"
if [[ "${CHINAMIRRO:-0}" == "1" ]]; then
repo_url="${GH_PROXY:-https://gh-proxy.com}"
repo_url="${repo_url%/}/https://github.com/nyanmisaka/ffmpeg-rockchip.git"
fi
echo "Cloning FFmpeg source: $repo_url"
git init "$SOURCE_DIR"
(
cd "$SOURCE_DIR"
git remote add origin "$repo_url"
git fetch --depth 1 origin "$FFMPEG_ROCKCHIP_REV"
git checkout --detach FETCH_HEAD
)
[[ -n "$NDK_ROOT" ]] || fail "--ndk or ANDROID_NDK_HOME/ANDROID_NDK_ROOT is required"
[[ -d "$NDK_ROOT/toolchains/llvm/prebuilt" ]] || fail "Invalid NDK root: $NDK_ROOT"
SOURCE_DIR="$(cd "$SOURCE_DIR" && pwd)"
mkdir -p "$OUTPUT_DIR"
@@ -93,7 +96,6 @@ OUTPUT_DIR="$(cd "$OUTPUT_DIR" && pwd)"
HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-x86_64"
TOOLCHAIN="${NDK_ROOT}/toolchains/llvm/prebuilt/${HOST_TAG}"
[[ -d "$TOOLCHAIN/bin" ]] || fail "NDK LLVM toolchain not found: $TOOLCHAIN"
normalize_abis() {
printf '%s\n' "$BUILD_ABIS" | tr ',' ' '
@@ -106,11 +108,6 @@ patch_android_ffmpeg_mjpeg_mediacodec() {
local allcodecs="${avcodec_dir}/allcodecs.c"
local makefile="${avcodec_dir}/Makefile"
[[ -f "$mediacodecdec" ]] || fail "FFmpeg mediacodecdec.c not found: $mediacodecdec"
[[ -f "$allcodecs" ]] || fail "FFmpeg allcodecs.c not found: $allcodecs"
[[ -f "$makefile" ]] || fail "FFmpeg libavcodec Makefile not found: $makefile"
[[ -f "$configure_file" ]] || fail "FFmpeg configure not found: $configure_file"
python3 - "$mediacodecdec" "$allcodecs" "$configure_file" "$makefile" <<'PY'
from pathlib import Path
import sys
@@ -218,8 +215,6 @@ build_one() {
extra_cflags="-fPIC"
extra_ldflags=""
[[ -x "$cc" ]] || fail "Missing compiler: $cc"
if [[ "$abi" == "armeabi-v7a" ]]; then
extra_cflags="${extra_cflags} -march=armv7-a -mfloat-abi=softfp -mfpu=neon"
extra_ldflags="${extra_ldflags} -Wl,--fix-cortex-a8"

View File

@@ -5,14 +5,13 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
SOURCE_DIR=""
OUTPUT_DIR="${PROJECT_ROOT}/dist/android-libyuv"
JPEG_ROOT="${ONE_KVM_ANDROID_TURBOJPEG_ROOT:-${PROJECT_ROOT}/dist/android-turbojpeg}"
ANDROID_API="${ANDROID_API:-21}"
NDK_ROOT="${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}"
BUILD_ABIS="arm64-v8a armeabi-v7a"
JOBS="${JOBS:-$(nproc 2>/dev/null || echo 4)}"
LIBYUV_REPO="${LIBYUV_REPO:-https://github.com/lemenkov/libyuv.git}"
LIBYUV_REV="${LIBYUV_REV:-957f295ea946cbbd13fcfc46e7066f2efa801233}"
usage() {
cat <<'EOF'
@@ -20,8 +19,6 @@ Usage:
scripts/build-android-libyuv.sh [options]
Options:
--source <dir> Existing libyuv source checkout. If omitted, the script
clones libyuv into .tmp/android-libyuv-src.
--output <dir> Output root. Default: dist/android-libyuv
--ndk <dir> Android NDK root. Defaults to ANDROID_NDK_HOME or ANDROID_NDK_ROOT.
--api <level> Android API level. Default: 21.
@@ -51,10 +48,6 @@ fail() {
while [[ $# -gt 0 ]]; do
case "$1" in
--source)
SOURCE_DIR="${2:-}"
shift 2
;;
--output)
OUTPUT_DIR="${2:-}"
shift 2
@@ -88,27 +81,29 @@ done
[[ -n "$NDK_ROOT" ]] || fail "--ndk or ANDROID_NDK_HOME/ANDROID_NDK_ROOT is required"
[[ -d "$NDK_ROOT/toolchains/llvm/prebuilt" ]] || fail "Invalid NDK root: $NDK_ROOT"
if [[ -z "$SOURCE_DIR" ]]; then
SOURCE_DIR="${PROJECT_ROOT}/.tmp/android-libyuv-src"
if [[ ! -d "$SOURCE_DIR/.git" ]]; then
rm -rf "$SOURCE_DIR"
git clone --depth 1 "$LIBYUV_REPO" "$SOURCE_DIR"
fi
SOURCE_DIR="${PROJECT_ROOT}/.tmp/android-libyuv-src"
rm -rf "$SOURCE_DIR"
repo_url="https://github.com/lemenkov/libyuv.git"
if [[ "${CHINAMIRRO:-0}" == "1" ]]; then
repo_url="${GH_PROXY:-https://gh-proxy.com}"
repo_url="${repo_url%/}/https://github.com/lemenkov/libyuv.git"
fi
[[ -d "$SOURCE_DIR" ]] || fail "libyuv source not found: $SOURCE_DIR"
[[ -f "$SOURCE_DIR/CMakeLists.txt" ]] || fail "libyuv CMakeLists.txt not found under: $SOURCE_DIR"
echo "Cloning libyuv source: $repo_url"
git init "$SOURCE_DIR"
(
cd "$SOURCE_DIR"
git remote add origin "$repo_url"
git fetch --depth 1 origin "$LIBYUV_REV"
git checkout --detach FETCH_HEAD
)
SOURCE_DIR="$(cd "$SOURCE_DIR" && pwd)"
mkdir -p "$OUTPUT_DIR"
OUTPUT_DIR="$(cd "$OUTPUT_DIR" && pwd)"
HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-x86_64"
TOOLCHAIN="${NDK_ROOT}/toolchains/llvm/prebuilt/${HOST_TAG}"
ANDROID_TOOLCHAIN_FILE="${NDK_ROOT}/build/cmake/android.toolchain.cmake"
[[ -d "$TOOLCHAIN/bin" ]] || fail "NDK LLVM toolchain not found: $TOOLCHAIN"
[[ -f "$ANDROID_TOOLCHAIN_FILE" ]] || fail "NDK CMake toolchain not found: $ANDROID_TOOLCHAIN_FILE"
command -v cmake >/dev/null 2>&1 || fail "cmake is required"
normalize_abis() {
printf '%s\n' "$BUILD_ABIS" | tr ',' ' '

View File

@@ -5,16 +5,12 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
SOURCE_DIR=""
OUTPUT_DIR="${PROJECT_ROOT}/dist/android-opus"
ANDROID_API="${ANDROID_API:-21}"
NDK_ROOT="${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}"
BUILD_ABIS="arm64-v8a armeabi-v7a"
JOBS="${JOBS:-$(nproc 2>/dev/null || echo 4)}"
OPUS_VERSION="${OPUS_VERSION:-1.5.2}"
OPUS_TARBALL_URL="${OPUS_TARBALL_URL:-https://downloads.xiph.org/releases/opus/opus-${OPUS_VERSION}.tar.gz}"
OPUS_TARBALL_SHA256="${OPUS_TARBALL_SHA256:-65c1d2f78b9f2fb20082c38cbe47c951ad5839345876e46941612ee87f9a7ce1}"
LOCAL_OPUS_TARBALL="${LOCAL_OPUS_TARBALL:-${PROJECT_ROOT}/opus-${OPUS_VERSION}.tar.gz}"
usage() {
cat <<'EOF'
@@ -22,9 +18,6 @@ Usage:
scripts/build-android-opus.sh [options]
Options:
--source <dir> Existing opus source checkout. If omitted, the script
downloads and extracts the official source tarball
into .tmp/android-opus-src.
--output <dir> Output root. Default: dist/android-opus
--ndk <dir> Android NDK root. Defaults to ANDROID_NDK_HOME or ANDROID_NDK_ROOT.
--api <level> Android API level. Default: 21.
@@ -46,10 +39,6 @@ fail() {
while [[ $# -gt 0 ]]; do
case "$1" in
--source)
SOURCE_DIR="${2:-}"
shift 2
;;
--output)
OUTPUT_DIR="${2:-}"
shift 2
@@ -79,25 +68,13 @@ done
[[ -n "$NDK_ROOT" ]] || fail "--ndk or ANDROID_NDK_HOME/ANDROID_NDK_ROOT is required"
[[ -d "$NDK_ROOT/toolchains/llvm/prebuilt" ]] || fail "Invalid NDK root: $NDK_ROOT"
if [[ -z "$SOURCE_DIR" ]]; then
SOURCE_DIR="${PROJECT_ROOT}/.tmp/android-opus-src"
if [[ ! -f "$SOURCE_DIR/configure" ]]; then
rm -rf "$SOURCE_DIR"
mkdir -p "$SOURCE_DIR"
tarball="${PROJECT_ROOT}/.tmp/opus-${OPUS_VERSION}.tar.gz"
if [[ -f "$LOCAL_OPUS_TARBALL" ]]; then
cp "$LOCAL_OPUS_TARBALL" "$tarball"
else
command -v curl >/dev/null 2>&1 || fail "curl is required to download opus source"
curl -fsSL "$OPUS_TARBALL_URL" -o "$tarball"
fi
echo "${OPUS_TARBALL_SHA256} ${tarball}" | sha256sum -c -
tar -xzf "$tarball" -C "$SOURCE_DIR" --strip-components=1
fi
fi
[[ -d "$SOURCE_DIR" ]] || fail "opus source not found: $SOURCE_DIR"
[[ -x "$SOURCE_DIR/configure" || -f "$SOURCE_DIR/configure.ac" ]] || fail "opus source layout not recognized under: $SOURCE_DIR"
SOURCE_DIR="${PROJECT_ROOT}/.tmp/android-opus-src"
rm -rf "$SOURCE_DIR"
mkdir -p "$SOURCE_DIR"
tarball="${PROJECT_ROOT}/.tmp/opus-${OPUS_VERSION}.tar.gz"
url="https://downloads.xiph.org/releases/opus/opus-${OPUS_VERSION}.tar.gz"
curl -fsSL "$url" -o "$tarball"
tar -xzf "$tarball" -C "$SOURCE_DIR" --strip-components=1
SOURCE_DIR="$(cd "$SOURCE_DIR" && pwd)"
mkdir -p "$OUTPUT_DIR"
@@ -105,10 +82,6 @@ OUTPUT_DIR="$(cd "$OUTPUT_DIR" && pwd)"
HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-x86_64"
TOOLCHAIN="${NDK_ROOT}/toolchains/llvm/prebuilt/${HOST_TAG}"
ANDROID_TOOLCHAIN_FILE="${NDK_ROOT}/build/cmake/android.toolchain.cmake"
[[ -d "$TOOLCHAIN/bin" ]] || fail "NDK LLVM toolchain not found: $TOOLCHAIN"
[[ -f "$ANDROID_TOOLCHAIN_FILE" ]] || fail "NDK CMake toolchain not found: $ANDROID_TOOLCHAIN_FILE"
command -v cmake >/dev/null 2>&1 || fail "cmake is required"
normalize_abis() {
printf '%s\n' "$BUILD_ABIS" | tr ',' ' '
@@ -161,14 +134,6 @@ build_one() {
make install
)
mkdir -p "$prefix/lib" "$prefix/include"
if [[ -f "$prefix/include/opus/opus.h" ]]; then
:
elif [[ -f "$SOURCE_DIR/include/opus/opus.h" ]]; then
mkdir -p "$prefix/include/opus"
cp "$SOURCE_DIR/include/opus/opus.h" "$prefix/include/opus/opus.h"
fi
echo "Built Opus for ${abi}: ${prefix}"
}

View File

@@ -5,13 +5,12 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
SOURCE_DIR=""
OUTPUT_DIR="${PROJECT_ROOT}/dist/android-turbojpeg"
ANDROID_API="${ANDROID_API:-21}"
NDK_ROOT="${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}"
BUILD_ABIS="arm64-v8a armeabi-v7a"
JOBS="${JOBS:-$(nproc 2>/dev/null || echo 4)}"
LIBJPEG_TURBO_REPO="${LIBJPEG_TURBO_REPO:-https://github.com/libjpeg-turbo/libjpeg-turbo.git}"
LIBJPEG_TURBO_VERSION="${LIBJPEG_TURBO_VERSION:-3.1.4.1}"
usage() {
cat <<'EOF'
@@ -19,8 +18,6 @@ Usage:
scripts/build-android-turbojpeg.sh [options]
Options:
--source <dir> Existing libjpeg-turbo source checkout. If omitted,
the script clones it into .tmp/android-turbojpeg-src.
--output <dir> Output root. Default: dist/android-turbojpeg
--ndk <dir> Android NDK root. Defaults to ANDROID_NDK_HOME or ANDROID_NDK_ROOT.
--api <level> Android API level. Default: 21.
@@ -46,10 +43,6 @@ fail() {
while [[ $# -gt 0 ]]; do
case "$1" in
--source)
SOURCE_DIR="${2:-}"
shift 2
;;
--output)
OUTPUT_DIR="${2:-}"
shift 2
@@ -79,27 +72,29 @@ done
[[ -n "$NDK_ROOT" ]] || fail "--ndk or ANDROID_NDK_HOME/ANDROID_NDK_ROOT is required"
[[ -d "$NDK_ROOT/toolchains/llvm/prebuilt" ]] || fail "Invalid NDK root: $NDK_ROOT"
if [[ -z "$SOURCE_DIR" ]]; then
SOURCE_DIR="${PROJECT_ROOT}/.tmp/android-turbojpeg-src"
if [[ ! -d "$SOURCE_DIR/.git" ]]; then
rm -rf "$SOURCE_DIR"
git clone --depth 1 "$LIBJPEG_TURBO_REPO" "$SOURCE_DIR"
fi
SOURCE_DIR="${PROJECT_ROOT}/.tmp/android-turbojpeg-src"
rm -rf "$SOURCE_DIR"
repo_url="https://github.com/libjpeg-turbo/libjpeg-turbo.git"
if [[ "${CHINAMIRRO:-0}" == "1" ]]; then
repo_url="${GH_PROXY:-https://gh-proxy.com}"
repo_url="${repo_url%/}/https://github.com/libjpeg-turbo/libjpeg-turbo.git"
fi
[[ -d "$SOURCE_DIR" ]] || fail "libjpeg-turbo source not found: $SOURCE_DIR"
[[ -f "$SOURCE_DIR/CMakeLists.txt" ]] || fail "libjpeg-turbo CMakeLists.txt not found under: $SOURCE_DIR"
echo "Cloning libjpeg-turbo ${LIBJPEG_TURBO_VERSION}: $repo_url"
git init "$SOURCE_DIR"
(
cd "$SOURCE_DIR"
git remote add origin "$repo_url"
git fetch --depth 1 origin "refs/tags/$LIBJPEG_TURBO_VERSION"
git checkout --detach FETCH_HEAD
)
SOURCE_DIR="$(cd "$SOURCE_DIR" && pwd)"
mkdir -p "$OUTPUT_DIR"
OUTPUT_DIR="$(cd "$OUTPUT_DIR" && pwd)"
HOST_TAG="$(uname -s | tr '[:upper:]' '[:lower:]')-x86_64"
TOOLCHAIN="${NDK_ROOT}/toolchains/llvm/prebuilt/${HOST_TAG}"
ANDROID_TOOLCHAIN_FILE="${NDK_ROOT}/build/cmake/android.toolchain.cmake"
[[ -d "$TOOLCHAIN/bin" ]] || fail "NDK LLVM toolchain not found: $TOOLCHAIN"
[[ -f "$ANDROID_TOOLCHAIN_FILE" ]] || fail "NDK CMake toolchain not found: $ANDROID_TOOLCHAIN_FILE"
command -v cmake >/dev/null 2>&1 || fail "cmake is required"
normalize_abis() {
printf '%s\n' "$BUILD_ABIS" | tr ',' ' '