From be8c42ec01368df7b1e4998631a30fac9a3a9f2d Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sun, 17 Aug 2025 13:35:50 -0700 Subject: [PATCH 1/7] Fix `minimal-winit-android` build. Allows the example to correctly run as a binary on desktop platforms. Includes a container that avoids dependency management hell with the Android SDK. And basic instructions for using it. CI support is currently missing for Android builds. --- Cargo.lock | 21 +++++--- examples/minimal-winit-android/.dockerignore | 2 + examples/minimal-winit-android/Cargo.toml | 16 ++++++- examples/minimal-winit-android/Containerfile | 50 ++++++++++++++++++++ examples/minimal-winit-android/README.md | 42 ++++++++++++++-- examples/minimal-winit-android/src/lib.rs | 44 ++++++++--------- examples/minimal-winit-android/src/main.rs | 12 +++++ 7 files changed, 152 insertions(+), 35 deletions(-) create mode 100644 examples/minimal-winit-android/.dockerignore create mode 100644 examples/minimal-winit-android/Containerfile create mode 100644 examples/minimal-winit-android/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 8a7541eb..3c759a44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,20 +90,19 @@ checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] name = "android_log-sys" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" [[package]] name = "android_logger" -version = "0.11.3" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8619b80c242aa7bd638b5c7ddd952addeecb71f69c75e33f1d47b2804f8f883a" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" dependencies = [ "android_log-sys", - "env_logger", + "env_filter", "log", - "once_cell", ] [[package]] @@ -1048,6 +1047,16 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" version = "0.10.2" diff --git a/examples/minimal-winit-android/.dockerignore b/examples/minimal-winit-android/.dockerignore new file mode 100644 index 00000000..ea32980d --- /dev/null +++ b/examples/minimal-winit-android/.dockerignore @@ -0,0 +1,2 @@ +# Ignore everything +* diff --git a/examples/minimal-winit-android/Cargo.toml b/examples/minimal-winit-android/Cargo.toml index 0c3cfddc..d367fada 100644 --- a/examples/minimal-winit-android/Cargo.toml +++ b/examples/minimal-winit-android/Cargo.toml @@ -14,11 +14,23 @@ winit = { workspace = true, features = ["android-native-activity"] } env_logger.workspace = true [target.'cfg(target_os = "android")'.dependencies] -android_logger = "0.11.0" +android_logger = "0.15.0" + +[[bin]] +# Rename the bin to avoid https://github.com/rust-lang/cargo/issues/6313 +name = "desktop-example" +path = "src/main.rs" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "lib"] [package.metadata.android.signing.release] path = "./path/to/KeyStoreFile.jks" keystore_password = "password" + +[package.metadata.android] +build_targets = ["armv7-linux-androideabi", "aarch64-linux-android"] + +[package.metadata.android.sdk] +min_sdk_version = 23 +target_sdk_version = 35 diff --git a/examples/minimal-winit-android/Containerfile b/examples/minimal-winit-android/Containerfile new file mode 100644 index 00000000..bf25be07 --- /dev/null +++ b/examples/minimal-winit-android/Containerfile @@ -0,0 +1,50 @@ +# Build with the `pixels` MSRV. This will be updated in lockstep. +FROM rust:1.82.0-slim + +# Variable arguments +ARG JAVA_VERSION=17 +ARG NDK_VERSION=28.2.13676358 +ARG BUILDTOOLS_VERSION=35.0.1 +ARG PLATFORM_VERSION=android-35 +ARG CLITOOLS_VERSION=13114758_latest + +# Install Android requirements +RUN apt-get update -yqq && \ + apt-get install -y --no-install-recommends \ + libcurl4-openssl-dev libssl-dev pkg-config build-essential git python3 wget zip unzip openjdk-${JAVA_VERSION}-jdk && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Install android targets +RUN rustup target add armv7-linux-androideabi aarch64-linux-android + +# Install cargo-apk +RUN cargo install --locked cargo-apk + +# Generate Environment Variables +ENV JAVA_VERSION=${JAVA_VERSION} +ENV ANDROID_HOME=/opt/Android +ENV NDK_HOME=/opt/Android/ndk/${NDK_VERSION} +ENV ANDROID_NDK_ROOT=${NDK_HOME} +ENV PATH=$PATH:${ANDROID_HOME}:${ANDROID_NDK_ROOT}:${ANDROID_HOME}/build-tools/${BUILDTOOLS_VERSION}:${ANDROID_HOME}/cmdline-tools/bin + +# Install command line tools +RUN mkdir -p ${ANDROID_HOME}/cmdline-tools && \ + wget -qc "https://dl.google.com/android/repository/commandlinetools-linux-${CLITOOLS_VERSION}.zip" -P /tmp && \ + unzip -d ${ANDROID_HOME} /tmp/commandlinetools-linux-${CLITOOLS_VERSION}.zip && \ + rm -fr /tmp/commandlinetools-linux-${CLITOOLS_VERSION}.zip +# Install sdk requirements +RUN echo y | sdkmanager --sdk_root=${ANDROID_HOME} --install \ + "build-tools;${BUILDTOOLS_VERSION}" "ndk;${NDK_VERSION}" "platforms;${PLATFORM_VERSION}" + +# Create APK keystore for debug profile +# Adapted from https://github.com/rust-mobile/cargo-apk/blob/caa806283dc26733ad8232dce1fa4896c566f7b8/ndk-build/src/ndk.rs#L393-L423 +RUN keytool -genkey -v -keystore ${HOME}/.android/debug.keystore -storepass android -alias androiddebugkey \ + -keypass android -dname 'CN=Android Debug,O=Android,C=US' -keyalg RSA -keysize 2048 -validity 10000 + +# Cleanup +RUN rm -rf /tmp/* + +WORKDIR /mnt + +ENTRYPOINT [ "cargo", "apk", "build", "--lib" ] diff --git a/examples/minimal-winit-android/README.md b/examples/minimal-winit-android/README.md index 75f7928d..174f4cf1 100644 --- a/examples/minimal-winit-android/README.md +++ b/examples/minimal-winit-android/README.md @@ -4,15 +4,51 @@ Minimal example to run on android using `winit` with `android-native-activity` feature -## Running -``` +## Running on Android + +```bash export ANDROID_HOME="path/to/sdk" export ANDROID_NDK_HOME="path/to/ndk" rustup target add aarch64-linux-android cargo install cargo-apk ``` + Connect your Android device via USB cable to your computer in debug mode and run the following command + +```bash +cargo apk run --package minimal-winit-android +``` + +## Running on Desktop + +Sometimes it is helpful to run your Android apps on a Desktop environment (e.g., Windows, macOS, or +Linux). It works the same way as all other `pixels` examples: + +```bash +cargo run --package minimal-winit-android +``` + +## Containerized Builds + +A `Containerfile` is included, allowing containerized builds without the need to install the Android +SDK and dependencies on the build host. + +The following commands are assumed to be run from the repository root directory. + +Build the image in Docker: + +```bash +docker build -t rust-android:latest ./examples/minimal-winit-android/ ``` -cargo apk run + +Build the example in the container: + +```bash +docker run --rm -it -v "$PWD:/src" rust-android:latest --package minimal-winit-android ``` + +The APKs are written to `./target/debug/apk/`. + +For release builds, add `--release` to the end of the `run` command. (This will need the path to +the keystore corrected in the Cargo.toml manifest.) The APKs are written to `./target/release/apk/`. diff --git a/examples/minimal-winit-android/src/lib.rs b/examples/minimal-winit-android/src/lib.rs index 2471f6ba..da2e5017 100644 --- a/examples/minimal-winit-android/src/lib.rs +++ b/examples/minimal-winit-android/src/lib.rs @@ -1,5 +1,5 @@ #![deny(clippy::all)] -#![forbid(unsafe_code)] +#![cfg_attr(not(target_os = "android"), forbid(unsafe_code))] #[cfg(target_os = "android")] use winit::platform::android::activity::AndroidApp; @@ -22,14 +22,13 @@ struct World { velocity_y: i16, } -struct Display<'win> { +struct Display { window: Arc, - pixels: Pixels<'win>, + pixels: Pixels<'static>, } -fn _main(event_loop: EventLoop<()>) { - let mut display: Option = None; - +pub fn _main(event_loop: EventLoop<()>) { + let mut display = None; let mut world = World::new(); let res = event_loop.run(|event, elwt| { @@ -52,10 +51,17 @@ fn _main(event_loop: EventLoop<()>) { Event::Suspended => { display = None; } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + elwt.exit(); + } Event::WindowEvent { event: WindowEvent::RedrawRequested, .. } => { + world.update(); if let Some(display) = &mut display { world.draw(display.pixels.frame_mut()); display.pixels.render().unwrap(); @@ -64,9 +70,6 @@ fn _main(event_loop: EventLoop<()>) { } _ => {} } - if display.is_some() { - world.update(); - } }); res.unwrap(); } @@ -119,25 +122,18 @@ impl World { } } -#[allow(dead_code)] #[cfg(target_os = "android")] #[no_mangle] fn android_main(app: AndroidApp) { + use android_logger::Config; + use winit::event_loop::EventLoopBuilder; use winit::platform::android::EventLoopBuilderExtAndroid; - android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info)); - let event_loop = EventLoopBuilder::new().with_android_app(app).build(); - log::info!("Hello from android!"); - _main(event_loop); -} -#[allow(dead_code)] -#[cfg(not(target_os = "android"))] -fn main() { - env_logger::builder() - .filter_level(log::LevelFilter::Info) // Default Log Level - .parse_default_env() - .init(); - let event_loop = EventLoop::new().unwrap(); - log::info!("Hello from desktop!"); + android_logger::init_once(Config::default().with_max_level(log::LevelFilter::Info)); + let event_loop = EventLoopBuilder::new() + .with_android_app(app) + .build() + .unwrap(); + log::info!("Hello from android!"); _main(event_loop); } diff --git a/examples/minimal-winit-android/src/main.rs b/examples/minimal-winit-android/src/main.rs new file mode 100644 index 00000000..955a2341 --- /dev/null +++ b/examples/minimal-winit-android/src/main.rs @@ -0,0 +1,12 @@ +use minimal_winit_android::_main; +use winit::event_loop::EventLoop; + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Info) // Default Log Level + .parse_default_env() + .init(); + let event_loop = EventLoop::new().unwrap(); + log::info!("Hello from desktop!"); + _main(event_loop); +} From ec780f551235d6a11d3aa6ffa00c1dcb2aa01ca0 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sun, 17 Aug 2025 13:50:24 -0700 Subject: [PATCH 2/7] Add Android CI support --- .github/workflows/ci.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07308d5e..2eba60da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,6 +121,35 @@ jobs: - name: WASM build run: cargo run-wasm --build-only --package ${{ matrix.example }} + android: + name: Android + runs-on: ubuntu-latest + needs: [checks, lints] + strategy: + matrix: + example: + - minimal-winit-android + steps: + - name: Checkout sources + uses: actions/checkout@v3 + - name: Install toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + target: + - armv7-linux-androideabi + - aarch64-linux-android + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: common + - name: Install cargo-apk + uses: baptiste0928/cargo-install@v2 + with: + crate: cargo-apk + - name: Android build + run: cargo apk build --package ${{ matrix.example }} + # See https://github.com/parasyte/pixels-ci-rust-version rust-version: name: Rust-Version From b0230efd2a00eeca200322fcc1e588e85d82dcfd Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sun, 17 Aug 2025 13:52:17 -0700 Subject: [PATCH 3/7] Fix syntax error --- .github/workflows/ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2eba60da..97302370 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,6 +127,9 @@ jobs: needs: [checks, lints] strategy: matrix: + target: + - armv7-linux-androideabi + - aarch64-linux-android example: - minimal-winit-android steps: @@ -136,9 +139,7 @@ jobs: uses: dtolnay/rust-toolchain@master with: toolchain: stable - target: - - armv7-linux-androideabi - - aarch64-linux-android + target: ${{ matrix.target }} - name: Rust cache uses: Swatinem/rust-cache@v2 with: From bffcd8caa271e19ef7eb38db90bac57826ab1de2 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sun, 17 Aug 2025 14:08:42 -0700 Subject: [PATCH 4/7] Install JDK and Android SDK Use `targets` (plural) instead of matrix --- .github/workflows/ci.yml | 41 +++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97302370..6513a4b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,10 +22,10 @@ jobs: - name: Install dependencies run: sudo apt -y install libgtk-3-dev libudev-dev libxdo-dev - name: Install toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust }} - target: wasm32-unknown-unknown + targets: wasm32-unknown-unknown,armv7-linux-androideabi,aarch64-linux-android - name: Rust cache uses: Swatinem/rust-cache@v2 with: @@ -34,6 +34,10 @@ jobs: run: cargo check --workspace - name: Cargo check WASM run: cargo check --target wasm32-unknown-unknown --package minimal-web + - name: Cargo check Android ARMv7 + run: cargo check --target armv7-linux-androideabi --package minimal-winit-android + - name: Cargo check Android AARCH64 + run: cargo check --target aarch64-linux-android --package minimal-winit-android lints: name: Lints @@ -46,11 +50,11 @@ jobs: - name: Install dependencies run: sudo apt -y install libgtk-3-dev libudev-dev libxdo-dev - name: Install toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: clippy, rustfmt - target: wasm32-unknown-unknown + targets: wasm32-unknown-unknown,armv7-linux-androideabi,aarch64-linux-android - name: Rust cache uses: Swatinem/rust-cache@v2 with: @@ -67,6 +71,10 @@ jobs: run: cargo clippy --workspace --tests -- -D warnings - name: Cargo clippy WASM run: cargo clippy --target wasm32-unknown-unknown --package minimal-web --tests -- -D warnings + - name: Cargo clippy Android ARMv7 + run: cargo clippy --target armv7-linux-androideabi --package minimal-winit-android + - name: Cargo clippy Android AARCH64 + run: cargo clippy --target aarch64-linux-android --package minimal-winit-android - name: Cargo machete run: cargo machete @@ -88,7 +96,7 @@ jobs: - name: Install dependencies run: sudo apt -y install libgtk-3-dev libudev-dev libxdo-dev - name: Install toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust }} - name: Rust cache @@ -110,10 +118,10 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - name: Install toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@stable with: toolchain: stable - target: wasm32-unknown-unknown + targets: wasm32-unknown-unknown - name: Rust cache uses: Swatinem/rust-cache@v2 with: @@ -127,19 +135,26 @@ jobs: needs: [checks, lints] strategy: matrix: - target: - - armv7-linux-androideabi - - aarch64-linux-android example: - minimal-winit-android steps: - name: Checkout sources uses: actions/checkout@v3 - name: Install toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@stable with: toolchain: stable - target: ${{ matrix.target }} + targets: armv7-linux-androideabi,aarch64-linux-android + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + with: + cmdline-tools-version: 13114758 + packages: build-tools;35.0.1 ndk;28.2.13676358 platforms;android-35 - name: Rust cache uses: Swatinem/rust-cache@v2 with: @@ -166,7 +181,7 @@ jobs: with: repository: parasyte/pixels-ci-rust-version - name: Install toolchain - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.rust }} - name: Rust cache From 6162c53cdd1354e76053d07250712ccddbdfe804 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sun, 17 Aug 2025 14:14:49 -0700 Subject: [PATCH 5/7] Trying to fix check/clippy with Android targets --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6513a4b3..c2525994 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,9 +35,9 @@ jobs: - name: Cargo check WASM run: cargo check --target wasm32-unknown-unknown --package minimal-web - name: Cargo check Android ARMv7 - run: cargo check --target armv7-linux-androideabi --package minimal-winit-android + run: cargo check --target armv7-linux-androideabi --package minimal-winit-android --lib - name: Cargo check Android AARCH64 - run: cargo check --target aarch64-linux-android --package minimal-winit-android + run: cargo check --target aarch64-linux-android --package minimal-winit-android --lib lints: name: Lints @@ -72,9 +72,9 @@ jobs: - name: Cargo clippy WASM run: cargo clippy --target wasm32-unknown-unknown --package minimal-web --tests -- -D warnings - name: Cargo clippy Android ARMv7 - run: cargo clippy --target armv7-linux-androideabi --package minimal-winit-android + run: cargo clippy --target armv7-linux-androideabi --package minimal-winit-android --lib - name: Cargo clippy Android AARCH64 - run: cargo clippy --target aarch64-linux-android --package minimal-winit-android + run: cargo clippy --target aarch64-linux-android --package minimal-winit-android --lib - name: Cargo machete run: cargo machete From 756ec08df70db72b05d2288fbdd0a6e064862303 Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sun, 17 Aug 2025 14:23:59 -0700 Subject: [PATCH 6/7] Fixes for android clippy and build --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2525994..f7c2ea8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,9 +72,9 @@ jobs: - name: Cargo clippy WASM run: cargo clippy --target wasm32-unknown-unknown --package minimal-web --tests -- -D warnings - name: Cargo clippy Android ARMv7 - run: cargo clippy --target armv7-linux-androideabi --package minimal-winit-android --lib + run: cargo clippy --target armv7-linux-androideabi --package minimal-winit-android --lib --tests -- -D warnings - name: Cargo clippy Android AARCH64 - run: cargo clippy --target aarch64-linux-android --package minimal-winit-android --lib + run: cargo clippy --target aarch64-linux-android --package minimal-winit-android --lib --tests -- -D warnings - name: Cargo machete run: cargo machete @@ -164,7 +164,7 @@ jobs: with: crate: cargo-apk - name: Android build - run: cargo apk build --package ${{ matrix.example }} + run: cargo apk build --package ${{ matrix.example }} --lib # See https://github.com/parasyte/pixels-ci-rust-version rust-version: From 790900e3c6d283ccab762d0763e4b80a75d0518d Mon Sep 17 00:00:00 2001 From: Jay Oster Date: Sun, 17 Aug 2025 14:35:23 -0700 Subject: [PATCH 7/7] Trying to fix clippy again --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7c2ea8f..c10ba443 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,9 +72,9 @@ jobs: - name: Cargo clippy WASM run: cargo clippy --target wasm32-unknown-unknown --package minimal-web --tests -- -D warnings - name: Cargo clippy Android ARMv7 - run: cargo clippy --target armv7-linux-androideabi --package minimal-winit-android --lib --tests -- -D warnings + run: cargo clippy --target armv7-linux-androideabi --package minimal-winit-android --lib -- -D warnings - name: Cargo clippy Android AARCH64 - run: cargo clippy --target aarch64-linux-android --package minimal-winit-android --lib --tests -- -D warnings + run: cargo clippy --target aarch64-linux-android --package minimal-winit-android --lib -- -D warnings - name: Cargo machete run: cargo machete