diff --git a/.github/workflows/android_test.sh b/.github/workflows/android_test.sh deleted file mode 100755 index 6368cabc..00000000 --- a/.github/workflows/android_test.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -set -ex - -# Make sure the package is removed since it may end up in the AVD cache. This causes -# INSTALL_FAILED_UPDATE_INCOMPATIBLE errors when the debug keystore is regenerated, -# as it is not stored/cached on the CI: -# https://github.com/rust-windowing/android-ndk-rs/blob/240389f1e281f582b84a8049e2afaa8677d901c2/ndk-build/src/ndk.rs#L308-L332 -adb uninstall rust.example.hello_world || true - -if [ -z "$1" ]; -then - cargo apk run -p ndk-examples --target x86_64-linux-android --example hello_world --no-logcat -else - adb install -r "$1/hello_world.apk" - adb shell am start -a android.intent.action.MAIN -n "rust.example.hello_world/android.app.NativeActivity" -fi - -sleep 30s - -adb logcat *:E hello-world:V -d | tee ~/logcat.log - -if grep 'hello world' ~/logcat.log; -then - echo "App running" -else - echo "::error::App not running" - exit 1 -fi - -ERROR_MSG=$(grep -e 'thread.*panicked at' "$HOME"/logcat.log | true) -if [ -z "${ERROR_MSG}" ]; -then - exit 0 -else - echo "::error::${ERROR_MSG}" - exit 1 -fi diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 22b83e91..092895bf 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,11 +16,6 @@ jobs: crate: - { name: "ndk-sys", target: "armv7-linux-androideabi" } - { name: "ndk", target: "armv7-linux-androideabi" } - - { name: "ndk-macro", target: "x86_64-unknown-linux-gnu" } - - { name: "ndk-context", target: "armv7-linux-androideabi" } - - { name: "ndk-glue", target: "armv7-linux-androideabi" } - - { name: "ndk-build", target: "x86_64-unknown-linux-gnu" } - - { name: "cargo-apk", target: "x86_64-unknown-linux-gnu" } steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a6e2b01c..6506c6e6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -30,10 +30,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - # Use one of our supported targets to lint all crates including - # the target-specific `ndk` in one go. - # This assumes our host-tools (cargo-apk and dependencies) - # also compile cleanly under this target. + # Use a cross-compilation target (that's typical for Android) to lint everything args: --all --all-targets --all-features --target aarch64-linux-android -- -Dwarnings check_msrv: @@ -47,7 +44,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: check - # See comment above about using one of our supported targets. + # Use a cross-compilation target (that's typical for Android) to lint everything args: --workspace --all-targets --all-features --target aarch64-linux-android build: @@ -57,123 +54,39 @@ jobs: os: [ubuntu-latest] rust-channel: ['stable', 'nightly'] rust-target: - - 'armv7-linux-androideabi' - - 'aarch64-linux-android' - - 'i686-linux-android' - - 'x86_64-linux-android' + - { triple: 'armv7-linux-androideabi', abi: armeabi-v7a } + - { triple: 'aarch64-linux-android', abi: arm64-v8a } + - { triple: 'i686-linux-android', abi: x86 } + - { triple: 'x86_64-linux-android', abi: x86_64 } include: - os: windows-latest rust-channel: 'stable' - rust-target: 'aarch64-linux-android' + rust-target: { triple: 'aarch64-linux-android', abi: arm64-v8a } - os: windows-latest rust-channel: 'stable' - rust-target: 'x86_64-linux-android' + rust-target: { triple: 'x86_64-linux-android', abi: x86_64 } runs-on: ${{ matrix.os }} - name: Build apk + name: Cross-compile steps: - uses: actions/checkout@v2 - - name: Installing Rust ${{ matrix.rust-channel }} w/ ${{ matrix.rust-target }} + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + + - name: Install cargo-ndk + run: cargo install cargo-ndk + + - name: Installing Rust ${{ matrix.rust-channel }} w/ ${{ matrix.rust-target.triple }} uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust-channel }} - target: ${{ matrix.rust-target }} + target: ${{ matrix.rust-target.triple }} override: true - - name: Install cargo-apk - run: - cargo install --path cargo-apk - - - name: Cargo check for target ${{ matrix.rust-target }} - run: cargo check -p ndk --target ${{ matrix.rust-target }} --all-features - - - name: Cargo apk build for target ${{ matrix.rust-target }} - run: cargo apk build -p ndk-examples --target ${{ matrix.rust-target }} --examples - - - uses: actions/upload-artifact@v2 - # Only need this for CI, unless users are interested in downloading - # a ready-made app that does nothing but printing "hello world". - if: ${{ matrix.rust-target == 'x86_64-linux-android' }} - name: Upload hello_world apk - with: - name: hello_world_${{ matrix.os }}_${{ matrix.rust-target }} - path: ./target/debug/apk/examples/hello_world.apk - - android_emulator: - name: hello_world example on emulator - needs: build - runs-on: macos-latest - strategy: - matrix: - source_os: [ubuntu-latest, windows-latest, local] - env: - api-level: 29 - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - arch: x86_64 - # the `googleapis` emulator target is considerably slower on CI. - target: default - profile: Nexus 6 - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - uses: actions/download-artifact@v2 - name: Download hello_world APK - if: ${{ matrix.source_os != 'local' }} - id: download - with: - name: hello_world_${{ matrix.source_os }}_x86_64-linux-android - - - name: Install `cargo-apk` and add `x86_64-linux-android` target - if: ${{ matrix.source_os == 'local' }} - run: | - cargo install --path cargo-apk - rustup target add x86_64-linux-android - - - name: AVD cache - uses: actions/cache@v2 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - # Bump the trailing number when making changes to the emulator setup below - key: avd-${{ env.api-level }}-1 - - - name: create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ env.api-level }} - arch: ${{ env.arch }} - target: ${{ env.target }} - profile: ${{ env.profile }} - emulator-options: ${{ env.emulator-options }} - disable-animations: true - force-avd-creation: false - script: echo "Generated AVD snapshot for caching." - - - name: Start hello_world example - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ env.api-level }} - arch: ${{ env.arch }} - target: ${{ env.target }} - profile: ${{ env.profile }} - emulator-options: -no-snapshot-save ${{ env.emulator-options }} - disable-animations: true - force-avd-creation: false - script: ./.github/workflows/android_test.sh "${{ steps.download.outputs.download-path }}" - - - name: Upload emulator logs - uses: actions/upload-artifact@v2 - if: ${{ always() }} - with: - name: log - path: ~/logcat.log + - name: Compile for ${{ matrix.rust-target.triple }} + run: cargo ndk -t ${{ matrix.rust-target.abi }} build build-host: strategy: @@ -202,22 +115,6 @@ jobs: run: cargo test -p ndk --all-features - - name: Test ndk-build - run: - cargo test -p ndk-build --all-features - - - name: Test ndk-glue - run: - cargo test -p ndk-glue --all-features - - - name: Test ndk-macro - run: - cargo test -p ndk-macro --all-features - - - name: Test cargo-apk - run: - cargo test -p cargo-apk --all-features - docs: strategy: fail-fast: false diff --git a/Cargo.toml b/Cargo.toml index b189f3e0..44881983 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,5 @@ [workspace] members = [ "ndk", - "ndk-context", - "ndk-macro", - "ndk-build", - "ndk-examples", - "ndk-glue", "ndk-sys", - "cargo-apk", ] diff --git a/cargo-apk/CHANGELOG.md b/cargo-apk/CHANGELOG.md deleted file mode 100644 index ee3e6447..00000000 --- a/cargo-apk/CHANGELOG.md +++ /dev/null @@ -1,105 +0,0 @@ -# Unreleased - -# 0.9.6 (2022-11-23) - -- Profile signing information can now be specified via the `CARGO_APK__KEYSTORE` and `CARGO_APK__KEYSTORE_PASSWORD` environment variables. The environment variables take precedence over signing information in the cargo manifest. Both environment variables are required except in the case of the `dev` profile, which will fall back to the default password if `CARGO_APK_DEV_KEYSTORE_PASSWORD` is not set. ([#358](https://github.com/rust-windowing/android-ndk-rs/pull/358)) -- Add `strip` option to `android` metadata, allowing a user to specify how they want debug symbols treated after cargo has finished building, but before the shared object is copied into the APK. ([#356](https://github.com/rust-windowing/android-ndk-rs/pull/356)) -- Support [`[workspace.package]` inheritance](https://doc.rust-lang.org/cargo/reference/workspaces.html#the-workspacepackage-table) from a workspace root manifest for the `version` field under `[package]`. ([#360](https://github.com/rust-windowing/android-ndk-rs/pull/360)) - -(0.9.5, released on 2022-10-14, was yanked due to unintentionally bumping MSRV through the `quick-xml` crate, and breaking `cargo apk --` parsing after switching to `clap`.) - -- Update to `cargo-subcommand 0.8.0` with `clap` argument parser. ([#238](https://github.com/rust-windowing/android-ndk-rs/pull/238)) -- Automate `adb reverse` port forwarding through `Cargo.toml` metadata ([#348](https://github.com/rust-windowing/android-ndk-rs/pull/348)) - -# 0.9.4 (2022-09-12) - -- Upgrade to latest `ndk-build` to deduplicate libraries before packaging them into the APK. ([#333](https://github.com/rust-windowing/android-ndk-rs/pull/333)) -- Support `android:resizeableActivity`. ([#338](https://github.com/rust-windowing/android-ndk-rs/pull/338)) -- Add `--device` argument to select `adb` device by serial (see `adb devices` for connected devices and their serial). ([#329](https://github.com/rust-windowing/android-ndk-rs/pull/329)) -- Print and follow `adb logcat` output after starting app. ([#332](https://github.com/rust-windowing/android-ndk-rs/pull/332)) - -# 0.9.3 (2022-07-05) - -- Allow configuration of alternate debug keystore location; require keystore location for release builds. ([#299](https://github.com/rust-windowing/android-ndk-rs/pull/299)) -- **Breaking:** Rename `Activity::intent_filters` back to `Activity::intent_filter`. ([#305](https://github.com/rust-windowing/android-ndk-rs/pull/305)) - -# 0.9.2 (2022-06-11) - -- Move NDK r23 `-lgcc` workaround to `ndk_build::cargo::cargo_ndk()`, to also apply to our `cargo apk --` invocations. ([#286](https://github.com/rust-windowing/android-ndk-rs/pull/286)) -- Disable `aapt` compression for the [(default) `dev` profile](https://doc.rust-lang.org/cargo/reference/profiles.html). ([#283](https://github.com/rust-windowing/android-ndk-rs/pull/283)) -- Append `--target` to blanket `cargo apk --` calls when not provided by the user. ([#287](https://github.com/rust-windowing/android-ndk-rs/pull/287)) - -# 0.9.1 (2022-05-12) - -- Reimplement NDK r23 `-lgcc` workaround using `RUSTFLAGS`, to apply to transitive `cdylib` compilations. (#270) - -# 0.9.0 (2022-05-07) - -- **Breaking:** Use `min_sdk_version` to select compiler target instead of `target_sdk_version`. ([#197](https://github.com/rust-windowing/android-ndk-rs/pull/197)) - See for more details. -- **Breaking:** Default `target_sdk_version` to `30` or lower (instead of the highest supported SDK version by the detected NDK toolchain) - for more consistent interaction with Android backwards compatibility handling and its increasingly strict usage rules: - - ([#203](https://github.com/rust-windowing/android-ndk-rs/pull/203)) -- Allow manifest `package` property to be provided in `Cargo.toml`. ([#236](https://github.com/rust-windowing/android-ndk-rs/pull/236)) -- Add `MAIN` intent filter in `from_subcommand` instead of relying on a custom serialization function in `ndk-build`. ([#241](https://github.com/rust-windowing/android-ndk-rs/pull/241)) -- Export the sole `NativeActivity` (through `android:exported="true"`) to allow it to be started by default if targeting Android S or higher. ([#242](https://github.com/rust-windowing/android-ndk-rs/pull/242)) -- `cargo-apk` version can now be queried through `cargo apk version`. ([#218](https://github.com/rust-windowing/android-ndk-rs/pull/218)) -- Environment variables from `.cargo/config.toml`'s `[env]` section are now propagated to the process environment. ([#249](https://github.com/rust-windowing/android-ndk-rs/pull/249)) - -# 0.8.2 (2021-11-22) - -- Fixed the library name in case of multiple build artifacts in the Android manifest. -- Work around missing `libgcc` on NDK r23 beta 3 and above, by providing linker script that "redirects" to `libunwind`. - See and for more details. - -# 0.8.1 (2021-08-06) - -- Updated to use [ndk-build 0.4.2](../ndk-build/CHANGELOG.md#042-2021-08-06) - -# 0.8.0 (2021-07-06) - -- Added `runtime_libs` path to android metadata for packaging extra dynamic libraries into the apk. - -# 0.7.0 (2021-05-10) - -- Added `cargo apk check`. Useful for compile-testing crates that contain C/C++ dependencies or - target-specific conditional compilation, but do not provide a cdylib target. -- Added `apk_name` field to android metadata for APK file naming (defaults to Rust library name if unspecified). - The application label is now no longer used for this purpose, and can contain a string resource ID from now on. - -# 0.6.0 (2021-04-20) - -- **Breaking:** uses `ndk-build`'s new (de)serialized `Manifest` struct to properly serialize a toml's `[package.metadata.android]` to an `AndroidManifest.xml`. The `[package.metadata.android]` now closely resembles the structure of [an android manifest file](https://developer.android.com/guide/topics/manifest/manifest-element). See [README](README.md) for an example of the new `[package.metadata.android]` structure and all manifest attributes that are currently supported. - -# 0.5.6 (2020-11-25) - -- Use `dunce::simplified` when extracting the manifest's assets and resource folder -- Updated to use [ndk-build 0.1.4](../ndk-build/CHANGELOG.md#014-2020-11-25) - -# 0.5.5 (2020-11-21) - -- Updated to use [ndk-build 0.1.3](../ndk-build/CHANGELOG.md#013-2020-11-21) - -# 0.5.4 (2020-11-01) - -- Added support for activity metadata entries. -- Fix glob member resolution in workspaces. - -# 0.5.3 (2020-10-15) - -- Fix `res` folder resolve. - -# 0.5.2 (2020-09-15) - -- Updated to use [ndk-build 0.1.2](../ndk-build/CHANGELOG.md#012-2020-09-15) - -# 0.5.1 (2020-07-15) - -- Updated to use [ndk-build 0.1.1](../ndk-build/CHANGELOG.md#011-2020-07-15) - -# 0.5.0 (2020-04-22) - -- Updated to use [ndk-build 0.1.0](../ndk-build/CHANGELOG.md#010-2020-04-22) -- First release in almost 3 years! 🎉 -- **Breaking:** A ton of things changed! diff --git a/cargo-apk/Cargo.toml b/cargo-apk/Cargo.toml deleted file mode 100644 index f37fe5ac..00000000 --- a/cargo-apk/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "cargo-apk" -version = "0.9.6" -authors = ["The Rust Windowing contributors"] -edition = "2018" -description = "Helps cargo build APKs" -license = "MIT OR Apache-2.0" -keywords = ["android", "ndk", "apk"] -documentation = "https://docs.rs/cargo-apk" -homepage = "https://github.com/rust-windowing/android-ndk-rs" -repository = "https://github.com/rust-windowing/android-ndk-rs" -rust-version = "1.60" - -[dependencies] -anyhow = "1.0.57" -cargo-subcommand = "0.10" -clap = { version = "4", features = ["derive"] } -dunce = "1" -env_logger = "0.9" -log = "0.4" -ndk-build = { path = "../ndk-build", version = "0.9.0" } -serde = "1" -thiserror = "1" -toml = "0.5" diff --git a/cargo-apk/README.md b/cargo-apk/README.md deleted file mode 100644 index edae6e11..00000000 --- a/cargo-apk/README.md +++ /dev/null @@ -1,232 +0,0 @@ -# cargo apk - -Tool for creating Android packages. - -## Installation - -From crates.io: -```console -$ cargo install cargo-apk -``` - -From source: -```console -$ cargo install --path . -``` - -## Commands - -- `build`: Compiles the current package -- `run`: Run a binary or example of the local package -- `gdb`: Start a gdb session attached to an adb device with symbols loaded - -## Manifest - -`cargo` supports the `metadata` table for configurations for external tools like `cargo apk`. -Following configuration options are supported by `cargo apk` under `[package.metadata.android]`: - -```toml -[package.metadata.android] -# Specifies the package property of the manifest. -package = "com.foo.bar" - -# Specifies the array of targets to build for. -build_targets = [ "armv7-linux-androideabi", "aarch64-linux-android", "i686-linux-android", "x86_64-linux-android" ] - -# Path to your application's resources folder. -# If not specified, resources will not be included in the APK. -resources = "path/to/resources_folder" - -# Path to the folder containing your application's assets. -# If not specified, assets will not be included in the APK. -assets = "path/to/assets_folder" - -# Name for final APK file. -# Defaults to package name. -apk_name = "myapp" - -# `default` (or unspecified) - Debug symbols, if they exist, are not treated -# specially. -# -# `strip` - Debug symbols are stripped from the shared -# libraries before being copied into the APK. -# -# `split` - Functions the same as `strip`, except the debug -# symbols are written to the apk output directory -# alongside the stripped shared libraries, with -# a `.dwarf` extension. -# -# Note that the `strip` and `split` options will only have an effect if -# debug symbols are present in the `.so` file(s) produced by your build, enabling -# https://doc.rust-lang.org/cargo/reference/profiles.html#strip or -# https://doc.rust-lang.org/cargo/reference/profiles.html#split-debuginfo -# in your cargo manifest can cause debug symbols to no longer be present -# in the `.so`. -strip = "default" - -# Folder containing extra shared libraries intended to be dynamically loaded at runtime. -# Files matching `libs_folder/${android_abi}/*.so` are added to the apk -# according to the specified build_targets. -runtime_libs = "path/to/libs_folder" - -# Defaults to `$HOME/.android/debug.keystore` for the `dev` profile. Will ONLY -# generate a new debug.keystore if this file does NOT exist. A keystore is never -# auto-generated for other profiles. -# -# The keystore path can be absolute, or relative to the Cargo.toml file. -# -# The environment variables `CARGO_APK__KEYSTORE` and -# `CARGO_APK__KEYSTORE_PASSWORD` can be set to a keystore path -# and keystore password respectively. The profile portion follows the same rules -# as ``, it is the uppercased profile name with `-` replaced with `_`. -# -# If present they take precedence over the signing information in the manifest. -[package.metadata.android.signing.] -path = "relative/or/absolute/path/to/my.keystore" -keystore_password = "android" - -# See https://developer.android.com/guide/topics/manifest/uses-sdk-element -# -# Defaults to a `min_sdk_version` of 23 and `target_sdk_version` of 30 (or lower if the detected NDK doesn't support this). -[package.metadata.android.sdk] -min_sdk_version = 23 -target_sdk_version = 30 -max_sdk_version = 29 - -# See https://developer.android.com/guide/topics/manifest/uses-feature-element -# -# Note: there can be multiple .uses_feature entries. -[[package.metadata.android.uses_feature]] -name = "android.hardware.vulkan.level" -required = true -version = 1 - -# See https://developer.android.com/guide/topics/manifest/uses-permission-element -# -# Note: there can be multiple .uses_permission entries. -[[package.metadata.android.uses_permission]] -name = "android.permission.WRITE_EXTERNAL_STORAGE" -max_sdk_version = 18 - -# See https://developer.android.com/guide/topics/manifest/queries-element#provider -[[package.metadata.android.queries.provider]] -authorities = "org.khronos.openxr.runtime_broker;org.khronos.openxr.system_runtime_broker" -# Note: The `name` attribute is normally not required for a queries provider, but is non-optional -# as a workaround for aapt throwing errors about missing `android:name` attribute. -# This will be made optional if/when cargo-apk migrates to aapt2. -name = "org.khronos.openxr" - -# See https://developer.android.com/guide/topics/manifest/queries-element#intent -[[package.metadata.android.queries.intent]] -actions = ["android.intent.action.SEND"] - -# See https://developer.android.com/guide/topics/manifest/queries-element#intent -# Note: there can be several .data entries. -[[package.metadata.android.queries.intent.data]] -mime_type = "image/jpeg" - -# See https://developer.android.com/guide/topics/manifest/queries-element#package -[[package.metadata.android.queries.package]] -name = "org.freedesktop.monado.openxr_runtime.in_process" - -# See https://developer.android.com/guide/topics/manifest/application-element -[package.metadata.android.application] - -# See https://developer.android.com/guide/topics/manifest/application-element#debug -# -# Defaults to false. -debuggable = false - -# See https://developer.android.com/guide/topics/manifest/application-element#theme -# -# Example shows setting the theme of an application to fullscreen. -theme = "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen" - -# Virtual path your application's icon for any mipmap level. -# If not specified, an icon will not be included in the APK. -icon = "@mipmap/ic_launcher" - -# See https://developer.android.com/guide/topics/manifest/application-element#label -# -# Defaults to the compiled artifact's name. -label = "Application Name" - -# See https://developer.android.com/guide/topics/manifest/meta-data-element -# -# Note: there can be several .meta_data entries. -# Note: the `resource` attribute is currently not supported. -[[package.metadata.android.application.meta_data]] -name = "com.samsung.android.vr.application.mode" -value = "vr_only" - -# See https://developer.android.com/guide/topics/manifest/activity-element -[package.metadata.android.application.activity] - -# See https://developer.android.com/guide/topics/manifest/activity-element#config -# -# Defaults to "orientation|keyboardHidden|screenSize". -config_changes = "orientation" - -# See https://developer.android.com/guide/topics/manifest/activity-element#label -# -# Defaults to the application's label. -label = "Activity Name" - -# See https://developer.android.com/guide/topics/manifest/activity-element#lmode -# -# Defaults to "standard". -launch_mode = "singleTop" - -# See https://developer.android.com/guide/topics/manifest/activity-element#screen -# -# Defaults to "unspecified". -orientation = "landscape" - -# See https://developer.android.com/guide/topics/manifest/activity-element#exported -# -# Unset by default, or true when targeting Android >= 31 (S and up). -exported = true - -# See https://developer.android.com/guide/topics/manifest/activity-element#resizeableActivity -# -# Defaults to true on Android >= 24, no effect on earlier API levels -resizeable_activity = false - -# See https://developer.android.com/guide/topics/manifest/meta-data-element -# -# Note: there can be several .meta_data entries. -# Note: the `resource` attribute is currently not supported. -[[package.metadata.android.application.activity.meta_data]] -name = "com.oculus.vr.focusaware" -value = "true" - -# See https://developer.android.com/guide/topics/manifest/intent-filter-element -# -# Note: there can be several .intent_filter entries. -[[package.metadata.android.application.activity.intent_filter]] -# See https://developer.android.com/guide/topics/manifest/action-element -actions = ["android.intent.action.VIEW", "android.intent.action.WEB_SEARCH"] -# See https://developer.android.com/guide/topics/manifest/category-element -categories = ["android.intent.category.DEFAULT", "android.intent.category.BROWSABLE"] - -# See https://developer.android.com/guide/topics/manifest/data-element -# -# Note: there can be several .data entries. -# Note: not specifying an attribute excludes it from the final data specification. -[[package.metadata.android.application.activity.intent_filter.data]] -scheme = "https" -host = "github.com" -port = "8080" -path = "/rust-windowing/android-ndk-rs/tree/master/cargo-apk" -path_prefix = "/rust-windowing/" -mime_type = "image/jpeg" - -# Set up reverse port forwarding through `adb reverse`, meaning that if the -# Android device connects to `localhost` on port `1338` it will be routed to -# the host on port `1338` instead. Source and destination ports can differ, -# see the `adb` help page for possible configurations. -[package.metadata.android.reverse_port_forward] -"tcp:1338" = "tcp:1338" -``` - -If a manifest attribute is not supported by `cargo apk` feel free to create a PR that adds the missing attribute. diff --git a/cargo-apk/src/apk.rs b/cargo-apk/src/apk.rs deleted file mode 100644 index a807135f..00000000 --- a/cargo-apk/src/apk.rs +++ /dev/null @@ -1,383 +0,0 @@ -use crate::error::Error; -use crate::manifest::{Inheritable, Manifest, Root}; -use cargo_subcommand::{Artifact, CrateType, Profile, Subcommand}; -use ndk_build::apk::{Apk, ApkConfig}; -use ndk_build::cargo::{cargo_ndk, VersionCode}; -use ndk_build::dylibs::get_libs_search_paths; -use ndk_build::error::NdkError; -use ndk_build::manifest::{IntentFilter, MetaData}; -use ndk_build::ndk::{Key, Ndk}; -use ndk_build::target::Target; -use std::path::PathBuf; - -pub struct ApkBuilder<'a> { - cmd: &'a Subcommand, - ndk: Ndk, - manifest: Manifest, - build_dir: PathBuf, - build_targets: Vec, - device_serial: Option, -} - -impl<'a> ApkBuilder<'a> { - pub fn from_subcommand( - cmd: &'a Subcommand, - device_serial: Option, - ) -> Result { - println!( - "Using package `{}` in `{}`", - cmd.package(), - cmd.manifest().display() - ); - let ndk = Ndk::from_env()?; - let mut manifest = Manifest::parse_from_toml(cmd.manifest())?; - let workspace_manifest: Option = cmd - .workspace_manifest() - .map(Root::parse_from_toml) - .transpose()?; - let build_targets = if let Some(target) = cmd.target() { - vec![Target::from_rust_triple(target)?] - } else if !manifest.build_targets.is_empty() { - manifest.build_targets.clone() - } else { - vec![ndk - .detect_abi(device_serial.as_deref()) - .unwrap_or(Target::Arm64V8a)] - }; - let build_dir = dunce::simplified(cmd.target_dir()) - .join(cmd.profile()) - .join("apk"); - - let package_version = match &manifest.version { - Inheritable::Value(v) => v.clone(), - Inheritable::Inherited { workspace: true } => { - let workspace = workspace_manifest - .ok_or(Error::InheritanceMissingWorkspace)? - .workspace - .unwrap_or_else(|| { - // Unlikely to fail as cargo-subcommand should give us - // a `Cargo.toml` containing a `[workspace]` table - panic!( - "Manifest `{:?}` must contain a `[workspace]` table", - cmd.workspace_manifest().unwrap() - ) - }); - - workspace - .package - .ok_or(Error::WorkspaceMissingInheritedField("package"))? - .version - .ok_or(Error::WorkspaceMissingInheritedField("package.version"))? - } - Inheritable::Inherited { workspace: false } => return Err(Error::InheritedFalse), - }; - let version_code = VersionCode::from_semver(&package_version)?.to_code(1); - - // Set default Android manifest values - if manifest - .android_manifest - .version_name - .replace(package_version) - .is_some() - { - panic!("version_name should not be set in TOML"); - } - - if manifest - .android_manifest - .version_code - .replace(version_code) - .is_some() - { - panic!("version_code should not be set in TOML"); - } - - let target_sdk_version = *manifest - .android_manifest - .sdk - .target_sdk_version - .get_or_insert_with(|| ndk.default_target_platform()); - - manifest - .android_manifest - .application - .debuggable - .get_or_insert_with(|| *cmd.profile() == Profile::Dev); - - let activity = &mut manifest.android_manifest.application.activity; - - // Add a default `MAIN` action to launch the activity, if the user didn't supply it by hand. - if activity - .intent_filter - .iter() - .all(|i| i.actions.iter().all(|f| f != "android.intent.action.MAIN")) - { - activity.intent_filter.push(IntentFilter { - actions: vec!["android.intent.action.MAIN".to_string()], - categories: vec!["android.intent.category.LAUNCHER".to_string()], - data: vec![], - }); - } - - // Export the sole Rust activity on Android S and up, if the user didn't explicitly do so. - // Without this, apps won't start on S+. - // https://developer.android.com/about/versions/12/behavior-changes-12#exported - if target_sdk_version >= 31 { - activity.exported.get_or_insert(true); - } - - Ok(Self { - cmd, - ndk, - manifest, - build_dir, - build_targets, - device_serial, - }) - } - - pub fn check(&self) -> Result<(), Error> { - for target in &self.build_targets { - let mut cargo = cargo_ndk( - &self.ndk, - *target, - self.min_sdk_version(), - self.cmd.target_dir(), - )?; - cargo.arg("check"); - if self.cmd.target().is_none() { - let triple = target.rust_triple(); - cargo.arg("--target").arg(triple); - } - self.cmd.args().apply(&mut cargo); - if !cargo.status()?.success() { - return Err(NdkError::CmdFailed(cargo).into()); - } - } - Ok(()) - } - - pub fn build(&self, artifact: &Artifact) -> Result { - // Set artifact specific manifest default values. - let mut manifest = self.manifest.android_manifest.clone(); - - if manifest.package.is_empty() { - manifest.package = match artifact { - Artifact::Root(name) => format!("rust.{}", name.replace('-', "_")), - Artifact::Example(name) => format!("rust.example.{}", name.replace('-', "_")), - }; - } - - if manifest.application.label.is_empty() { - manifest.application.label = artifact.name().to_string(); - } - - manifest.application.activity.meta_data.push(MetaData { - name: "android.app.lib_name".to_string(), - value: artifact.name().replace('-', "_"), - }); - - let crate_path = self.cmd.manifest().parent().expect("invalid manifest path"); - - let is_debug_profile = *self.cmd.profile() == Profile::Dev; - - let assets = self - .manifest - .assets - .as_ref() - .map(|assets| dunce::simplified(&crate_path.join(assets)).to_owned()); - let resources = self - .manifest - .resources - .as_ref() - .map(|res| dunce::simplified(&crate_path.join(res)).to_owned()); - let runtime_libs = self - .manifest - .runtime_libs - .as_ref() - .map(|libs| dunce::simplified(&crate_path.join(libs)).to_owned()); - let apk_name = self - .manifest - .apk_name - .clone() - .unwrap_or_else(|| artifact.name().to_string()); - - let config = ApkConfig { - ndk: self.ndk.clone(), - build_dir: self.build_dir.join(artifact), - apk_name, - assets, - resources, - manifest, - disable_aapt_compression: is_debug_profile, - strip: self.manifest.strip, - reverse_port_forward: self.manifest.reverse_port_forward.clone(), - }; - let mut apk = config.create_apk()?; - - for target in &self.build_targets { - let triple = target.rust_triple(); - let build_dir = self.cmd.build_dir(Some(triple)); - let artifact = self.cmd.artifact(artifact, Some(triple), CrateType::Cdylib); - - let mut cargo = cargo_ndk( - &self.ndk, - *target, - self.min_sdk_version(), - self.cmd.target_dir(), - )?; - cargo.arg("build"); - if self.cmd.target().is_none() { - cargo.arg("--target").arg(triple); - } - self.cmd.args().apply(&mut cargo); - - if !cargo.status()?.success() { - return Err(NdkError::CmdFailed(cargo).into()); - } - - let mut libs_search_paths = - get_libs_search_paths(self.cmd.target_dir(), triple, self.cmd.profile().as_ref())?; - libs_search_paths.push(build_dir.join("deps")); - - let libs_search_paths = libs_search_paths - .iter() - .map(|path| path.as_path()) - .collect::>(); - - apk.add_lib_recursively(&artifact, *target, libs_search_paths.as_slice())?; - - if let Some(runtime_libs) = &runtime_libs { - apk.add_runtime_libs(runtime_libs, *target, libs_search_paths.as_slice())?; - } - } - - let profile_name = match self.cmd.profile() { - Profile::Dev => "dev", - Profile::Release => "release", - Profile::Custom(c) => c.as_str(), - }; - - let keystore_env = format!( - "CARGO_APK_{}_KEYSTORE", - profile_name.to_uppercase().replace('-', "_") - ); - let password_env = format!("{}_PASSWORD", keystore_env); - - let path = std::env::var_os(&keystore_env).map(PathBuf::from); - let password = std::env::var(&password_env).ok(); - - let signing_key = match (path, password) { - (Some(path), Some(password)) => Key { path, password }, - (Some(path), None) if is_debug_profile => { - eprintln!( - "{} not specified, falling back to default password", - password_env - ); - Key { - path, - password: ndk_build::ndk::DEFAULT_DEV_KEYSTORE_PASSWORD.to_owned(), - } - } - (Some(path), None) => { - eprintln!("`{}` was specified via `{}`, but `{}` was not specified, both or neither must be present for profiles other than `dev`", path.display(), keystore_env, password_env); - return Err(Error::MissingReleaseKey(profile_name.to_owned())); - } - (None, _) => { - if let Some(msk) = self.manifest.signing.get(profile_name) { - Key { - path: crate_path.join(&msk.path), - password: msk.keystore_password.clone(), - } - } else if is_debug_profile { - self.ndk.debug_key()? - } else { - return Err(Error::MissingReleaseKey(profile_name.to_owned())); - } - } - }; - - let unsigned = apk.add_pending_libs_and_align()?; - - println!( - "Signing `{}` with keystore `{}`", - config.apk().display(), - signing_key.path.display() - ); - Ok(unsigned.sign(signing_key)?) - } - - pub fn run(&self, artifact: &Artifact, no_logcat: bool) -> Result<(), Error> { - let apk = self.build(artifact)?; - apk.reverse_port_forwarding(self.device_serial.as_deref())?; - apk.install(self.device_serial.as_deref())?; - let pid = apk.start(self.device_serial.as_deref())?; - - if !no_logcat { - self.ndk - .adb(self.device_serial.as_deref())? - .arg("logcat") - .arg("-v") - .arg("color") - .arg("--pid") - .arg(pid.to_string()) - .status()?; - } - - Ok(()) - } - - pub fn gdb(&self, artifact: &Artifact) -> Result<(), Error> { - let apk = self.build(artifact)?; - apk.install(self.device_serial.as_deref())?; - - let target_dir = self.build_dir.join(artifact); - self.ndk.ndk_gdb( - &target_dir, - "android.app.NativeActivity", - self.device_serial.as_deref(), - )?; - Ok(()) - } - - pub fn default(&self, cargo_cmd: &str, cargo_args: &[String]) -> Result<(), Error> { - for target in &self.build_targets { - let mut cargo = cargo_ndk( - &self.ndk, - *target, - self.min_sdk_version(), - self.cmd.target_dir(), - )?; - cargo.arg(cargo_cmd); - self.cmd.args().apply(&mut cargo); - - if self.cmd.target().is_none() { - let triple = target.rust_triple(); - cargo.arg("--target").arg(triple); - } - - for additional_arg in cargo_args { - cargo.arg(additional_arg); - } - - if !cargo.status()?.success() { - return Err(NdkError::CmdFailed(cargo).into()); - } - } - Ok(()) - } - - /// Returns `minSdkVersion` for use in compiler target selection: - /// - /// - /// Has a lower bound of `23` to retain backwards compatibility with - /// the previous default. - fn min_sdk_version(&self) -> u32 { - self.manifest - .android_manifest - .sdk - .min_sdk_version - .unwrap_or(23) - .max(23) - } -} diff --git a/cargo-apk/src/error.rs b/cargo-apk/src/error.rs deleted file mode 100644 index 95228655..00000000 --- a/cargo-apk/src/error.rs +++ /dev/null @@ -1,31 +0,0 @@ -use cargo_subcommand::Error as SubcommandError; -use ndk_build::error::NdkError; -use std::io::Error as IoError; -use thiserror::Error; -use toml::de::Error as TomlError; - -#[derive(Debug, Error)] -pub enum Error { - #[error(transparent)] - Subcommand(#[from] SubcommandError), - #[error("Failed to parse config.")] - Config(#[from] TomlError), - #[error(transparent)] - Ndk(#[from] NdkError), - #[error(transparent)] - Io(#[from] IoError), - #[error("Configure a release keystore via `[package.metadata.android.signing.{0}]`")] - MissingReleaseKey(String), - #[error("`workspace=false` is unsupported")] - InheritedFalse, - #[error("`workspace=true` requires a workspace")] - InheritanceMissingWorkspace, - #[error("Failed to inherit field: `workspace.{0}` was not defined in workspace root manifest")] - WorkspaceMissingInheritedField(&'static str), -} - -impl Error { - pub fn invalid_args() -> Self { - Self::Subcommand(SubcommandError::InvalidArgs) - } -} diff --git a/cargo-apk/src/lib.rs b/cargo-apk/src/lib.rs deleted file mode 100644 index 4288b6e7..00000000 --- a/cargo-apk/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod apk; -mod error; -mod manifest; - -pub use apk::ApkBuilder; -pub use error::Error; diff --git a/cargo-apk/src/main.rs b/cargo-apk/src/main.rs deleted file mode 100644 index fab80d0d..00000000 --- a/cargo-apk/src/main.rs +++ /dev/null @@ -1,322 +0,0 @@ -use std::collections::HashMap; - -use cargo_apk::{ApkBuilder, Error}; -use cargo_subcommand::Subcommand; -use clap::{CommandFactory, FromArgMatches, Parser}; - -#[derive(Parser)] -struct Cmd { - #[clap(subcommand)] - apk: ApkCmd, -} - -#[derive(clap::Subcommand)] -enum ApkCmd { - /// Helps cargo build apks for Android - Apk { - #[clap(subcommand)] - cmd: ApkSubCmd, - }, -} - -#[derive(Clone, Debug, Eq, PartialEq, Parser)] -#[group(skip)] -struct Args { - #[clap(flatten)] - subcommand_args: cargo_subcommand::Args, - /// Use device with the given serial (see `adb devices`) - #[clap(short, long)] - device: Option, -} - -#[derive(clap::Subcommand)] -enum ApkSubCmd { - /// Analyze the current package and report errors, but don't build object files nor an apk - #[clap(visible_alias = "c")] - Check { - #[clap(flatten)] - args: Args, - }, - /// Compile the current package and create an apk - #[clap(visible_alias = "b")] - Build { - #[clap(flatten)] - args: Args, - }, - /// Invoke `cargo` under the detected NDK environment - #[clap(name = "--")] - Ndk { - /// `cargo` subcommand to run - cargo_cmd: String, - - // This struct will be filled up later by arguments that are intermixed - // with unknown args and ended up in `cargo_args` below. - #[clap(flatten)] - args: Args, - - /// Arguments passed to cargo. Some arguments will be used to configure - /// the environment similar to other `cargo apk` commands - #[clap(trailing_var_arg = true, allow_hyphen_values = true)] - cargo_args: Vec, - }, - /// Run a binary or example apk of the local package - #[clap(visible_alias = "r")] - Run { - #[clap(flatten)] - args: Args, - /// Do not print or follow `logcat` after running the app - #[clap(short, long)] - no_logcat: bool, - }, - /// Start a gdb session attached to an adb device with symbols loaded - Gdb { - #[clap(flatten)] - args: Args, - }, - /// Print the version of cargo-apk - Version, -} - -fn split_apk_and_cargo_args(mut args: Args, input: Vec) -> (Args, Vec) { - // Clap doesn't support parsing unknown args properly: - // https://github.com/clap-rs/clap/issues/1404 - // https://github.com/clap-rs/clap/issues/4498 - // Introspect the `Args` struct and extract every known arg, and whether it takes a value. Use - // this information to separate out known args from unknown args, and re-parse all the known - // args into an existing `args: Args` struct instance. - - let known_args_taking_value = Args::command() - .get_arguments() - .flat_map(|arg| { - assert!(!arg.is_positional()); - arg.get_short_and_visible_aliases() - .iter() - .flat_map(|shorts| shorts.iter().map(|short| format!("-{}", short))) - .chain( - arg.get_long_and_visible_aliases() - .iter() - .flat_map(|longs| longs.iter().map(|short| format!("--{}", short))), - ) - .map(|arg_str| (arg_str, arg.get_action().takes_values())) - // Collect to prevent lifetime issues on temporaries created above - .collect::>() - }) - .collect::>(); - - #[derive(Debug, Default)] - struct SplitArgs { - apk_args: Vec, - cargo_args: Vec, - next_takes_value: bool, - } - - let split_args = input - .into_iter() - .fold(SplitArgs::default(), |mut split_args, elem| { - let known_arg = known_args_taking_value.get(&elem); - if known_arg.is_some() || split_args.next_takes_value { - // Recognized arg or value for previously recognized arg - split_args.apk_args.push(elem) - } else { - split_args.cargo_args.push(elem) - } - - split_args.next_takes_value = known_arg.copied().unwrap_or(false); - split_args - }); - - let m = Args::command() - .no_binary_name(true) - .get_matches_from(&split_args.apk_args); - args.update_from_arg_matches(&m).unwrap(); - (args, split_args.cargo_args) -} - -fn main() -> anyhow::Result<()> { - env_logger::init(); - let Cmd { - apk: ApkCmd::Apk { cmd }, - } = Cmd::parse(); - match cmd { - ApkSubCmd::Check { args } => { - let cmd = Subcommand::new(args.subcommand_args)?; - let builder = ApkBuilder::from_subcommand(&cmd, args.device)?; - builder.check()?; - } - ApkSubCmd::Build { args } => { - let cmd = Subcommand::new(args.subcommand_args)?; - let builder = ApkBuilder::from_subcommand(&cmd, args.device)?; - for artifact in cmd.artifacts() { - builder.build(artifact)?; - } - } - ApkSubCmd::Ndk { - cargo_cmd, - args, - cargo_args, - } => { - let (args, cargo_args) = split_apk_and_cargo_args(args, cargo_args); - - let cmd = Subcommand::new(args.subcommand_args)?; - let builder = ApkBuilder::from_subcommand(&cmd, args.device)?; - builder.default(&cargo_cmd, &cargo_args)?; - } - ApkSubCmd::Run { args, no_logcat } => { - let cmd = Subcommand::new(args.subcommand_args)?; - let builder = ApkBuilder::from_subcommand(&cmd, args.device)?; - anyhow::ensure!(cmd.artifacts().len() == 1, Error::invalid_args()); - builder.run(&cmd.artifacts()[0], no_logcat)?; - } - ApkSubCmd::Gdb { args } => { - let cmd = Subcommand::new(args.subcommand_args)?; - let builder = ApkBuilder::from_subcommand(&cmd, args.device)?; - anyhow::ensure!(cmd.artifacts().len() == 1, Error::invalid_args()); - builder.gdb(&cmd.artifacts()[0])?; - } - ApkSubCmd::Version => { - println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); - } - } - Ok(()) -} - -#[test] -fn test_split_apk_and_cargo_args() { - // Set up a default because cargo-subcommand doesn't derive/implement Default - let args_default = Args::parse_from(std::iter::empty::<&str>()); - - assert_eq!( - split_apk_and_cargo_args(args_default.clone(), vec!["--quiet".to_string()]), - ( - Args { - subcommand_args: cargo_subcommand::Args { - quiet: true, - ..args_default.subcommand_args.clone() - }, - ..args_default.clone() - }, - vec![] - ) - ); - - assert_eq!( - split_apk_and_cargo_args( - args_default.clone(), - vec!["unrecognized".to_string(), "--quiet".to_string()] - ), - ( - Args { - subcommand_args: cargo_subcommand::Args { - quiet: true, - ..args_default.subcommand_args.clone() - }, - ..args_default.clone() - }, - vec!["unrecognized".to_string()] - ) - ); - - assert_eq!( - split_apk_and_cargo_args( - args_default.clone(), - vec!["--unrecognized".to_string(), "--quiet".to_string()] - ), - ( - Args { - subcommand_args: cargo_subcommand::Args { - quiet: true, - ..args_default.subcommand_args.clone() - }, - ..args_default.clone() - }, - vec!["--unrecognized".to_string()] - ) - ); - - assert_eq!( - split_apk_and_cargo_args( - args_default.clone(), - vec!["-p".to_string(), "foo".to_string()] - ), - ( - Args { - subcommand_args: cargo_subcommand::Args { - package: vec!["foo".to_string()], - ..args_default.subcommand_args.clone() - }, - ..args_default.clone() - }, - vec![] - ) - ); - - assert_eq!( - split_apk_and_cargo_args( - args_default.clone(), - vec![ - "-p".to_string(), - "foo".to_string(), - "--unrecognized".to_string(), - "--quiet".to_string() - ] - ), - ( - Args { - subcommand_args: cargo_subcommand::Args { - quiet: true, - package: vec!["foo".to_string()], - ..args_default.subcommand_args.clone() - }, - ..args_default.clone() - }, - vec!["--unrecognized".to_string()] - ) - ); - - assert_eq!( - split_apk_and_cargo_args( - args_default.clone(), - vec![ - "--no-deps".to_string(), - "-p".to_string(), - "foo".to_string(), - "--unrecognized".to_string(), - "--quiet".to_string() - ] - ), - ( - Args { - subcommand_args: cargo_subcommand::Args { - quiet: true, - package: vec!["foo".to_string()], - ..args_default.subcommand_args.clone() - }, - ..args_default.clone() - }, - vec!["--no-deps".to_string(), "--unrecognized".to_string()] - ) - ); - - assert_eq!( - split_apk_and_cargo_args( - args_default.clone(), - vec![ - "--no-deps".to_string(), - "--device".to_string(), - "adb:test".to_string(), - "--unrecognized".to_string(), - "--quiet".to_string() - ] - ), - ( - Args { - subcommand_args: cargo_subcommand::Args { - quiet: true, - ..args_default.subcommand_args - }, - device: Some("adb:test".to_string()), - }, - vec!["--no-deps".to_string(), "--unrecognized".to_string()] - ) - ); -} diff --git a/cargo-apk/src/manifest.rs b/cargo-apk/src/manifest.rs deleted file mode 100644 index de4460a5..00000000 --- a/cargo-apk/src/manifest.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::error::Error; -use ndk_build::apk::StripConfig; -use ndk_build::manifest::AndroidManifest; -use ndk_build::target::Target; -use serde::Deserialize; -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; - -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] -pub enum Inheritable { - Value(T), - Inherited { workspace: bool }, -} - -pub(crate) struct Manifest { - pub(crate) version: Inheritable, - pub(crate) apk_name: Option, - pub(crate) android_manifest: AndroidManifest, - pub(crate) build_targets: Vec, - pub(crate) assets: Option, - pub(crate) resources: Option, - pub(crate) runtime_libs: Option, - /// Maps profiles to keystores - pub(crate) signing: HashMap, - pub(crate) reverse_port_forward: HashMap, - pub(crate) strip: StripConfig, -} - -impl Manifest { - pub(crate) fn parse_from_toml(path: &Path) -> Result { - let toml = Root::parse_from_toml(path)?; - // Unlikely to fail as cargo-subcommand should give us a `Cargo.toml` containing - // a `[package]` table (with a matching `name` when requested by the user) - let package = toml - .package - .unwrap_or_else(|| panic!("Manifest `{:?}` must contain a `[package]`", path)); - let metadata = package - .metadata - .unwrap_or_default() - .android - .unwrap_or_default(); - Ok(Self { - version: package.version, - apk_name: metadata.apk_name, - android_manifest: metadata.android_manifest, - build_targets: metadata.build_targets, - assets: metadata.assets, - resources: metadata.resources, - runtime_libs: metadata.runtime_libs, - signing: metadata.signing, - reverse_port_forward: metadata.reverse_port_forward, - strip: metadata.strip, - }) - } -} - -#[derive(Debug, Clone, Deserialize)] -pub(crate) struct Root { - pub(crate) package: Option, - pub(crate) workspace: Option, -} - -impl Root { - pub(crate) fn parse_from_toml(path: &Path) -> Result { - let contents = std::fs::read_to_string(path)?; - toml::from_str(&contents).map_err(|e| e.into()) - } -} - -#[derive(Debug, Clone, Deserialize)] -pub(crate) struct Package { - pub(crate) version: Inheritable, - pub(crate) metadata: Option, -} - -#[derive(Clone, Debug, Deserialize)] -pub(crate) struct Workspace { - pub(crate) package: Option, -} - -/// Almost the same as [`Package`], except that this must provide -/// root values instead of possibly inheritable values -#[derive(Clone, Debug, Deserialize)] -pub(crate) struct WorkspacePackage { - pub(crate) version: Option, -} - -#[derive(Clone, Debug, Default, Deserialize)] -pub(crate) struct PackageMetadata { - android: Option, -} - -#[derive(Clone, Debug, Default, Deserialize)] -struct AndroidMetadata { - apk_name: Option, - #[serde(flatten)] - android_manifest: AndroidManifest, - #[serde(default)] - build_targets: Vec, - assets: Option, - resources: Option, - runtime_libs: Option, - /// Maps profiles to keystores - #[serde(default)] - signing: HashMap, - /// Set up reverse port forwarding before launching the application - #[serde(default)] - reverse_port_forward: HashMap, - #[serde(default)] - strip: StripConfig, -} - -#[derive(Clone, Debug, Default, Deserialize)] -pub(crate) struct Signing { - pub(crate) path: PathBuf, - pub(crate) keystore_password: String, -} diff --git a/ndk-build/CHANGELOG.md b/ndk-build/CHANGELOG.md deleted file mode 100644 index 17986c5f..00000000 --- a/ndk-build/CHANGELOG.md +++ /dev/null @@ -1,97 +0,0 @@ -# Unreleased - -# 0.9.0 (2022-11-23) - -- Add `ndk::DEFAULT_DEV_KEYSTORE_PASSWORD` and make `apk::ApkConfig::apk` public. ([#358](https://github.com/rust-windowing/android-ndk-rs/pull/358)) -- `RUSTFLAGS` is now considered if `CARGO_ENCODED_RUSTFLAGS` is not present allowing `cargo apk build` to not break users' builds if they depend on `RUSTFLAGS` being set prior to the build, - as `CARGO_ENCODED_RUSTFLAGS` set by `ndk-build` before invoking `cargo` will take precedence over [all other sources of build flags](https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags). ([#357](https://github.com/rust-windowing/android-ndk-rs/pull/357)) -- Add `ApkConfig::strip`, allowing a user to specify how they want debug symbols treated after cargo has finished building, but before the shared object is copied into the APK. ([#356](https://github.com/rust-windowing/android-ndk-rs/pull/356)) - -(0.8.1, released on 2022-10-14, was yanked due to violating semver.) - -- **Breaking:** Provide `reverse_port_forwarding()` to set up `adb reverse` ([#348](https://github.com/rust-windowing/android-ndk-rs/pull/348)) - -# 0.8.0 (2022-09-12) - -- **Breaking:** Postpone APK library packaging until before zip alignment, to deduplicate possibly overlapping entries. ([#333](https://github.com/rust-windowing/android-ndk-rs/pull/333)) -- Add `adb` device serial parameter to `detect_abi()` and `Apk::{install,start}()`. ([#329](https://github.com/rust-windowing/android-ndk-rs/pull/329)) -- Fix missing `.exe` extension for `adb` on Windows inside `detect_abi()`. ([#339](https://github.com/rust-windowing/android-ndk-rs/pull/339)) -- `start()` now returns the PID of the started app process (useful for passing to `adb logcat --pid`). ([#331](https://github.com/rust-windowing/android-ndk-rs/pull/331)) -- Inherit `ndk_gdb()` function from `cargo-apk` with the appropriate script extension across platforms. ([#330](https://github.com/rust-windowing/android-ndk-rs/pull/330), [#258](https://github.com/rust-windowing/android-ndk-rs/pull/258)) -- Provide `adb` path to `ndk-gdb`, allowing it to run without `adb` in `PATH`. ([#343](https://github.com/rust-windowing/android-ndk-rs/pull/343)) -- Remove quotes from `Android.mk` to fix `ndk-gdb` on Windows. ([#344](https://github.com/rust-windowing/android-ndk-rs/pull/344)) -- Launch Android activity through `ndk-gdb` to block app start until the debugger is attached. ([#345](https://github.com/rust-windowing/android-ndk-rs/pull/345)) -- Consider `ANDROID_SDK_ROOT` as deprecated instead of `ANDROID_HOME`. ([#346](https://github.com/rust-windowing/android-ndk-rs/pull/346)) -- **Breaking:** Rename `fn android_dir()` to `fn android_user_home()` and seed with `ANDROID_SDK_HOME` or `ANDROID_USER_HOME`. ([#347](https://github.com/rust-windowing/android-ndk-rs/pull/347)) - -# 0.7.0 (2022-07-05) - -- Fix NDK r23 `-lgcc` workaround for target directories containing spaces. ([#298](https://github.com/rust-windowing/android-ndk-rs/pull/298)) -- Invoke `clang` directly instead of through the NDK's wrapper scripts. ([#306](https://github.com/rust-windowing/android-ndk-rs/pull/306)) -- **Breaking:** Rename `Activity::intent_filters` back to `Activity::intent_filter`. ([#305](https://github.com/rust-windowing/android-ndk-rs/pull/305)) - -# 0.6.0 (2022-06-11) - -- **Breaking:** Provide NDK r23 `-lgcc` workaround in `cargo_ndk()` function, now requiring `target_dir` as argument. ([#286](https://github.com/rust-windowing/android-ndk-rs/pull/286)) -- **Breaking:** Add `disable_aapt_compression` field to `ApkConfig` to disable `aapt` compression. ([#283](https://github.com/rust-windowing/android-ndk-rs/pull/283)) - -# 0.5.0 (2022-05-07) - -- **Breaking:** Default `target_sdk_version` to `30` or lower (instead of the highest supported SDK version by the detected NDK toolchain) - for more consistent interaction with Android backwards compatibility handling and its increasingly strict usage rules: - -- **Breaking:** Remove default insertion of `MAIN` intent filter through a custom serialization function, this is better filled in by - the default setup in `cargo-apk`. ([#241](https://github.com/rust-windowing/android-ndk-rs/pull/241)) -- Add `android:exported` attribute to the manifest's `Activity` element. ([#242](https://github.com/rust-windowing/android-ndk-rs/pull/242)) -- Add `android:sharedUserId` attribute to the manifest's top-level `manifest` element. ([#252](https://github.com/rust-windowing/android-ndk-rs/pull/252)) -- Add `queries` element to the manifest's top-level `manifest` element. ([#259](https://github.com/rust-windowing/android-ndk-rs/pull/259)) - -# 0.4.3 (2021-11-22) - -- Provide NDK `build_tag` version from `source.properties` in the NDK root. - -# 0.4.2 (2021-08-06) - -- Pass UNIX path separators to `aapt` on non-UNIX systems, ensuring the resulting separator is compatible with the target device instead of the host platform. - -# 0.4.1 (2021-08-02) - -- Only the highest platform supported by the NDK is now selected as default platform. - -# 0.4.0 (2021-07-06) - -- Added `add_runtime_libs` function for including extra dynamic libraries in the APK. - -# 0.3.0 (2021-05-10) - -- New `ApkConfig` field `apk_name` is now used for APK file naming, instead of the application label. -- Renamed `cargo_apk` utility to `cargo_ndk`. - -# 0.2.0 (2021-04-20) - -- **Breaking:** refactored `Manifest` into a proper (de)serialization struct. `Manifest` now closely matches [an android manifest file](https://developer.android.com/guide/topics/manifest/manifest-element). -- **Breaking:** removed `Config` in favor of using the new `Manifest` struct directly. Instead of using `Config::from_config` to create a `Manifest`, now you instantiate `Manifest` directly using, almost all, the same values. - -# 0.1.4 (2020-11-25) - -- On Windows, fixed UNC path handling for resource folder. - -# 0.1.3 (2020-11-21) - -- `android:launchMode` is configurable. - -# 0.1.2 (2020-09-15) - -- `android:label` is configurable. -- Library search paths are much more intelligent. -- `android:screenOrientation` is configurable. - -# 0.1.1 (2020-07-15) - -- Added support for custom intent filters. -- On Windows, fixed UNC path handling. -- Fixed toolchain path handling when the NDK installation has no host arch suffix on its prebuilt LLVM directories. - -# 0.1.0 (2020-04-22) - -- Initial release! 🎉 diff --git a/ndk-build/Cargo.toml b/ndk-build/Cargo.toml deleted file mode 100644 index 586d8b37..00000000 --- a/ndk-build/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "ndk-build" -version = "0.9.0" -authors = ["The Rust Windowing contributors"] -edition = "2018" -description = "Utilities for building Android binaries" -license = "MIT OR Apache-2.0" -keywords = ["android", "ndk", "apk"] -documentation = "https://docs.rs/ndk-build" -homepage = "https://github.com/rust-windowing/android-ndk-rs" -repository = "https://github.com/rust-windowing/android-ndk-rs" -rust-version = "1.60" - -[dependencies] -dirs = "4" -dunce = "1" -quick-xml = { version = "0.26", features = ["serialize"] } -serde = { version = "1", features = ["derive"] } -thiserror = "1" -which = "4" diff --git a/ndk-build/src/apk.rs b/ndk-build/src/apk.rs deleted file mode 100644 index 0057661c..00000000 --- a/ndk-build/src/apk.rs +++ /dev/null @@ -1,328 +0,0 @@ -use crate::error::NdkError; -use crate::manifest::AndroidManifest; -use crate::ndk::{Key, Ndk}; -use crate::target::Target; -use std::collections::HashMap; -use std::collections::HashSet; -use std::ffi::OsStr; -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::Command; - -/// The options for how to treat debug symbols that are present in any `.so` -/// files that are added to the APK. -/// -/// Using [`strip`](https://doc.rust-lang.org/cargo/reference/profiles.html#strip) -/// or [`split-debuginfo`](https://doc.rust-lang.org/cargo/reference/profiles.html#split-debuginfo) -/// in your cargo manifest(s) may cause debug symbols to not be present in a -/// `.so`, which would cause these options to do nothing. -#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum StripConfig { - /// Does not treat debug symbols specially - Default, - /// Removes debug symbols from the library before copying it into the APK - Strip, - /// Splits the library into into an ELF (`.so`) and DWARF (`.dwarf`). Only the - /// `.so` is copied into the APK - Split, -} - -impl Default for StripConfig { - fn default() -> Self { - Self::Default - } -} - -pub struct ApkConfig { - pub ndk: Ndk, - pub build_dir: PathBuf, - pub apk_name: String, - pub assets: Option, - pub resources: Option, - pub manifest: AndroidManifest, - pub disable_aapt_compression: bool, - pub strip: StripConfig, - pub reverse_port_forward: HashMap, -} - -impl ApkConfig { - fn build_tool(&self, tool: &'static str) -> Result { - let mut cmd = self.ndk.build_tool(tool)?; - cmd.current_dir(&self.build_dir); - Ok(cmd) - } - - fn unaligned_apk(&self) -> PathBuf { - self.build_dir - .join(format!("{}-unaligned.apk", self.apk_name)) - } - - /// Retrieves the path of the APK that will be written when [`UnsignedApk::sign`] - /// is invoked - #[inline] - pub fn apk(&self) -> PathBuf { - self.build_dir.join(format!("{}.apk", self.apk_name)) - } - - pub fn create_apk(&self) -> Result { - std::fs::create_dir_all(&self.build_dir)?; - self.manifest.write_to(&self.build_dir)?; - - let target_sdk_version = self - .manifest - .sdk - .target_sdk_version - .unwrap_or_else(|| self.ndk.default_target_platform()); - let mut aapt = self.build_tool(bin!("aapt"))?; - aapt.arg("package") - .arg("-f") - .arg("-F") - .arg(self.unaligned_apk()) - .arg("-M") - .arg("AndroidManifest.xml") - .arg("-I") - .arg(self.ndk.android_jar(target_sdk_version)?); - - if self.disable_aapt_compression { - aapt.arg("-0").arg(""); - } - - if let Some(res) = &self.resources { - aapt.arg("-S").arg(res); - } - - if let Some(assets) = &self.assets { - aapt.arg("-A").arg(assets); - } - - if !aapt.status()?.success() { - return Err(NdkError::CmdFailed(aapt)); - } - - Ok(UnalignedApk { - config: self, - pending_libs: HashSet::default(), - }) - } -} - -pub struct UnalignedApk<'a> { - config: &'a ApkConfig, - pending_libs: HashSet, -} - -impl<'a> UnalignedApk<'a> { - pub fn config(&self) -> &ApkConfig { - self.config - } - - pub fn add_lib(&mut self, path: &Path, target: Target) -> Result<(), NdkError> { - if !path.exists() { - return Err(NdkError::PathNotFound(path.into())); - } - let abi = target.android_abi(); - let lib_path = Path::new("lib").join(abi).join(path.file_name().unwrap()); - let out = self.config.build_dir.join(&lib_path); - std::fs::create_dir_all(out.parent().unwrap())?; - - match self.config.strip { - StripConfig::Default => { - std::fs::copy(path, out)?; - } - StripConfig::Strip | StripConfig::Split => { - let obj_copy = self.config.ndk.toolchain_bin("objcopy", target)?; - - { - let mut cmd = Command::new(&obj_copy); - cmd.arg("--strip-debug"); - cmd.arg(path); - cmd.arg(&out); - - if !cmd.status()?.success() { - return Err(NdkError::CmdFailed(cmd)); - } - } - - if self.config.strip == StripConfig::Split { - let dwarf_path = out.with_extension("dwarf"); - - { - let mut cmd = Command::new(&obj_copy); - cmd.arg("--only-keep-debug"); - cmd.arg(path); - cmd.arg(&dwarf_path); - - if !cmd.status()?.success() { - return Err(NdkError::CmdFailed(cmd)); - } - } - - let mut cmd = Command::new(obj_copy); - cmd.arg(format!("--add-gnu-debuglink={}", dwarf_path.display())); - cmd.arg(out); - - if !cmd.status()?.success() { - return Err(NdkError::CmdFailed(cmd)); - } - } - } - } - - // Pass UNIX path separators to `aapt` on non-UNIX systems, ensuring the resulting separator - // is compatible with the target device instead of the host platform. - // Otherwise, it results in a runtime error when loading the NativeActivity `.so` library. - let lib_path_unix = lib_path.to_str().unwrap().replace('\\', "/"); - - self.pending_libs.insert(lib_path_unix); - - Ok(()) - } - - pub fn add_runtime_libs( - &mut self, - path: &Path, - target: Target, - search_paths: &[&Path], - ) -> Result<(), NdkError> { - let abi_dir = path.join(target.android_abi()); - for entry in fs::read_dir(&abi_dir).map_err(|e| NdkError::IoPathError(abi_dir, e))? { - let entry = entry?; - let path = entry.path(); - if path.extension() == Some(OsStr::new("so")) { - self.add_lib_recursively(&path, target, search_paths)?; - } - } - Ok(()) - } - - pub fn add_pending_libs_and_align(self) -> Result, NdkError> { - let mut aapt = self.config.build_tool(bin!("aapt"))?; - aapt.arg("add"); - - if self.config.disable_aapt_compression { - aapt.arg("-0").arg(""); - } - - aapt.arg(self.config.unaligned_apk()); - - for lib_path_unix in self.pending_libs { - aapt.arg(lib_path_unix); - } - - if !aapt.status()?.success() { - return Err(NdkError::CmdFailed(aapt)); - } - - let mut zipalign = self.config.build_tool(bin!("zipalign"))?; - zipalign - .arg("-f") - .arg("-v") - .arg("4") - .arg(self.config.unaligned_apk()) - .arg(self.config.apk()); - - if !zipalign.status()?.success() { - return Err(NdkError::CmdFailed(zipalign)); - } - - Ok(UnsignedApk(self.config)) - } -} - -pub struct UnsignedApk<'a>(&'a ApkConfig); - -impl<'a> UnsignedApk<'a> { - pub fn sign(self, key: Key) -> Result { - let mut apksigner = self.0.build_tool(bat!("apksigner"))?; - apksigner - .arg("sign") - .arg("--ks") - .arg(&key.path) - .arg("--ks-pass") - .arg(format!("pass:{}", &key.password)) - .arg(self.0.apk()); - if !apksigner.status()?.success() { - return Err(NdkError::CmdFailed(apksigner)); - } - Ok(Apk::from_config(self.0)) - } -} - -pub struct Apk { - path: PathBuf, - package_name: String, - ndk: Ndk, - reverse_port_forward: HashMap, -} - -impl Apk { - pub fn from_config(config: &ApkConfig) -> Self { - let ndk = config.ndk.clone(); - Self { - path: config.apk(), - package_name: config.manifest.package.clone(), - ndk, - reverse_port_forward: config.reverse_port_forward.clone(), - } - } - - pub fn reverse_port_forwarding(&self, device_serial: Option<&str>) -> Result<(), NdkError> { - for (from, to) in &self.reverse_port_forward { - println!("Reverse port forwarding from {} to {}", from, to); - let mut adb = self.ndk.adb(device_serial)?; - - adb.arg("reverse").arg(from).arg(to); - - if !adb.status()?.success() { - return Err(NdkError::CmdFailed(adb)); - } - } - - Ok(()) - } - - pub fn install(&self, device_serial: Option<&str>) -> Result<(), NdkError> { - let mut adb = self.ndk.adb(device_serial)?; - - adb.arg("install").arg("-r").arg(&self.path); - if !adb.status()?.success() { - return Err(NdkError::CmdFailed(adb)); - } - Ok(()) - } - - pub fn start(&self, device_serial: Option<&str>) -> Result { - let mut am_start = self.ndk.adb(device_serial)?; - am_start - .arg("shell") - .arg("am") - .arg("start") - .arg("-W") - .arg("-a") - .arg("android.intent.action.MAIN") - .arg("-n") - .arg(format!("{}/android.app.NativeActivity", &self.package_name)); - if !am_start.status()?.success() { - return Err(NdkError::CmdFailed(am_start)); - } - - let pid_vec = self - .ndk - .adb(device_serial)? - .arg("shell") - .arg("pidof") - .arg(&self.package_name) - .output()? - .stdout; - - let pid = std::str::from_utf8(&pid_vec).unwrap().trim(); - let pid: u32 = pid - .parse() - .map_err(|e| NdkError::NotAPid(e, pid.to_owned()))?; - - println!("Launched with PID {}", pid); - - Ok(pid) - } -} diff --git a/ndk-build/src/cargo.rs b/ndk-build/src/cargo.rs deleted file mode 100644 index d3abf348..00000000 --- a/ndk-build/src/cargo.rs +++ /dev/null @@ -1,165 +0,0 @@ -use crate::error::NdkError; -use crate::ndk::Ndk; -use crate::target::Target; -use std::path::Path; -use std::process::Command; - -pub fn cargo_ndk( - ndk: &Ndk, - target: Target, - sdk_version: u32, - target_dir: impl AsRef, -) -> Result { - let triple = target.rust_triple(); - let clang_target = format!("--target={}{}", target.ndk_llvm_triple(), sdk_version); - let mut cargo = Command::new("cargo"); - - const SEP: &str = "\x1f"; - - // Read initial CARGO_ENCODED_/RUSTFLAGS - let mut rustflags = match std::env::var("CARGO_ENCODED_RUSTFLAGS") { - Ok(val) => { - if std::env::var_os("RUSTFLAGS").is_some() { - panic!( - "Both `CARGO_ENCODED_RUSTFLAGS` and `RUSTFLAGS` were found in the environment, please clear one or the other before invoking this script" - ); - } - - val - } - Err(std::env::VarError::NotPresent) => { - match std::env::var("RUSTFLAGS") { - Ok(val) => { - cargo.env_remove("RUSTFLAGS"); - - // Same as cargo - // https://github.com/rust-lang/cargo/blob/f6de921a5d807746e972d9d10a4d8e1ca21e1b1f/src/cargo/core/compiler/build_context/target_info.rs#L682-L690 - val.split(' ') - .map(str::trim) - .filter(|s| !s.is_empty()) - .collect::>() - .join(SEP) - } - Err(std::env::VarError::NotPresent) => String::new(), - Err(std::env::VarError::NotUnicode(_)) => { - panic!("RUSTFLAGS environment variable contains non-unicode characters") - } - } - } - Err(std::env::VarError::NotUnicode(_)) => { - panic!("CARGO_ENCODED_RUSTFLAGS environment variable contains non-unicode characters") - } - }; - - let (clang, clang_pp) = ndk.clang()?; - - // Configure cross-compiler for `cc` crate - // https://github.com/rust-lang/cc-rs#external-configuration-via-environment-variables - cargo.env(format!("CC_{}", triple), &clang); - cargo.env(format!("CFLAGS_{}", triple), &clang_target); - cargo.env(format!("CXX_{}", triple), &clang_pp); - cargo.env(format!("CXXFLAGS_{}", triple), &clang_target); - - // Configure LINKER for `rustc` - // https://doc.rust-lang.org/beta/cargo/reference/environment-variables.html#configuration-environment-variables - cargo.env(cargo_env_target_cfg("LINKER", triple), &clang); - if !rustflags.is_empty() { - rustflags.push_str(SEP); - } - rustflags.push_str("-Clink-arg="); - rustflags.push_str(&clang_target); - - let ar = ndk.toolchain_bin("ar", target)?; - cargo.env(format!("AR_{}", triple), &ar); - cargo.env(cargo_env_target_cfg("AR", triple), &ar); - - // Workaround for https://github.com/rust-windowing/android-ndk-rs/issues/149: - // Rust (1.56 as of writing) still requires libgcc during linking, but this does - // not ship with the NDK anymore since NDK r23 beta 3. - // See https://github.com/rust-lang/rust/pull/85806 for a discussion on why libgcc - // is still required even after replacing it with libunwind in the source. - // XXX: Add an upper-bound on the Rust version whenever this is not necessary anymore. - if ndk.build_tag() > 7272597 { - let cargo_apk_link_dir = target_dir - .as_ref() - .join("cargo-apk-temp-extra-link-libraries"); - std::fs::create_dir_all(&cargo_apk_link_dir) - .map_err(|e| NdkError::IoPathError(cargo_apk_link_dir.clone(), e))?; - let libgcc = cargo_apk_link_dir.join("libgcc.a"); - std::fs::write(&libgcc, "INPUT(-lunwind)").map_err(|e| NdkError::IoPathError(libgcc, e))?; - - // cdylibs in transitive dependencies still get built and also need this - // workaround linker flag, yet arguments passed to `cargo rustc` are only - // forwarded to the final compiler invocation rendering our workaround ineffective. - // The cargo page documenting this discrepancy (https://doc.rust-lang.org/cargo/commands/cargo-rustc.html) - // suggests to resort to RUSTFLAGS. - // Note that `rustflags` will never be empty because of an unconditional `.push_str` above, - // so we can safely start with appending \x1f here. - rustflags.push_str(SEP); - rustflags.push_str("-L"); - rustflags.push_str(SEP); - rustflags.push_str( - cargo_apk_link_dir - .to_str() - .expect("Target dir must be valid UTF-8"), - ); - } - - cargo.env("CARGO_ENCODED_RUSTFLAGS", rustflags); - - Ok(cargo) -} - -fn cargo_env_target_cfg(tool: &str, target: &str) -> String { - let utarget = target.replace('-', "_"); - let env = format!("CARGO_TARGET_{}_{}", &utarget, tool); - env.to_uppercase() -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct VersionCode { - major: u8, - minor: u8, - patch: u8, -} - -impl VersionCode { - pub fn new(major: u8, minor: u8, patch: u8) -> Self { - Self { - major, - minor, - patch, - } - } - - pub fn from_semver(version: &str) -> Result { - let mut iter = version.split(|c1| ['.', '-', '+'].iter().any(|c2| c1 == *c2)); - let mut p = || { - iter.next() - .ok_or(NdkError::InvalidSemver)? - .parse() - .map_err(|_| NdkError::InvalidSemver) - }; - Ok(Self::new(p()?, p()?, p()?)) - } - - pub fn to_code(&self, apk_id: u8) -> u32 { - (apk_id as u32) << 24 - | (self.major as u32) << 16 - | (self.minor as u32) << 8 - | self.patch as u32 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn from_semver() { - let v = VersionCode::from_semver("0.0.0").unwrap(); - assert_eq!(v, VersionCode::new(0, 0, 0)); - let v = VersionCode::from_semver("254.254.254-alpha.fix+2").unwrap(); - assert_eq!(v, VersionCode::new(254, 254, 254)); - } -} diff --git a/ndk-build/src/dylibs.rs b/ndk-build/src/dylibs.rs deleted file mode 100644 index 14347605..00000000 --- a/ndk-build/src/dylibs.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::io::Result; -use std::path::{Path, PathBuf}; - -pub fn get_libs_search_paths( - target_dir: &Path, - target_triple: &str, - target_profile: &Path, -) -> Result> { - let mut paths = Vec::new(); - - let deps_dir = target_dir - .join(target_triple) - .join(target_profile) - .join("build"); - - for dep_dir in deps_dir.read_dir()? { - let output_file = dep_dir?.path().join("output"); - if output_file.is_file() { - use std::{ - fs::File, - io::{BufRead, BufReader}, - }; - for line in BufReader::new(File::open(output_file)?).lines() { - let line = line?; - if let Some(link_search) = line.strip_prefix("cargo:rustc-link-search=") { - let mut pie = link_search.split('='); - let (kind, path) = match (pie.next(), pie.next()) { - (Some(kind), Some(path)) => (kind, path), - (Some(path), None) => ("all", path), - _ => unreachable!(), - }; - match kind { - // FIXME: which kinds of search path we interested in - "dependency" | "native" | "all" => paths.push(path.into()), - _ => (), - }; - } - } - } - } - - Ok(paths) -} diff --git a/ndk-build/src/error.rs b/ndk-build/src/error.rs deleted file mode 100644 index 9dee0317..00000000 --- a/ndk-build/src/error.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::io::Error as IoError; -use std::num::ParseIntError; -use std::path::PathBuf; -use std::process::Command; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum NdkError { - #[error( - "Android SDK is not found. \ - Please set the path to the Android SDK with the $ANDROID_SDK_ROOT \ - environment variable." - )] - SdkNotFound, - #[error( - "Android NDK is not found. \ - Please set the path to the Android NDK with $ANDROID_NDK_ROOT \ - environment variable." - )] - NdkNotFound, - #[error("GNU toolchain binary `{gnu_bin}` nor LLVM toolchain binary `{llvm_bin}` found in `{toolchain_path:?}`.")] - ToolchainBinaryNotFound { - toolchain_path: PathBuf, - gnu_bin: String, - llvm_bin: String, - }, - #[error("Path `{0:?}` doesn't exist.")] - PathNotFound(PathBuf), - #[error("Command `{0}` not found.")] - CmdNotFound(String), - #[error("Android SDK has no build tools.")] - BuildToolsNotFound, - #[error("Android SDK has no platforms installed.")] - NoPlatformFound, - #[error("Platform `{0}` is not installed.")] - PlatformNotFound(u32), - #[error("Target is not supported.")] - UnsupportedTarget, - #[error("Host `{0}` is not supported.")] - UnsupportedHost(String), - #[error(transparent)] - Io(#[from] IoError), - #[error("IoError on `{0:?}`: {1}")] - IoPathError(PathBuf, #[source] IoError), - #[error("Invalid semver")] - InvalidSemver, - #[error("Command `{}` had a non-zero exit code.", format!("{:?}", .0).replace('"', ""))] - CmdFailed(Command), - #[error(transparent)] - Serialize(#[from] quick_xml::de::DeError), - #[error("String `{1}` is not a PID")] - NotAPid(#[source] ParseIntError, String), -} diff --git a/ndk-build/src/lib.rs b/ndk-build/src/lib.rs deleted file mode 100644 index efd6dc6f..00000000 --- a/ndk-build/src/lib.rs +++ /dev/null @@ -1,38 +0,0 @@ -macro_rules! bin { - ($bin:expr) => { - if cfg!(target_os = "windows") { - concat!($bin, ".exe") - } else { - $bin - } - }; -} - -macro_rules! bat { - ($bat:expr) => { - if cfg!(target_os = "windows") { - concat!($bat, ".bat") - } else { - $bat - } - }; -} - -macro_rules! cmd { - ($cmd:expr) => { - if cfg!(target_os = "windows") { - concat!($cmd, ".cmd") - } else { - $cmd - } - }; -} - -pub mod apk; -pub mod cargo; -pub mod dylibs; -pub mod error; -pub mod manifest; -pub mod ndk; -pub mod readelf; -pub mod target; diff --git a/ndk-build/src/manifest.rs b/ndk-build/src/manifest.rs deleted file mode 100644 index 924fdbb6..00000000 --- a/ndk-build/src/manifest.rs +++ /dev/null @@ -1,329 +0,0 @@ -use crate::error::NdkError; -use serde::{Deserialize, Serialize, Serializer}; -use std::{fs::File, path::Path}; - -/// Android [manifest element](https://developer.android.com/guide/topics/manifest/manifest-element), containing an [`Application`] element. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename = "manifest")] -pub struct AndroidManifest { - #[serde(rename(serialize = "xmlns:android"))] - #[serde(default = "default_namespace")] - ns_android: String, - #[serde(default)] - pub package: String, - #[serde(rename(serialize = "android:sharedUserId"))] - pub shared_user_id: Option, - #[serde(rename(serialize = "android:versionCode"))] - pub version_code: Option, - #[serde(rename(serialize = "android:versionName"))] - pub version_name: Option, - - #[serde(rename(serialize = "uses-sdk"))] - #[serde(default)] - pub sdk: Sdk, - - #[serde(rename(serialize = "uses-feature"))] - #[serde(default)] - pub uses_feature: Vec, - #[serde(rename(serialize = "uses-permission"))] - #[serde(default)] - pub uses_permission: Vec, - - #[serde(default)] - pub queries: Option, - - #[serde(default)] - pub application: Application, -} - -impl Default for AndroidManifest { - fn default() -> Self { - Self { - ns_android: default_namespace(), - package: Default::default(), - shared_user_id: Default::default(), - version_code: Default::default(), - version_name: Default::default(), - sdk: Default::default(), - uses_feature: Default::default(), - uses_permission: Default::default(), - queries: Default::default(), - application: Default::default(), - } - } -} - -impl AndroidManifest { - pub fn write_to(&self, dir: &Path) -> Result<(), NdkError> { - let file = File::create(dir.join("AndroidManifest.xml"))?; - let w = std::io::BufWriter::new(file); - quick_xml::se::to_writer(w, &self)?; - Ok(()) - } -} - -/// Android [application element](https://developer.android.com/guide/topics/manifest/application-element), containing an [`Activity`] element. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct Application { - #[serde(rename(serialize = "android:debuggable"))] - pub debuggable: Option, - #[serde(rename(serialize = "android:theme"))] - pub theme: Option, - #[serde(rename(serialize = "android:hasCode"))] - #[serde(default)] - pub has_code: bool, - #[serde(rename(serialize = "android:icon"))] - pub icon: Option, - #[serde(rename(serialize = "android:label"))] - #[serde(default)] - pub label: String, - - #[serde(rename(serialize = "meta-data"))] - #[serde(default)] - pub meta_data: Vec, - #[serde(default)] - pub activity: Activity, -} - -/// Android [activity element](https://developer.android.com/guide/topics/manifest/activity-element). -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Activity { - #[serde(rename(serialize = "android:configChanges"))] - #[serde(default = "default_config_changes")] - pub config_changes: Option, - #[serde(rename(serialize = "android:label"))] - pub label: Option, - #[serde(rename(serialize = "android:launchMode"))] - pub launch_mode: Option, - #[serde(rename(serialize = "android:name"))] - #[serde(default = "default_activity_name")] - pub name: String, - #[serde(rename(serialize = "android:screenOrientation"))] - pub orientation: Option, - #[serde(rename(serialize = "android:exported"))] - pub exported: Option, - #[serde(rename(serialize = "android:resizeableActivity"))] - pub resizeable_activity: Option, - - #[serde(rename(serialize = "meta-data"))] - #[serde(default)] - pub meta_data: Vec, - /// If no `MAIN` action exists in any intent filter, a default `MAIN` filter is serialized by `cargo-apk`. - #[serde(rename(serialize = "intent-filter"))] - #[serde(default)] - pub intent_filter: Vec, -} - -impl Default for Activity { - fn default() -> Self { - Self { - config_changes: default_config_changes(), - label: None, - launch_mode: None, - name: default_activity_name(), - orientation: None, - exported: None, - resizeable_activity: None, - meta_data: Default::default(), - intent_filter: Default::default(), - } - } -} - -/// Android [intent filter element](https://developer.android.com/guide/topics/manifest/intent-filter-element). -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct IntentFilter { - /// Serialize strings wrapped in `` - #[serde(serialize_with = "serialize_actions")] - #[serde(rename(serialize = "action"))] - #[serde(default)] - pub actions: Vec, - /// Serialize as vector of structs for proper xml formatting - #[serde(serialize_with = "serialize_catergories")] - #[serde(rename(serialize = "category"))] - #[serde(default)] - pub categories: Vec, - #[serde(default)] - pub data: Vec, -} - -fn serialize_actions(actions: &[String], serializer: S) -> Result -where - S: Serializer, -{ - use serde::ser::SerializeSeq; - - #[derive(Serialize)] - struct Action { - #[serde(rename = "android:name")] - name: String, - } - let mut seq = serializer.serialize_seq(Some(actions.len()))?; - for action in actions { - seq.serialize_element(&Action { - name: action.clone(), - })?; - } - seq.end() -} - -fn serialize_catergories(categories: &[String], serializer: S) -> Result -where - S: Serializer, -{ - use serde::ser::SerializeSeq; - - #[derive(Serialize)] - struct Category { - #[serde(rename = "android:name")] - pub name: String, - } - - let mut seq = serializer.serialize_seq(Some(categories.len()))?; - for category in categories { - seq.serialize_element(&Category { - name: category.clone(), - })?; - } - seq.end() -} - -/// Android [intent filter data element](https://developer.android.com/guide/topics/manifest/data-element). -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct IntentFilterData { - #[serde(rename(serialize = "android:scheme"))] - pub scheme: Option, - #[serde(rename(serialize = "android:host"))] - pub host: Option, - #[serde(rename(serialize = "android:port"))] - pub port: Option, - #[serde(rename(serialize = "android:path"))] - pub path: Option, - #[serde(rename(serialize = "android:pathPattern"))] - pub path_pattern: Option, - #[serde(rename(serialize = "android:pathPrefix"))] - pub path_prefix: Option, - #[serde(rename(serialize = "android:mimeType"))] - pub mime_type: Option, -} - -/// Android [meta-data element](https://developer.android.com/guide/topics/manifest/meta-data-element). -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct MetaData { - #[serde(rename(serialize = "android:name"))] - pub name: String, - #[serde(rename(serialize = "android:value"))] - pub value: String, -} - -/// Android [uses-feature element](https://developer.android.com/guide/topics/manifest/uses-feature-element). -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct Feature { - #[serde(rename(serialize = "android:name"))] - pub name: Option, - #[serde(rename(serialize = "android:required"))] - pub required: Option, - /// The `version` field is currently used for the following features: - /// - /// - `name="android.hardware.vulkan.compute"`: The minimum level of compute features required. See the [Android documentation](https://developer.android.com/reference/android/content/pm/PackageManager#FEATURE_VULKAN_HARDWARE_COMPUTE) - /// for available levels and the respective Vulkan features required/provided. - /// - /// - `name="android.hardware.vulkan.level"`: The minimum Vulkan requirements. See the [Android documentation](https://developer.android.com/reference/android/content/pm/PackageManager#FEATURE_VULKAN_HARDWARE_LEVEL) - /// for available levels and the respective Vulkan features required/provided. - /// - /// - `name="android.hardware.vulkan.version"`: Represents the value of Vulkan's `VkPhysicalDeviceProperties::apiVersion`. See the [Android documentation](https://developer.android.com/reference/android/content/pm/PackageManager#FEATURE_VULKAN_HARDWARE_VERSION) - /// for available levels and the respective Vulkan features required/provided. - #[serde(rename(serialize = "android:version"))] - pub version: Option, - #[serde(rename(serialize = "android:glEsVersion"))] - #[serde(serialize_with = "serialize_opengles_version")] - pub opengles_version: Option<(u8, u8)>, -} - -fn serialize_opengles_version( - version: &Option<(u8, u8)>, - serializer: S, -) -> Result -where - S: Serializer, -{ - match version { - Some(version) => { - let opengles_version = format!("0x{:04}{:04}", version.0, version.1); - serializer.serialize_some(&opengles_version) - } - None => serializer.serialize_none(), - } -} - -/// Android [uses-permission element](https://developer.android.com/guide/topics/manifest/uses-permission-element). -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct Permission { - #[serde(rename(serialize = "android:name"))] - pub name: String, - #[serde(rename(serialize = "android:maxSdkVersion"))] - pub max_sdk_version: Option, -} - -/// Android [package element](https://developer.android.com/guide/topics/manifest/queries-element#package). -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct Package { - #[serde(rename(serialize = "android:name"))] - pub name: String, -} - -/// Android [provider element](https://developer.android.com/guide/topics/manifest/queries-element#provider). -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct QueryProvider { - #[serde(rename(serialize = "android:authorities"))] - pub authorities: String, - - // The specs say only an `authorities` attribute is required for providers contained in a `queries` element - // however this is required for aapt support and should be made optional if/when cargo-apk migrates to aapt2 - #[serde(rename(serialize = "android:name"))] - pub name: String, -} - -/// Android [queries element](https://developer.android.com/guide/topics/manifest/queries-element). -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct Queries { - #[serde(default)] - pub package: Vec, - #[serde(default)] - pub intent: Vec, - #[serde(default)] - pub provider: Vec, -} - -/// Android [uses-sdk element](https://developer.android.com/guide/topics/manifest/uses-sdk-element). -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Sdk { - #[serde(rename(serialize = "android:minSdkVersion"))] - pub min_sdk_version: Option, - #[serde(rename(serialize = "android:targetSdkVersion"))] - pub target_sdk_version: Option, - #[serde(rename(serialize = "android:maxSdkVersion"))] - pub max_sdk_version: Option, -} - -impl Default for Sdk { - fn default() -> Self { - Self { - min_sdk_version: Some(23), - target_sdk_version: None, - max_sdk_version: None, - } - } -} - -fn default_namespace() -> String { - "http://schemas.android.com/apk/res/android".to_string() -} - -fn default_activity_name() -> String { - "android.app.NativeActivity".to_string() -} - -fn default_config_changes() -> Option { - Some("orientation|keyboardHidden|screenSize".to_string()) -} diff --git a/ndk-build/src/ndk.rs b/ndk-build/src/ndk.rs deleted file mode 100644 index 962ce936..00000000 --- a/ndk-build/src/ndk.rs +++ /dev/null @@ -1,504 +0,0 @@ -use crate::error::NdkError; -use crate::target::Target; -use std::collections::HashMap; -use std::path::{Path, PathBuf}; -use std::process::Command; - -/// The default password used when creating the default `debug.keystore` via -/// [`Ndk::debug_key`] -pub const DEFAULT_DEV_KEYSTORE_PASSWORD: &str = "android"; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Ndk { - sdk_path: PathBuf, - user_home: PathBuf, - ndk_path: PathBuf, - build_tools_version: String, - build_tag: u32, - platforms: Vec, -} - -impl Ndk { - pub fn from_env() -> Result { - let sdk_path = { - let sdk_path = std::env::var("ANDROID_SDK_ROOT").ok(); - if sdk_path.is_some() { - eprintln!( - "Warning: Environment variable ANDROID_SDK_ROOT is deprecated \ - (https://developer.android.com/studio/command-line/variables#envar). \ - It will be used until it is unset and replaced by ANDROID_HOME." - ); - } - - PathBuf::from( - sdk_path - .or_else(|| std::env::var("ANDROID_HOME").ok()) - .ok_or(NdkError::SdkNotFound)?, - ) - }; - - let user_home = { - let user_home = std::env::var("ANDROID_SDK_HOME") - .map(PathBuf::from) - // Unlike ANDROID_USER_HOME, ANDROID_SDK_HOME points to the _parent_ directory of .android: - // https://developer.android.com/studio/command-line/variables#envar - .map(|home| home.join(".android")) - .ok(); - - if user_home.is_some() { - eprintln!( - "Warning: Environment variable ANDROID_SDK_HOME is deprecated \ - (https://developer.android.com/studio/command-line/variables#envar). \ - It will be used until it is unset and replaced by ANDROID_USER_HOME." - ); - } - - // Default to $HOME/.android - user_home - .or_else(|| std::env::var("ANDROID_USER_HOME").map(PathBuf::from).ok()) - .or_else(|| dirs::home_dir().map(|home| home.join(".android"))) - .ok_or_else(|| NdkError::PathNotFound(PathBuf::from("$HOME")))? - }; - - let ndk_path = { - let ndk_path = std::env::var("ANDROID_NDK_ROOT") - .ok() - .or_else(|| std::env::var("ANDROID_NDK_PATH").ok()) - .or_else(|| std::env::var("ANDROID_NDK_HOME").ok()) - .or_else(|| std::env::var("NDK_HOME").ok()); - - // default ndk installation path - if ndk_path.is_none() && sdk_path.join("ndk-bundle").exists() { - sdk_path.join("ndk-bundle") - } else { - PathBuf::from(ndk_path.ok_or(NdkError::NdkNotFound)?) - } - }; - - let build_tools_dir = sdk_path.join("build-tools"); - let build_tools_version = std::fs::read_dir(&build_tools_dir) - .or(Err(NdkError::PathNotFound(build_tools_dir)))? - .filter_map(|path| path.ok()) - .filter(|path| path.path().is_dir()) - .filter_map(|path| path.file_name().into_string().ok()) - .filter(|name| name.chars().next().unwrap().is_ascii_digit()) - .max() - .ok_or(NdkError::BuildToolsNotFound)?; - - let build_tag = std::fs::read_to_string(ndk_path.join("source.properties")) - .expect("Failed to read source.properties"); - - let build_tag = build_tag - .split('\n') - .find_map(|line| { - let (key, value) = line - .split_once('=') - .expect("Failed to parse `key = value` from source.properties"); - if key.trim() == "Pkg.Revision" { - // AOSP writes a constantly-incrementing build version to the patch field. - // This number is incrementing across NDK releases. - let mut parts = value.trim().split('.'); - let _major = parts.next().unwrap(); - let _minor = parts.next().unwrap(); - let patch = parts.next().unwrap(); - // Can have an optional `XXX-beta1` - let patch = patch.split_once('-').map_or(patch, |(patch, _beta)| patch); - Some(patch.parse().expect("Failed to parse patch field")) - } else { - None - } - }) - .expect("No `Pkg.Revision` in source.properties"); - - let ndk_platforms = std::fs::read_to_string(ndk_path.join("build/core/platforms.mk"))?; - let ndk_platforms = ndk_platforms - .split('\n') - .map(|s| s.split_once(" := ").unwrap()) - .collect::>(); - - let min_platform_level = ndk_platforms["NDK_MIN_PLATFORM_LEVEL"] - .parse::() - .unwrap(); - let max_platform_level = ndk_platforms["NDK_MAX_PLATFORM_LEVEL"] - .parse::() - .unwrap(); - - let platforms_dir = sdk_path.join("platforms"); - let platforms: Vec = std::fs::read_dir(&platforms_dir) - .or(Err(NdkError::PathNotFound(platforms_dir)))? - .filter_map(|path| path.ok()) - .filter(|path| path.path().is_dir()) - .filter_map(|path| path.file_name().into_string().ok()) - .filter_map(|name| { - name.strip_prefix("android-") - .and_then(|api| api.parse::().ok()) - }) - .filter(|level| (min_platform_level..=max_platform_level).contains(level)) - .collect(); - - if platforms.is_empty() { - return Err(NdkError::NoPlatformFound); - } - - Ok(Self { - sdk_path, - user_home, - ndk_path, - build_tools_version, - build_tag, - platforms, - }) - } - - pub fn sdk(&self) -> &Path { - &self.sdk_path - } - - pub fn ndk(&self) -> &Path { - &self.ndk_path - } - - pub fn build_tools_version(&self) -> &str { - &self.build_tools_version - } - - pub fn build_tag(&self) -> u32 { - self.build_tag - } - - pub fn platforms(&self) -> &[u32] { - &self.platforms - } - - pub fn build_tool(&self, tool: &str) -> Result { - let path = self - .sdk_path - .join("build-tools") - .join(&self.build_tools_version) - .join(tool); - if !path.exists() { - return Err(NdkError::CmdNotFound(tool.to_string())); - } - Ok(Command::new(dunce::canonicalize(path)?)) - } - - pub fn platform_tool_path(&self, tool: &str) -> Result { - let path = self.sdk_path.join("platform-tools").join(tool); - if !path.exists() { - return Err(NdkError::CmdNotFound(tool.to_string())); - } - Ok(dunce::canonicalize(path)?) - } - - pub fn adb_path(&self) -> Result { - self.platform_tool_path(bin!("adb")) - } - - pub fn platform_tool(&self, tool: &str) -> Result { - Ok(Command::new(self.platform_tool_path(tool)?)) - } - - pub fn highest_supported_platform(&self) -> u32 { - self.platforms().iter().max().cloned().unwrap() - } - - /// Returns platform `30` as currently [required by Google Play], or lower - /// when the detected SDK does not support it yet. - /// - /// [required by Google Play]: https://developer.android.com/distribute/best-practices/develop/target-sdk - pub fn default_target_platform(&self) -> u32 { - self.highest_supported_platform().min(30) - } - - pub fn platform_dir(&self, platform: u32) -> Result { - let dir = self - .sdk_path - .join("platforms") - .join(format!("android-{}", platform)); - if !dir.exists() { - return Err(NdkError::PlatformNotFound(platform)); - } - Ok(dir) - } - - pub fn android_jar(&self, platform: u32) -> Result { - let android_jar = self.platform_dir(platform)?.join("android.jar"); - if !android_jar.exists() { - return Err(NdkError::PathNotFound(android_jar)); - } - Ok(android_jar) - } - - fn host_arch() -> Result<&'static str, NdkError> { - let host_os = std::env::var("HOST").ok(); - let host_contains = |s| host_os.as_ref().map(|h| h.contains(s)).unwrap_or(false); - - Ok(if host_contains("linux") { - "linux" - } else if host_contains("macos") { - "darwin" - } else if host_contains("windows") { - "windows" - } else if cfg!(target_os = "linux") { - "linux" - } else if cfg!(target_os = "macos") { - "darwin" - } else if cfg!(target_os = "windows") { - "windows" - } else { - return match host_os { - Some(host_os) => Err(NdkError::UnsupportedHost(host_os)), - _ => Err(NdkError::UnsupportedTarget), - }; - }) - } - - pub fn toolchain_dir(&self) -> Result { - let arch = Self::host_arch()?; - let mut toolchain_dir = self - .ndk_path - .join("toolchains") - .join("llvm") - .join("prebuilt") - .join(format!("{}-x86_64", arch)); - if !toolchain_dir.exists() { - toolchain_dir.set_file_name(arch); - } - if !toolchain_dir.exists() { - return Err(NdkError::PathNotFound(toolchain_dir)); - } - Ok(toolchain_dir) - } - - pub fn clang(&self) -> Result<(PathBuf, PathBuf), NdkError> { - let ext = if cfg!(target_os = "windows") { - "exe" - } else { - "" - }; - - let bin_path = self.toolchain_dir()?.join("bin"); - - let clang = bin_path.join("clang").with_extension(ext); - if !clang.exists() { - return Err(NdkError::PathNotFound(clang)); - } - - let clang_pp = bin_path.join("clang++").with_extension(ext); - if !clang_pp.exists() { - return Err(NdkError::PathNotFound(clang_pp)); - } - - Ok((clang, clang_pp)) - } - - pub fn toolchain_bin(&self, name: &str, target: Target) -> Result { - let ext = if cfg!(target_os = "windows") { - ".exe" - } else { - "" - }; - - let toolchain_path = self.toolchain_dir()?.join("bin"); - - // Since r21 (https://github.com/android/ndk/wiki/Changelog-r21) LLVM binutils are included _for testing_; - // Since r22 (https://github.com/android/ndk/wiki/Changelog-r22) GNU binutils are deprecated in favour of LLVM's; - // Since r23 (https://github.com/android/ndk/wiki/Changelog-r23) GNU binutils have been removed. - // To maintain stability with the current ndk-build crate release, prefer GNU binutils for - // as long as it is provided by the NDK instead of trying to use llvm-* from r21 onwards. - let gnu_bin = format!("{}-{}{}", target.ndk_triple(), name, ext); - let gnu_path = toolchain_path.join(&gnu_bin); - if gnu_path.exists() { - Ok(gnu_path) - } else { - let llvm_bin = format!("llvm-{}{}", name, ext); - let llvm_path = toolchain_path.join(&llvm_bin); - if llvm_path.exists() { - Ok(llvm_path) - } else { - Err(NdkError::ToolchainBinaryNotFound { - toolchain_path, - gnu_bin, - llvm_bin, - }) - } - } - } - - pub fn prebuilt_dir(&self) -> Result { - let arch = Self::host_arch()?; - let prebuilt_dir = self - .ndk_path - .join("prebuilt") - .join(format!("{}-x86_64", arch)); - if !prebuilt_dir.exists() { - Err(NdkError::PathNotFound(prebuilt_dir)) - } else { - Ok(prebuilt_dir) - } - } - - pub fn ndk_gdb( - &self, - launch_dir: impl AsRef, - launch_activity: &str, - device_serial: Option<&str>, - ) -> Result<(), NdkError> { - let abi = self.detect_abi(device_serial)?; - let jni_dir = launch_dir.as_ref().join("jni"); - std::fs::create_dir_all(&jni_dir)?; - std::fs::write( - jni_dir.join("Android.mk"), - format!("APP_ABI={}\nTARGET_OUT=\n", abi.android_abi()), - )?; - let mut ndk_gdb = Command::new(self.prebuilt_dir()?.join("bin").join(cmd!("ndk-gdb"))); - - if let Some(device_serial) = &device_serial { - ndk_gdb.arg("-s").arg(device_serial); - } - - ndk_gdb - .arg("--adb") - .arg(self.adb_path()?) - .arg("--launch") - .arg(launch_activity) - .current_dir(launch_dir) - .status()?; - Ok(()) - } - - pub fn android_user_home(&self) -> Result { - let android_user_home = self.user_home.clone(); - std::fs::create_dir_all(&android_user_home)?; - Ok(android_user_home) - } - - pub fn keytool(&self) -> Result { - if let Ok(keytool) = which::which(bin!("keytool")) { - return Ok(Command::new(keytool)); - } - if let Ok(java) = std::env::var("JAVA_HOME") { - let keytool = PathBuf::from(java).join("bin").join(bin!("keytool")); - if keytool.exists() { - return Ok(Command::new(keytool)); - } - } - Err(NdkError::CmdNotFound("keytool".to_string())) - } - - pub fn debug_key(&self) -> Result { - let path = self.android_user_home()?.join("debug.keystore"); - let password = DEFAULT_DEV_KEYSTORE_PASSWORD.to_owned(); - - if !path.exists() { - let mut keytool = self.keytool()?; - keytool - .arg("-genkey") - .arg("-v") - .arg("-keystore") - .arg(&path) - .arg("-storepass") - .arg(&password) - .arg("-alias") - .arg("androiddebugkey") - .arg("-keypass") - .arg(&password) - .arg("-dname") - .arg("CN=Android Debug,O=Android,C=US") - .arg("-keyalg") - .arg("RSA") - .arg("-keysize") - .arg("2048") - .arg("-validity") - .arg("10000"); - if !keytool.status()?.success() { - return Err(NdkError::CmdFailed(keytool)); - } - } - Ok(Key { path, password }) - } - - pub fn sysroot_lib_dir(&self, target: Target) -> Result { - let sysroot_lib_dir = self - .toolchain_dir()? - .join("sysroot") - .join("usr") - .join("lib") - .join(target.ndk_triple()); - if !sysroot_lib_dir.exists() { - return Err(NdkError::PathNotFound(sysroot_lib_dir)); - } - Ok(sysroot_lib_dir) - } - - pub fn sysroot_platform_lib_dir( - &self, - target: Target, - min_sdk_version: u32, - ) -> Result { - let sysroot_lib_dir = self.sysroot_lib_dir(target)?; - - // Look for a platform <= min_sdk_version - let mut tmp_platform = min_sdk_version; - while tmp_platform > 1 { - let path = sysroot_lib_dir.join(tmp_platform.to_string()); - if path.exists() { - return Ok(path); - } - tmp_platform += 1; - } - - // Look for the minimum API level supported by the NDK - let mut tmp_platform = min_sdk_version; - while tmp_platform < 100 { - let path = sysroot_lib_dir.join(tmp_platform.to_string()); - if path.exists() { - return Ok(path); - } - tmp_platform += 1; - } - - Err(NdkError::PlatformNotFound(min_sdk_version)) - } - - pub fn detect_abi(&self, device_serial: Option<&str>) -> Result { - let mut adb = self.adb(device_serial)?; - - let stdout = adb - .arg("shell") - .arg("getprop") - .arg("ro.product.cpu.abi") - .output()? - .stdout; - let abi = std::str::from_utf8(&stdout).or(Err(NdkError::UnsupportedTarget))?; - Target::from_android_abi(abi.trim()) - } - - pub fn adb(&self, device_serial: Option<&str>) -> Result { - let mut adb = Command::new(self.adb_path()?); - - if let Some(device_serial) = device_serial { - adb.arg("-s").arg(device_serial); - } - - Ok(adb) - } -} - -pub struct Key { - pub path: PathBuf, - pub password: String, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[ignore] - fn test_detect() { - let ndk = Ndk::from_env().unwrap(); - assert_eq!(ndk.build_tools_version(), "29.0.2"); - assert_eq!(ndk.platforms(), &[29, 28]); - } -} diff --git a/ndk-build/src/readelf.rs b/ndk-build/src/readelf.rs deleted file mode 100644 index 1f8778f2..00000000 --- a/ndk-build/src/readelf.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::apk::UnalignedApk; -use crate::error::NdkError; -use crate::target::Target; -use std::collections::HashSet; -use std::io::BufRead; -use std::path::{Path, PathBuf}; -use std::process::Command; - -impl<'a> UnalignedApk<'a> { - pub fn add_lib_recursively( - &mut self, - lib: &Path, - target: Target, - search_paths: &[&Path], - ) -> Result<(), NdkError> { - let ndk = &self.config().ndk; - let default_min_sdk = crate::manifest::Sdk::default().min_sdk_version.unwrap(); - let min_sdk_version = self - .config() - .manifest - .sdk - .min_sdk_version - .unwrap_or(default_min_sdk); - let readelf_path = ndk.toolchain_bin("readelf", target)?; - - let android_search_paths = [ - &*ndk.sysroot_lib_dir(target)?, - &*ndk.sysroot_platform_lib_dir(target, min_sdk_version)?, - ]; - - let mut provided = HashSet::new(); - for path in &android_search_paths { - for lib in list_libs(path)? { - if lib != "libc++_shared.so" { - provided.insert(lib); - } - } - } - - let mut artifacts = vec![lib.to_path_buf()]; - while let Some(artifact) = artifacts.pop() { - self.add_lib(&artifact, target)?; - for need in list_needed_libs(&readelf_path, &artifact)? { - // c++_shared is available in the NDK but not on-device. - // Must be bundled with the apk if used: - // https://developer.android.com/ndk/guides/cpp-support#libc - let search_paths = if need == "libc++_shared.so" { - &android_search_paths - } else if !provided.contains(&need) { - search_paths - } else { - continue; - }; - - if let Some(path) = find_library_path(search_paths, &need)? { - if provided.insert(path.file_name().unwrap().to_str().unwrap().to_string()) { - artifacts.push(path); - } - } else { - eprintln!("Shared library \"{}\" not found.", need); - } - } - } - - Ok(()) - } -} - -/// List all linked shared libraries -fn list_needed_libs(readelf_path: &Path, library_path: &Path) -> Result, NdkError> { - let mut readelf = Command::new(readelf_path); - let output = readelf.arg("-d").arg(library_path).output()?; - if !output.status.success() { - return Err(NdkError::CmdFailed(readelf)); - } - let mut needed = HashSet::new(); - for line in output.stdout.lines() { - let line = line?; - if line.contains("(NEEDED)") { - let lib = line - .split("Shared library: [") - .last() - .and_then(|line| line.split(']').next()); - if let Some(lib) = lib { - needed.insert(lib.to_string()); - } - } - } - Ok(needed) -} - -/// List shared libraries -fn list_libs(path: &Path) -> Result, NdkError> { - let mut libs = HashSet::new(); - let entries = std::fs::read_dir(path)?; - for entry in entries { - let entry = entry?; - if !entry.path().is_dir() { - if let Some(file_name) = entry.file_name().to_str() { - if file_name.ends_with(".so") { - libs.insert(file_name.to_string()); - } - } - } - } - Ok(libs) -} - -/// Resolves native library using search paths -fn find_library_path>( - paths: &[&Path], - library: S, -) -> Result, NdkError> { - for path in paths { - let lib_path = path.join(&library); - if lib_path.exists() { - return Ok(Some(dunce::canonicalize(lib_path)?)); - } - } - Ok(None) -} diff --git a/ndk-build/src/target.rs b/ndk-build/src/target.rs deleted file mode 100644 index 676b9005..00000000 --- a/ndk-build/src/target.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::error::NdkError; -use serde::Deserialize; - -#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] -#[repr(u8)] -pub enum Target { - #[serde(rename = "armv7-linux-androideabi")] - ArmV7a = 1, - #[serde(rename = "aarch64-linux-android")] - Arm64V8a = 2, - #[serde(rename = "i686-linux-android")] - X86 = 3, - #[serde(rename = "x86_64-linux-android")] - X86_64 = 4, -} - -impl Target { - /// Identifier used in the NDK to refer to the ABI - pub fn android_abi(self) -> &'static str { - match self { - Self::Arm64V8a => "arm64-v8a", - Self::ArmV7a => "armeabi-v7a", - Self::X86 => "x86", - Self::X86_64 => "x86_64", - } - } - - /// Returns `Target` for abi. - pub fn from_android_abi(abi: &str) -> Result { - match abi { - "arm64-v8a" => Ok(Self::Arm64V8a), - "armeabi-v7a" => Ok(Self::ArmV7a), - "x86" => Ok(Self::X86), - "x86_64" => Ok(Self::X86_64), - _ => Err(NdkError::UnsupportedTarget), - } - } - - /// Returns the triple used by the rust build tools - pub fn rust_triple(self) -> &'static str { - match self { - Self::Arm64V8a => "aarch64-linux-android", - Self::ArmV7a => "armv7-linux-androideabi", - Self::X86 => "i686-linux-android", - Self::X86_64 => "x86_64-linux-android", - } - } - - /// Returns `Target` for rust triple. - pub fn from_rust_triple(triple: &str) -> Result { - match triple { - "aarch64-linux-android" => Ok(Self::Arm64V8a), - "armv7-linux-androideabi" => Ok(Self::ArmV7a), - "i686-linux-android" => Ok(Self::X86), - "x86_64-linux-android" => Ok(Self::X86_64), - _ => Err(NdkError::UnsupportedTarget), - } - } - - // Returns the triple NDK provided LLVM - pub fn ndk_llvm_triple(self) -> &'static str { - match self { - Self::Arm64V8a => "aarch64-linux-android", - Self::ArmV7a => "armv7a-linux-androideabi", - Self::X86 => "i686-linux-android", - Self::X86_64 => "x86_64-linux-android", - } - } - - /// Returns the triple used by the non-LLVM parts of the NDK - pub fn ndk_triple(self) -> &'static str { - match self { - Self::Arm64V8a => "aarch64-linux-android", - Self::ArmV7a => "arm-linux-androideabi", - Self::X86 => "i686-linux-android", - Self::X86_64 => "x86_64-linux-android", - } - } -} diff --git a/ndk-context/CHANGELOG.md b/ndk-context/CHANGELOG.md deleted file mode 100644 index 585e9ae9..00000000 --- a/ndk-context/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# Unreleased - -# 0.1.1 (2022-04-19) - -- Add `release_android_context()` function to remove `AndroidContext` when activity is destroyed. (#263) - -# 0.1.0 (2022-02-14) - -- Initial release! 🎉 diff --git a/ndk-context/Cargo.toml b/ndk-context/Cargo.toml deleted file mode 100644 index 484bd28d..00000000 --- a/ndk-context/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "ndk-context" -version = "0.1.1" -authors = ["The Rust Windowing contributors"] -edition = "2021" -description = "Handles for accessing Android APIs" -license = "MIT OR Apache-2.0" -keywords = ["android", "ndk", "apk", "jni"] -documentation = "https://docs.rs/ndk-context" -homepage = "https://github.com/rust-windowing/android-ndk-rs" -repository = "https://github.com/rust-windowing/android-ndk-rs" -rust-version = "1.60" diff --git a/ndk-context/README.md b/ndk-context/README.md deleted file mode 100644 index 38941eaf..00000000 --- a/ndk-context/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# ndk-context - -Provides a stable api to rust crates for interfacing with the Android platform. It is -initialized by the runtime, usually [__ndk-glue__](https://crates.io/crates/ndk-glue), -but could also be initialized by Java or Kotlin code when embedding in an existing Android -project. diff --git a/ndk-context/src/lib.rs b/ndk-context/src/lib.rs deleted file mode 100644 index 1e1e54f5..00000000 --- a/ndk-context/src/lib.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! Provides a stable api to rust crates for interfacing with the Android platform. It is -//! initialized by the runtime, usually [__ndk-glue__](https://crates.io/crates/ndk-glue), -//! but could also be initialized by Java or Kotlin code when embedding in an existing Android -//! project. -//! -//! ```no_run -//! let ctx = ndk_context::android_context(); -//! let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?; -//! let env = vm.attach_current_thread(); -//! let class_ctx = env.find_class("android/content/Context")?; -//! let audio_service = env.get_static_field(class_ctx, "AUDIO_SERVICE", "Ljava/lang/String;")?; -//! let audio_manager = env -//! .call_method( -//! ctx.context() as jni::sys::jobject, -//! "getSystemService", -//! "(Ljava/lang/String;)Ljava/lang/Object;", -//! &[audio_service], -//! )? -//! .l()?; -//! ``` -use std::ffi::c_void; - -static mut ANDROID_CONTEXT: Option = None; - -/// [`AndroidContext`] provides the pointers required to interface with the jni on Android -/// platforms. -#[derive(Clone, Copy, Debug)] -pub struct AndroidContext { - java_vm: *mut c_void, - context_jobject: *mut c_void, -} - -impl AndroidContext { - /// A handle to the `JavaVM` object. - /// - /// Usage with [__jni__](https://crates.io/crates/jni) crate: - /// ```no_run - /// let ctx = ndk_context::android_context(); - /// let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?; - /// let env = vm.attach_current_thread(); - /// ``` - pub fn vm(self) -> *mut c_void { - self.java_vm - } - - /// A handle to an [android.content.Context](https://developer.android.com/reference/android/content/Context). - /// In most cases this will be a ptr to an `Activity`, but this isn't guaranteed. - /// - /// Usage with [__jni__](https://crates.io/crates/jni) crate: - /// ```no_run - /// let ctx = ndk_context::android_context(); - /// let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?; - /// let env = vm.attach_current_thread(); - /// let class_ctx = env.find_class("android/content/Context")?; - /// let audio_service = env.get_static_field(class_ctx, "AUDIO_SERVICE", "Ljava/lang/String;")?; - /// let audio_manager = env - /// .call_method( - /// ctx.context() as jni::sys::jobject, - /// "getSystemService", - /// "(Ljava/lang/String;)Ljava/lang/Object;", - /// &[audio_service], - /// )? - /// .l()?; - /// ``` - pub fn context(self) -> *mut c_void { - self.context_jobject - } -} - -/// Main entry point to this crate. Returns an [`AndroidContext`]. -pub fn android_context() -> AndroidContext { - unsafe { ANDROID_CONTEXT.expect("android context was not initialized") } -} - -/// Initializes the [`AndroidContext`]. [`AndroidContext`] is initialized by [__ndk-glue__](https://crates.io/crates/ndk-glue) -/// before `main` is called. -/// -/// # Safety -/// -/// The pointers must be valid and this function must be called exactly once before `main` is -/// called. -pub unsafe fn initialize_android_context(java_vm: *mut c_void, context_jobject: *mut c_void) { - let previous = ANDROID_CONTEXT.replace(AndroidContext { - java_vm, - context_jobject, - }); - assert!(previous.is_none()); -} - -/// Removes the [`AndroidContext`]. It is released by [__ndk-glue__](https://crates.io/crates/ndk-glue) -/// when the activity is finished and destroyed. -/// -/// # Safety -/// -/// This function must only be called after [`initialize_android_context()`], -/// when the activity is subsequently destroyed according to Android. -pub unsafe fn release_android_context() { - let previous = ANDROID_CONTEXT.take(); - assert!(previous.is_some()); -} diff --git a/ndk-examples/Cargo.toml b/ndk-examples/Cargo.toml deleted file mode 100644 index 5c22b30f..00000000 --- a/ndk-examples/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "ndk-examples" -version = "0.1.0" -authors = ["David Craven "] -edition = "2018" -publish = false - -[target.'cfg(target_os = "android")'.dependencies] -jni = "0.20" -libc = "0.2" -log = "0.4.14" -ndk = { path = "../ndk", features = ["api-level-23"] } -ndk-context = { path = "../ndk-context" } -ndk-glue = { path = "../ndk-glue", features = ["logger"] } - -[[example]] -name = "hello_world" -crate-type = ["cdylib"] - -[[example]] -name = "jni_audio" -crate-type = ["cdylib"] - -[[example]] -name = "looper" -crate-type = ["cdylib"] - -[package.metadata.android.sdk] -min_sdk_version = 16 -target_sdk_version = 29 diff --git a/ndk-examples/README.md b/ndk-examples/README.md deleted file mode 100644 index c4eec76b..00000000 --- a/ndk-examples/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# ndk-examples - -Collection of examples showing different parts of the libraries. - -## Examples - -In order to see logs of the sample apps execute in a console: -```console -$ adb logcat RustStdoutStderr:D '*:S' -``` - -### hello_world - -Prints `hello world` in the console - -```console -$ cargo apk build --example hello_world -``` - -### jni_audio - -Prints output audio devices in the console - -```console -$ cargo apk run --example jni_audio -``` diff --git a/ndk-examples/examples/hello_world.rs b/ndk-examples/examples/hello_world.rs deleted file mode 100644 index 68fab11f..00000000 --- a/ndk-examples/examples/hello_world.rs +++ /dev/null @@ -1,14 +0,0 @@ -use log::info; -use ndk::trace; - -#[cfg_attr( - target_os = "android", - ndk_glue::main(backtrace = "on", logger(level = "debug", tag = "hello-world")) -)] -fn main() { - let _trace; - if trace::is_trace_enabled() { - _trace = trace::Section::new("ndk-rs example main").unwrap(); - } - info!("hello world"); -} diff --git a/ndk-examples/examples/jni_audio.rs b/ndk-examples/examples/jni_audio.rs deleted file mode 100644 index 3a339858..00000000 --- a/ndk-examples/examples/jni_audio.rs +++ /dev/null @@ -1,75 +0,0 @@ -use jni::objects::JObject; - -#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))] -fn main() { - enumerate_audio_devices().unwrap(); -} - -const GET_DEVICES_OUTPUTS: jni::sys::jint = 2; - -fn enumerate_audio_devices() -> Result<(), Box> { - // Create a VM for executing Java calls - let ctx = ndk_context::android_context(); - let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?; - let context = unsafe { JObject::from_raw(ctx.context().cast()) }; - let env = vm.attach_current_thread()?; - - // Query the global Audio Service - let class_ctxt = env.find_class("android/content/Context")?; - let audio_service = env.get_static_field(class_ctxt, "AUDIO_SERVICE", "Ljava/lang/String;")?; - - let audio_manager = env - .call_method( - context, - "getSystemService", - // JNI type signature needs to be derived from the Java API - // (ArgTys)ResultTy - "(Ljava/lang/String;)Ljava/lang/Object;", - &[audio_service], - )? - .l()?; - - // Enumerate output devices - let devices = env.call_method( - audio_manager, - "getDevices", - "(I)[Landroid/media/AudioDeviceInfo;", - &[GET_DEVICES_OUTPUTS.into()], - )?; - - println!("-- Output Audio Devices --"); - - let device_array = devices.l()?.into_raw(); - let len = env.get_array_length(device_array)?; - for i in 0..len { - let device = env.get_object_array_element(device_array, i)?; - - // Collect device information - // See https://developer.android.com/reference/android/media/AudioDeviceInfo - let product_name: String = { - let name = - env.call_method(device, "getProductName", "()Ljava/lang/CharSequence;", &[])?; - let name = env.call_method(name.l()?, "toString", "()Ljava/lang/String;", &[])?; - env.get_string(name.l()?.into())?.into() - }; - let id = env.call_method(device, "getId", "()I", &[])?.i()?; - let ty = env.call_method(device, "getType", "()I", &[])?.i()?; - - let sample_rates = { - let sample_array = env - .call_method(device, "getSampleRates", "()[I", &[])? - .l()? - .into_raw(); - let len = env.get_array_length(sample_array)?; - - let mut sample_rates = vec![0; len as usize]; - env.get_int_array_region(sample_array, 0, &mut sample_rates)?; - sample_rates - }; - - println!("Device {}: Id {}, Type {}", product_name, id, ty); - println!("sample rates: {:#?}", sample_rates); - } - - Ok(()) -} diff --git a/ndk-examples/examples/looper.rs b/ndk-examples/examples/looper.rs deleted file mode 100644 index 180c7082..00000000 --- a/ndk-examples/examples/looper.rs +++ /dev/null @@ -1,158 +0,0 @@ -//! Demonstrates how to manage application lifetime using Android's `Looper` - -use std::mem::MaybeUninit; -use std::os::unix::prelude::RawFd; -use std::time::Duration; - -use log::info; -use ndk::event::{InputEvent, Keycode}; -use ndk::looper::{FdEvent, Poll, ThreadLooper}; - -const U32_SIZE: usize = std::mem::size_of::(); - -#[cfg_attr( - target_os = "android", - ndk_glue::main(backtrace = "on", logger(level = "debug")) -)] -fn main() { - // Retrieve the Looper that ndk_glue created for us on the current thread. - // Android uses this to block on events and poll file descriptors with a single mechanism. - let looper = - ThreadLooper::for_thread().expect("ndk-glue did not attach thread looper before main()!"); - - // First free number after ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT. This might be fragile. - const CUSTOM_EVENT_IDENT: i32 = ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT + 1; - - fn create_pipe() -> [RawFd; 2] { - let mut ends = MaybeUninit::<[RawFd; 2]>::uninit(); - assert_eq!(unsafe { libc::pipe(ends.as_mut_ptr().cast()) }, 0); - unsafe { ends.assume_init() } - } - - // Create a Unix pipe to send custom events to the Looper. ndk-glue uses a similar mechanism to deliver - // ANativeActivityCallbacks asynchronously to the Looper through NDK_GLUE_LOOPER_EVENT_PIPE_IDENT. - let custom_event_pipe = create_pipe(); - let custom_callback_pipe = create_pipe(); - - // Attach the reading end of the pipe to the looper, so that it wakes up - // whenever data is available for reading (FdEvent::INPUT) - looper - .as_foreign() - .add_fd( - custom_event_pipe[0], - CUSTOM_EVENT_IDENT, - FdEvent::INPUT, - std::ptr::null_mut(), - ) - .expect("Failed to add file descriptor to Looper"); - - // Attach the reading end of a pipe to a callback, too - looper - .as_foreign() - .add_fd_with_callback(custom_callback_pipe[0], FdEvent::INPUT, |fd| { - let mut recv = !0u32; - assert_eq!( - unsafe { libc::read(fd, &mut recv as *mut _ as *mut _, U32_SIZE) } as usize, - U32_SIZE - ); - info!("Read custom event from pipe, in callback: {}", recv); - // Detach this handler by returning `false` once the count reaches 5 - recv < 5 - }) - .expect("Failed to add file descriptor to Looper"); - - std::thread::spawn(move || { - // Send a "custom event" to the looper every second - for i in 0.. { - let i_addr = &i as *const _ as *const _; - std::thread::sleep(Duration::from_secs(1)); - assert_eq!( - unsafe { libc::write(custom_event_pipe[1], i_addr, U32_SIZE) }, - U32_SIZE as isize - ); - assert_eq!( - unsafe { libc::write(custom_callback_pipe[1], i_addr, U32_SIZE,) }, - U32_SIZE as isize - ); - } - }); - - let mut exit = false; - - while !exit { - // looper.poll_*_timeout(timeout) to not block indefinitely. - // Pass a timeout of Duration::ZERO to never block. - match looper.poll_all().unwrap() { - Poll::Wake => { /* looper.as_foreign().wake() was called */ } - Poll::Callback => { - /* An event with a registered callback was received. - * Only received when polling for single events with poll_once_* - */ - unreachable!() - } - Poll::Timeout => { - /* Timed out as per poll_*_timeout */ - unreachable!() - } - Poll::Event { - ident, - fd, - events: _, - data: _, - } => { - info!("File descriptor event on identifier {}", ident); - match ident { - ndk_glue::NDK_GLUE_LOOPER_EVENT_PIPE_IDENT => { - // One of the callbacks in ANativeActivityCallbacks is called, and delivered - // to this application asynchronously by ndk_glue through a pipe. - // These consist mostly of important lifecycle and window events! Graphics - // applications will create and destroy their output surface/swapchain here. - info!( - "Event pipe yields: {:?}", - ndk_glue::poll_events() - .expect("Looper says event-pipe has data available!") - ) - } - ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT => { - let input_queue = ndk_glue::input_queue(); - let input_queue = input_queue.as_ref().expect("Input queue not attached"); - assert!(input_queue.has_events()); - // Consume as many events as possible - while let Some(event) = input_queue.get_event().unwrap() { - // Pass the event by a possible IME (Input Method Editor, ie. an open keyboard) first - if let Some(event) = input_queue.pre_dispatch(event) { - info!("Input event {:?}", event); - let mut handled = false; - if let InputEvent::KeyEvent(key_event) = &event { - if key_event.key_code() == Keycode::Back { - // Gracefully stop the app when the user presses the back button - exit = true; - handled = true; - } - } - // Let Android know that we did not consume the event - // (Pass true here if you did) - input_queue.finish_event(event, handled); - } - } - } - CUSTOM_EVENT_IDENT => { - // Expect to receive 32-bit numbers to describe events, - // as sent by the thread above - let mut recv = !0u32; - assert_eq!( - unsafe { libc::read(fd, &mut recv as *mut _ as *mut _, U32_SIZE) } - as usize, - U32_SIZE - ); - info!("Read custom event from pipe: {}", recv); - } - i => panic!("Unexpected event identifier {}", i), - } - } - } - } - - // Stop the activity - ndk_glue::native_activity().unwrap().finish() -} diff --git a/ndk-glue/CHANGELOG.md b/ndk-glue/CHANGELOG.md deleted file mode 100644 index f4c96e06..00000000 --- a/ndk-glue/CHANGELOG.md +++ /dev/null @@ -1,76 +0,0 @@ -# Unreleased - -- **Breaking:** `NativeActivity` is now behind a lock, and will be removed as soon as `onDestroy` - is called. Due to the async nature of events, make sure to hold on to the lock received from - `native_activity()` _beforehand_ if you wish to use it during handling of `Event::Destroy`. The - lock should be released to allow `onDestroy` to return. - -# 0.7.0 (2022-07-24) - -- **Breaking:** Provide a `LockReadGuard` newtype around `NativeWindow`/`InputQueue` to hide the underlying lock implementation. (#288) -- **Breaking:** Transpose `LockReadGuard>` into `Option>` to only necessitate an `Option` unpack/`unwrap()` once. (#282) - -# 0.6.2 (2022-04-19) - -- Call `ndk_context::release_android_context()` function to remove `AndroidContext` when activity is destroyed. (#263) - -# 0.6.1 (2022-02-14) - -- Initialize `ndk-context` for cross-version access to the Java `VM` and Android `Context`. - -# 0.6.0 (2022-01-05) - -- **Breaking:** Update to `ndk-sys 0.3.0` and `ndk 0.6.0`. (#214) - -# 0.5.2 (2022-04-19) - -- Call `ndk_context::release_android_context()` function to remove `AndroidContext` when activity is destroyed. (#263) - -# 0.5.1 (2022-02-15) - -- Initialize `ndk-context` for cross-version access to the Java `VM` and Android `Context`. - -# 0.5.0 (2021-11-22) - -- Document when to lock and unlock the window/input queue when certain events are received. -- **Breaking:** Update to `ndk 0.5.0` and `ndk-macros 0.3.0`. - -# 0.4.2 (2022-04-19) - -- Call `ndk_context::release_android_context()` function to remove `AndroidContext` when activity is destroyed. (#263) - -# 0.4.1 (2022-02-15) - -- Initialize `ndk-context` for cross-version access to the Java `VM` and Android `Context`. - -# 0.4.0 (2021-08-02) - -- Looper is now created before returning from `ANativeActivity_onCreate`, solving - race conditions in `onInputQueueCreated`. -- Event pipe and looper are now notified of removal _before_ destroying `NativeWindow` - and `InputQueue`. This allows applications to unlock their read-locks of these instances - first (which they are supposed to hold on to during use) instead of deadlocking in - Android callbacks. -- Reexport `android_logger` and `log` from the crate root for `ndk-macro` to use. -- Use new `FdEvents` `bitflags` for looper file descriptor events. -- Update to `ndk` 0.4.0. - This minor dependency bump causes a minor bump for `ndk-glue` too. - -# 0.3.0 (2021-01-30) - -- **Breaking:** Looper `ident` not passed in `data` pointer anymore. - If you are relying on `Poll::Event::data` to tell event fd and - input queue apart, please use `Poll::Event::ident` and the new - constants introduced in `ndk-glue`! - -# 0.2.1 (2020-10-15) - -- Fix documentation build on docs.rs - -# 0.2.0 (2020-09-15) - -- **Breaking:** Removed `ndk_glue` macro in favor of new `main` attribute macro. - -# 0.1.0 (2020-04-22) - -- Initial release! 🎉 diff --git a/ndk-glue/Cargo.toml b/ndk-glue/Cargo.toml deleted file mode 100644 index beb91894..00000000 --- a/ndk-glue/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "ndk-glue" -version = "0.7.0" -authors = ["The Rust Windowing contributors"] -edition = "2018" -description = "Startup code for android binaries" -license = "MIT OR Apache-2.0" -keywords = ["android", "ndk"] -readme = "README.md" -documentation = "https://docs.rs/ndk-glue" -homepage = "https://github.com/rust-windowing/android-ndk-rs" -repository = "https://github.com/rust-windowing/android-ndk-rs" -rust-version = "1.60" - -[dependencies] -android_logger = { version = "0.11", optional = true } -libc = "0.2.84" -log = "0.4.14" -ndk = { path = "../ndk", version = "0.7.0" } -ndk-context = { path = "../ndk-context", version = "0.1.1" } -ndk-macro = { path = "../ndk-macro", version = "0.3.0" } -ndk-sys = { path = "../ndk-sys", version = "0.4.0" } -once_cell = "1" -parking_lot = "0.12" - -[features] -default = [] -test = ["ndk/test", "ndk-sys/test"] -logger = ["android_logger", "ndk-macro/logger"] - -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] -targets = [ - "aarch64-linux-android", - "armv7-linux-androideabi", - "i686-linux-android", - "x86_64-linux-android", -] diff --git a/ndk-glue/README.md b/ndk-glue/README.md deleted file mode 100644 index c434770e..00000000 --- a/ndk-glue/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# ndk-glue - -Interoperability library for a native Rust application with the Android framework. -Provides access to `NativeActivity`, `NativeWindow` and `InputQueue`. - -This library exports the `main` attribute macro from `ndk-macro`, see the corresponding README for more details. diff --git a/ndk-glue/src/lib.rs b/ndk-glue/src/lib.rs deleted file mode 100644 index 60a0d6ff..00000000 --- a/ndk-glue/src/lib.rs +++ /dev/null @@ -1,431 +0,0 @@ -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -use log::Level; -use ndk::input_queue::InputQueue; -use ndk::looper::{FdEvent, ForeignLooper, ThreadLooper}; -use ndk::native_activity::NativeActivity; -use ndk::native_window::NativeWindow; -use ndk_sys::{AInputQueue, ANativeActivity, ANativeWindow, ARect}; -use once_cell::sync::Lazy; -use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; -use std::ffi::{CStr, CString}; -use std::fmt; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::ops::Deref; -use std::os::raw; -use std::os::unix::prelude::*; -use std::ptr::NonNull; -use std::sync::{Arc, Condvar, Mutex}; -use std::thread; - -#[cfg(feature = "logger")] -pub use android_logger; -#[cfg(feature = "logger")] -pub use log; - -pub use ndk_macro::main; - -/// `ndk-glue` macros register the reading end of an event pipe with the -/// main [`ThreadLooper`] under this `ident`. -/// When returned from [`ThreadLooper::poll_*`][ThreadLooper::poll_once] -/// an event can be retrieved from [`poll_events()`]. -pub const NDK_GLUE_LOOPER_EVENT_PIPE_IDENT: i32 = 0; - -/// The [`InputQueue`] received from Android is registered with the main -/// [`ThreadLooper`] under this `ident`. -/// When returned from [`ThreadLooper::poll_*`][ThreadLooper::poll_once] -/// an event can be retrieved from [`input_queue()`]. -pub const NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT: i32 = 1; - -pub fn android_log(level: Level, tag: &CStr, msg: &CStr) { - let prio = match level { - Level::Error => ndk_sys::android_LogPriority::ANDROID_LOG_ERROR, - Level::Warn => ndk_sys::android_LogPriority::ANDROID_LOG_WARN, - Level::Info => ndk_sys::android_LogPriority::ANDROID_LOG_INFO, - Level::Debug => ndk_sys::android_LogPriority::ANDROID_LOG_DEBUG, - Level::Trace => ndk_sys::android_LogPriority::ANDROID_LOG_VERBOSE, - }; - unsafe { - ndk_sys::__android_log_write(prio.0 as raw::c_int, tag.as_ptr(), msg.as_ptr()); - } -} - -static NATIVE_ACTIVITY: Lazy>> = Lazy::new(Default::default); -static NATIVE_WINDOW: Lazy>> = Lazy::new(Default::default); -static INPUT_QUEUE: Lazy>> = Lazy::new(Default::default); -static CONTENT_RECT: Lazy> = Lazy::new(Default::default); -static LOOPER: Lazy>> = Lazy::new(Default::default); - -/// This function accesses a `static` variable internally and must only be used if you are sure -/// there is exactly one version of [`ndk_glue`][crate] in your dependency tree. -/// -/// If you need access to the `JavaVM` through [`NativeActivity::vm()`] or Activity `Context` -/// through [`NativeActivity::activity()`], please use the [`ndk_context`] crate and its -/// [`ndk_context::android_context()`] getter to acquire the `JavaVM` and `Context` instead. -pub fn native_activity() -> Option> { - LockReadGuard::from_wrapped_option(NATIVE_ACTIVITY.read()) -} - -pub struct LockReadGuard(MappedRwLockReadGuard<'static, T>); - -impl LockReadGuard { - /// Transpose an [`Option`] wrapped inside a [`LockReadGuard`] - /// - /// This is a _read_ lock for which the contents can't change; hence allowing the user to only - /// check for [`None`] once and hold a lock containing `T` directly thereafter, without - /// subsequent infallible [`Option::unwrap()`]s. - fn from_wrapped_option(wrapped: RwLockReadGuard<'static, Option>) -> Option { - RwLockReadGuard::try_map(wrapped, Option::as_ref) - .ok() - .map(Self) - } -} - -impl Deref for LockReadGuard { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl fmt::Debug for LockReadGuard { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for LockReadGuard { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -/// Returns a [`NativeWindow`] held inside a lock, preventing Android from freeing it immediately -/// in [its `NativeWindow` destructor]. -/// -/// If the window is in use by e.g. a graphics API, make sure to hold on to this lock. -/// -/// After receiving [`Event::WindowDestroyed`] `ndk-glue` will block in Android's [`NativeWindow`] destructor -/// callback until the lock is released, returning to Android and allowing it to free the window. -/// -/// [its `NativeWindow` destructor]: https://developer.android.com/ndk/reference/struct/a-native-activity-callbacks#onnativewindowdestroyed -/// -/// # Warning -/// This function accesses a `static` variable internally and must only be used if you are sure -/// there is exactly one version of `ndk_glue` in your dependency tree. -pub fn native_window() -> Option> { - LockReadGuard::from_wrapped_option(NATIVE_WINDOW.read()) -} - -/// Returns an [`InputQueue`] held inside a lock, preventing Android from freeing it immediately -/// in [its `InputQueue` destructor]. -/// -/// After receiving [`Event::InputQueueDestroyed`] `ndk-glue` will block in Android's [`InputQueue`] destructor -/// callback until the lock is released, returning to Android and allowing it to free the window. -/// -/// [its `InputQueue` destructor]: https://developer.android.com/ndk/reference/struct/a-native-activity-callbacks#oninputqueuedestroyed -/// -/// # Warning -/// This function accesses a `static` variable internally and must only be used if you are sure -/// there is exactly one version of `ndk_glue` in your dependency tree. -pub fn input_queue() -> Option> { - LockReadGuard::from_wrapped_option(INPUT_QUEUE.read()) -} - -/// This function accesses a `static` variable internally and must only be used if you are sure -/// there is exactly one version of `ndk_glue` in your dependency tree. -pub fn content_rect() -> Rect { - CONTENT_RECT.read().clone() -} - -static PIPE: Lazy<[RawFd; 2]> = Lazy::new(|| { - let mut pipe: [RawFd; 2] = Default::default(); - unsafe { libc::pipe(pipe.as_mut_ptr()) }; - pipe -}); - -pub fn poll_events() -> Option { - unsafe { - let size = std::mem::size_of::(); - let mut event = Event::Start; - if libc::read(PIPE[0], &mut event as *mut _ as *mut _, size) == size as _ { - Some(event) - } else { - None - } - } -} - -unsafe fn wake(_activity: *mut ANativeActivity, event: Event) { - log::trace!("{:?}", event); - let size = std::mem::size_of::(); - let res = libc::write(PIPE[1], &event as *const _ as *const _, size); - assert_eq!(res, size as _); -} - -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct Rect { - pub left: u32, - pub top: u32, - pub right: u32, - pub bottom: u32, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -#[repr(u8)] -pub enum Event { - Start, - Resume, - SaveInstanceState, - Pause, - Stop, - /// The native activity will be stopped and destroyed after this event. - /// Due to the async nature of these events, make sure to hold on to the - /// lock received from [`native_activity()`] _beforehand_ if you wish to use - /// it during handling of [`Event::Destroy`]. The lock should be released to - /// allow `onDestroy` to return. - Destroy, - ConfigChanged, - LowMemory, - WindowLostFocus, - WindowHasFocus, - /// A [`NativeWindow`] is now available through [`native_window()`]. See that function for more - /// details about holding on to the returned [`LockReadGuard`]. - /// - /// Be sure to release any resources (e.g. Vulkan/OpenGL graphics surfaces) created from - /// it followed by releasing this lock upon receiving [`Event::WindowDestroyed`]. - WindowCreated, - WindowResized, - WindowRedrawNeeded, - /// If the window is in use by e.g. a graphics API, make sure the [`LockReadGuard`] from - /// [`native_window()`] is held on to until after freeing those resources. - /// - /// After receiving this [`Event`] `ndk_glue` will block inside its [`NativeWindow`] destructor - /// until that read-lock is released before returning to Android and allowing it to free the - /// window. - /// - /// From this point [`native_window()`] will return [`None`] until receiving - /// [`Event::WindowCreated`] again. - WindowDestroyed, - /// An [`InputQueue`] is now available through [`input_queue()`]. - /// - /// Be sure to release the returned lock upon receiving [`Event::InputQueueDestroyed`]. - InputQueueCreated, - /// After receiving this [`Event`] `ndk_glue` will block inside its [`InputQueue`] destructor - /// until the read-lock from [`input_queue()`] is released before returning to Android and - /// allowing it to free the input queue. - /// - /// From this point [`input_queue()`] will return [`None`] until receiving - /// [`Event::InputQueueCreated`] again. - InputQueueDestroyed, - ContentRectChanged, -} - -/// # Safety -/// `activity` must either be null (resulting in a safe panic) -/// or a pointer to a valid Android `ANativeActivity`. -pub unsafe fn init( - activity: *mut ANativeActivity, - _saved_state: *mut u8, - _saved_state_size: usize, - main: fn(), -) { - let mut activity = NonNull::new(activity).unwrap(); - let mut callbacks = activity.as_mut().callbacks.as_mut().unwrap(); - callbacks.onStart = Some(on_start); - callbacks.onResume = Some(on_resume); - callbacks.onSaveInstanceState = Some(on_save_instance_state); - callbacks.onPause = Some(on_pause); - callbacks.onStop = Some(on_stop); - callbacks.onDestroy = Some(on_destroy); - callbacks.onWindowFocusChanged = Some(on_window_focus_changed); - callbacks.onNativeWindowCreated = Some(on_window_created); - callbacks.onNativeWindowResized = Some(on_window_resized); - callbacks.onNativeWindowRedrawNeeded = Some(on_window_redraw_needed); - callbacks.onNativeWindowDestroyed = Some(on_window_destroyed); - callbacks.onInputQueueCreated = Some(on_input_queue_created); - callbacks.onInputQueueDestroyed = Some(on_input_queue_destroyed); - callbacks.onContentRectChanged = Some(on_content_rect_changed); - callbacks.onConfigurationChanged = Some(on_configuration_changed); - callbacks.onLowMemory = Some(on_low_memory); - - let activity = NativeActivity::from_ptr(activity); - ndk_context::initialize_android_context(activity.vm().cast(), activity.activity().cast()); - NATIVE_ACTIVITY.write().replace(activity); - - let mut logpipe: [RawFd; 2] = Default::default(); - libc::pipe(logpipe.as_mut_ptr()); - libc::dup2(logpipe[1], libc::STDOUT_FILENO); - libc::dup2(logpipe[1], libc::STDERR_FILENO); - thread::spawn(move || { - let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap(); - let file = File::from_raw_fd(logpipe[0]); - let mut reader = BufReader::new(file); - let mut buffer = String::new(); - loop { - buffer.clear(); - if let Ok(len) = reader.read_line(&mut buffer) { - if len == 0 { - break; - } else if let Ok(msg) = CString::new(buffer.clone()) { - android_log(Level::Info, tag, &msg); - } - } - } - }); - - let looper_ready = Arc::new(Condvar::new()); - let signal_looper_ready = looper_ready.clone(); - - thread::spawn(move || { - let looper = ThreadLooper::prepare(); - let foreign = looper.into_foreign(); - foreign - .add_fd( - PIPE[0], - NDK_GLUE_LOOPER_EVENT_PIPE_IDENT, - FdEvent::INPUT, - std::ptr::null_mut(), - ) - .unwrap(); - - { - let mut locked_looper = LOOPER.lock().unwrap(); - locked_looper.replace(foreign); - signal_looper_ready.notify_one(); - } - - main() - }); - - // Don't return from this function (`ANativeActivity_onCreate`) until the thread - // has created its `ThreadLooper` and assigned it to the static `LOOPER` - // variable. It will be used from `on_input_queue_created` as soon as this - // function returns. - let locked_looper = LOOPER.lock().unwrap(); - let _mutex_guard = looper_ready - .wait_while(locked_looper, |looper| looper.is_none()) - .unwrap(); -} - -unsafe extern "C" fn on_start(activity: *mut ANativeActivity) { - wake(activity, Event::Start); -} - -unsafe extern "C" fn on_resume(activity: *mut ANativeActivity) { - wake(activity, Event::Resume); -} - -unsafe extern "C" fn on_save_instance_state( - activity: *mut ANativeActivity, - _out_size: *mut ndk_sys::size_t, -) -> *mut raw::c_void { - // TODO - wake(activity, Event::SaveInstanceState); - std::ptr::null_mut() -} - -unsafe extern "C" fn on_pause(activity: *mut ANativeActivity) { - wake(activity, Event::Pause); -} - -unsafe extern "C" fn on_stop(activity: *mut ANativeActivity) { - wake(activity, Event::Stop); -} - -unsafe extern "C" fn on_destroy(activity: *mut ANativeActivity) { - wake(activity, Event::Destroy); - ndk_context::release_android_context(); - let mut native_activity_guard = NATIVE_ACTIVITY.write(); - let native_activity = native_activity_guard.take().unwrap(); - assert_eq!(native_activity.ptr().as_ptr(), activity); -} - -unsafe extern "C" fn on_configuration_changed(activity: *mut ANativeActivity) { - wake(activity, Event::ConfigChanged); -} - -unsafe extern "C" fn on_low_memory(activity: *mut ANativeActivity) { - wake(activity, Event::LowMemory); -} - -unsafe extern "C" fn on_window_focus_changed( - activity: *mut ANativeActivity, - has_focus: raw::c_int, -) { - let event = if has_focus == 0 { - Event::WindowLostFocus - } else { - Event::WindowHasFocus - }; - wake(activity, event); -} - -unsafe extern "C" fn on_window_created(activity: *mut ANativeActivity, window: *mut ANativeWindow) { - NATIVE_WINDOW - .write() - .replace(NativeWindow::clone_from_ptr(NonNull::new(window).unwrap())); - wake(activity, Event::WindowCreated); -} - -unsafe extern "C" fn on_window_resized( - activity: *mut ANativeActivity, - _window: *mut ANativeWindow, -) { - wake(activity, Event::WindowResized); -} - -unsafe extern "C" fn on_window_redraw_needed( - activity: *mut ANativeActivity, - _window: *mut ANativeWindow, -) { - wake(activity, Event::WindowRedrawNeeded); -} - -unsafe extern "C" fn on_window_destroyed( - activity: *mut ANativeActivity, - window: *mut ANativeWindow, -) { - wake(activity, Event::WindowDestroyed); - let mut native_window_guard = NATIVE_WINDOW.write(); - assert_eq!(native_window_guard.as_ref().unwrap().ptr().as_ptr(), window); - native_window_guard.take(); -} - -unsafe extern "C" fn on_input_queue_created( - activity: *mut ANativeActivity, - queue: *mut AInputQueue, -) { - let input_queue = InputQueue::from_ptr(NonNull::new(queue).unwrap()); - let locked_looper = LOOPER.lock().unwrap(); - // The looper should always be `Some` after `fn init()` returns, unless - // future code cleans it up and sets it back to `None` again. - let looper = locked_looper.as_ref().expect("Looper does not exist"); - input_queue.attach_looper(looper, NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT); - INPUT_QUEUE.write().replace(input_queue); - wake(activity, Event::InputQueueCreated); -} - -unsafe extern "C" fn on_input_queue_destroyed( - activity: *mut ANativeActivity, - queue: *mut AInputQueue, -) { - wake(activity, Event::InputQueueDestroyed); - let mut input_queue_guard = INPUT_QUEUE.write(); - let input_queue = input_queue_guard.take().unwrap(); - assert_eq!(input_queue.ptr().as_ptr(), queue); - input_queue.detach_looper(); -} - -unsafe extern "C" fn on_content_rect_changed(activity: *mut ANativeActivity, rect: *const ARect) { - let rect = Rect { - left: (*rect).left as _, - top: (*rect).top as _, - right: (*rect).right as _, - bottom: (*rect).bottom as _, - }; - *CONTENT_RECT.write() = rect; - wake(activity, Event::ContentRectChanged); -} diff --git a/ndk-macro/CHANGELOG.md b/ndk-macro/CHANGELOG.md deleted file mode 100644 index d4521b36..00000000 --- a/ndk-macro/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# Unreleased - -# 0.3.0 (2021-11-22) - -- **Breaking:** Removed `android_logger` and `log` crate path overrides from macro input attributes in favour of using the reexports from `ndk-glue`. - Applications no longer have to provide these crates in scope of the `ndk_glue::main` macro when logging is enabled. - -# 0.2.0 (2020-09-15) - -- Added crate name override option -- **Breaking:** Changed macro attribute syntax - -# 0.1.0 (2020-07-29) - -- Initial release! 🎉 diff --git a/ndk-macro/Cargo.toml b/ndk-macro/Cargo.toml deleted file mode 100644 index af0cc8db..00000000 --- a/ndk-macro/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "ndk-macro" -version = "0.3.0" -authors = ["The Rust Windowing contributors"] -edition = "2018" -description = "Helper macros for android ndk" -license = "MIT OR Apache-2.0" -keywords = ["android", "ndk"] -readme = "README.md" -documentation = "https://docs.rs/ndk-macro" -homepage = "https://github.com/rust-windowing/android-ndk-rs" -repository = "https://github.com/rust-windowing/android-ndk-rs" -rust-version = "1.60" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0.24" -proc-macro-crate = "1.0" -quote = "1.0.8" -syn = { version = "1.0.60", features = ["full"] } -darling = "0.14" - -[features] -default = [] -logger = [] - -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] diff --git a/ndk-macro/README.md b/ndk-macro/README.md deleted file mode 100644 index d09671f3..00000000 --- a/ndk-macro/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# ndk-macro - -Implementation of the attribute procedural macro `main` which applied directly to main function. - -This macro is re-exported in `ndk-glue`. Typically, it's not needed to depend on this library directly! - -## Usage -```Rust -#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))] -pub fn main() { - println!("hello world"); -} -``` - -The attribute macro supports optional input attributes: - -- `backtrace = "on|full"`: Enables backtraces by setting the `RUST_BACKTRACE` env var -- `ndk_glue = "path::to::ndk_glue"`: Overrides default path to __ndk_glue__ crate -- `logger(...props)`: Configures android logger with the passed configuration (requires the `logger` feature): - - `level = "error|warn|info|debug|trace"`: Changes log level for logger - - `tag = "my-tag"`: Assigns tag to logger - - `filter = "filtering-rules"`: Changes default filtering rules diff --git a/ndk-macro/src/expand.rs b/ndk-macro/src/expand.rs deleted file mode 100644 index 28df80ce..00000000 --- a/ndk-macro/src/expand.rs +++ /dev/null @@ -1,503 +0,0 @@ -use core::iter::once; -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::ItemFn; - -use crate::{ - helper::crate_path, - parse::{BacktraceProp, MainAttr}, -}; - -impl ToTokens for BacktraceProp { - fn to_tokens(&self, tokens: &mut TokenStream) { - use BacktraceProp::*; - - let prop = match self { - On => Some(quote! { "1" }), - Full => Some(quote! { "full" }), - }; - - tokens.extend(quote! { - std::env::set_var("RUST_BACKTRACE", #prop); - }); - } -} - -#[cfg(feature = "logger")] -mod logger { - use super::*; - use crate::parse::{LogLevel, LoggerProp}; - use syn::Path; - - impl LoggerProp { - pub(crate) fn expand(&self, glue_crate: &Path) -> TokenStream { - let mut withs = Vec::new(); - - if let Some(tag) = &self.tag { - withs.push(quote! { with_tag(#tag) }); - } - if let Some(level) = &self.level { - withs.push(quote! { with_min_level(#glue_crate::log::Level::#level) }); - } - if let Some(filter) = &self.filter { - withs.push(quote! { - with_filter(#glue_crate::android_logger::FilterBuilder::new().parse(#filter).build()) - }); - } - - quote! { - #glue_crate::android_logger::init_once( - #glue_crate::android_logger::Config::default() - #(.#withs)* - ); - } - } - } - - impl ToTokens for LogLevel { - fn to_tokens(&self, tokens: &mut TokenStream) { - use LogLevel::*; - - tokens.extend(match self { - Error => quote! { Error }, - Warn => quote! { Warn }, - Info => quote! { Info }, - Debug => quote! { Debug }, - Trace => quote! { Trace }, - }); - } - } -} - -impl MainAttr { - pub fn expand(&self, main_fn_item: &ItemFn) -> TokenStream { - let main_fn_name = &main_fn_item.sig.ident; - let glue_crate = crate_path("ndk-glue", &self.ndk_glue); - - let preamble = { - let backtrace = &self.backtrace; - once(quote! { #backtrace }) - }; - - #[cfg(feature = "logger")] - let preamble = preamble.chain( - self.logger - .as_ref() - .map(|l| l.expand(&glue_crate)) - .into_iter(), - ); - - quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - #(#preamble)* - #glue_crate::init( - activity as _, - saved_state as _, - saved_state_size as _, - #main_fn_name, - ); - } - - #main_fn_item - } - } -} - -#[cfg(test)] -mod test { - use crate::parse::{BacktraceProp, MainAttr}; - use quote::quote; - use syn::parse_quote; - - #[test] - fn main_without_props() { - let attr = MainAttr::default(); - let item = parse_quote! { fn main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - main, - ); - } - fn main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - - #[test] - fn main_with_backtrace_on() { - let attr = MainAttr { - backtrace: Some(BacktraceProp::On), - ..Default::default() - }; - let item = parse_quote! { fn main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - std::env::set_var("RUST_BACKTRACE", "1"); - ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - main, - ); - } - fn main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - - #[test] - fn main_with_backtrace_full() { - let attr = MainAttr { - backtrace: Some(BacktraceProp::Full), - ..Default::default() - }; - let item = parse_quote! { fn main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - std::env::set_var("RUST_BACKTRACE", "full"); - ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - main, - ); - } - fn main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - - #[test] - fn main_with_overridden_ndk_glue() { - let attr = MainAttr { - ndk_glue: Some(parse_quote! { my::re::exported::ndk_glue }), - ..Default::default() - }; - let item = parse_quote! { fn main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - my::re::exported::ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - main, - ); - } - fn main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - - #[cfg(feature = "logger")] - mod logger { - use super::*; - use crate::parse::{LogLevel, LoggerProp}; - - #[test] - fn main_with_logger_default() { - let attr = MainAttr { - logger: Some(LoggerProp::default()), - ..Default::default() - }; - let item = parse_quote! { fn main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - ndk_glue::android_logger::init_once( - ndk_glue::android_logger::Config::default() - ); - ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - main, - ); - } - fn main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - - #[test] - fn main_with_logger_with_min_level() { - let attr = MainAttr { - logger: Some(LoggerProp { - level: Some(LogLevel::Debug), - ..Default::default() - }), - ..Default::default() - }; - let item = parse_quote! { fn main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - ndk_glue::android_logger::init_once( - ndk_glue::android_logger::Config::default() - .with_min_level(ndk_glue::log::Level::Debug) - ); - ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - main, - ); - } - fn main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - - #[test] - fn main_with_logger_with_tag() { - let attr = MainAttr { - logger: Some(LoggerProp { - tag: Some("my-tag".into()), - ..Default::default() - }), - ..Default::default() - }; - let item = parse_quote! { fn my_main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - ndk_glue::android_logger::init_once( - ndk_glue::android_logger::Config::default() - .with_tag("my-tag") - ); - ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - my_main, - ); - } - fn my_main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - - #[test] - fn main_with_logger_with_filter() { - let attr = MainAttr { - logger: Some(LoggerProp { - filter: Some("debug,hellow::world=trace".into()), - ..Default::default() - }), - ..Default::default() - }; - let item = parse_quote! { fn my_main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - ndk_glue::android_logger::init_once( - ndk_glue::android_logger::Config::default() - .with_filter(ndk_glue::android_logger::FilterBuilder::new().parse("debug,hellow::world=trace").build()) - ); - ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - my_main, - ); - } - fn my_main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - - #[test] - fn main_with_logger_with_min_level_and_with_tag() { - let attr = MainAttr { - logger: Some(LoggerProp { - level: Some(LogLevel::Warn), - tag: Some("my-tag".into()), - ..Default::default() - }), - ..Default::default() - }; - let item = parse_quote! { fn my_main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - ndk_glue::android_logger::init_once( - ndk_glue::android_logger::Config::default() - .with_tag("my-tag") - .with_min_level(ndk_glue::log::Level::Warn) - ); - ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - my_main, - ); - } - fn my_main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - - #[test] - fn main_with_backtrace_on_and_logger_with_tag() { - let attr = MainAttr { - backtrace: Some(BacktraceProp::On), - logger: Some(LoggerProp { - tag: Some("my-tag".into()), - ..Default::default() - }), - ..Default::default() - }; - let item = parse_quote! { fn main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - std::env::set_var("RUST_BACKTRACE", "1"); - ndk_glue::android_logger::init_once( - ndk_glue::android_logger::Config::default() - .with_tag("my-tag") - ); - ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - main, - ); - } - fn main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - - #[test] - fn main_with_logger_with_overridden_ndk_glue_and_filter() { - let attr = MainAttr { - logger: Some(LoggerProp { - filter: Some("debug,hellow::world=trace".into()), - ..Default::default() - }), - ndk_glue: Some(parse_quote! { my::re::exported::ndk_glue }), - ..Default::default() - }; - let item = parse_quote! { fn main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - my::re::exported::ndk_glue::android_logger::init_once( - my::re::exported::ndk_glue::android_logger::Config::default() - .with_filter(my::re::exported::ndk_glue::android_logger::FilterBuilder::new().parse("debug,hellow::world=trace").build()) - ); - my::re::exported::ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - main, - ); - } - fn main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - - #[test] - fn main_with_logger_with_overridden_ndk_glue_and_log_level() { - let attr = MainAttr { - logger: Some(LoggerProp { - level: Some(LogLevel::Trace), - ..Default::default() - }), - ndk_glue: Some(parse_quote! { my::re::exported::ndk_glue }), - ..Default::default() - }; - let item = parse_quote! { fn main() {} }; - let actual = attr.expand(&item); - let expected = quote! { - #[no_mangle] - unsafe extern "C" fn ANativeActivity_onCreate( - activity: *mut std::os::raw::c_void, - saved_state: *mut std::os::raw::c_void, - saved_state_size: usize, - ) { - my::re::exported::ndk_glue::android_logger::init_once( - my::re::exported::ndk_glue::android_logger::Config::default() - .with_min_level(my::re::exported::ndk_glue::log::Level::Trace) - ); - my::re::exported::ndk_glue::init( - activity as _, - saved_state as _, - saved_state_size as _, - main, - ); - } - fn main() {} - }; - assert_eq!(actual.to_string(), expected.to_string()); - } - } -} diff --git a/ndk-macro/src/helper.rs b/ndk-macro/src/helper.rs deleted file mode 100644 index caf53bf9..00000000 --- a/ndk-macro/src/helper.rs +++ /dev/null @@ -1,66 +0,0 @@ -use core::ops::Deref; -use proc_macro2::{Ident, Span}; -use proc_macro_crate::FoundCrate; -use syn::{ - parse::{Parse, ParseStream, Result}, - Path, Token, -}; - -/// A newtype for testing -/// -/// This needed because AttributeArgs from syn crate is not a newtype and does not implements `Parse` trait -#[derive(Debug)] -pub struct AttributeArgs(syn::AttributeArgs); - -impl Deref for AttributeArgs { - type Target = syn::AttributeArgs; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Parse for AttributeArgs { - fn parse(input: ParseStream) -> Result { - let mut metas = Vec::new(); - - loop { - if input.is_empty() { - break; - } - let value = input.parse()?; - metas.push(value); - if input.is_empty() { - break; - } - input.parse::()?; - } - - Ok(Self(metas)) - } -} - -#[cfg(not(test))] -use proc_macro_crate::crate_name; - -#[cfg(test)] -fn crate_name(name: &str) -> Result { - Ok(FoundCrate::Name(name.replace('-', "_"))) -} - -pub fn crate_path(name: &str, overridden_path: &Option) -> Path { - // try to use overridden crate path - overridden_path.clone().unwrap_or_else(|| { - Ident::new( - // try to determine crate name from Cargo.toml - match crate_name(name) - .as_ref() - .expect("Crate not found in `Cargo.toml`") - { - FoundCrate::Itself => "ndk_macro", - FoundCrate::Name(n) => n.as_str(), - }, - Span::call_site(), - ) - .into() - }) -} diff --git a/ndk-macro/src/lib.rs b/ndk-macro/src/lib.rs deleted file mode 100644 index 19daf679..00000000 --- a/ndk-macro/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] - -use darling::FromMeta; -use proc_macro::TokenStream; -use syn::{parse_macro_input, ItemFn}; - -mod expand; -mod helper; -mod parse; - -use helper::AttributeArgs; -use parse::MainAttr; - -#[proc_macro_attribute] -pub fn main(attr_input: TokenStream, item_input: TokenStream) -> TokenStream { - let item_ast = parse_macro_input!(item_input as ItemFn); - let attr_ast = parse_macro_input!(attr_input as AttributeArgs); - let attr: MainAttr = match FromMeta::from_list(&attr_ast) { - Ok(attr) => attr, - Err(errs) => return TokenStream::from(errs.write_errors()), - }; - - attr.expand(&item_ast).into() -} diff --git a/ndk-macro/src/parse.rs b/ndk-macro/src/parse.rs deleted file mode 100644 index 70d0cd55..00000000 --- a/ndk-macro/src/parse.rs +++ /dev/null @@ -1,197 +0,0 @@ -use darling::FromMeta; -use syn::Path; - -#[cfg(feature = "logger")] -pub use logger::{LogLevel, LoggerProp}; - -#[derive(Default, FromMeta, Debug)] -#[darling(default)] -pub struct MainAttr { - pub backtrace: Option, - // Path to `ndk_glue` to override - pub ndk_glue: Option, - #[cfg(feature = "logger")] - pub logger: Option, -} - -#[derive(FromMeta, PartialEq, Eq, Debug, Clone, Copy)] -#[darling(default)] -pub enum BacktraceProp { - On, - Full, -} - -impl Default for BacktraceProp { - fn default() -> Self { - BacktraceProp::On - } -} - -#[cfg(feature = "logger")] -mod logger { - use super::*; - - #[derive(FromMeta, PartialEq, Eq, Default, Debug, Clone)] - #[darling(default)] - pub struct LoggerProp { - // Minimum log level - pub level: Option, - // Tag name for logger - pub tag: Option, - // Filtering rules - pub filter: Option, - } - - #[derive(FromMeta, PartialEq, Eq, Debug, Clone, Copy)] - #[darling(default)] - pub enum LogLevel { - Error, - Warn, - Info, - Debug, - Trace, - } - - impl Default for LogLevel { - fn default() -> Self { - LogLevel::Error - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::AttributeArgs; - use syn::parse_quote; - - #[test] - fn empty_attr() { - let attr: AttributeArgs = parse_quote! {}; - let attr: MainAttr = FromMeta::from_list(&attr).unwrap(); - - assert_eq!(attr.backtrace, None); - #[cfg(feature = "logger")] - assert_eq!(attr.logger, None); - } - - #[should_panic] - #[test] - fn invalid_attr() { - let attr: AttributeArgs = parse_quote! { - wrong - }; - let _attr: MainAttr = FromMeta::from_list(&attr).unwrap(); - } - - #[test] - fn backtrace_on() { - let attr: AttributeArgs = parse_quote! { - backtrace = "on" - }; - let attr: MainAttr = FromMeta::from_list(&attr).unwrap(); - - assert_eq!(attr.backtrace, Some(BacktraceProp::On)); - #[cfg(feature = "logger")] - assert_eq!(attr.logger, None); - } - - #[test] - fn backtrace_full() { - let attr: AttributeArgs = parse_quote! { - backtrace = "full" - }; - let attr: MainAttr = FromMeta::from_list(&attr).unwrap(); - - assert_eq!(attr.backtrace, Some(BacktraceProp::Full)); - #[cfg(feature = "logger")] - assert_eq!(attr.logger, None); - } - - #[test] - fn overridden_ndk_glue() { - let attr: AttributeArgs = parse_quote! { - ndk_glue = "my::re::exported::ndk_glue" - }; - let attr: MainAttr = FromMeta::from_list(&attr).unwrap(); - - let expected_path: Path = parse_quote! { - my::re::exported::ndk_glue - }; - - assert_eq!(attr.ndk_glue.unwrap(), expected_path); - } - - #[cfg(feature = "logger")] - mod logger { - use super::*; - - #[test] - fn logger_with_level() { - let attr: AttributeArgs = parse_quote! { - logger(level = "debug") - }; - let attr: MainAttr = FromMeta::from_list(&attr).unwrap(); - - let logger = attr.logger.unwrap(); - - assert_eq!(logger.level, Some(LogLevel::Debug)); - assert_eq!(logger.tag, None); - } - - #[test] - fn logger_with_tag() { - let attr: AttributeArgs = parse_quote! { - logger(tag = "my-tag") - }; - let attr: MainAttr = FromMeta::from_list(&attr).unwrap(); - - let logger = attr.logger.unwrap(); - - assert_eq!(logger.level, None); - assert_eq!(logger.tag.unwrap(), "my-tag"); - } - - #[test] - fn logger_with_filter() { - let attr: AttributeArgs = parse_quote! { - logger(filter = "debug,hello::world=trace") - }; - let attr: MainAttr = FromMeta::from_list(&attr).unwrap(); - - let logger = attr.logger.unwrap(); - - assert_eq!(logger.level, None); - assert_eq!(logger.filter.unwrap(), "debug,hello::world=trace"); - } - - #[test] - fn logger_with_level_and_with_tag() { - let attr: AttributeArgs = parse_quote! { - logger(level = "error", tag = "my-app") - }; - let attr: MainAttr = FromMeta::from_list(&attr).unwrap(); - - let logger = attr.logger.unwrap(); - - assert_eq!(logger.level, Some(LogLevel::Error)); - assert_eq!(logger.tag.unwrap(), "my-app"); - } - - #[test] - fn backtrace_on_and_logger_with_level_and_with_tag() { - let attr: AttributeArgs = parse_quote! { - logger(level = "warn", tag = "my-app"), - backtrace = "on" - }; - let attr: MainAttr = FromMeta::from_list(&attr).unwrap(); - - assert_eq!(attr.backtrace, Some(BacktraceProp::On)); - - let logger = attr.logger.unwrap(); - - assert_eq!(logger.level, Some(LogLevel::Warn)); - assert_eq!(logger.tag.unwrap(), "my-app"); - } - } -}