Skip to content

Conversation

@wheregmis
Copy link
Contributor

@wheregmis wheregmis commented Oct 26, 2025

This PR introduces a comprehensive linker-based permission management for automatic update of manifest files and android native plugin building foundation for Dioxus. The implementation is inspired by Manganis's linker-based asset collection approach.

Build on top of #4932 so diff is huge for now

Two main motivation for this PR

  1. To automatically handle the manifest file and info.plist file instead of invoking, ejecting or providing custom file.
  2. Able to pass in the java file path through linker, copy and bundle them (Opening doors for android native apis development)

Currently packages/geolocation and geolocation example resides within this PR, its just for the ease of testing, and will not be upstreamed

Here is a breakdown of how things are working for now.

Android (Kotlin / Gradle)

Source layout

  • Kotlin sources + Gradle wrapper live under packages/geolocation/android/
  • The module builds a release .aar: android/build/outputs/aar/geolocation-plugin-release.aar

build.rs responsibilities

packages/geolocation/build.rs builds the Gradle module whenever the target triple contains
android:

  1. Respects the toolchain env exported by the CLI (DX_ANDROID_JAVA_HOME, DX_ANDROID_SDK_ROOT,
    DX_ANDROID_NDK_HOME, DX_ANDROID_ARTIFACT_DIR). If those are absent (non-dx builds) it falls
    back to ANDROID_* or JAVA_HOME.
  2. Runs ./gradlew assembleRelease inside packages/geolocation/android.
  3. Copies the resulting .aar into the CLI-provided artifact staging dir
    ($DX_ANDROID_ARTIFACT_DIR/geolocation-plugin-release.aar or
    $OUT_DIR/android-artifacts/... as a fallback).
  4. Emits cargo:rustc-env=DIOXUS_ANDROID_ARTIFACT=<absolute path> so the Rust macro can reference
    the built artifact.

Metadata embedded in the Rust binary

In packages/geolocation/src/lib.rs we declare the Android artifact like this:

#[cfg(all(feature = "metadata", target_os = "android"))]
dioxus_platform_bridge::android_plugin!(
    plugin = "geolocation",
    aar = { env = "DIOXUS_ANDROID_ARTIFACT" },
    deps = ["implementation(\"com.google.android.gms:play-services-location:21.3.0\")"]
);

The aar block only needs a stable way to refer to the file that build.rs produced. Emitting a
cargo:rustc-env=... from the build script is the idiomatic Rust approach for passing data from
build.rs to the crate being compiled, which is why each plugin publishes its own environment
variable. Nothing prevents you from using unique names—DIOXUS_GEO_ANDROID_ARTIFACT,
DIOXUS_CAMERA_ANDROID_ARTIFACT, etc.—as long as your macro invocation references the same key that
your build script sets. Automatically inferring the path from the plugin name is brittle because the
artifact location lives in target/ (or another $OUT_DIR) that only build.rs knows about after
running Gradle, so the build script remains the single source of truth.

The macro serializes:

  • plugin name
  • Absolute aar path (resolved via the env var emitted by build.rs)
  • Any Gradle dependency lines listed in deps

The metadata is wrapped in SymbolData::AndroidArtifact and stored under the shared
__ASSETS__* symbol prefix (the same one used for assets and permissions). Nothing runs at runtime—
the symbols are just scanned later by the CLI.

What the CLI does (dx serve --android)

  1. After building the Rust binary, dx walks every __ASSETS__* symbol once. When it encounters a
    SymbolData::AndroidArtifact, it adds it to the manifest alongside assets and permissions.
  2. install_android_artifacts() (in packages/cli/src/build/request.rs) copies each .aar into
    target/dx/.../app/libs/ in the generated Gradle project.
  3. For every artifact it ensures the Gradle dependency file contains both:
    • implementation(files("libs/<artifact>.aar"))
    • Any additional strings from deps (e.g. the Play Services location runtime)
  4. When Gradle runs for the host app, those libraries are already on disk and referenced in
    app/build.gradle.kts, so they get packaged automatically.

Note: because the .aar is built inside the Rust crate, plugin authors do not touch the app’s
Gradle project. Everything happens by copying artifacts + editing app/build.gradle.kts inside the
generated dx workspace.


iOS / macOS (Swift Package)

Source layout

  • Swift sources + Package.swift live under packages/geolocation/ios/
  • The Swift Package exports a single product: GeolocationPlugin

