mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 03:32:00 +08:00
310 lines
13 KiB
Docker
310 lines
13 KiB
Docker
# Android build image for One-KVM
|
|
# Based on Debian 11 for stable toolchain/runtime compatibility
|
|
|
|
FROM debian:11
|
|
|
|
ARG CHINAMIRRO=0
|
|
ARG ANDROID_SDK_ROOT=/root/android-sdk
|
|
ARG ANDROID_CMDLINE_TOOLS_VERSION=11076708_latest
|
|
ARG ANDROID_NDK_VERSION=27.3.13750724
|
|
ARG ANDROID_PLATFORM=36
|
|
ARG ANDROID_BUILD_TOOLS=36.0.0
|
|
ARG CARGO_NDK_VERSION=4.1.2
|
|
ARG RUSTUP_DIST_SERVER_CN=https://mirrors.tuna.tsinghua.edu.cn/rustup
|
|
ARG RUSTUP_UPDATE_ROOT_CN=https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup
|
|
ARG CARGO_REGISTRY_CN=sparse+https://mirrors.tuna.tsinghua.edu.cn/crates.io-index/
|
|
ARG MAVEN_REPOSITORY_CN=https://maven.aliyun.com/repository/public
|
|
ARG GOOGLE_MAVEN_REPOSITORY_CN=https://maven.aliyun.com/repository/google
|
|
ARG GRADLE_DISTRIBUTION_URL_CN=https://mirrors.cloud.tencent.com/gradle/gradle-9.1.0-bin.zip
|
|
ARG ANDROID_CMDLINE_TOOLS_URL=
|
|
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
ENV ANDROID_HOME=${ANDROID_SDK_ROOT}
|
|
ENV ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}
|
|
ENV ANDROID_NDK_HOME=${ANDROID_SDK_ROOT}/ndk/${ANDROID_NDK_VERSION}
|
|
ENV ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/${ANDROID_NDK_VERSION}
|
|
ENV ANDROID_BUILD_TOOLS=${ANDROID_BUILD_TOOLS}
|
|
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
|
|
ENV PATH=/root/.cargo/bin:${PATH}
|
|
ENV ONE_KVM_GRADLE_DISTRIBUTION_URL_CN=${GRADLE_DISTRIBUTION_URL_CN}
|
|
|
|
RUN if [ "$CHINAMIRRO" = "1" ]; then \
|
|
sed -i \
|
|
-e 's|http://deb.debian.org/debian|http://mirrors.tuna.tsinghua.edu.cn/debian|g' \
|
|
-e 's|http://security.debian.org/debian-security|http://mirrors.tuna.tsinghua.edu.cn/debian-security|g' \
|
|
/etc/apt/sources.list; \
|
|
fi
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
ca-certificates \
|
|
curl \
|
|
wget \
|
|
unzip \
|
|
zip \
|
|
git \
|
|
bash \
|
|
build-essential \
|
|
pkg-config \
|
|
cmake \
|
|
ninja-build \
|
|
autoconf \
|
|
automake \
|
|
libtool \
|
|
nasm \
|
|
yasm \
|
|
python3 \
|
|
openjdk-17-jdk-headless \
|
|
libstdc++6 \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
RUN if [ "$CHINAMIRRO" = "1" ]; then \
|
|
export RUSTUP_DIST_SERVER=${RUSTUP_DIST_SERVER_CN}; \
|
|
export RUSTUP_UPDATE_ROOT=${RUSTUP_UPDATE_ROOT_CN}; \
|
|
mkdir -p /root/.cargo; \
|
|
printf '%s\n' \
|
|
'[source.crates-io]' \
|
|
"replace-with = 'tuna'" \
|
|
'[source.tuna]' \
|
|
"registry = '${CARGO_REGISTRY_CN}'" \
|
|
'[registries.tuna]' \
|
|
"index = '${CARGO_REGISTRY_CN}'" \
|
|
> /root/.cargo/config.toml; \
|
|
fi \
|
|
&& curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable \
|
|
&& cargo install cargo-ndk --version ${CARGO_NDK_VERSION} --locked \
|
|
&& rustup target add armv7-linux-androideabi aarch64-linux-android
|
|
|
|
RUN mkdir -p /opt/android-cmdline-tools \
|
|
&& cd /tmp \
|
|
&& if [ -n "$ANDROID_CMDLINE_TOOLS_URL" ]; then \
|
|
wget -q "$ANDROID_CMDLINE_TOOLS_URL" -O cmdline-tools.zip; \
|
|
else \
|
|
wget -q https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS_VERSION}.zip -O cmdline-tools.zip; \
|
|
fi \
|
|
&& unzip -q cmdline-tools.zip -d /opt/android-cmdline-tools \
|
|
&& mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools/latest \
|
|
&& mv /opt/android-cmdline-tools/cmdline-tools/* ${ANDROID_SDK_ROOT}/cmdline-tools/latest/ \
|
|
&& rm -rf /tmp/cmdline-tools.zip /opt/android-cmdline-tools
|
|
|
|
RUN mkdir -p ${ANDROID_SDK_ROOT}/licenses \
|
|
&& yes | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=${ANDROID_SDK_ROOT} --licenses >/dev/null \
|
|
&& ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=${ANDROID_SDK_ROOT} \
|
|
"platform-tools" \
|
|
"platforms;android-${ANDROID_PLATFORM}" \
|
|
"build-tools;${ANDROID_BUILD_TOOLS}" \
|
|
"ndk;${ANDROID_NDK_VERSION}" \
|
|
"cmake;3.22.1" \
|
|
&& mkdir -p ${ANDROID_NDK_HOME}
|
|
|
|
RUN if [ "$CHINAMIRRO" = "1" ]; then \
|
|
mkdir -p /root/.gradle; \
|
|
printf '%s\n' \
|
|
"beforeSettings { settings ->" \
|
|
" settings.pluginManagement.repositories.maven { url = uri('${GOOGLE_MAVEN_REPOSITORY_CN}') }" \
|
|
" settings.pluginManagement.repositories.maven { url = uri('${MAVEN_REPOSITORY_CN}') }" \
|
|
" settings.dependencyResolutionManagement.repositories.maven { url = uri('${GOOGLE_MAVEN_REPOSITORY_CN}') }" \
|
|
" settings.dependencyResolutionManagement.repositories.maven { url = uri('${MAVEN_REPOSITORY_CN}') }" \
|
|
"}" \
|
|
"allprojects {" \
|
|
" buildscript.repositories.maven { url = uri('${GOOGLE_MAVEN_REPOSITORY_CN}') }" \
|
|
" buildscript.repositories.maven { url = uri('${MAVEN_REPOSITORY_CN}') }" \
|
|
"}" \
|
|
> /root/.gradle/init.gradle; \
|
|
fi
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
libclang-dev \
|
|
llvm \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
ENV LIBCLANG_PATH=/usr/lib/llvm-11/lib
|
|
|
|
RUN printf '%s\n' \
|
|
'#!/usr/bin/env bash' \
|
|
'set -euo pipefail' \
|
|
'' \
|
|
'PROJECT_ROOT="${ONE_KVM_ANDROID_PROJECT_ROOT:-/workspace}"' \
|
|
'ANDROID_DIR="${PROJECT_ROOT}/android"' \
|
|
'BUILD_TYPE="release"' \
|
|
'ARCH="${1:-all}"' \
|
|
'FFMPEG_ROOT="${ONE_KVM_ANDROID_FFMPEG_ROOT:-${PROJECT_ROOT}/dist/android-ffmpeg-mediacodec}"' \
|
|
'OUTPUT_DIR="${PROJECT_ROOT}/target/android"' \
|
|
'SIGNING_DIR="${PROJECT_ROOT}/target/android-signing"' \
|
|
'KEYSTORE_PATH="${SIGNING_DIR}/one-kvm-release.jks"' \
|
|
'KEY_ALIAS="one-kvm-release"' \
|
|
'KEY_PASSWORD="one-kvm-release"' \
|
|
'ANDROID_BUILD_TOOLS_DIR="${ANDROID_SDK_ROOT}/build-tools/${ANDROID_BUILD_TOOLS}"' \
|
|
'WRAPPER_PROPERTIES="$ANDROID_DIR/gradle/wrapper/gradle-wrapper.properties"' \
|
|
'GRADLE_DISTRIBUTION_URL="${ONE_KVM_GRADLE_DISTRIBUTION_URL:-}"' \
|
|
'GRADLE_DISTRIBUTION_URL_CN="${ONE_KVM_GRADLE_DISTRIBUTION_URL_CN:-https://mirrors.cloud.tencent.com/gradle/gradle-9.1.0-bin.zip}"' \
|
|
'GRADLE_NETWORK_TIMEOUT="${ONE_KVM_GRADLE_NETWORK_TIMEOUT:-120000}"' \
|
|
'' \
|
|
'usage() {' \
|
|
' cat <<EOF' \
|
|
'Usage:' \
|
|
' docker run --rm -v "$PWD:/workspace" one-kvm-android-build:cn [arm64|armv7|all|help]' \
|
|
'' \
|
|
'Commands:' \
|
|
' all Build arm64 and armv7 APKs. Default.' \
|
|
' arm64 Build only arm64 APK.' \
|
|
' armv7 Build only ARMv7 APK.' \
|
|
' help Show this help.' \
|
|
'' \
|
|
'APK output:' \
|
|
' target/android/one-kvm_<version>_<arm32|arm64>.apk' \
|
|
'EOF' \
|
|
'}' \
|
|
'' \
|
|
'fail() {' \
|
|
' echo "Error: $*" >&2' \
|
|
' exit 1' \
|
|
'}' \
|
|
'' \
|
|
'read_project_version() {' \
|
|
' local version' \
|
|
' version="$(awk -F "\"" '"'"'/^version[[:space:]]*=/ { print $2; exit }'"'"' "$PROJECT_ROOT/Cargo.toml")"' \
|
|
' [[ -n "$version" ]] || fail "Failed to resolve version from $PROJECT_ROOT/Cargo.toml"' \
|
|
' printf "%s\n" "$version"' \
|
|
'}' \
|
|
'' \
|
|
'copy_apks() {' \
|
|
' local flavor="$1"' \
|
|
' local src_dir="$ANDROID_DIR/app/build/outputs/apk/$flavor/$BUILD_TYPE"' \
|
|
' local found=0' \
|
|
' mkdir -p "$OUTPUT_DIR"' \
|
|
' for apk in "$src_dir"/*.apk; do' \
|
|
' [[ -f "$apk" ]] || continue' \
|
|
' sign_apk "$apk" "$OUTPUT_DIR/one-kvm_${PROJECT_VERSION}_${flavor}.apk"' \
|
|
' found=1' \
|
|
' done' \
|
|
' [[ "$found" == "1" ]] || fail "No APK files found in: $src_dir"' \
|
|
'}' \
|
|
'' \
|
|
'ensure_keystore() {' \
|
|
' if [[ -f "$KEYSTORE_PATH" ]]; then' \
|
|
' return' \
|
|
' fi' \
|
|
' mkdir -p "$SIGNING_DIR"' \
|
|
' keytool -genkeypair -noprompt -keystore "$KEYSTORE_PATH" -storetype PKCS12 -alias "$KEY_ALIAS" -keyalg RSA -keysize 2048 -validity 10000 -storepass "$KEY_PASSWORD" -keypass "$KEY_PASSWORD" -dname "CN=One-KVM, OU=One-KVM, O=One-KVM, L=Local, S=Local, C=US" >/dev/null' \
|
|
'}' \
|
|
'' \
|
|
'sign_apk() {' \
|
|
' local input_apk="$1"' \
|
|
' local output_apk="$2"' \
|
|
' local aligned_apk' \
|
|
' aligned_apk="$(mktemp --suffix=.apk)"' \
|
|
' "$ANDROID_BUILD_TOOLS_DIR/zipalign" -f -p 4 "$input_apk" "$aligned_apk"' \
|
|
' "$ANDROID_BUILD_TOOLS_DIR/apksigner" sign --ks "$KEYSTORE_PATH" --ks-key-alias "$KEY_ALIAS" --ks-pass "pass:$KEY_PASSWORD" --key-pass "pass:$KEY_PASSWORD" --out "$output_apk" "$aligned_apk"' \
|
|
' "$ANDROID_BUILD_TOOLS_DIR/apksigner" verify --verbose "$output_apk" >/dev/null' \
|
|
' rm -f "$aligned_apk"' \
|
|
'}' \
|
|
'' \
|
|
'cd "$PROJECT_ROOT"' \
|
|
'' \
|
|
'case "$ARCH" in' \
|
|
'help | --help | -h)' \
|
|
' usage' \
|
|
' exit 0' \
|
|
' ;;' \
|
|
'esac' \
|
|
'' \
|
|
'[[ -d "$ANDROID_DIR" ]] || fail "Android project not found: $ANDROID_DIR"' \
|
|
'[[ -x "$ANDROID_DIR/gradlew" ]] || fail "Gradle wrapper is not executable: $ANDROID_DIR/gradlew"' \
|
|
'[[ -f "$WRAPPER_PROPERTIES" ]] || fail "Gradle wrapper properties not found: $WRAPPER_PROPERTIES"' \
|
|
'' \
|
|
'ORIGINAL_WRAPPER_PROPERTIES="$(mktemp)"' \
|
|
'cp "$WRAPPER_PROPERTIES" "$ORIGINAL_WRAPPER_PROPERTIES"' \
|
|
'cleanup_wrapper_properties() {' \
|
|
' cp "$ORIGINAL_WRAPPER_PROPERTIES" "$WRAPPER_PROPERTIES"' \
|
|
' rm -f "$ORIGINAL_WRAPPER_PROPERTIES"' \
|
|
'}' \
|
|
'trap cleanup_wrapper_properties EXIT' \
|
|
'' \
|
|
'if [[ "${CHINAMIRRO:-0}" == "1" && -z "$GRADLE_DISTRIBUTION_URL" ]]; then' \
|
|
' GRADLE_DISTRIBUTION_URL="$GRADLE_DISTRIBUTION_URL_CN"' \
|
|
'fi' \
|
|
'' \
|
|
'if [[ -n "$GRADLE_DISTRIBUTION_URL" ]]; then' \
|
|
' WRAPPER_PROPERTIES_TMP="$(mktemp)"' \
|
|
' awk -v url="$GRADLE_DISTRIBUTION_URL" -v timeout="$GRADLE_NETWORK_TIMEOUT" '"'"'' \
|
|
' BEGIN { seen_url = 0; seen_timeout = 0 }' \
|
|
' /^distributionUrl=/ { print "distributionUrl=" url; seen_url = 1; next }' \
|
|
' /^networkTimeout=/ { print "networkTimeout=" timeout; seen_timeout = 1; next }' \
|
|
' { print }' \
|
|
' END {' \
|
|
' if (!seen_url) print "distributionUrl=" url;' \
|
|
' if (!seen_timeout) print "networkTimeout=" timeout;' \
|
|
' }' \
|
|
' '"'"' "$WRAPPER_PROPERTIES" > "$WRAPPER_PROPERTIES_TMP"' \
|
|
' cp "$WRAPPER_PROPERTIES_TMP" "$WRAPPER_PROPERTIES"' \
|
|
' rm -f "$WRAPPER_PROPERTIES_TMP"' \
|
|
' find /root/.gradle/wrapper/dists -name "*.lck" -o -name "*.part" 2>/dev/null | xargs -r rm -f' \
|
|
'fi' \
|
|
'' \
|
|
'ensure_keystore' \
|
|
'' \
|
|
'case "$ARCH" in' \
|
|
'arm64)' \
|
|
' ANDROID_ABIS="arm64-v8a"' \
|
|
' GRADLE_TASK=":app:assembleArm64Release"' \
|
|
' APK_FLAVORS="arm64"' \
|
|
' ;;' \
|
|
'armv7)' \
|
|
' ANDROID_ABIS="armeabi-v7a"' \
|
|
' GRADLE_TASK=":app:assembleArm32Release"' \
|
|
' APK_FLAVORS="arm32"' \
|
|
' ;;' \
|
|
'all)' \
|
|
' ANDROID_ABIS="arm64-v8a,armeabi-v7a"' \
|
|
' GRADLE_TASK=":app:assembleRelease"' \
|
|
' APK_FLAVORS="arm64 arm32"' \
|
|
' ;;' \
|
|
'*) fail "Unsupported architecture: $ARCH (expected arm64, armv7, or all)" ;;' \
|
|
'esac' \
|
|
'' \
|
|
'printf "sdk.dir=%s\n" "$ANDROID_HOME" > "$ANDROID_DIR/local.properties"' \
|
|
'mkdir -p "$OUTPUT_DIR"' \
|
|
'PROJECT_VERSION="$(read_project_version)"' \
|
|
'' \
|
|
'export ONE_KVM_ANDROID_PROFILE="$BUILD_TYPE"' \
|
|
'export ONE_KVM_ANDROID_ABIS="$ANDROID_ABIS"' \
|
|
'export ONE_KVM_ANDROID_FFMPEG_ROOT="$FFMPEG_ROOT"' \
|
|
'export ANDROID_HOME' \
|
|
'export ANDROID_SDK_ROOT' \
|
|
'export ANDROID_NDK_HOME' \
|
|
'export ANDROID_NDK_ROOT' \
|
|
'' \
|
|
'echo "Building Android APK"' \
|
|
'echo " task: $GRADLE_TASK"' \
|
|
'echo " profile: $ONE_KVM_ANDROID_PROFILE"' \
|
|
'echo " version: $PROJECT_VERSION"' \
|
|
'echo " abis: $ONE_KVM_ANDROID_ABIS"' \
|
|
'echo " output: $OUTPUT_DIR"' \
|
|
'echo " sdk: $ANDROID_HOME"' \
|
|
'echo " ndk: $ANDROID_NDK_HOME"' \
|
|
'echo " build tools: $ANDROID_BUILD_TOOLS_DIR"' \
|
|
'echo " ffmpeg root: $ONE_KVM_ANDROID_FFMPEG_ROOT"' \
|
|
'if [[ -n "$GRADLE_DISTRIBUTION_URL" ]]; then' \
|
|
' echo " gradle distribution: $GRADLE_DISTRIBUTION_URL"' \
|
|
'fi' \
|
|
'' \
|
|
'(' \
|
|
' cd "$ANDROID_DIR"' \
|
|
' ./gradlew "$GRADLE_TASK"' \
|
|
')' \
|
|
'' \
|
|
'for flavor in $APK_FLAVORS; do' \
|
|
' copy_apks "$flavor"' \
|
|
'done' \
|
|
'' \
|
|
'echo' \
|
|
'echo "APK output:"' \
|
|
'ls -1 "$OUTPUT_DIR"' \
|
|
> /usr/local/bin/build-one-kvm-android \
|
|
&& chmod +x /usr/local/bin/build-one-kvm-android
|
|
|
|
WORKDIR /workspace
|
|
|
|
ENTRYPOINT ["/usr/local/bin/build-one-kvm-android"]
|
|
CMD ["all"]
|