├── .github └── workflows │ └── make_build_ape.yaml ├── .gitignore ├── .vscode ├── build.sh └── tasks.json ├── README.md ├── config.sh ├── setup_gradle.sh ├── setup_ndk.sh ├── setup_sdk.sh ├── setup_toolchain.sh ├── tests ├── build_termux_app │ ├── build.sh │ └── setup.sh ├── hello.c ├── hello.cpp └── test_toolchain.sh └── wrappers ├── ld.android ├── ld.lld └── target_wrapper /.github/workflows/make_build_ape.yaml: -------------------------------------------------------------------------------- 1 | name: Make NDK APE 2 | run-name: Make NDK-r${{inputs.ndk_version}} with llvm-${{inputs.llvm_version}}-ape 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | ndk_version: 7 | required: true 8 | default: '28' 9 | llvm_version: 10 | required: true 11 | default: '19.1.7' 12 | 13 | jobs: 14 | make: 15 | name: Make NDK APE 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Get NDK and LLVM 19 | run: | 20 | curl -LkSs https://dl.google.com/android/repository/android-ndk-r${{inputs.ndk_version}}-linux.zip >ndk.zip && unzip -q ndk.zip 21 | curl -LkSs https://github.com/zongou/build/releases/download/llvm/clang+clang-tools-extra+lld-${{inputs.llvm_version}}-ape.tar.xz | xz -d | tar -x 22 | 23 | - name: Make NDK APE 24 | run: | 25 | NDK="${PWD}/android-ndk-r${{inputs.ndk_version}}" 26 | HOST_TOOLCHAIN="${PWD}/clang+clang-tools-extra+lld-${{inputs.llvm_version}}-ape" 27 | NDK_TOOLCHAIN="${NDK}/toolchains/llvm/prebuilt/linux-x86_64" 28 | 29 | find "${NDK_TOOLCHAIN}/bin" -type f | while IFS= read -r file; do 30 | bname="$(basename "${file}")" 31 | if [ -f "${HOST_TOOLCHAIN}/bin/${bname}" ] && file "${file}" | grep -q 'ELF'; then 32 | echo "Replacing ${bname}" 33 | cp "${HOST_TOOLCHAIN}/bin/${bname}" "${file}" 34 | elif file "${file}" | grep -q 'Bourne-Again shell script'; then 35 | echo "Replacing SheBang ${bname}" 36 | sed -i 's,#!/usr/bin/env bash,#!/usr/bin/env sh,' "${file}" 37 | fi 38 | done 39 | 40 | ## Fix: ERROR: Unknown host CPU architecture: aarch64 41 | sed -i 's/arm64)/arm64|aarch64)/' "${NDK}/build/tools/ndk_bin_common.sh" 42 | 43 | ## Remove unused resource 44 | rm -rf "${NDK_TOOLCHAIN}/python3" 45 | rm -rf "${NDK_TOOLCHAIN}/musl" 46 | find "${NDK_TOOLCHAIN}/lib" -maxdepth 1 -mindepth 1 -not -name clang -exec rm -rf {} \; 47 | find "${NDK_TOOLCHAIN}" -maxdepth 5 -path "*/lib/clang/[0-9][0-9]/lib/*" -not -name linux -exec rm -rf {} \; 48 | 49 | curl -LkSs https://cosmo.zip/pub/cosmos/bin/make > "${NDK}/prebuilt/linux-x86_64/bin/make" && chmod +x "${NDK}/prebuilt/linux-x86_64/bin/make" 50 | mkdir -p "${NDK_TOOLCHAIN}/python3/bin" 51 | curl -LkSs https://cosmo.zip/pub/cosmos/bin/python >"${NDK_TOOLCHAIN}/python3/bin/python3" && chmod +x "${NDK_TOOLCHAIN}/python3/bin/python3" 52 | 53 | mv android-ndk-r${{inputs.ndk_version}} android-ndk-r${{inputs.ndk_version}}-ape 54 | 55 | - name: Archive 56 | run: | 57 | tar -c android-ndk-r${{inputs.ndk_version}}-ape >android-ndk-r${{inputs.ndk_version}}-ape.tar 58 | 59 | - uses: actions/upload-artifact@v4 60 | with: 61 | name: android-ndk-r${{inputs.ndk_version}}-ape.tar 62 | path: android-ndk-r${{inputs.ndk_version}}-ape.tar 63 | 64 | test: 65 | needs: make 66 | name: Test on ${{ matrix.runner }} 67 | runs-on: ${{ matrix.runner }} 68 | strategy: 69 | fail-fast: false 70 | matrix: 71 | runner: [ubuntu-24.04, ubuntu-24.04-arm, macos-14, windows-latest] 72 | steps: 73 | - name: Checkout 74 | uses: actions/checkout@v4.1.1 75 | 76 | - uses: actions/download-artifact@v4 77 | with: 78 | merge-multiple: true 79 | 80 | - name: Extract artifacts 81 | run: tar -xf android-ndk-r${{inputs.ndk_version}}-ape.tar 82 | 83 | - name: Test 84 | shell: bash 85 | run: | 86 | NDK="${PWD}/android-ndk-r${{inputs.ndk_version}}-ape" 87 | NDK_TOOLCHAIN="${NDK}/toolchains/llvm/prebuilt/linux-x86_64" 88 | 89 | grep '"triple"' <"${NDK}/meta/abis.json" | awk -F"\"" '{print $4}' | while IFS= read -r triple; do 90 | # find ${NDK_TOOLCHAIN}/sysroot/usr/lib -maxdepth 1 -mindepth 1 | while IFS= read -r triple; do 91 | target=$(basename "${triple}" | sed 's/arm/armv7a/')35 92 | 93 | echo "Test ${target}-clang" 94 | o="helloc-${target}" 95 | "${NDK_TOOLCHAIN}/bin/${target}-clang" -o "$o" tests/hello.c 96 | file "$o" 97 | 98 | echo "Test ${target}-clang++" 99 | o="hellocpp-${target}" 100 | "${NDK_TOOLCHAIN}/bin/${target}-clang++" -xc++ -o "$o" tests/hello.cpp 101 | file "$o" 102 | done 103 | 104 | - uses: zongou/run-vscode-server@0.0.3 105 | name: Open VS Code server to Handle Failure (Run on any failure) 106 | if: ${{ failure() }} 107 | 108 | release: 109 | needs: test 110 | name: Release 111 | permissions: 112 | contents: write 113 | actions: write 114 | runs-on: ubuntu-latest 115 | steps: 116 | - name: Download artifacts 117 | uses: actions/download-artifact@v4 118 | with: 119 | merge-multiple: true 120 | 121 | - name: Compress 122 | run: | 123 | xz -T0 android-ndk-r${{inputs.ndk_version}}-ape.tar 124 | 125 | - name: Release 126 | uses: ncipollo/release-action@v1.15.0 127 | with: 128 | tag: "ndk" 129 | artifacts: android-ndk-r${{inputs.ndk_version}}-ape.tar.xz 130 | allowUpdates: true 131 | replacesArtifacts: true 132 | body: | 133 | [action](${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}) 134 | 135 | - uses: zongou/run-vscode-server@0.0.3 136 | name: Open VS Code server to Handle Failure (Run on any failure) 137 | if: ${{ failure() }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | ndk_root 3 | resource 4 | sysroot 5 | a.out 6 | android-ndk* 7 | tests/build_termux_app/termux-app 8 | -------------------------------------------------------------------------------- /.vscode/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | export ZIG=/data/data/com.termux/files/home/zig-linux-aarch64-0.12.0-dev.3609+ac21ade66/zig 5 | 6 | export CLANG="${ZIG} clang" 7 | export CLANGXX="${ZIG} clang -lc++" 8 | 9 | # export CLANG=/data/data/com.termux/files/home/static-clang/bin/clang 10 | # export CLANGXX=/data/data/com.termux/files/home/static-clang/bin/clang++ 11 | 12 | ./bin/aarch64-linux-android24-clang tests/hello.c -o hello_c 13 | ./bin/aarch64-linux-android24-clang++ tests/hello.cpp -o hello_cpp 14 | /system/bin/file hello_c 15 | /system/bin/file hello_cpp 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Build Task", 8 | "type": "shell", 9 | "command": "sh .vscode/build.sh", 10 | "problemMatcher": [], 11 | "group": { 12 | "kind": "build", 13 | "isDefault": true 14 | } 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Build Enviroment 2 | 3 | Build Android Application and Android targetd binary on unsupported os/arch. 4 | For example: 5 | 6 | - [x] android 7 | - [x] prooted linux distro on android 8 | - [x] aarch64-linux 9 | 10 | ## Setup enviroment to build android app 11 | 12 | ### Setup SDK 13 | 14 | First. Install JDK. 15 | 16 | > **_NOTE that on android prooted alpine. jdk > 17 may not work_** 17 | 18 | Then run 19 | 20 | ```sh 21 | ./setup_sdk.sh [PREFIX_DIR] 22 | ``` 23 | 24 | ### Setup NDK 25 | 26 | First. Get NDK [official](https://developer.android.google.com/ndk/downloads) / [github](https://github.com/android/ndk/releases) and decompress 27 | 28 | Then. Run 29 | 30 | ```sh 31 | ./setup_ndk.sh [ANDROID_NDK_ROOT] 32 | ``` 33 | 34 | ### Get aapt2 35 | 36 | - https://github.com/ReVanced/aapt2/actions 37 | - https://github.com/lzhiyong/android-sdk-tools/releases 38 | 39 | > aapt2 is needed when building android application. 40 | 41 | ### Example: Compiling termux in prooted alpine 42 | 43 | ```sh 44 | apt update 45 | apt install git -y 46 | git clone https://github.com/zongou/termux-app 47 | cd termux-app 48 | echo "ndk.dir=${ANDROID_NDK_ROOT}" >> local.properties 49 | echo "android.aapt2FromMavenOverride=/usr/local/bin/aapt2 >> local.properties 50 | gradlew assembleRelease 51 | ``` 52 | 53 | ## Setup toolchain for compiling C/C++ programs only 54 | 55 | First. Get NDK [official](https://developer.android.google.com/ndk/downloads) / [github](https://github.com/android/ndk/releases) and decompress 56 | 57 | Then. Run 58 | 59 | ```sh 60 | ./setup_toolchain.sh [ANDROID_NDK_ROOT] 61 | ``` 62 | 63 | ### Test 64 | 65 | ```bash 66 | ./bin/aarch64-linux-android21-clang tests/hello.c -o hello-c 67 | file hello-c 68 | ./bin/aarch64-linux-android21-clang++ tests/hello.cpp -o hello-cpp 69 | file hello-cpp 70 | ``` 71 | 72 | ### How does it work? 73 | 74 | We can make use of NDK prebuilted sysroot and clang resource dir with host clang toolchain. 75 | 76 | ```sh 77 | TOOLCHAIN="/toolchains/llvm/prebuilt/linux-x86_64" 78 | RESOURCE_DIR="${TOOLCHAIN}/lib/clang/" 79 | SYSROOT="${TOOLCHAIN}/sysroot" 80 | TARGET="aarch64-linux-android21" 81 | 82 | clang \ 83 | -resource-dir "${RESOURCE_DIR}" \ 84 | --sysroot="${SYSROOT}" \ 85 | --target="${TARGET}" \ 86 | -xc - \ 87 | -o "hello-c" \ 88 | <<-EOF 89 | #include 90 | 91 | int main() { 92 | printf("%s\n", "Hello, C!"); 93 | return 0; 94 | } 95 | EOF 96 | ``` 97 | -------------------------------------------------------------------------------- /config.sh: -------------------------------------------------------------------------------- 1 | msg() { printf "%s\n" "$*" >&2; } 2 | 3 | TMPDIR=${TMPDIR-/tmp} 4 | PROGRAM=$(basename $0) 5 | 6 | dl_cmd="curl -Lk" 7 | if ! command -v curl >/dev/null && command -v wget >/dev/null; then 8 | dl_cmd="wget -O-" 9 | fi 10 | 11 | check_tools() { 12 | command_not_found='' 13 | for tool in "$@"; do 14 | msg "checking for $tool ..." 15 | if ! command -v "$tool" >/dev/null; then 16 | if test -z "$command_not_found"; then 17 | command_not_found="$tool" 18 | else 19 | command_not_found="${command_not_found}, $tool" 20 | fi 21 | fi 22 | done 23 | 24 | if [ -n "$command_not_found" ]; then 25 | msg "Error: Command not found: ${command_not_found}" 26 | exit 1 27 | fi 28 | } 29 | 30 | GRADLE_VERSION=8.11.1 31 | GRADLE_VARIANT=bin 32 | CMDLINETOOLS_VERSION=11076708 33 | -------------------------------------------------------------------------------- /setup_gradle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | ROOT_DIR=$(dirname $(realpath $0)) 5 | . ${ROOT_DIR}/config.sh 6 | 7 | ## NOTES: 8 | ## On alpine, gradle version should be >= 8.8 9 | ## https://github.com/gradle/gradle/issues/24875 10 | ## On android, alpine openjdk > 17 may not work 11 | 12 | setup() { 13 | PREFIX_DIR="$1" 14 | 15 | msg "Setting up gradle ..." 16 | check_tools java 17 | GRADLE_URL=https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-${GRADLE_VARIANT}.zip 18 | # GRADLE_URL=https://mirrors.cloud.tencent.com/gradle/gradle-${GRADLE_VERSION}-${GRADLE_VARIANT}.zip 19 | # GRADLE_URL=https://mirrors.huaweicloud.com/gradle/gradle-${GRADLE_VERSION}-${GRADLE_VARIANT}.zip 20 | 21 | GRADLE_ROOT=${PREFIX_DIR}/gradle-${GRADLE_VERSION} 22 | gradle_archive="${TMPDIR}/gradle-${GRADLE_VERSION}.zip" 23 | if ! test -d "${GRADLE_ROOT}"; then 24 | if ! test -f "${gradle_archive}"; then 25 | ${dl_cmd} "${GRADLE_URL}" >"${gradle_archive}.tmp" 26 | mv "${gradle_archive}.tmp" "${gradle_archive}" 27 | fi 28 | unzip -q -d "$(dirname ${GRADLE_ROOT})" "${gradle_archive}" 29 | fi 30 | 31 | msg "Checking gradle ..." 32 | "${GRADLE_ROOT}/bin/gradle" --version 33 | } 34 | 35 | if test $# -gt 0; then 36 | setup "$1" 37 | else 38 | msg "Usage: $PROGRAM [PREFIX_DIR]" 39 | fi 40 | -------------------------------------------------------------------------------- /setup_ndk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | ROOT_DIR=$(dirname $(realpath $0)) 5 | . ${ROOT_DIR}/config.sh 6 | 7 | setup() { 8 | ANDROID_NDK_ROOT="$1" 9 | 10 | msg "Setting up NDK ..." 11 | check_tools clang clang++ ld.lld llvm-strip which make 12 | 13 | ## Fix: ERROR: Unknown host CPU architecture: aarch64 14 | sed -i 's/arm64)/arm64|aarch64)/' "${ANDROID_NDK_ROOT}/build/tools/ndk_bin_common.sh" 15 | 16 | # ## Replace toolchain 17 | TOOLCHAIN="${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64" 18 | 19 | ## Create target wrapper 20 | rm -rf "${TOOLCHAIN}/bin" && mkdir "${TOOLCHAIN}/bin" 21 | cp "${ROOT_DIR}/wrappers/target_wrapper" "${TOOLCHAIN}/bin/target_wrapper" 22 | RESOURCE="$(find "${TOOLCHAIN}/lib/clang" -path "*/[0-9][0-9]" -type d -exec realpath {} \;)" 23 | sed -i "s^RESOURCE=.*^RESOURCE=${RESOURCE}^" "${TOOLCHAIN}/bin/target_wrapper" 24 | 25 | find "${TOOLCHAIN}/sysroot/usr/lib" -maxdepth 1 -mindepth 1 -type d | while IFS= read -r dir; do 26 | ANDROID_ABI=$(basename $dir) 27 | find "${dir}" -maxdepth 1 -mindepth 1 -type d | sort | while IFS= read -r digit_dir; do 28 | ANDROID_API=$(basename "${digit_dir}") 29 | msg "softlink ${ANDROID_ABI}${ANDROID_API}-clang" 30 | ln -snf "target_wrapper" "${TOOLCHAIN}/bin/${ANDROID_ABI}${ANDROID_API}-clang" 31 | msg "softlink ${ANDROID_ABI}${ANDROID_API}-clang++" 32 | ln -snf "target_wrapper" "${TOOLCHAIN}/bin/${ANDROID_ABI}${ANDROID_API}-clang++" 33 | done 34 | done 35 | 36 | ## Link llvm-wrapper 37 | find "${PREFIX-/usr}/bin" -name "llvm-*" | while IFS= read -r f; do 38 | msg "softlink $(basename $f)" 39 | ln -snf "$f" "${TOOLCHAIN}/bin/$(basename $f)" 40 | done 41 | 42 | # ln -snf target_wrapper ${TOOLCHAIN}/bin/clang 43 | # ln -snf target_wrapper ${TOOLCHAIN}/bin/clang++ 44 | 45 | ## Remove unused resource 46 | rm -rf "${TOOLCHAIN}/python3" 47 | rm -rf "${TOOLCHAIN}/musl" 48 | find "${TOOLCHAIN}/lib" -maxdepth 1 -mindepth 1 -not -name clang -exec rm -rf {} \; 49 | find "${TOOLCHAIN}" -maxdepth 5 -path "*/lib/clang/[0-9][0-9]/lib/*" -not -name linux -exec rm -rf {} \; 50 | 51 | ## Replace python 52 | mkdir -p "${TOOLCHAIN}/python3/bin" 53 | if command -v python3 >/dev/null; then 54 | ln -snf "$(command -v python3)" "${TOOLCHAIN}/python3/bin/python3" 55 | else 56 | msg "Warning: Cannot find 'python3', ignored." 57 | cat <<-EOF >"${TOOLCHAIN}/python3/bin/python3" 58 | #!/bin/sh 59 | printf "dry run python with args: '%s' In dir: '%s'\n" "\$*" "\${PWD}" >&2 60 | EOF 61 | chmod +x "${TOOLCHAIN}/python3/bin/python3" 62 | fi 63 | 64 | if ${CLANG-clang} -v 2>&1 | grep -q alpine; then 65 | cp "${ROOT_DIR}/wrappers/ld.lld" "${TOOLCHAIN}/bin/ld.lld" 66 | fi 67 | 68 | # cp "${ROOT_DIR}/wrappers/ld.android" "${TOOLCHAIN}/bin/ld.android" 69 | } 70 | 71 | check() { 72 | msg "Checking NDK ..." 73 | TOOLCHAIN="${1}/toolchains/llvm/prebuilt/linux-x86_64" 74 | TARGET=aarch64-linux-android35 75 | 76 | ${TOOLCHAIN}/bin/${TARGET}-clang ${ROOT_DIR}/tests/hello.c -o ${TMPDIR}/helloc 77 | ${TOOLCHAIN}/bin/${TARGET}-clang++ ${ROOT_DIR}/tests/hello.cpp -o ${TMPDIR}/hellocpp 78 | ${TOOLCHAIN}/bin/${TARGET}-clang ${ROOT_DIR}/tests/hello.c -o ${TMPDIR}/helloc-static -static 79 | ${TOOLCHAIN}/bin/${TARGET}-clang++ ${ROOT_DIR}/tests/hello.cpp -o ${TMPDIR}/hellocpp-static -static 80 | } 81 | 82 | if test $# -eq 1; then 83 | setup "$1" 84 | check "$1" 85 | else 86 | msg "Usage: ${PROGRAM} [ANDROID_NDK_ROOT]" 87 | fi 88 | -------------------------------------------------------------------------------- /setup_sdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | ROOT_DIR=$(dirname $(realpath $0)) 5 | . ${ROOT_DIR}/config.sh 6 | 7 | setup() { 8 | PREFIX_DIR="$1" 9 | 10 | msg "Setting up SDK ..." 11 | check_tools java 12 | ANDROID_SDK_ROOT=${PREFIX_DIR}/android-sdk 13 | CMDLINETOOLS_URL="https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINETOOLS_VERSION}_latest.zip" 14 | SDKMANAGER="${ANDROID_SDK_ROOT}/tools/bin/sdkmanager" 15 | 16 | if ! test -d "${ANDROID_SDK_ROOT}/tools"; then 17 | mkdir -p "${ANDROID_SDK_ROOT}" 18 | CMDLINETOOLS_PACKAGE="${TMPDIR}/cmdline-tools.zip" 19 | if ! test -f "${CMDLINETOOLS_PACKAGE}"; then 20 | $dl_cmd "${CMDLINETOOLS_URL}" >"${CMDLINETOOLS_PACKAGE}.tmp" 21 | mv "${CMDLINETOOLS_PACKAGE}.tmp" "${CMDLINETOOLS_PACKAGE}" 22 | fi 23 | 24 | unzip -q -d "$TMPDIR" "${CMDLINETOOLS_PACKAGE}" 25 | mv "${TMPDIR}/cmdline-tools" "${ANDROID_SDK_ROOT}/tools" 26 | 27 | ## Accept licenses 28 | ## Place cmdline-tools in $ANDROID_SDK_ROOT/tools and run sdkmanager will generate package.xml if not exists 29 | yes | "${SDKMANAGER}" --sdk_root="${ANDROID_SDK_ROOT}" --licenses >/dev/null 2>&1 30 | 31 | ## Work around Issue: Dependant package with key emulator not found! 32 | sed -i 's/path=".*" obsolete/path="tools" obsolete/' "${ANDROID_SDK_ROOT}/tools/package.xml" 33 | fi 34 | 35 | msg "Checking SDK ..." 36 | "${SDKMANAGER}" --sdk_root="${ANDROID_SDK_ROOT}" --list_installed 37 | } 38 | 39 | if test $# -gt 0; then 40 | setup "$1" 41 | else 42 | msg "Usage: ${PROGRAM} [PREFIX_DIR]" 43 | fi 44 | -------------------------------------------------------------------------------- /setup_toolchain.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | ROOT_DIR=$(dirname "$(realpath "$0")") 5 | PROGRAM="$(basename "$0")" 6 | 7 | # shellcheck disable=SC2059 8 | msg() { printf "%s\n" "$*" >&2; } 9 | 10 | setup() { 11 | ## Get ndk resource 12 | ANDROID_NDK_ROOT="$1" 13 | TOOLCHAIN="${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64" 14 | NDK_SYSROOT="${TOOLCHAIN}/sysroot" 15 | NDK_CLANG_RESOURCE="$(find "${TOOLCHAIN}" -path "*/lib/clang/[0-9][0-9]" -type d)" 16 | 17 | msg "Cleaning ..." 18 | rm -rf "${ROOT_DIR}/sysroot" 19 | rm -rf "${ROOT_DIR}/resource" 20 | 21 | msg "Copying sysroot ..." 22 | cp -r "${NDK_SYSROOT}" "${ROOT_DIR}/sysroot" 23 | 24 | msg "Copying clang resource ..." 25 | cp -r "${NDK_CLANG_RESOURCE}" "${ROOT_DIR}/resource" 26 | 27 | find "${ROOT_DIR}/resource/lib" -maxdepth 1 -mindepth 1 -not -name linux -exec rm -rf {} \; 28 | 29 | ## Create target wrapper 30 | mkdir -p "${ROOT_DIR}/bin" 31 | find "${ROOT_DIR}/sysroot/usr/lib" -maxdepth 1 -mindepth 1 -type d | while IFS= read -r dir; do 32 | ANDROID_ABI=$(basename $dir) 33 | find "${dir}" -maxdepth 1 -mindepth 1 -type d | sort | while IFS= read -r digit_dir; do 34 | ANDROID_API=$(basename "${digit_dir}") 35 | msg "link ${ANDROID_ABI}${ANDROID_API}-clang" 36 | ln -snf "../wrappers/target_wrapper" "${ROOT_DIR}/bin/${ANDROID_ABI}${ANDROID_API}-clang" 37 | msg "link ${ANDROID_ABI}${ANDROID_API}-clang++" 38 | ln -snf "../wrappers/target_wrapper" "${ROOT_DIR}/bin/${ANDROID_ABI}${ANDROID_API}-clang++" 39 | done 40 | done 41 | # cp "${ROOT_DIR}/wrappers/ld.android" "${ROOT_DIR}/bin/ld.android" 42 | } 43 | 44 | check() { 45 | msg "Checking NDK ..." 46 | TOOLCHAIN="${ROOT_DIR}" 47 | TARGET=aarch64-linux-android35 48 | 49 | ${TOOLCHAIN}/bin/${TARGET}-clang ${ROOT_DIR}/tests/hello.c -o ${TMPDIR}/helloc 50 | ${TOOLCHAIN}/bin/${TARGET}-clang++ ${ROOT_DIR}/tests/hello.cpp -o ${TMPDIR}/hellocpp 51 | ${TOOLCHAIN}/bin/${TARGET}-clang ${ROOT_DIR}/tests/hello.c -o ${TMPDIR}/helloc-static -static 52 | ${TOOLCHAIN}/bin/${TARGET}-clang++ ${ROOT_DIR}/tests/hello.cpp -o ${TMPDIR}/hellocpp-static -static 53 | } 54 | 55 | main() { 56 | if test $# -eq 1; then 57 | setup "$1" 58 | check 59 | else 60 | msg "Usage: ${PROGRAM} [ANDROID_NDK_ROOT]" 61 | fi 62 | } 63 | 64 | main "$@" 65 | -------------------------------------------------------------------------------- /tests/build_termux_app/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | SCRIPT_DIR=$(dirname $(realpath $0)) 5 | ROOT_DIR=$(dirname $(dirname "${SCRIPT_DIR}")) 6 | 7 | . ${PREFIX-}/etc/profile.d/android_build.sh 8 | 9 | ## Clone termux 10 | if ! test -d "${SCRIPT_DIR}/termux-app"; then 11 | git clone https://github.com/zongou/termux-app "${SCRIPT_DIR}/termux-app" --depth=1 12 | fi 13 | 14 | cd "${SCRIPT_DIR}/termux-app" 15 | git clean -xdf 16 | 17 | ## Gradle properties files 18 | ## https://developer.android.google.cn/build?hl=en#properties-files 19 | rm -f local.properties 20 | cat <<-EOF >local.properties 21 | # sdk.dir=${ANDROID_HOME} 22 | ndk.dir=${ANDROID_NDK_ROOT} 23 | EOF 24 | 25 | ${GRADLE} assembleRelease "$@" 26 | -------------------------------------------------------------------------------- /tests/build_termux_app/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | ROOT_DIR=$(dirname $(dirname $(dirname $(realpath $0)))) 5 | . ${ROOT_DIR}/config.sh 6 | PREFIX=${PREFIX-} 7 | INSTALL_PREFIX=${PREFIX}/opt 8 | 9 | # SDK 10 | ${ROOT_DIR}/setup_sdk.sh "${INSTALL_PREFIX}" 11 | 12 | # NDK 13 | NDK_VERSION=r27c 14 | if ! test -d ${INSTALL_PREFIX}/android-ndk-${NDK_VERSION}; then 15 | if ! test -f ${TMPDIR}/android-ndk-${NDK_VERSION}-linux.zip; then 16 | ndk_url=https://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux.zip 17 | ${dl_cmd} ${ndk_url} >${TMPDIR}/android-ndk-${NDK_VERSION}-linux.zip.tmp 18 | mv ${TMPDIR}/android-ndk-${NDK_VERSION}-linux.zip.tmp ${TMPDIR}/android-ndk-${NDK_VERSION}-linux.zip 19 | fi 20 | unzip -d ${INSTALL_PREFIX} ${TMPDIR}/android-ndk-${NDK_VERSION}-linux.zip 21 | fi 22 | ${ROOT_DIR}/setup_ndk.sh ${INSTALL_PREFIX}/android-ndk-${NDK_VERSION} 23 | 24 | # Gradle 25 | ${ROOT_DIR}/setup_gradle.sh ${INSTALL_PREFIX} 26 | 27 | ## AAPT2 28 | check_tools aapt2 29 | aapt2 version 30 | mkdir -p "${HOME}/.gradle" 31 | GRADLE_CONFIG="${HOME}/.gradle/gradle.properties" 32 | cat <"${GRADLE_CONFIG}" 33 | android.aapt2FromMavenOverride=$(command -v aapt2) 34 | EOF 35 | 36 | ## On alpine, gradle without option -Paapt2FromMavenOverride will fail 37 | ## Profile 38 | cat <<-EOF >${PREFIX}/etc/profile.d/android_build.sh 39 | export GRADLE="${INSTALL_PREFIX}/gradle-${GRADLE_VERSION}/bin/gradle -Pandroid.aapt2FromMavenOverride=$(command -v aapt2)" 40 | export ANDROID_HOME=${INSTALL_PREFIX}/android-sdk 41 | export ANDROID_NDK_ROOT=${INSTALL_PREFIX}/android-ndk-${NDK_VERSION} 42 | EOF 43 | -------------------------------------------------------------------------------- /tests/hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | printf("%s\n", "Hello, C!"); 5 | return 0; 6 | } -------------------------------------------------------------------------------- /tests/hello.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | int main() { 5 | cout << "Hello, C++!\n"; 6 | return 0; 7 | } -------------------------------------------------------------------------------- /tests/test_toolchain.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | ROOT_DIR=$(dirname $(dirname $(realpath $0))) 5 | 6 | ## ndk clang resource dir, get by command: /toolchains/llvm/prebuilt/linux-x86_64/bin/clang --print-resource-dir 7 | RESOURCE_DIR="${ROOT_DIR}/resource" 8 | 9 | ## ndk sysroot, typically /toolchains/llvm/prebuilt/linux-x86_64/sysroot/ 10 | SYSROOT="${ROOT_DIR}/sysroot" 11 | 12 | ## Android target triple 13 | TARGET=aarch64-linux-android21 14 | 15 | if command -v clang; then 16 | CLANG=clang 17 | elif command -v zig; then 18 | CLANG="zig clang" 19 | else 20 | print "Cannot find clang or zig\n" >&2 21 | exit 1 22 | fi 23 | 24 | ## These options are needed for llvmbox 25 | # -isystem "${SYSROOT}/usr/include/c++/v1" \ 26 | # -isystem "${SYSROOT}/usr/include" \ 27 | # -isystem "${SYSROOT}/usr/include/aarch64-linux-android" 28 | 29 | mkdir -p "${ROOT_DIR}/tests/output" 30 | 31 | echo "Test C compiler..." 32 | ${CLANG} \ 33 | -B "${ROOT_DIR}/bin" \ 34 | -resource-dir "${RESOURCE_DIR}" \ 35 | --sysroot="${SYSROOT}" \ 36 | --target="${TARGET}" \ 37 | -xc - \ 38 | "$@" \ 39 | -o "${ROOT_DIR}/tests/output/hello-c" \ 40 | <<-EOF 41 | #include 42 | 43 | int main() { 44 | printf("%s\n", "Hello, C!"); 45 | return 0; 46 | } 47 | EOF 48 | 49 | echo "Test C++ compiler..." 50 | ${CLANG} \ 51 | -B "${ROOT_DIR}/bin" \ 52 | -resource-dir "${RESOURCE_DIR}" \ 53 | --sysroot="${SYSROOT}" \ 54 | --target="${TARGET}" \ 55 | -xc++ -lc++ - \ 56 | "$@" \ 57 | -o "${ROOT_DIR}/tests/output/hello-cpp" \ 58 | <<-EOF 59 | #include 60 | using namespace std; 61 | 62 | int main() { 63 | cout << "Hello, C++!\n"; 64 | return 0; 65 | } 66 | EOF 67 | 68 | if command -v file >/dev/null; then 69 | file "${ROOT_DIR}/tests/output/hello-c" "${ROOT_DIR}/tests/output/hello-cpp" 70 | fi 71 | -------------------------------------------------------------------------------- /wrappers/ld.android: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cleared= 3 | 4 | for x; do 5 | test "$cleared" || set -- 6 | cleared=1 7 | 8 | case "$x" in 9 | -lssp_nonshared) ;; 10 | *) set -- "$@" "$x" ;; 11 | esac 12 | done 13 | 14 | exec ld.lld -nostdlib "$@" 15 | -------------------------------------------------------------------------------- /wrappers/ld.lld: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | ## Remove the default lib ssp_nonshared for android 5 | ## https://github.com/alpinelinux/aports/blob/master/main/clang17/30-Enable-stack-protector-by-default-for-Alpine-Linux.patch#L10 6 | for argv in "$@"; do 7 | case "${argv}" in 8 | -lssp_nonshared) ;; 9 | *) set -- "$@" "${argv}" ;; 10 | esac 11 | shift 12 | done 13 | 14 | /usr/bin/ld.lld "$@" 15 | -------------------------------------------------------------------------------- /wrappers/target_wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eu 3 | 4 | PROGRAM=$(basename $0) 5 | TOOLCHAIN=$(dirname "$(dirname "$(realpath "$0")")") 6 | RESOURCE=${TOOLCHAIN}/resource 7 | SYSROOT=${TOOLCHAIN}/sysroot 8 | CLANG=${CLANG-clang} 9 | 10 | case "${PROGRAM}" in 11 | *clang) BIN=${CLANG} ;; 12 | *clang++) BIN="${CLANG} -lc++" ;; 13 | esac 14 | 15 | case ${PROGRAM} in 16 | clang | clang++) TARGET=aarch64-linux-android21 ;; 17 | *) TARGET=${TARGET-$(echo "${PROGRAM}" | grep -Eo ".+-linux-android(eabi)?[0-9]+")} ;; 18 | esac 19 | 20 | ANDROID_ABI=$(echo "${TARGET}" | grep -Eo ".+-linux-android(eabi)?") 21 | ANDROID_API=$(echo "${TARGET}" | grep -Eo "[0-9]+$") 22 | # echo ${TARGET} ${ANDROID_ABI} ${ANDROID_API} 23 | 24 | ## Some variant of clang dose not support flag -fuse-ld=android 25 | set -- \ 26 | -B "${TOOLCHAIN}/bin" \ 27 | -resource-dir="${RESOURCE}" \ 28 | --sysroot="${SYSROOT}" \ 29 | --target="${TARGET}" \ 30 | "$@" 31 | 32 | ## Static compilation for Android api level < 29 33 | ## https://github.com/termux/termux-packages/issues/8273 34 | if test "${ANDROID_API}" -lt 29; then 35 | for arg in "$@"; do 36 | if test "${arg}" = "-static"; then 37 | set -- "$@" -Wl,--gc-sections 38 | break 39 | fi 40 | done 41 | fi 42 | 43 | # echo ${BIN} "$@" 44 | exec ${BIN} "$@" 45 | --------------------------------------------------------------------------------