--- /dev/null
+name: Build static rsync for Android
+
+# Cross-compiles statically-linked rsync binaries with the Android NDK,
+# suitable for dropping onto a phone (adb push / Termux) with no shared
+# libraries. arm64-v8a covers all modern phones; armeabi-v7a covers older
+# 32-bit devices. The binaries are uploaded as workflow artifacts.
+#
+# These are cross-compiled, so the test suite can't run here; we sanity
+# check that each binary is the right architecture, is static, and that
+# it executes (`--version`) under qemu-user.
+
+on:
+ push:
+ branches: [ master ]
+ paths-ignore:
+ - '.github/workflows/*.yml'
+ - '!.github/workflows/android-static-build.yml'
+ pull_request:
+ branches: [ master ]
+ paths-ignore:
+ - '.github/workflows/*.yml'
+ - '!.github/workflows/android-static-build.yml'
+ schedule:
+ - cron: '42 8 * * *'
+ workflow_dispatch:
+
+env:
+ # Minimum supported API level. 24 (Android 7.0) runs on every modern
+ # phone while keeping broad reach; bump if you need newer Bionic APIs.
+ ANDROID_API: 24
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ name: ${{ matrix.abi }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - abi: arm64-v8a # modern phones
+ triple: aarch64-linux-android
+ qemu: qemu-aarch64-static
+ - abi: armeabi-v7a # older 32-bit phones
+ triple: armv7a-linux-androideabi
+ qemu: qemu-arm-static
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Install build prerequisites
+ run: sudo apt-get update && sudo apt-get install -y autoconf automake gawk qemu-user-static
+
+ - name: Configure and build (${{ matrix.abi }})
+ shell: bash
+ run: |
+ set -euo pipefail
+ NDK="${ANDROID_NDK_LATEST_HOME:-$ANDROID_NDK_ROOT}"
+ TC="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin"
+ export CC="$TC/${{ matrix.triple }}${ANDROID_API}-clang"
+ export AR="$TC/llvm-ar" RANLIB="$TC/llvm-ranlib" STRIP="$TC/llvm-strip"
+ export CFLAGS="-O2" LDFLAGS="-static"
+
+ # Bionic doesn't declare lchmod()/lutimes() until API 36, but the
+ # symbols link, so configure mis-detects them -- force them off so
+ # rsync uses its fallbacks. The other cache vars restore values
+ # that configure can't probe when cross-compiling (Android runs a
+ # normal Linux kernel, so these match the native Linux result).
+ export ac_cv_func_lchmod=no ac_cv_func_lutimes=no \
+ rsync_cv_HAVE_SOCKETPAIR=yes \
+ rsync_cv_MKNOD_CREATES_FIFOS=yes \
+ rsync_cv_MKNOD_CREATES_SOCKETS=yes
+
+ # Self-contained build: drop optional external libraries so the
+ # static binary needs nothing at runtime. rsync keeps md5/md4
+ # checksums and its bundled zlib.
+ ./configure --host=${{ matrix.triple }} --build=x86_64-pc-linux-gnu \
+ --enable-ipv6 \
+ --disable-zstd --disable-lz4 --disable-xxhash --disable-openssl \
+ --disable-iconv --disable-iconv-open \
+ --disable-acl-support --disable-xattr-support \
+ --disable-md2man --disable-roll-simd \
+ --with-included-popt --with-included-zlib
+
+ # Generate the awk-built headers serially first so the parallel
+ # build can't race on proto.h <- daemon-parm.h.
+ make proto.h
+ make -j"$(nproc)" rsync
+ "$STRIP" rsync
+
+ - name: Verify binary
+ shell: bash
+ run: |
+ set -euo pipefail
+ file rsync
+ # Gate: must be a statically-linked executable (no interpreter).
+ file rsync | grep -q "statically linked"
+ if file rsync | grep -q "dynamically linked"; then
+ echo "ERROR: binary is not static" >&2; exit 1
+ fi
+ # Best-effort: confirm it actually runs under qemu-user.
+ ${{ matrix.qemu }} ./rsync --version | head -3 || \
+ echo "WARNING: qemu smoke test did not run cleanly (check on a real device)"
+
+ - name: Package
+ shell: bash
+ run: |
+ set -euo pipefail
+ VER=$(sed -n 's/.*RSYNC_VERSION "\([^"]*\)".*/\1/p' version.h)
+ out="rsync-${VER}-android-${{ matrix.abi }}"
+ mkdir -p dist
+ cp rsync "dist/$out"
+ ( cd dist && sha256sum "$out" > "$out.sha256" )
+ echo "ARTIFACT_NAME=rsync-android-${{ matrix.abi }}" >>"$GITHUB_ENV"
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ env.ARTIFACT_NAME }}
+ path: dist/