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 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}")