build.rs responsibilities

For Apple targets, build.rs:

  1. Detects the desired triple (aarch64-apple-ios, aarch64-apple-ios-sim, x86_64-apple-ios) and
    finds the matching SDK via xcrun --show-sdk-path.
  2. Runs xcrun swift build --package-path ios --configuration <debug|release> --triple <...> --sdk <...> --product GeolocationPlugin.
  3. Locates the resulting static library (libGeolocationPlugin.a) and emits:
    • cargo:rustc-link-search for both the Swift build output and the Xcode toolchain Swift libs
    • cargo:rustc-link-lib=static=GeolocationPlugin
    • cargo:rustc-link-lib=swiftCompatibility* + -force_load flags so the ObjC runtime sees the
      plugin class
    • cargo:rustc-link-lib=framework=CoreLocation / Foundation

At this point the Swift code is already linked into the Mach-O produced by Rust; no additional build
steps are required later.

Metadata embedded in the Rust binary

In packages/geolocation/src/lib.rs we declare the Swift package via:

#[cfg(all(feature = "metadata", any(target_os = "ios", target_os = "macos")))]
dioxus_platform_bridge::ios_plugin!(
    plugin = "geolocation",
    spm = { path = "ios", product = "GeolocationPlugin" }
);

This writes an entry into the shared __ASSETS__* stream as SymbolData::SwiftPackage. Again,
nothing is executed at runtime; it is just discoverable metadata alongside assets and permissions.

What the CLI does (dx serve --ios / --macos)

  1. The CLI sees the SymbolData::SwiftPackage entries while it’s already processing the asset
    stream.
  2. When at least one Swift package is present, embed_swift_stdlibs() runs (see
    packages/cli/src/build/request.rs). This invokes xcrun swift-stdlib-tool --scan-executable on
    the final app binary and copies the required Swift runtime libraries into the bundle’s
    Frameworks/ folder (iOS) or Contents/Frameworks/ (macOS).

Because the Swift package was linked during the Rust build, the CLI’s only job is to make sure the
Swift runtime ships alongside the executable.


Summary table

Platform Native sources live Compiled by Metadata stored CLI responsibilities
Android packages/geolocation/android (Gradle lib) build.rs running gradlew assembleRelease SymbolData::AndroidArtifact (plugin, path, deps) Copy .aar into app/libs, append Gradle dependencies
iOS/macOS packages/geolocation/ios (SwiftPM) build.rs running xcrun swift build SymbolData::SwiftPackage (plugin, package path, product) Embed Swift stdlibs when Swift metadata is present

This setup keeps each plugin self-contained:

  • All native code + build tooling stays inside the crate.
  • build.rs produces platform artifacts and emits env vars for linker metadata.
  • The Dioxus CLI only needs the linker symbols to know what to copy/embed when bundling user apps.

New crates: (Please suggest the names as i am bad at naming stuff)

  • permissions: Public API that re-exports permissions-core and permissions-macro for easy integration.
  • permissions-core: Types for permission declaration and platform identifiers (Android, IOS, MacOS) (We can extend the identifiers as needed or requested)
  • permissions-macro: Procedural Macros (static_permission!) for declaring permissions via linker symbol (So cli can extract the symbols and update the manifest files automatically)
  • dx-macro-helpers: Common helpers for linker based stuff, shared by manganis and permission
  • platform-bridge: Bareminimal Cross-platform FFI utilities and plugin metadata for Android (JNI) and Darwin (objc2)
  • platform-bridge-macro: android_plugin! macro for embedding Java source file paths in binaries via linker symbols

@wheregmis
Copy link
Contributor Author

Yey, Android Permission Dialog is working.
Screenshot_1761495440

Improves code formatting in has_location_permission and check_permission functions by splitting long lines and updating function signatures for better readability. No functional changes were made.
The java_plugin macro now accepts full relative paths for Java files instead of assuming a fixed directory structure. Documentation and usage examples have been updated to reflect this change, improving flexibility and clarity for specifying file locations.
Added checks for authorization status before requesting location permissions and retrieving location data. Now only requests permission if not determined, and attempts to retrieve cached location before starting location updates, improving efficiency and user experience.
Introduces the ios_plugin! macro in mobile-core-macro for declarative iOS framework metadata embedding. Updates mobile-core to re-export the macro and refactors mobile-geolocation to use ios_plugin! instead of manual linker attributes.
@wheregmis
Copy link
Contributor Author

