From 87cda3c560f2c9928a8a613a9f900948090d6545 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Mon, 10 Mar 2025 12:28:17 +0100 Subject: [PATCH 1/2] Build-test (documentation) on the host and fix broken doc samples --- .github/workflows/ci.yml | 5 +++++ android-activity/Cargo.toml | 4 ++++ android-activity/src/input/sdk.rs | 7 +++---- android-activity/src/lib.rs | 27 ++++++++++++++------------- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3154a3a..b2e5dba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,6 +96,11 @@ jobs: run: > cargo ndk -t arm64-v8a doc --no-deps + - name: Tests (host build-testing) + run: | + cargo test -F native-activity -F test + cargo test -F game-activity -F test + format: runs-on: ubuntu-latest steps: diff --git a/android-activity/Cargo.toml b/android-activity/Cargo.toml index 2695132..449565c 100644 --- a/android-activity/Cargo.toml +++ b/android-activity/Cargo.toml @@ -28,6 +28,10 @@ native-activity = [] api-level-30 = ["ndk/api-level-30"] api-level-33 = ["api-level-30", "ndk/api-level-33"] +# "Internal" feature to allow build-testing this crate for the host +# architecture, needed to make doctests actually compile sample code. +test = ["ndk/test", "ndk-sys/test"] + [dependencies] log = "0.4" jni-sys = "0.3" diff --git a/android-activity/src/input/sdk.rs b/android-activity/src/input/sdk.rs index 70a2c2c..978ad5a 100644 --- a/android-activity/src/input/sdk.rs +++ b/android-activity/src/input/sdk.rs @@ -284,13 +284,12 @@ impl KeyCharacterMap { /// Get the character that is produced by combining the dead key producing accent with the key producing character c. /// - /// For example, ```get_dead_char('`', 'e')``` returns 'è'. `get_dead_char('^', ' ')` returns '^' and `get_dead_char('^', '^')` returns '^'. + /// For example, ``get_dead_char('`', 'e')`` returns `'è'`. `get_dead_char('^', ' ')` returns `'^'` and `get_dead_char('^', '^')` returns `'^'`. /// /// # Errors /// - /// Since this API needs to use JNI internally to call into the Android JVM it may return - /// a [`AppError::JavaError`] in case there is a spurious JNI error or an exception - /// is caught. + /// Since this API needs to use JNI internally to call into the Android JVM it may return a + /// [`AppError::JavaError`] in case there is a spurious JNI error or an exception is caught. pub fn get_dead_char( &self, accent_char: char, diff --git a/android-activity/src/lib.rs b/android-activity/src/lib.rs index e8984fe..c027cba 100644 --- a/android-activity/src/lib.rs +++ b/android-activity/src/lib.rs @@ -13,15 +13,15 @@ //! a wider range of devices. //! //! Standalone applications based on this crate need to be built as `cdylib` libraries, like: -//! ``` +//! ```toml //! [lib] //! crate_type=["cdylib"] //! ``` //! //! and implement a `#[no_mangle]` `android_main` entry point like this: -//! ```rust +//! ``` //! #[no_mangle] -//! fn android_main(app: AndroidApp) { +//! fn android_main(app: android_activity::AndroidApp) { //! //! } //! ``` @@ -64,6 +64,7 @@ //! These are undone after `android_main()` returns //! //! # Android Extensible Enums +// TODO: Move this to the NDK crate, which now implements this for most of the code? //! //! There are numerous enums in the `android-activity` API which are effectively //! bindings to enums declared in the Android SDK which need to be considered @@ -95,7 +96,7 @@ //! For example, here is how you could ensure forwards compatibility with both //! compile-time and runtime extensions of a `SomeEnum` enum: //! -//! ```rust +//! ```ignore //! match some_enum { //! SomeEnum::Foo => {}, //! SomeEnum::Bar => {}, @@ -131,7 +132,7 @@ use ndk::native_window::NativeWindow; pub use ndk; pub use ndk_sys; -#[cfg(not(target_os = "android"))] +#[cfg(all(not(target_os = "android"), not(feature = "test")))] compile_error!("android-activity only supports compiling for Android"); #[cfg(all(feature = "game-activity", feature = "native-activity"))] @@ -556,10 +557,10 @@ impl AndroidApp { /// between native Rust code and Java/Kotlin code running within the JVM. /// /// If you use the [`jni`] crate you can wrap this as a [`JavaVM`] via: - /// ```ignore + /// ```no_run /// # use jni::JavaVM; - /// # let app: AndroidApp = todo!(); - /// let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr()) }; + /// # let app: android_activity::AndroidApp = todo!(); + /// let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr().ast()) }; /// ``` /// /// [`jni`]: https://crates.io/crates/jni @@ -571,10 +572,10 @@ impl AndroidApp { /// Returns a JNI object reference for this application's JVM `Activity` as a pointer /// /// If you use the [`jni`] crate you can wrap this as an object reference via: - /// ```ignore + /// ```no_run /// # use jni::objects::JObject; - /// # let app: AndroidApp = todo!(); - /// let activity = unsafe { JObject::from_raw(app.activity_as_ptr()) }; + /// # let app: android_activity::AndroidApp = todo!(); + /// let activity = unsafe { JObject::from_raw(app.activity_as_ptr().cast()) }; /// ``` /// /// # JNI Safety @@ -731,7 +732,7 @@ impl AndroidApp { /// # Example /// Code to iterate all pending input events would look something like this: /// - /// ```rust + /// ``` /// match app.input_events_iter() { /// Ok(mut iter) => { /// loop { @@ -786,7 +787,7 @@ impl AndroidApp { /// /// Code to handle unicode character mapping as well as combining dead keys could look some thing like: /// - /// ```rust + /// ``` /// let mut combining_accent = None; /// // Snip /// From 019ad634a21ae257ac5823be9eb3810bf5285241 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Fri, 8 Aug 2025 10:44:54 +0200 Subject: [PATCH 2/2] Switch doctests back to native cross-compilation, supported since Rust 1.89 https://blog.rust-lang.org/2025/08/07/Rust-1.89.0/#cross-compiled-doctests --- .github/workflows/ci.yml | 11 ++++++++--- android-activity/Cargo.toml | 4 ---- android-activity/src/lib.rs | 36 +++++++++++++++++++++--------------- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2e5dba..30c8133 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,10 +96,15 @@ jobs: run: > cargo ndk -t arm64-v8a doc --no-deps - - name: Tests (host build-testing) + - name: Build doctests + # All doctests are set to no_run, because they require running in the + # context of an Android app. + # Only run on stable because cross-compiling doctests is only supported + # since Rust 1.89. + if: ${{ matrix.rust-version == 'stable' }} run: | - cargo test -F native-activity -F test - cargo test -F game-activity -F test + cargo test --doc -F native-activity --target aarch64-linux-android + cargo ndk -t arm64-v8a -- test --doc -F game-activity format: runs-on: ubuntu-latest diff --git a/android-activity/Cargo.toml b/android-activity/Cargo.toml index 449565c..2695132 100644 --- a/android-activity/Cargo.toml +++ b/android-activity/Cargo.toml @@ -28,10 +28,6 @@ native-activity = [] api-level-30 = ["ndk/api-level-30"] api-level-33 = ["api-level-30", "ndk/api-level-33"] -# "Internal" feature to allow build-testing this crate for the host -# architecture, needed to make doctests actually compile sample code. -test = ["ndk/test", "ndk-sys/test"] - [dependencies] log = "0.4" jni-sys = "0.3" diff --git a/android-activity/src/lib.rs b/android-activity/src/lib.rs index c027cba..42c207c 100644 --- a/android-activity/src/lib.rs +++ b/android-activity/src/lib.rs @@ -19,7 +19,7 @@ //! ``` //! //! and implement a `#[no_mangle]` `android_main` entry point like this: -//! ``` +//! ```no_run //! #[no_mangle] //! fn android_main(app: android_activity::AndroidApp) { //! @@ -132,7 +132,7 @@ use ndk::native_window::NativeWindow; pub use ndk; pub use ndk_sys; -#[cfg(all(not(target_os = "android"), not(feature = "test")))] +#[cfg(not(target_os = "android"))] compile_error!("android-activity only supports compiling for Android"); #[cfg(all(feature = "game-activity", feature = "native-activity"))] @@ -560,7 +560,7 @@ impl AndroidApp { /// ```no_run /// # use jni::JavaVM; /// # let app: android_activity::AndroidApp = todo!(); - /// let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr().ast()) }; + /// let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr().cast()) }; /// ``` /// /// [`jni`]: https://crates.io/crates/jni @@ -732,7 +732,9 @@ impl AndroidApp { /// # Example /// Code to iterate all pending input events would look something like this: /// - /// ``` + /// ```no_run + /// # use android_activity::{AndroidApp, InputStatus, input::InputEvent}; + /// # let app: AndroidApp = todo!(); /// match app.input_events_iter() { /// Ok(mut iter) => { /// loop { @@ -740,12 +742,13 @@ impl AndroidApp { /// let handled = match event { /// InputEvent::KeyEvent(key_event) => { /// // Snip + /// InputStatus::Handled /// } /// InputEvent::MotionEvent(motion_event) => { - /// // Snip + /// InputStatus::Unhandled /// } /// event => { - /// // Snip + /// InputStatus::Unhandled /// } /// }; /// @@ -767,7 +770,7 @@ impl AndroidApp { /// /// This must only be called from your `android_main()` thread and it may panic if called /// from another thread. - pub fn input_events_iter(&self) -> Result { + pub fn input_events_iter(&self) -> Result> { let receiver = { let guard = self.inner.read().unwrap(); guard.input_events_receiver()? @@ -787,44 +790,47 @@ impl AndroidApp { /// /// Code to handle unicode character mapping as well as combining dead keys could look some thing like: /// - /// ``` + /// ```no_run + /// # use android_activity::{AndroidApp, input::{InputEvent, KeyEvent, KeyMapChar}}; + /// # let app: AndroidApp = todo!(); + /// # let key_event: KeyEvent = todo!(); /// let mut combining_accent = None; /// // Snip /// - /// let combined_key_char = if let Ok(map) = app.device_key_character_map(device_id) { + /// let combined_key_char = if let Ok(map) = app.device_key_character_map(key_event.device_id()) { /// match map.get(key_event.key_code(), key_event.meta_state()) { /// Ok(KeyMapChar::Unicode(unicode)) => { /// let combined_unicode = if let Some(accent) = combining_accent { /// match map.get_dead_char(accent, unicode) { /// Ok(Some(key)) => { - /// info!("KeyEvent: Combined '{unicode}' with accent '{accent}' to give '{key}'"); + /// println!("KeyEvent: Combined '{unicode}' with accent '{accent}' to give '{key}'"); /// Some(key) /// } /// Ok(None) => None, /// Err(err) => { - /// log::error!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}"); + /// eprintln!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}"); /// None /// } /// } /// } else { - /// info!("KeyEvent: Pressed '{unicode}'"); + /// println!("KeyEvent: Pressed '{unicode}'"); /// Some(unicode) /// }; /// combining_accent = None; /// combined_unicode.map(|unicode| KeyMapChar::Unicode(unicode)) /// } /// Ok(KeyMapChar::CombiningAccent(accent)) => { - /// info!("KeyEvent: Pressed 'dead key' combining accent '{accent}'"); + /// println!("KeyEvent: Pressed 'dead key' combining accent '{accent}'"); /// combining_accent = Some(accent); /// Some(KeyMapChar::CombiningAccent(accent)) /// } /// Ok(KeyMapChar::None) => { - /// info!("KeyEvent: Pressed non-unicode key"); + /// println!("KeyEvent: Pressed non-unicode key"); /// combining_accent = None; /// None /// } /// Err(err) => { - /// log::error!("KeyEvent: Failed to get key map character: {err:?}"); + /// eprintln!("KeyEvent: Failed to get key map character: {err:?}"); /// combining_accent = None; /// None /// }