From 97533686b504d2e3d7a7bfe6c2b71918967ddc91 Mon Sep 17 00:00:00 2001 From: JP Cottin Date: Mon, 1 Jun 2026 17:17:45 -0700 Subject: [PATCH 1/2] Fix Android NDK build on Linux (Boost include resolution) The native libtorrent build only ever ran on macOS in CI, so the Linux path was effectively untested and broke in two ways: 1. build.gradle.kts read the Boost paths from environment variables only, falling back to macOS Homebrew defaults. Builds from the IDE (where shell exports aren't visible) always used the wrong paths. 2. On Debian/Ubuntu, Boost lives in /usr/include alongside glibc. Putting that dir on the Android cross-compiler's search path (in any position) pulls host libc headers into the NDK's #include_next chains, breaking the build (missing bits/wordsize.h, bits/libc-header-start.h). BOOST_ INCLUDE_DIR must point at a Boost-only directory, as Homebrew already provides on macOS. Changes: - build.gradle.kts: resolve BOOST_CMAKE_DIR / BOOST_INCLUDE_DIR via env var -> local.properties -> macOS default, so the IDE works without shell exports. macOS behavior is unchanged. - CMakeLists.txt: document that BOOST_INCLUDE_DIR must be a Boost-only dir, never /usr/include (comment only; the -I flag is unchanged). --- app/build.gradle.kts | 23 ++++++++++++++++++----- app/src/main/cpp/CMakeLists.txt | 8 ++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b095b57..e7c93c8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.compose.compiler) @@ -5,6 +7,16 @@ plugins { alias(libs.plugins.screenshot) } +// Boost paths resolve in this order: environment variable, then local.properties +// (gitignored, same place as sdk.dir — handy when building from the IDE where shell +// exports aren't visible), then the macOS Homebrew default. See README "Linux". +val localProperties = Properties().apply { + val file = rootProject.file("local.properties") + if (file.exists()) file.inputStream().use { load(it) } +} +fun boostProperty(name: String, default: String): String = + System.getenv(name) ?: localProperties.getProperty(name) ?: default + android { namespace = "com.jpcottin.simpletorrent" compileSdk = 36 @@ -19,14 +31,15 @@ android { externalNativeBuild { cmake { cppFlags += "-std=c++17" - // BOOST_CMAKE_DIR env var lets CI override the Homebrew default path - val boostDir = System.getenv("BOOST_CMAKE_DIR") - ?: "/opt/homebrew/lib/cmake/Boost-1.90.0" + // BOOST_CMAKE_DIR (env or local.properties) overrides the Homebrew default path + val boostDir = boostProperty("BOOST_CMAKE_DIR", + "/opt/homebrew/lib/cmake/Boost-1.90.0") // BOOST_INCLUDE_DIR: passed to CMake as a cache var; used via // target_compile_options() in CMakeLists.txt so it bypasses both // CMake's implicit-include filtering AND configure-time feature checks. - val boostInclude = System.getenv("BOOST_INCLUDE_DIR") - ?: "/opt/homebrew/include" + // On Linux this must be a Boost-only dir, never /usr/include (see CMakeLists.txt). + val boostInclude = boostProperty("BOOST_INCLUDE_DIR", + "/opt/homebrew/include") arguments( // libtorrent is in libs/libtorrent submodule — no path override needed "-DBoost_DIR=$boostDir", diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 5356593..f42edc5 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -39,6 +39,14 @@ add_library(simpletorrent SHARED torrent_jni.cpp) # emits raw -I flags that bypass this filtering. Using cppFlags/CMAKE_CXX_FLAGS is # also wrong because those affect configure-time feature probes (e.g. std::atomic # check in libtorrent) and break cross-compilation with host headers in the way. +# +# IMPORTANT: BOOST_INCLUDE_DIR must point at a directory that contains ONLY Boost +# (i.e. just a `boost/` subdir), never the host's /usr/include. The NDK headers use +# #include_next, which walks past the sysroot into any dir on the search path; if +# host libc headers (limits.h, features.h, ...) are reachable there, the cross +# build breaks. On macOS, Homebrew's prefix already satisfies this. On Debian/Ubuntu +# the system Boost lives in /usr/include alongside glibc, so point BOOST_INCLUDE_DIR +# at an isolated Boost prefix instead (see README "Linux"). if(DEFINED BOOST_INCLUDE_DIR) target_compile_options(torrent-rasterbar PRIVATE "-I${BOOST_INCLUDE_DIR}") target_compile_options(simpletorrent PRIVATE "-I${BOOST_INCLUDE_DIR}") From 75f02cd73acc054a2c5b3da59521ada74b3371e4 Mon Sep 17 00:00:00 2001 From: JP Cottin Date: Mon, 1 Jun 2026 17:20:14 -0700 Subject: [PATCH 2/2] Document Linux build and add Linux NDK build to CI - README: correct the Linux instructions to use an isolated Boost include dir (not /usr/include, which breaks the NDK #include_next chains) and document the local.properties fallback for Android Studio. - CI: add a Linux "Build Debug APK" job that runs on every push/PR, so the Linux NDK toolchain is exercised and can't silently regress again. It reuses the same isolated-Boost-dir setup already proven in the instrumented-tests job. --- .github/workflows/android.yml | 44 +++++++++++++++++++++++++++++++++++ README.md | 27 +++++++++++++++++++-- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 56d460b..7036dd0 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -75,6 +75,50 @@ jobs: name: app-debug path: app/build/outputs/apk/debug/app-debug.apk + # ── Job 2b: Build debug APK on Linux (NDK cross-compile sanity on every PR) ── + # The macOS job above covers the primary dev path; this one guards the Linux + # toolchain (apt Boost in /usr/include + #include_next) so it can't silently rot. + build-linux: + name: Build Debug APK (Linux) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + + - uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: temurin + cache: gradle + + - uses: gradle/actions/setup-gradle@v6 + + - name: Install Boost via apt + run: sudo apt-get install -y libboost-dev + + - name: Resolve Boost dirs + run: | + BOOST_DIR=$(find /usr/lib -maxdepth 4 -name "Boost-*" -type d 2>/dev/null \ + | sort -V | tail -1) + echo "BOOST_CMAKE_DIR=$BOOST_DIR" >> "$GITHUB_ENV" + # Isolated dir with ONLY a boost/ symlink: -I finds boost/* but does + # not expose glibc system headers (features-time64.h → bits/wordsize.h) + # which are absent in the NDK cross-compilation sysroot. + mkdir -p /tmp/boost-headers + ln -sfn /usr/include/boost /tmp/boost-headers/boost + echo "BOOST_INCLUDE_DIR=/tmp/boost-headers" >> "$GITHUB_ENV" + echo "Found Boost CMake dir: $BOOST_DIR" + + - name: Build debug APK + run: ./gradlew assembleDebug --no-daemon + + - name: Upload APK + uses: actions/upload-artifact@v7 + with: + name: app-debug-linux + path: app/build/outputs/apk/debug/app-debug.apk + # ── Job 3: Lint ───────────────────────────────────────────────────────────── lint: name: Lint diff --git a/README.md b/README.md index 8d48524..4e6eb51 100644 --- a/README.md +++ b/README.md @@ -169,12 +169,35 @@ export BOOST_CMAKE_DIR=/path/to/cmake/Boost-X.Y.Z ```bash sudo apt-get install -y libboost-dev -BOOST_CMAKE_DIR=$(find /usr -name "BoostConfig.cmake" 2>/dev/null \ - | head -1 | xargs dirname) +# 1. Boost's CMake config dir (for find_package) +BOOST_CMAKE_DIR=$(dirname "$(find /usr -name BoostConfig.cmake 2>/dev/null | head -1)") export BOOST_CMAKE_DIR + +# 2. An *isolated* Boost include dir — a directory containing ONLY a boost/ entry. +# Do NOT use /usr/include directly: the Android NDK headers use #include_next, +# which would pull the host glibc headers from /usr/include into the cross +# build and break it (missing bits/wordsize.h, bits/libc-header-start.h). +mkdir -p "$HOME/.boost-include" +ln -sfn /usr/include/boost "$HOME/.boost-include/boost" +export BOOST_INCLUDE_DIR="$HOME/.boost-include" + ./gradlew assembleDebug ``` +> On macOS this isolation is automatic — Homebrew's prefix holds Boost but no +> competing libc — which is why only `BOOST_CMAKE_DIR` is needed there. + +#### Building from Android Studio on Linux + +The IDE doesn't see your shell `export`s. Instead of env vars, put the same two +paths in `local.properties` (gitignored, alongside `sdk.dir`); the build falls +back to them automatically: + +```properties +BOOST_CMAKE_DIR=/usr/lib/x86_64-linux-gnu/cmake/Boost-1.83.0 +BOOST_INCLUDE_DIR=/home//.boost-include +``` + ## Running Tests ```bash