I think i am able to package swift files as swift package and package it and run. (Like how tauri does for swift files)

@wheregmis
Copy link
Contributor Author

wheregmis commented Nov 13, 2025

I am also able to build each android plugin as a gradle submodule and can be tested like this inside each plugin

./gradlew assembleRelease --no-daemon --warning-mode all

-Oops i broke it lol- my bad it worked

@wheregmis
Copy link
Contributor Author

So the current plugin structure is something like this.
image

@wheregmis
Copy link
Contributor Author

Running gradle assembly takes time now, And i expect with the number of apis you add, it will grow exponentially

@wheregmis
Copy link
Contributor Author

wheregmis commented Nov 13, 2025

Here is a breakdown of how things are working for now.

Android (Kotlin / Gradle)

Source layout

  • Kotlin sources + Gradle wrapper live under packages/geolocation/android/
  • The module builds a release .aar: android/build/outputs/aar/geolocation-plugin-release.aar

build.rs responsibilities

packages/geolocation/build.rs builds the Gradle module whenever the target triple contains
android:

  1. Respects the toolchain env exported by the CLI (DX_ANDROID_JAVA_HOME, DX_ANDROID_SDK_ROOT,
    DX_ANDROID_NDK_HOME, DX_ANDROID_ARTIFACT_DIR). If those are absent (non-dx builds) it falls
    back to ANDROID_* or JAVA_HOME.
  2. Runs ./gradlew assembleRelease inside packages/geolocation/android.
  3. Copies the resulting .aar into the CLI-provided artifact staging dir
    ($DX_ANDROID_ARTIFACT_DIR/geolocation-plugin-release.aar or
    $OUT_DIR/android-artifacts/... as a fallback).
  4. Emits cargo:rustc-env=DIOXUS_ANDROID_ARTIFACT=<absolute path> so the Rust macro can reference
    the built artifact.

Metadata embedded in the Rust binary

In packages/geolocation/src/lib.rs we declare the Android artifact like this:

#[cfg(all(feature = "metadata", target_os = "android"))]
dioxus_platform_bridge::android_plugin!(
    plugin = "geolocation",
    aar = { env = "DIOXUS_ANDROID_ARTIFACT" },
    deps = ["implementation(\"com.google.android.gms:play-services-location:21.3.0\")"]
);

The aar block only needs a stable way to refer to the file that build.rs produced. Emitting a
cargo:rustc-env=... from the build script is the idiomatic Rust approach for passing data from
build.rs to the crate being compiled, which is why each plugin publishes its own environment
variable. Nothing prevents you from using unique names—DIOXUS_GEO_ANDROID_ARTIFACT,
DIOXUS_CAMERA_ANDROID_ARTIFACT, etc.—as long as your macro invocation references the same key that
your build script sets. Automatically inferring the path from the plugin name is brittle because the
artifact location lives in target/ (or another $OUT_DIR) that only build.rs knows about after
running Gradle, so the build script remains the single source of truth.

The macro serializes:

  • plugin name
  • Absolute aar path (resolved via the env var emitted by build.rs)
  • Any Gradle dependency lines listed in deps

The metadata is wrapped in SymbolData::AndroidArtifact and stored under the shared
__ASSETS__* symbol prefix (the same one used for assets and permissions). Nothing runs at runtime—
the symbols are just scanned later by the CLI.

What the CLI does (dx serve --android)

  1. After building the Rust binary, dx walks every __ASSETS__* symbol once. When it encounters a
    SymbolData::AndroidArtifact, it adds it to the manifest alongside assets and permissions.
  2. install_android_artifacts() (in packages/cli/src/build/request.rs) copies each .aar into
    target/dx/.../app/libs/ in the generated Gradle project.
  3. For every artifact it ensures the Gradle dependency file contains both:
    • implementation(files("libs/<artifact>.aar"))
    • Any additional strings from deps (e.g. the Play Services location runtime)
  4. When Gradle runs for the host app, those libraries are already on disk and referenced in
    app/build.gradle.kts, so they get packaged automatically.

Note: because the .aar is built inside the Rust crate, plugin authors do not touch the app’s
Gradle project. Everything happens by copying artifacts + editing app/build.gradle.kts inside the
generated dx workspace.


