mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Build the store engines with cargo-ndk, binaries out of tree
Gradle task buildRustStores runs cargo ndk for arm64-v8a (API 26) into app/build/rustJniLibs, one more jniLibs source, before preBuild; -PskipRust=true leaves the library out for a checkout without a Rust toolchain. cargo-ndk exports the NDK clang/llvm-ar for ring (rustls), the step Bannerlator's CI does by hand. CI adds the Android target and cargo-ndk, caches the cargo registry, cargo-ndk and the crate's target tree keyed on rustc + Cargo.lock, and after the build checks the exports in the apk's libdroiddeckstores.so and that the lockfile did not move (uploading it when it did). tools/build_local.sh gets the same step, DROIDDECK_SKIP_RUST=1 to opt out.
This commit is contained in:
1 parent
401a650b5b
commit
05097b0eea
4 files changed
+124
-1
No files matched your search
@@ -405,6 +405,39 @@ jobs:
|
||||
test -f "$D/usr/local/lib/droiddeck-msitools/$f" || { echo "::error::droiddeck-msitools/$f missing"; exit 1; }
|
||||
done
|
||||
|
||||
# The store download engines (app/src/main/rust/stores) are Rust, built by Gradle's
|
||||
# buildRustStores task through cargo-ndk. rustup and cargo are on the runner image; the Android
|
||||
# target and cargo-ndk are added here. Cache = cargo registry + cargo-ndk + the crate's target
|
||||
# tree, keyed on the rustc version and Cargo.lock, so a source-only change rebuilds this crate
|
||||
# on top of the cached dependency objects, and a lockfile change still starts from the
|
||||
# previous objects (restore-keys).
|
||||
- name: Rust toolchain for the store engines (aarch64-linux-android)
|
||||
id: rust
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rustup target add aarch64-linux-android
|
||||
echo "rustc=$(rustc -V | awk '{print $2}')" >> "$GITHUB_OUTPUT"
|
||||
rustc -V; cargo -V
|
||||
|
||||
- name: Cache cargo registry and the store engines' target
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
~/.cargo/bin/cargo-ndk
|
||||
app/src/main/rust/stores/target
|
||||
key: stores-${{ steps.rust.outputs.rustc }}-${{ hashFiles('app/src/main/rust/stores/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
stores-${{ steps.rust.outputs.rustc }}-
|
||||
|
||||
- name: cargo-ndk
|
||||
run: |
|
||||
set -euo pipefail
|
||||
command -v cargo-ndk >/dev/null || cargo install cargo-ndk --locked
|
||||
cargo ndk --version
|
||||
|
||||
- name: Cache the CMake build tree
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -424,6 +457,39 @@ jobs:
|
||||
./gradlew assembleRelease --console=plain -PndkVersion="$NDK_VERSION" 2>&1 | tee /tmp/build.log
|
||||
python3 tools/release/check_session_assets.py app/build/outputs/apk/release/app-release.apk
|
||||
|
||||
- name: Check the store engines' library
|
||||
# The exports Kotlin binds must be in the apk's libdroiddeckstores.so, or the stores fail on
|
||||
# the device with an UnsatisfiedLinkError instead of here. The lockfile check is the
|
||||
# reproducibility guard: cargo must not have had to re-resolve anything.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
||||
NM="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-nm"
|
||||
SO=app/build/rustJniLibs/arm64-v8a/libdroiddeckstores.so
|
||||
test -s "$SO"
|
||||
ls -la "$SO"
|
||||
unzip -l app/build/outputs/apk/release/app-release.apk | grep -q 'lib/arm64-v8a/libdroiddeckstores.so' \
|
||||
|| { echo "::error::libdroiddeckstores.so is not in the apk"; exit 1; }
|
||||
for sym in \
|
||||
Java_com_droiddeck_launcher_stores_StoresNative_nativeVersion \
|
||||
Java_com_droiddeck_launcher_stores_gog_GogNative_nativeStart \
|
||||
Java_com_droiddeck_launcher_stores_epic_EpicNative_nativeStart \
|
||||
Java_com_droiddeck_launcher_stores_amazon_AmazonNative_nativeStart; do
|
||||
"$NM" -D --defined-only "$SO" | grep -q " ${sym}\$" \
|
||||
|| { echo "::error::libdroiddeckstores.so does not export $sym"; exit 1; }
|
||||
done
|
||||
git diff --stat -- app/src/main/rust/stores/Cargo.lock
|
||||
git diff --exit-code -- app/src/main/rust/stores/Cargo.lock \
|
||||
|| { echo "::error::Cargo.lock changed during the build; commit the regenerated lockfile (uploaded as stores-cargo-lock)"; exit 1; }
|
||||
|
||||
- name: Upload the regenerated Cargo.lock
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: stores-cargo-lock
|
||||
path: app/src/main/rust/stores/Cargo.lock
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Steam import and game environment tests
|
||||
run: python3 -m unittest discover -s tools/tests -p 'test_*.py'
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ app/src/main/jniLibs/arm64-v8a/libproot.so
|
||||
app/src/main/jniLibs/arm64-v8a/.proot-inputs
|
||||
app/src/main/jniLibs/arm64-v8a/libproot-loader.so
|
||||
app/src/main/jniLibs/arm64-v8a/libdirectaudiorelay.so
|
||||
# The store download engines (app/src/main/rust/stores) are cross-compiled by Gradle into
|
||||
# app/build/rustJniLibs; cargo's own build tree stays next to the crate. Neither is source.
|
||||
app/src/main/rust/stores/target/
|
||||
app/src/main/assets/droiddeck-esync/
|
||||
|
||||
# Kotlin compiler session files (build-tool state, never source).
|
||||
|
||||
@@ -188,6 +188,39 @@ def stageSessionScripts = tasks.register('stageSessionScripts', Copy) {
|
||||
}
|
||||
tasks.named('preBuild').configure { dependsOn(stageSessionScripts) }
|
||||
|
||||
// The GOG / Epic / Amazon download engines are Rust (app/src/main/rust/stores, ported from
|
||||
// Bannerlator's bl-steam-client crate). cargo-ndk cross-compiles them for arm64-v8a into the build
|
||||
// directory, which is one more jniLibs source below: nothing compiled is committed, the same rule
|
||||
// the preloads, proot and the DirectAudio relay follow. cargo-ndk exports the NDK clang/llvm-ar as
|
||||
// the C compiler for the one C-compiling crate (ring, under rustls) - the step Bannerlator's CI
|
||||
// does by hand - and reads the NDK from ANDROID_NDK_HOME, which is set to the NDK this build pins.
|
||||
// -PskipRust=true builds without the library, for a checkout with no Rust toolchain: the stores
|
||||
// then report themselves unavailable instead of the build failing.
|
||||
def rustCrateDir = layout.projectDirectory.dir('src/main/rust/stores')
|
||||
def rustJniLibsDir = layout.buildDirectory.dir('rustJniLibs')
|
||||
def skipRust = (project.findProperty('skipRust') ?: 'false').toString().toBoolean()
|
||||
def buildRustStores = tasks.register('buildRustStores', Exec) {
|
||||
description = 'Cross-compiles the store download engines (libdroiddeckstores.so) with cargo-ndk.'
|
||||
onlyIf { !skipRust }
|
||||
inputs.dir(rustCrateDir.dir('src'))
|
||||
inputs.files(rustCrateDir.file('Cargo.toml'), rustCrateDir.file('Cargo.lock'))
|
||||
outputs.dir(rustJniLibsDir)
|
||||
workingDir rustCrateDir
|
||||
// The configuration cache serialises the task, so everything it needs is resolved here, not
|
||||
// in doFirst. ~/.cargo/bin is where rustup puts cargo and cargo-ndk; an IDE-launched Gradle
|
||||
// does not always have it on PATH.
|
||||
def cargoBin = "${System.getProperty('user.home')}/.cargo/bin"
|
||||
environment 'PATH', "${cargoBin}${File.pathSeparator}${System.getenv('PATH')}"
|
||||
environment 'ANDROID_NDK_HOME', android.ndkDirectory.absolutePath
|
||||
environment 'CARGO_TERM_COLOR', 'never'
|
||||
// --platform 26 = minSdkVersion: the engines link against the API 26 libc, like the C++ code.
|
||||
commandLine 'cargo', 'ndk', '--target', 'arm64-v8a', '--platform', '26',
|
||||
'--output-dir', rustJniLibsDir.get().asFile.absolutePath,
|
||||
'build', '--release'
|
||||
}
|
||||
android.sourceSets.main.jniLibs.srcDir(rustJniLibsDir)
|
||||
tasks.named('preBuild').configure { dependsOn(buildRustStores) }
|
||||
|
||||
dependencies {
|
||||
// The runtime tarball is a zstd-compressed tar.
|
||||
implementation 'com.github.luben:zstd-jni:1.5.2-3@aar'
|
||||
|
||||
+22
-1
@@ -331,8 +331,29 @@ tar -cf - -C "${bundle_dir}" . | zstd -19 -T0 -c > "${staging_dir}/pulseaudio.tz
|
||||
bundle_replaced=1
|
||||
mv "${staging_dir}/pulseaudio.tzst" "${bundle_asset}"
|
||||
|
||||
# The GOG / Epic / Amazon download engines are Rust (app/src/main/rust/stores); Gradle's
|
||||
# buildRustStores task cross-compiles them with cargo-ndk, which needs the Android target and the
|
||||
# cargo-ndk binary installed once. DROIDDECK_SKIP_RUST=1 builds an apk without the engines (the
|
||||
# stores then report themselves unavailable) for a machine without a Rust toolchain.
|
||||
gradle_rust_arg=""
|
||||
if [[ "${DROIDDECK_SKIP_RUST:-0}" == 1 ]]; then
|
||||
gradle_rust_arg="-PskipRust=true"
|
||||
else
|
||||
export PATH="${HOME}/.cargo/bin:${PATH}"
|
||||
for tool in rustup cargo; do
|
||||
if ! command -v "${tool}" >/dev/null 2>&1; then
|
||||
echo "${tool} is required for the store engines (https://rustup.rs); set DROIDDECK_SKIP_RUST=1 to build without them." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
rustup target add aarch64-linux-android
|
||||
if ! command -v cargo-ndk >/dev/null 2>&1; then
|
||||
cargo install cargo-ndk --locked
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "${repo_root}"
|
||||
./gradlew "${gradle_task}" --console=plain -PndkVersion="${ndk_version}"
|
||||
./gradlew "${gradle_task}" --console=plain -PndkVersion="${ndk_version}" ${gradle_rust_arg}
|
||||
python3 tools/release/check_session_assets.py "app/build/outputs/apk/${build_variant}/app-${build_variant}.apk"
|
||||
cp -p "${bundle_backup}" "${bundle_asset}"
|
||||
bundle_replaced=0
|
||||
|
||||
Reference in new issue
Block a user