Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/build-plugin/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ runs:
shell: sh
run: |
rustc --print=target-list
- name: Build for Windows and Linux
- name: Build for Windows and Linux and Android
shell: sh
env:
CI: 1
Expand Down
52 changes: 52 additions & 0 deletions .github/workflows/android_builds.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: 🤖 Build Backstitch for Android
on:
workflow_call:

jobs:
android:
runs-on: "ubuntu-24.04"
name: 🔨 Build ${{ matrix.arch }}
strategy:
fail-fast: false
matrix:
arch: ["aarch64-linux-android", "x86_64-linux-android"]
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- id: dotenv
uses: falti/dotenv-action@v1.1.5
with:
path: build.env
keys-case: bypass
- name: Select pinned Android NDK
shell: bash
env:
NDK_VERSION: ${{ steps.dotenv.outputs.ANDROID_NDK_VERSION }}
run: |
set -eu
ndk="$ANDROID_HOME/ndk/$NDK_VERSION"
if [ ! -d "$ndk" ]; then
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "ndk;$NDK_VERSION"
fi
# The runner presets these to its own default NDK, which is older than the pinned one.
echo "ANDROID_NDK_ROOT=$ndk" >> "$GITHUB_ENV"
echo "ANDROID_NDK_HOME=$ndk" >> "$GITHUB_ENV"

- name: Build
uses: ./.github/actions/build-plugin
with:
arch: ${{ matrix.arch }}

# TODO: Remove the necessity for this workaround somehow; this is just here so we don't accidentally clobber the signed MacOS framework files.
- name: Remove MacOS framework
shell: bash
run: |
rm -rf ./build/backstitch/bin/libbackstitch_rust_core.macos.framework

- name: Upload
uses: actions/upload-artifact@v4
with:
name: backstitch-build-android-${{ matrix.arch }}
path: ./build/backstitch/*
if-no-files-found: error
6 changes: 5 additions & 1 deletion .github/workflows/runner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@ jobs:
name: 🐧 Build Backstitch for Linux
uses: ./.github/workflows/linux_builds.yml

android-plugin-builds:
name: 🤖 Build Backstitch for Android
uses: ./.github/workflows/android_builds.yml

merge-plugin-builds:
name: 🧩 Merge Backstitch Builds
needs: [macos-plugin-builds, windows-plugin-builds, linux-plugin-builds]
needs: [macos-plugin-builds, windows-plugin-builds, linux-plugin-builds, android-plugin-builds]
uses: ./.github/workflows/merge_builds.yml

compose-projects:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -342,5 +342,7 @@ cmake-build-*/
.cargo/dev-cert.p12
.cargo/*.p12
.cargo/*.base64
# generated by `just _build-plugin-android`
.cargo/android.toml
*.obj.import
rustc-ice-*.txt
35 changes: 32 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ tokio-util = "0.7.18"
sys-locale = "0.3.2"
blake3 = { version = "1.8.4", features = ["serde"] }
wincode = { version = "0.6.0", features = ["derive"] }
fjall = "3.1.4"
fjall = { git = "https://github.com/nikitalita/fjall", branch = "handle-unsupported-file-lock" } # Needed for Android build
jwalk = "0.9.0"
thiserror = "2.0.21"
indexmap = "2.14"
Expand All @@ -67,7 +67,7 @@ url = "2.5.8"
open = "5.4.0"
axum = "0.8.9"
html-escape = "0.3.0"
reqwest = { version = "0.13.5", features = ["json"] }
reqwest = { version = "0.13.5", features = ["json", "native-tls"] }
semver = { version = "1.0.28", features = ["serde"] }
async-trait = "0.1.91"
secrecy = "0.10.3"
Expand Down
7 changes: 7 additions & 0 deletions backstitch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,10 @@ static_vcruntime = { workspace = true }

[dev-dependencies]
tempfile = { workspace = true }

# `samod/tungstenite` forces `native-tls`, which resolves to openssl-sys on Android.
# There is no system OpenSSL to link against there, so we have to build
# it from source with the NDK toolchain.
[target.'cfg(target_os = "android")'.dependencies]
openssl = { version = "0.10", features = ["vendored"] }
reqwest = { version = "0.13.5", features = ["json", "native-tls-vendored"] }
38 changes: 38 additions & 0 deletions backstitch_godot/src/interop/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ unsafe impl ExtensionLibrary for MyExtension {
fn on_stage_init(level: InitStage) {
if level == InitStage::Scene {
initialize_tracing();
#[cfg(target_os = "android")]
initialize_android_cert_dir();
tracing::info!("** on_level_init: Scene");
Engine::singleton().register_singleton("GodotProject", &GodotProject::new_alloc());
let loader = BackstitchResourceLoader::new_gd();
Expand Down Expand Up @@ -73,6 +75,42 @@ unsafe impl ExtensionLibrary for MyExtension {
}
}

/// Point OpenSSL at Android's CA store.
///
/// We build OpenSSL from source for Android, so its compiled-in `OPENSSLDIR` refers to a path on
/// the build machine, and `openssl-probe` only looks for Termux and generic Linux locations. Left
/// alone, neither finds any root certificates and every TLS handshake fails to verify the peer.
/// Both directories below use OpenSSL's hashed-`CApath` layout, so `SSL_CERT_DIR` is enough.
#[cfg(target_os = "android")]
fn initialize_android_cert_dir() {
use std::path::Path;

const CERT_DIRS: [&str; 2] = [
// Updatable CA store, present from API 34 on and preferred when available.
"/apex/com.android.conscrypt/cacerts",
"/system/etc/security/cacerts",
];

if std::env::var_os("SSL_CERT_DIR").is_some() || std::env::var_os("SSL_CERT_FILE").is_some() {
return;
}

let Some(cert_dir) = CERT_DIRS
.iter()
.copied()
.find(|dir| Path::new(dir).is_dir())
else {
tracing::warn!(
"no Android CA store found; TLS connections will not be able to verify peers"
);
return;
};

// Sound because extension init runs before we spawn any threads that could read the env.
unsafe { std::env::set_var("SSL_CERT_DIR", cert_dir) };
tracing::info!("using Android CA store at {cert_dir}");
}

fn unregister_singleton(singleton_name: &str) {
if Engine::singleton().has_singleton(singleton_name) {
let my_singleton = Engine::singleton().get_singleton(singleton_name);
Expand Down
6 changes: 6 additions & 0 deletions build.env
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ GODOT_FORMATTERS_REF=master
MINIMUM_GODOT=4.7-stable
RECOMMENDED_GODOT=4.7.1-stable

# Android
# Keep in sync with platform/android/detect.py#get_ndk_version()
ANDROID_NDK_VERSION=29.0.14206865
# Keep in sync with platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION
ANDROID_MIN_SDK=24

# Launchers below this won't be able to download the release
MINIMUM_LAUNCHER=2.1.1
# Launcher version included in the project builds
Expand Down
Loading
Loading