iOS / macOS (Swift Package)

Source layout

  • Swift sources + Package.swift live under packages/geolocation/ios/
  • The Swift Package exports a single product: GeolocationPlugin

build.rs responsibilities

For Apple targets, build.rs:

  1. Detects the desired triple (aarch64-apple-ios, aarch64-apple-ios-sim, x86_64-apple-ios) and
    finds the matching SDK via xcrun --show-sdk-path.
  2. Runs xcrun swift build --package-path ios --configuration <debug|release> --triple <...> --sdk <...> --product GeolocationPlugin.
  3. Locates the resulting static library (libGeolocationPlugin.a) and emits:
    • cargo:rustc-link-search for both the Swift build output and the Xcode toolchain Swift libs
    • cargo:rustc-link-lib=static=GeolocationPlugin
    • cargo:rustc-link-lib=swiftCompatibility* + -force_load flags so the ObjC runtime sees the
      plugin class
    • cargo:rustc-link-lib=framework=CoreLocation / Foundation

At this point the Swift code is already linked into the Mach-O produced by Rust; no additional build
steps are required later.

Metadata embedded in the Rust binary

In packages/geolocation/src/lib.rs we declare the Swift package via:

#[cfg(all(feature = "metadata", any(target_os = "ios", target_os = "macos")))]
dioxus_platform_bridge::ios_plugin!(
    plugin = "geolocation",
    spm = { path = "ios", product = "GeolocationPlugin" }
);

This writes an entry into the shared __ASSETS__* stream as SymbolData::SwiftPackage. Again,
nothing is executed at runtime; it is just discoverable metadata alongside assets and permissions.

What the CLI does (dx serve --ios / --macos)

  1. The CLI sees the SymbolData::SwiftPackage entries while it’s already processing the asset
    stream.
  2. When at least one Swift package is present, embed_swift_stdlibs() runs (see
    packages/cli/src/build/request.rs). This invokes xcrun swift-stdlib-tool --scan-executable on
    the final app binary and copies the required Swift runtime libraries into the bundle’s
    Frameworks/ folder (iOS) or Contents/Frameworks/ (macOS).

Because the Swift package was linked during the Rust build, the CLI’s only job is to make sure the
Swift runtime ships alongside the executable.


Summary table

Platform Native sources live Compiled by Metadata stored CLI responsibilities
Android packages/geolocation/android (Gradle lib) build.rs running gradlew assembleRelease SymbolData::AndroidArtifact (plugin, path, deps) Copy .aar into app/libs, append Gradle dependencies
iOS/macOS packages/geolocation/ios (SwiftPM) build.rs running xcrun swift build SymbolData::SwiftPackage (plugin, package path, product) Embed Swift stdlibs when Swift metadata is present

This setup keeps each plugin self-contained:

  • All native code + build tooling stays inside the crate.
  • build.rs produces platform artifacts and emits env vars for linker metadata.
  • The Dioxus CLI only needs the linker symbols to know what to copy/embed when bundling user apps.

@wheregmis
Copy link
Contributor Author

wheregmis commented Nov 13, 2025

I have added the geolocation package + example for geolocation in for easy testing so the diff is huge as of now. I need to delete them before we upstream it.

@wheregmis
Copy link
Contributor Author

image image image

@wheregmis
Copy link
Contributor Author

wheregmis commented Nov 13, 2025

@jkelleyrtp I am sorry for a large diff atm, it has changes from #4932 as well as package/geolocation and example inside. So its easier for you to check. I am going out for few weeks after 2-3 days so i wanted to make sure this PR is in good shape before that. A small review on the build process would be really appreciated. I think the way it turned out is really interesting + approach is something that can be shared across other rust UI projects (I wonder someone can fork tauri plugins and make it compatiable with dioxus as this opens the door for that as well)

@jkelleyrtp
Copy link
Member

@jkelleyrtp I am sorry for a large diff atm, it has changes from #4932 as well as package/geolocation and example inside. So its easier for you to check. I am going out for few weeks after 2-3 days so i wanted to make sure this PR is in good shape before that. A small review on the build process would be really appreciated. I think the way it turned out is really interesting + approach is something that can be shared across other rust UI projects (I wonder someone can fork tauri plugins and make it compatiable with dioxus as this opens the door for that as well)

Awesome! Will try to get it in next week and then hopefully in a patch release soon after that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants