Skip to content

Commit 21bae15

Browse files
authored
Fix minimal-winit-android build. (#430)
* 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. * Add Android CI support
1 parent 513c411 commit 21bae15

File tree

8 files changed

+205
-43
lines changed

8 files changed

+205
-43
lines changed

.github/workflows/ci.yml

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ jobs:
2222
- name: Install dependencies
2323
run: sudo apt -y install libgtk-3-dev libudev-dev libxdo-dev
2424
- name: Install toolchain
25-
uses: dtolnay/rust-toolchain@master
25+
uses: dtolnay/rust-toolchain@stable
2626
with:
2727
toolchain: ${{ matrix.rust }}
28-
target: wasm32-unknown-unknown
28+
targets: wasm32-unknown-unknown,armv7-linux-androideabi,aarch64-linux-android
2929
- name: Rust cache
3030
uses: Swatinem/rust-cache@v2
3131
with:
@@ -34,6 +34,10 @@ jobs:
3434
run: cargo check --workspace
3535
- name: Cargo check WASM
3636
run: cargo check --target wasm32-unknown-unknown --package minimal-web
37+
- name: Cargo check Android ARMv7
38+
run: cargo check --target armv7-linux-androideabi --package minimal-winit-android --lib
39+
- name: Cargo check Android AARCH64
40+
run: cargo check --target aarch64-linux-android --package minimal-winit-android --lib
3741

3842
lints:
3943
name: Lints
@@ -46,11 +50,11 @@ jobs:
4650
- name: Install dependencies
4751
run: sudo apt -y install libgtk-3-dev libudev-dev libxdo-dev
4852
- name: Install toolchain
49-
uses: dtolnay/rust-toolchain@master
53+
uses: dtolnay/rust-toolchain@stable
5054
with:
5155
toolchain: stable
5256
components: clippy, rustfmt
53-
target: wasm32-unknown-unknown
57+
targets: wasm32-unknown-unknown,armv7-linux-androideabi,aarch64-linux-android
5458
- name: Rust cache
5559
uses: Swatinem/rust-cache@v2
5660
with:
@@ -67,6 +71,10 @@ jobs:
6771
run: cargo clippy --workspace --tests -- -D warnings
6872
- name: Cargo clippy WASM
6973
run: cargo clippy --target wasm32-unknown-unknown --package minimal-web --tests -- -D warnings
74+
- name: Cargo clippy Android ARMv7
75+
run: cargo clippy --target armv7-linux-androideabi --package minimal-winit-android --lib -- -D warnings
76+
- name: Cargo clippy Android AARCH64
77+
run: cargo clippy --target aarch64-linux-android --package minimal-winit-android --lib -- -D warnings
7078
- name: Cargo machete
7179
run: cargo machete
7280

@@ -88,7 +96,7 @@ jobs:
8896
- name: Install dependencies
8997
run: sudo apt -y install libgtk-3-dev libudev-dev libxdo-dev
9098
- name: Install toolchain
91-
uses: dtolnay/rust-toolchain@master
99+
uses: dtolnay/rust-toolchain@stable
92100
with:
93101
toolchain: ${{ matrix.rust }}
94102
- name: Rust cache
@@ -110,17 +118,54 @@ jobs:
110118
- name: Checkout sources
111119
uses: actions/checkout@v3
112120
- name: Install toolchain
113-
uses: dtolnay/rust-toolchain@master
121+
uses: dtolnay/rust-toolchain@stable
114122
with:
115123
toolchain: stable
116-
target: wasm32-unknown-unknown
124+
targets: wasm32-unknown-unknown
117125
- name: Rust cache
118126
uses: Swatinem/rust-cache@v2
119127
with:
120128
shared-key: common
121129
- name: WASM build
122130
run: cargo run-wasm --build-only --package ${{ matrix.example }}
123131

132+
android:
133+
name: Android
134+
runs-on: ubuntu-latest
135+
needs: [checks, lints]
136+
strategy:
137+
matrix:
138+
example:
139+
- minimal-winit-android
140+
steps:
141+
- name: Checkout sources
142+
uses: actions/checkout@v3
143+
- name: Install toolchain
144+
uses: dtolnay/rust-toolchain@stable
145+
with:
146+
toolchain: stable
147+
targets: armv7-linux-androideabi,aarch64-linux-android
148+
- name: Set up JDK 17
149+
uses: actions/setup-java@v3
150+
with:
151+
java-version: '17'
152+
distribution: 'temurin'
153+
- name: Setup Android SDK
154+
uses: android-actions/setup-android@v3
155+
with:
156+
cmdline-tools-version: 13114758
157+
packages: build-tools;35.0.1 ndk;28.2.13676358 platforms;android-35
158+
- name: Rust cache
159+
uses: Swatinem/rust-cache@v2
160+
with:
161+
shared-key: common
162+
- name: Install cargo-apk
163+
uses: baptiste0928/cargo-install@v2
164+
with:
165+
crate: cargo-apk
166+
- name: Android build
167+
run: cargo apk build --package ${{ matrix.example }} --lib
168+
124169
# See https://github.com/parasyte/pixels-ci-rust-version
125170
rust-version:
126171
name: Rust-Version
@@ -136,7 +181,7 @@ jobs:
136181
with:
137182
repository: parasyte/pixels-ci-rust-version
138183
- name: Install toolchain
139-
uses: dtolnay/rust-toolchain@master
184+
uses: dtolnay/rust-toolchain@stable
140185
with:
141186
toolchain: ${{ matrix.rust }}
142187
- name: Rust cache

Cargo.lock

Lines changed: 15 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Ignore everything
2+
*

examples/minimal-winit-android/Cargo.toml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,23 @@ winit = { workspace = true, features = ["android-native-activity"] }
1414
env_logger.workspace = true
1515

1616
[target.'cfg(target_os = "android")'.dependencies]
17-
android_logger = "0.11.0"
17+
android_logger = "0.15.0"
18+
19+
[[bin]]
20+
# Rename the bin to avoid https://github.com/rust-lang/cargo/issues/6313
21+
name = "desktop-example"
22+
path = "src/main.rs"
1823

1924
[lib]
20-
crate-type = ["cdylib"]
25+
crate-type = ["cdylib", "lib"]
2126

2227
[package.metadata.android.signing.release]
2328
path = "./path/to/KeyStoreFile.jks"
2429
keystore_password = "password"
30+
31+
[package.metadata.android]
32+
build_targets = ["armv7-linux-androideabi", "aarch64-linux-android"]
33+
34+
[package.metadata.android.sdk]
35+
min_sdk_version = 23
36+
target_sdk_version = 35
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Build with the `pixels` MSRV. This will be updated in lockstep.
2+
FROM rust:1.82.0-slim
3+
4+
# Variable arguments
5+
ARG JAVA_VERSION=17
6+
ARG NDK_VERSION=28.2.13676358
7+
ARG BUILDTOOLS_VERSION=35.0.1
8+
ARG PLATFORM_VERSION=android-35
9+
ARG CLITOOLS_VERSION=13114758_latest
10+
11+
# Install Android requirements
12+
RUN apt-get update -yqq && \
13+
apt-get install -y --no-install-recommends \
14+
libcurl4-openssl-dev libssl-dev pkg-config build-essential git python3 wget zip unzip openjdk-${JAVA_VERSION}-jdk && \
15+
apt-get clean && \
16+
rm -rf /var/lib/apt/lists/*
17+
18+
# Install android targets
19+
RUN rustup target add armv7-linux-androideabi aarch64-linux-android
20+
21+
# Install cargo-apk
22+
RUN cargo install --locked cargo-apk
23+
24+
# Generate Environment Variables
25+
ENV JAVA_VERSION=${JAVA_VERSION}
26+
ENV ANDROID_HOME=/opt/Android
27+
ENV NDK_HOME=/opt/Android/ndk/${NDK_VERSION}
28+
ENV ANDROID_NDK_ROOT=${NDK_HOME}
29+
ENV PATH=$PATH:${ANDROID_HOME}:${ANDROID_NDK_ROOT}:${ANDROID_HOME}/build-tools/${BUILDTOOLS_VERSION}:${ANDROID_HOME}/cmdline-tools/bin
30+
31+
# Install command line tools
32+
RUN mkdir -p ${ANDROID_HOME}/cmdline-tools && \
33+
wget -qc "https://dl.google.com/android/repository/commandlinetools-linux-${CLITOOLS_VERSION}.zip" -P /tmp && \
34+
unzip -d ${ANDROID_HOME} /tmp/commandlinetools-linux-${CLITOOLS_VERSION}.zip && \
35+
rm -fr /tmp/commandlinetools-linux-${CLITOOLS_VERSION}.zip
36+
# Install sdk requirements
37+
RUN echo y | sdkmanager --sdk_root=${ANDROID_HOME} --install \
38+
"build-tools;${BUILDTOOLS_VERSION}" "ndk;${NDK_VERSION}" "platforms;${PLATFORM_VERSION}"
39+
40+
# Create APK keystore for debug profile
41+
# Adapted from https://github.com/rust-mobile/cargo-apk/blob/caa806283dc26733ad8232dce1fa4896c566f7b8/ndk-build/src/ndk.rs#L393-L423
42+
RUN keytool -genkey -v -keystore ${HOME}/.android/debug.keystore -storepass android -alias androiddebugkey \
43+
-keypass android -dname 'CN=Android Debug,O=Android,C=US' -keyalg RSA -keysize 2048 -validity 10000
44+
45+
# Cleanup
46+
RUN rm -rf /tmp/*
47+
48+
WORKDIR /mnt
49+
50+
ENTRYPOINT [ "cargo", "apk", "build", "--lib" ]

examples/minimal-winit-android/README.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,51 @@
44

55
Minimal example to run on android using `winit` with `android-native-activity` feature
66

7-
## Running
8-
```
7+
## Running on Android
8+
9+
```bash
910
export ANDROID_HOME="path/to/sdk"
1011
export ANDROID_NDK_HOME="path/to/ndk"
1112

1213
rustup target add aarch64-linux-android
1314
cargo install cargo-apk
1415
```
16+
1517
Connect your Android device via USB cable to your computer in debug mode and run the following command
18+
19+
```bash
20+
cargo apk run --package minimal-winit-android
21+
```
22+
23+
## Running on Desktop
24+
25+
Sometimes it is helpful to run your Android apps on a Desktop environment (e.g., Windows, macOS, or
26+
Linux). It works the same way as all other `pixels` examples:
27+
28+
```bash
29+
cargo run --package minimal-winit-android
30+
```
31+
32+
## Containerized Builds
33+
34+
A `Containerfile` is included, allowing containerized builds without the need to install the Android
35+
SDK and dependencies on the build host.
36+
37+
The following commands are assumed to be run from the repository root directory.
38+
39+
Build the image in Docker:
40+
41+
```bash
42+
docker build -t rust-android:latest ./examples/minimal-winit-android/
1643
```
17-
cargo apk run
44+
45+
Build the example in the container:
46+
47+
```bash
48+
docker run --rm -it -v "$PWD:/src" rust-android:latest --package minimal-winit-android
1849
```
50+
51+
The APKs are written to `./target/debug/apk/`.
52+
53+
For release builds, add `--release` to the end of the `run` command. (This will need the path to
54+
the keystore corrected in the Cargo.toml manifest.) The APKs are written to `./target/release/apk/`.

examples/minimal-winit-android/src/lib.rs

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![deny(clippy::all)]
2-
#![forbid(unsafe_code)]
2+
#![cfg_attr(not(target_os = "android"), forbid(unsafe_code))]
33

44
#[cfg(target_os = "android")]
55
use winit::platform::android::activity::AndroidApp;
@@ -22,14 +22,13 @@ struct World {
2222
velocity_y: i16,
2323
}
2424

25-
struct Display<'win> {
25+
struct Display {
2626
window: Arc<Window>,
27-
pixels: Pixels<'win>,
27+
pixels: Pixels<'static>,
2828
}
2929

30-
fn _main(event_loop: EventLoop<()>) {
31-
let mut display: Option<Display> = None;
32-
30+
pub fn _main(event_loop: EventLoop<()>) {
31+
let mut display = None;
3332
let mut world = World::new();
3433

3534
let res = event_loop.run(|event, elwt| {
@@ -52,10 +51,17 @@ fn _main(event_loop: EventLoop<()>) {
5251
Event::Suspended => {
5352
display = None;
5453
}
54+
Event::WindowEvent {
55+
event: WindowEvent::CloseRequested,
56+
..
57+
} => {
58+
elwt.exit();
59+
}
5560
Event::WindowEvent {
5661
event: WindowEvent::RedrawRequested,
5762
..
5863
} => {
64+
world.update();
5965
if let Some(display) = &mut display {
6066
world.draw(display.pixels.frame_mut());
6167
display.pixels.render().unwrap();
@@ -64,9 +70,6 @@ fn _main(event_loop: EventLoop<()>) {
6470
}
6571
_ => {}
6672
}
67-
if display.is_some() {
68-
world.update();
69-
}
7073
});
7174
res.unwrap();
7275
}
@@ -119,25 +122,18 @@ impl World {
119122
}
120123
}
121124

122-
#[allow(dead_code)]
123125
#[cfg(target_os = "android")]
124126
#[no_mangle]
125127
fn android_main(app: AndroidApp) {
128+
use android_logger::Config;
129+
use winit::event_loop::EventLoopBuilder;
126130
use winit::platform::android::EventLoopBuilderExtAndroid;
127-
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info));
128-
let event_loop = EventLoopBuilder::new().with_android_app(app).build();
129-
log::info!("Hello from android!");
130-
_main(event_loop);
131-
}
132131

133-
#[allow(dead_code)]
134-
#[cfg(not(target_os = "android"))]
135-
fn main() {
136-
env_logger::builder()
137-
.filter_level(log::LevelFilter::Info) // Default Log Level
138-
.parse_default_env()
139-
.init();
140-
let event_loop = EventLoop::new().unwrap();
141-
log::info!("Hello from desktop!");
132+
android_logger::init_once(Config::default().with_max_level(log::LevelFilter::Info));
133+
let event_loop = EventLoopBuilder::new()
134+
.with_android_app(app)
135+
.build()
136+
.unwrap();
137+
log::info!("Hello from android!");
142138
_main(event_loop);
143139
}

0 commit comments

Comments
 (0)