permissions, manganis::ffi, full Info.plist/AndroidManifest.xml customization (#4842)
* initial version * make custom permission work without const-vec change * wiring up with cli * yey symbol found * wip * holy moly * proper ui * cleanup * okay * wip cleanup * reduce boilerplate code into mobile-core * Delete copilot-instructions.md * revert hello-world example for low diff * fix android build in mobile-core * rename permission to static_permission * small success with popup location on android * android popup working * Refactor permission checks for readability 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. * wip consolidate all java files and build from cli instead of build.rs * few cli cleanups * wip java_plugin macro * cleanup cleanup cleanup * Update java_plugin macro to use full relative file paths 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. * Improve iOS geolocation permission and retrieval logic 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. * Add ios_plugin macro for iOS framework metadata 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. * not needed * simplify the plugin * rename mobile-core to platform-bridge and mobile-core-macro to platform-bridge-macro * Refactor metadata serialization buffer initialization Replaces inline creation of ConstVec with a named EMPTY constant for buffer initialization in metadata serialization. This improves code clarity and maintainability. * mobile-geolocation out of dioxus * take the example also out of dioxus * remove example from cargo toml * remove demo css * revert hello-world example * revert mac.plist * revert gitignore * Remove basic usage example for permissions crate Deleted the basic_usage.rs example file from the permissions crate. This change may be part of a cleanup or refactoring to remove outdated or unnecessary example code. * Delete plan.md * remove dups * cleanup java build * cleanup permission symbol extraction * update todo * small slop of tested permissions in enums for minimum diff * Refactor symbol extraction into linker_symbols module Moved generic symbol extraction logic from android_java.rs and permissions.rs into a new linker_symbols.rs module. Updated both files to use the shared utilities for finding symbol offsets, reducing code duplication and improving maintainability. * wip consolidate ios and macos as darwin * Add docs and re-export for MainThreadCell and MainThreadMarker Expanded documentation for MainThreadCell, clarifying usage and safety. Changed visibility of MainThreadCell to public, improved method docs, and re-exported MainThreadMarker in darwin/mod.rs for easier access. * Update Darwin framework symbol and section names Changed the framework symbol prefix from '__DARWIN_FRAMEWORK__' to '__DARWIN_FW__' and updated the Mach-O section name from '__darwin_framework' to '__darwin_fw' for compliance with section name length limits and consistency. * Fix Info.plist path handling for iOS and macOS Adds comments and corrects the path to Info.plist for iOS and macOS app bundles. For iOS, Info.plist is at the root of the .app bundle, while for macOS it is inside Contents/. Also improves warning message to include the missing path. * Handle missing permission symbols and web bundle case Improves permission extraction by returning an empty manifest when no permission symbols are found or when building a web bundle, as permissions are runtime-only in that case. Adds debug logging for missing symbols and ensures robust handling of these scenarios. * objc2 over framework * windows, linux, web all permissions are runtime so cleanup * only include permissions for build time * cleanup unnecessary test * more cleanup * extract shared into dx-macro-helpers * rc.3 to 0.7.0 for const-serialize * Add CustomPermissionBuilder and enhance PermissionBuilder for better permission management - Introduced `CustomPermissionBuilder` for creating custom permissions with platform-specific identifiers. - Enhanced `PermissionBuilder` to support custom permissions and location permissions with a clear builder pattern. - Updated documentation and examples in the macro README to reflect new usage patterns for custom and location permissions. - Improved parsing logic in the macro to handle both builder and direct construction patterns for permissions. * clippy fixes * implement const cbor encoding + decoding for basic types * Add #[used] attribute to linker sections for optimization protection - Added #[used] attribute to linker sections in both permission and Android plugin macros to prevent the linker from optimizing away symbols, ensuring they are preserved even if unused. - Created static references to linker sections to enhance defense-in-depth against optimization issues. * implement map encoding and decoding * fix map test * wip unified linker symbol * cleanup permissions * pipe java sources with manganis as well * all tests passing * dynamically sized arrays * smaller str encoding * swap array and list and add more documentation * move serialization and deserialization for each struct together * support both legacy and new assets * bump const serialize version * fix asset conversion code * pull out manganis symbol struct from tuble * test reading old asset versions * more list/array cleanup and bump rust version * fix clippy * Revert "pipe java sources with manganis as well" This reverts commit ddea9a459d9ac26f3f037a74d9ae0de3edfced67. * Revert "cleanup permissions" This reverts commit 7b18500155237403e179f0783cb8903a4f158b80. * Revert "wip unified linker symbol" This reverts commit 813e33feddb30d26868f6ac6e6da318402ac138f. * Revert "Add #[used] attribute to linker sections for optimization protection" This reverts commit a94a7a3a5e8eee80060de9d66a53aabfcf554f96. * update cargo lock * wip permission through assets * abit cleanup * fix reexports * fix assets * Fix the permissions piping * some small cleanups * checkout const-serialize as per #4932 and fix build * invoking swift as a package * geolocation package & example dumping for easy testing * keep geolocation simple for testing * cleanups * pipe everything through __ASSETS__ and cleanup * remove linker_symbols helper file * example * re example * copy build methos into mobile-plugin-build crate * Add conditional import for dioxus_07 feature Introduces a conditional import to use the dioxus_07 crate when the corresponding feature is enabled, improving compatibility with different dioxus versions. * Improve error handling and JNI reference usage Adds a null check for Objective-C instance initialization in the iOS geolocation plugin to prevent errors on failed allocation. Refactors Android callback to use local JNI references instead of creating unnecessary global references, reducing potential memory leaks. * revert changes on playwright * Update dioxus dependency to 0.7.2 in Cargo.lock Removed dioxus 0.7.1 from dependencies, retaining only dioxus 0.7.2 for the dioxus-cli-optimization-test package. * small cleanup * Add legacy const-serialize 0.7 support to dx-macro-helpers Introduces support for the const-serialize 0.7 format in dx-macro-helpers by adding a new dependency and export, as well as a helper function for serialization with layout padding. Updates manganis to use the new helper for serializing assets in the legacy format, simplifying code and improving maintainability. * Add helper for legacy and current linker sections Introduces generate_link_sections_with_legacy in dx-macro-helpers to emit both legacy and current linker sections for compatibility. Refactors manganis-macro to use this new helper, reducing duplication and improving maintainability. * Fix lints * Fix clippy and doctest * Refactor asset bundling and allow large enum variant Refactored Asset::bundled to use Vec<u8> instead of ConstVec for reading volatile slices, simplifying the code and improving compatibility. Added #[allow(clippy::large_enum_variant)] to SymbolData enum to suppress Clippy warnings about large enum variants. * Update license URLs in README to use angle brackets Changed license URLs in the README from parentheses to angle brackets for improved formatting and clarity. * Fix asset deserialization with correct buffer type Replaces the use of `bytes.read()` with `ConstReadBuffer::new(bytes.as_ref())` for asset deserialization, ensuring compatibility with the expected deserialization API. * Refactor geolocation and asset handling for clarity Removed unnecessary clones in geolocation example event handlers to simplify state management. Updated SymbolDataOrAsset to store SymbolData in a Box for improved memory handling and adjusted related logic. Fixed documentation example in geolocation lib to include PermissionState in the import. * Update geolocation example and permissions usage docs Removes unused variables in the geolocation example's App function. Updates the permissions crate documentation to reflect new API usage with PermissionBuilder and LocationPrecision. * Update permissions-macro to use permissions crate Switched imports and dev-dependency from permissions_core to permissions in permissions-macro. Updated documentation and examples to reflect the new crate usage. * rollback const-serialize changes * move items around (doesn't compile) * convert gelocation plugin to example * remove dx-macro-helpers * we're just gonna full-send linker everything * rollback desktop changes * remove gradle * fix most compile? * fix dioxus vers * small cleanups * add claude design docs * add ffi macro * remove * loads but not linking yet * fix output leak * a little bit closer but not quite * improve android code gen * sheeeeee works!!! * small tweaks to demo * replace ios/android plugin with ffi macro entirely * simplify ffi module * simplify again * condense / rip out some stuff from manganis * remove types form public api * wip, permissions * oops I broke it * I unbroke it * move arch doc * remove extra manifest types * drop bindgen file * remove permission * small cleanup * remove unsafecall mtm thing * yoink perms * wire up manifest * Add deeplinks * add schema generation * update schema * wip.... * fix recurse limit schema generation * wip, muck around with schema * add inline dioxus.toml support * merge bundle config schemas * claude my man * Remove cdecl, annotate ffi * rename design docs * wip: live activities * wip: apple plugin bundling * wip * get live activities linking properly * small cleanups * remove sidecars * remove widget * yoink legacy * cleanup * more fixups * wire up more things to dioxus.toml * fix workspace join * final cleanups * fmt * final bit of clippy * ci tweak * update schema * fmt, doc * update schema docs --------- Co-authored-by: Evan Almloff <evanalmloff@gmail.com> Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
73 files changed
+13767
-336
No files matched your search
@@ -22,8 +22,10 @@ use std::{fmt::Debug, hash::Hash, path::PathBuf};
|
||||
pub struct BundledAsset {
|
||||
/// The absolute path of the asset
|
||||
absolute_source_path: ConstStr,
|
||||
|
||||
/// The bundled path of the asset
|
||||
bundled_path: ConstStr,
|
||||
|
||||
/// The options for the asset
|
||||
options: AssetOptions,
|
||||
}
|
||||
@@ -147,32 +149,32 @@ impl Asset {
|
||||
pub fn bundled(&self) -> BundledAsset {
|
||||
// Read the slice using volatile reads to prevent the compiler from optimizing
|
||||
// away the read at compile time
|
||||
fn read_slice_volatile(bundled: &'static [u8]) -> const_serialize_07::ConstVec<u8> {
|
||||
fn read_slice_volatile(bundled: &'static [u8]) -> Vec<u8> {
|
||||
let ptr = bundled as *const [u8] as *const u8;
|
||||
let len = bundled.len();
|
||||
if ptr.is_null() {
|
||||
panic!("Tried to use an asset that was not bundled. Make sure you are compiling dx as the linker");
|
||||
}
|
||||
let mut bytes = const_serialize_07::ConstVec::new();
|
||||
let mut bytes = Vec::with_capacity(len);
|
||||
for byte in 0..len {
|
||||
// SAFETY: We checked that the pointer was not null above. The pointer is valid for reads and
|
||||
// since we are reading a u8 there are no alignment requirements
|
||||
let byte = unsafe { std::ptr::read_volatile(ptr.add(byte)) };
|
||||
bytes = bytes.push(byte);
|
||||
bytes.push(byte);
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
let bundled = (self.bundled)();
|
||||
let bytes = read_slice_volatile(bundled);
|
||||
let read = bytes.as_ref();
|
||||
let read = bytes.as_slice();
|
||||
let asset = deserialize_const!(BundledAsset, read).expect("Failed to deserialize asset. Make sure you built with the matching version of the Dioxus CLI").1;
|
||||
|
||||
// If the asset wasn't bundled with the newer format, try the legacy format
|
||||
if asset.bundled_path() == BundledAsset::PLACEHOLDER_HASH {
|
||||
let bundled = (self.legacy)();
|
||||
let bytes = read_slice_volatile(bundled);
|
||||
let read = bytes.read();
|
||||
let read = const_serialize_07::ConstReadBuffer::new(bytes.as_ref());
|
||||
let asset = const_serialize_07::deserialize_const!(BundledAsset, read).expect("Failed to deserialize asset. Make sure you built with the matching version of the Dioxus CLI").1;
|
||||
asset
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
use crate::BundledAsset;
|
||||
use const_serialize::{ConstStr, SerializeConst};
|
||||
use const_serialize_08 as const_serialize;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Unified symbol data that can represent both assets and permissions
|
||||
///
|
||||
/// This enum is used to serialize different types of metadata into the binary
|
||||
/// using the same `__ASSETS__` symbol prefix. The CBOR format allows for
|
||||
/// self-describing data, making it easy to add new variants in the future.
|
||||
///
|
||||
/// Variant order does NOT matter for CBOR enum serialization - variants are
|
||||
/// matched by name (string), not by position or tag value.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, SerializeConst)]
|
||||
#[repr(C, u8)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[non_exhaustive]
|
||||
pub enum SymbolData {
|
||||
/// An asset that should be bundled with the application
|
||||
Asset(BundledAsset),
|
||||
|
||||
/// Android plugin metadata (prebuilt artifacts + Gradle deps)
|
||||
AndroidArtifact(AndroidArtifactMetadata),
|
||||
|
||||
/// Swift package metadata (SPM location + product)
|
||||
SwiftPackage(SwiftPackageMetadata),
|
||||
}
|
||||
|
||||
/// Platform categories for permission mapping
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SerializeConst)]
|
||||
pub enum Platform {
|
||||
Android,
|
||||
Ios,
|
||||
Macos,
|
||||
}
|
||||
|
||||
/// Bit flags for supported platforms
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SerializeConst)]
|
||||
pub struct PlatformFlags(u8);
|
||||
|
||||
impl PlatformFlags {
|
||||
pub const fn new() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PlatformFlags {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformFlags {
|
||||
pub const fn with_platform(mut self, platform: Platform) -> Self {
|
||||
self.0 |= 1 << platform as u8;
|
||||
self
|
||||
}
|
||||
|
||||
pub const fn supports(&self, platform: Platform) -> bool {
|
||||
(self.0 & (1 << platform as u8)) != 0
|
||||
}
|
||||
|
||||
pub const fn all() -> Self {
|
||||
Self(0b000111) // Android + iOS + macOS
|
||||
}
|
||||
|
||||
pub const fn mobile() -> Self {
|
||||
Self(0b000011) // Android + iOS
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform-specific permission identifiers
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct PlatformIdentifiers {
|
||||
pub android: Option<ConstStr>,
|
||||
pub ios: Option<ConstStr>,
|
||||
pub macos: Option<ConstStr>,
|
||||
}
|
||||
|
||||
/// Metadata describing an Android plugin artifact (.aar) that must be copied into the host Gradle project.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, SerializeConst)]
|
||||
pub struct AndroidArtifactMetadata {
|
||||
pub plugin_name: ConstStr,
|
||||
pub artifact_path: ConstStr,
|
||||
pub gradle_dependencies: ConstStr,
|
||||
}
|
||||
|
||||
impl AndroidArtifactMetadata {
|
||||
pub const fn new(
|
||||
plugin_name: &'static str,
|
||||
artifact_path: &'static str,
|
||||
gradle_dependencies: &'static str,
|
||||
) -> Self {
|
||||
Self {
|
||||
plugin_name: ConstStr::new(plugin_name),
|
||||
artifact_path: ConstStr::new(artifact_path),
|
||||
gradle_dependencies: ConstStr::new(gradle_dependencies),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata for a Swift package that needs to be linked into the app (iOS/macOS).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, SerializeConst)]
|
||||
pub struct SwiftPackageMetadata {
|
||||
pub plugin_name: ConstStr,
|
||||
pub package_path: ConstStr,
|
||||
pub product: ConstStr,
|
||||
}
|
||||
|
||||
impl SwiftPackageMetadata {
|
||||
pub const fn new(
|
||||
plugin_name: &'static str,
|
||||
package_path: &'static str,
|
||||
product: &'static str,
|
||||
) -> Self {
|
||||
Self {
|
||||
plugin_name: ConstStr::new(plugin_name),
|
||||
package_path: ConstStr::new(package_path),
|
||||
product: ConstStr::new(product),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,3 +21,6 @@ pub use css_module::*;
|
||||
|
||||
mod css_module_parser;
|
||||
pub use css_module_parser::*;
|
||||
|
||||
mod ffi;
|
||||
pub use ffi::*;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{resolve_path, AssetParseError};
|
||||
use crate::{linker::generate_link_section, resolve_path, AssetParseError};
|
||||
use macro_string::MacroString;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens};
|
||||
@@ -88,7 +88,7 @@ impl AssetParser {
|
||||
// Generate the link section for the asset. The link section includes the source path and the
|
||||
// output path of the asset. We force the asset to be included in the binary even if it is unused
|
||||
// if the asset is unhashed
|
||||
let link_section = crate::generate_link_section(quote!(__ASSET), &asset_hash);
|
||||
let link_section = generate_link_section(quote!(__ASSET), &asset_hash);
|
||||
|
||||
// generate the asset::new method to deprecate the `./assets/blah.css` syntax
|
||||
let constructor = if asset.is_relative() {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -13,10 +13,9 @@ use syn::{
|
||||
|
||||
pub(crate) mod asset;
|
||||
pub(crate) mod css_module;
|
||||
pub(crate) mod ffi;
|
||||
pub(crate) mod linker;
|
||||
|
||||
use linker::generate_link_section;
|
||||
|
||||
use crate::css_module::{expand_css_module_struct, CssModuleAttribute};
|
||||
|
||||
/// The asset macro collects assets that will be included in the final binary
|
||||
@@ -208,7 +207,7 @@ pub fn option_asset(input: TokenStream) -> TokenStream {
|
||||
/// fn app() -> Element {
|
||||
/// #[css_module("/assets/styles.css")]
|
||||
/// struct Styles;
|
||||
///
|
||||
///
|
||||
/// rsx! {
|
||||
/// div { class: Styles::container,
|
||||
/// button { class: Styles::button, "Click me" }
|
||||
@@ -226,6 +225,65 @@ pub fn css_module(input: TokenStream, item: TokenStream) -> TokenStream {
|
||||
tokens.into()
|
||||
}
|
||||
|
||||
/// Generate FFI bindings between Rust and native platforms (Swift/Kotlin)
|
||||
///
|
||||
/// This attribute macro parses an `extern "Swift"` or `extern "Kotlin"` block and generates:
|
||||
/// 1. Opaque type wrappers for foreign types
|
||||
/// 2. Function implementations with direct JNI/ObjC bindings
|
||||
/// 3. Linker metadata for the CLI to compile the native source
|
||||
///
|
||||
/// # Syntax
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// #[manganis::ffi("/src/ios")]
|
||||
/// extern "Swift" {
|
||||
/// pub type GeolocationPlugin;
|
||||
/// pub fn get_position(this: &GeolocationPlugin, high_accuracy: bool) -> Option<String>;
|
||||
/// }
|
||||
///
|
||||
/// #[manganis::ffi("/src/android")]
|
||||
/// extern "Kotlin" {
|
||||
/// pub type GeolocationPlugin;
|
||||
/// pub fn get_position(this: &GeolocationPlugin, high_accuracy: bool) -> Option<String>;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Path Parameter
|
||||
///
|
||||
/// The path in the attribute specifies the native source folder relative to `CARGO_MANIFEST_DIR`:
|
||||
/// - For Swift: A SwiftPM package folder containing `Package.swift`
|
||||
/// - For Kotlin: A Gradle project folder containing `build.gradle.kts`
|
||||
///
|
||||
/// # Type Declarations
|
||||
///
|
||||
/// Use `type Name;` to declare opaque foreign types. These become Rust structs wrapping
|
||||
/// the native object handle (GlobalRef for JNI, raw pointer for ObjC).
|
||||
///
|
||||
/// # Function Declarations
|
||||
///
|
||||
/// Functions can be:
|
||||
/// - **Instance methods**: First argument is `this: &TypeName`
|
||||
/// - **Static methods**: No `this` argument
|
||||
///
|
||||
/// # Supported Types
|
||||
///
|
||||
/// - Primitives: `bool`, `i8`-`i64`, `u8`-`u64`, `f32`, `f64`
|
||||
/// - Strings: `String`, `&str`
|
||||
/// - Options: `Option<T>` where T is supported
|
||||
/// - Opaque refs: `&TypeName` for foreign type references
|
||||
#[proc_macro_attribute]
|
||||
pub fn ffi(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
use ffi::{FfiAttribute, FfiBridgeParser};
|
||||
|
||||
let attr = parse_macro_input!(attr as FfiAttribute);
|
||||
let item = parse_macro_input!(item as syn::ItemForeignMod);
|
||||
|
||||
match FfiBridgeParser::parse_with_attr(attr, item) {
|
||||
Ok(parser) => parser.generate().into(),
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_path(raw: &str, span: Span) -> Result<PathBuf, AssetParseError> {
|
||||
// Get the location of the root of the crate which is where all assets are relative to
|
||||
//
|
||||
|
||||
@@ -1,43 +1,83 @@
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use quote::ToTokens;
|
||||
|
||||
/// We store description of the assets an application uses in the executable.
|
||||
/// We use the `link_section` attribute embed an extra section in the executable.
|
||||
/// We force rust to store a serialized representation of the asset description
|
||||
/// inside a particular region of the binary, with the label "manganis".
|
||||
/// After linking, the "manganis" sections of the different object files will be merged.
|
||||
pub fn generate_link_section(asset: impl ToTokens, asset_hash: &str) -> TokenStream2 {
|
||||
/// Generate a linker section for embedding arbitrary data in the binary
|
||||
///
|
||||
/// This is a generic version that allows customizing the serialization function,
|
||||
/// buffer type, and copy bytes function. Used by both asset and FFI metadata embedding.
|
||||
pub fn generate_link_section_inner(
|
||||
item: TokenStream2,
|
||||
hash: &str,
|
||||
prefix: &str,
|
||||
serialize_fn: TokenStream2,
|
||||
copy_bytes_fn: TokenStream2,
|
||||
buffer_type: TokenStream2,
|
||||
) -> TokenStream2 {
|
||||
let position = proc_macro2::Span::call_site();
|
||||
let export_name = syn::LitStr::new(&format!("__ASSETS__{}", asset_hash), position);
|
||||
let legacy_export_name = syn::LitStr::new(&format!("__MANGANIS__{}", asset_hash), position);
|
||||
|
||||
quote::quote! {
|
||||
// We bundle both the legacy and new link sections for compatibility with older CLIs
|
||||
static __LEGACY_LINK_SECTION: &'static [u8] = {
|
||||
// First serialize the asset into a constant sized buffer
|
||||
const __BUFFER: manganis::macro_helpers::const_serialize_07::ConstVec<u8> = manganis::macro_helpers::serialize_asset_07(&#asset);
|
||||
// Then pull out the byte slice
|
||||
const __BYTES: &[u8] = __BUFFER.as_ref();
|
||||
// And the length of the byte slice
|
||||
const __LEN: usize = __BYTES.len();
|
||||
|
||||
// Now that we have the size of the asset, copy the bytes into a static array
|
||||
#[unsafe(export_name = #legacy_export_name)]
|
||||
static __LINK_SECTION: [u8; __LEN] = manganis::macro_helpers::copy_bytes(__BYTES);
|
||||
&__LINK_SECTION
|
||||
};
|
||||
let export_name = syn::LitStr::new(&format!("{}{}", prefix, hash), position);
|
||||
|
||||
quote! {
|
||||
#[used]
|
||||
static __LINK_SECTION: &'static [u8] = {
|
||||
// First serialize the asset into a constant sized buffer
|
||||
const __BUFFER: manganis::macro_helpers::const_serialize::ConstVec<u8> = manganis::macro_helpers::serialize_asset(&#asset);
|
||||
// Then pull out the byte slice
|
||||
const __BUFFER: #buffer_type = #serialize_fn(&#item);
|
||||
const __BYTES: &[u8] = __BUFFER.as_ref();
|
||||
// And the length of the byte slice
|
||||
const __LEN: usize = __BYTES.len();
|
||||
|
||||
// Now that we have the size of the asset, copy the bytes into a static array
|
||||
#[unsafe(export_name = #export_name)]
|
||||
static __LINK_SECTION: [u8; __LEN] = manganis::macro_helpers::copy_bytes(__BYTES);
|
||||
#[used]
|
||||
static __LINK_SECTION: [u8; __LEN] = #copy_bytes_fn(__BYTES);
|
||||
&__LINK_SECTION
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a linker section for embedding asset data in the binary
|
||||
///
|
||||
/// This function creates a static array containing the serialized asset data
|
||||
/// and exports it with the __ASSETS__ prefix for unified symbol collection.
|
||||
/// Uses the generic linker helper from dx-macro-helpers for consistency.
|
||||
pub fn generate_link_section(asset: impl ToTokens, asset_hash: &str) -> TokenStream2 {
|
||||
let item = asset;
|
||||
let hash: &str = asset_hash;
|
||||
let prefix_current: &str = "__ASSETS__";
|
||||
let prefix_legacy: &str = "__MANGANIS__";
|
||||
let serialize_fn_current = quote! { manganis::macro_helpers::serialize_asset };
|
||||
let serialize_fn_legacy = quote! { manganis::macro_helpers::serialize_asset_07 };
|
||||
let copy_bytes_fn = quote! { manganis::macro_helpers::copy_bytes };
|
||||
let buffer_type_current = quote! { manganis::macro_helpers::ConstVec<u8, 4096> };
|
||||
let buffer_type_legacy = quote! { manganis::macro_helpers::const_serialize_07::ConstVec<u8> };
|
||||
let position = proc_macro2::Span::call_site();
|
||||
let export_name = syn::LitStr::new(&format!("{}{}", prefix_current, hash), position);
|
||||
let legacy_export_name = syn::LitStr::new(&format!("{}{}", prefix_legacy, hash), position);
|
||||
|
||||
let used_attr = if false {
|
||||
quote! { #[used] }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
quote! {
|
||||
// We bundle both the legacy and new link sections for compatibility.
|
||||
static __LEGACY_LINK_SECTION: &'static [u8] = {
|
||||
const __BUFFER: #buffer_type_legacy = #serialize_fn_legacy(&#item);
|
||||
const __BYTES: &[u8] = __BUFFER.as_ref();
|
||||
const __LEN: usize = __BYTES.len();
|
||||
|
||||
#used_attr
|
||||
#[unsafe(export_name = #legacy_export_name)]
|
||||
static __LINK_SECTION: [u8; __LEN] = #copy_bytes_fn(__BYTES);
|
||||
&__LINK_SECTION
|
||||
};
|
||||
|
||||
static __LINK_SECTION: &'static [u8] = {
|
||||
const __BUFFER: #buffer_type_current = #serialize_fn_current(&#item);
|
||||
const __BYTES: &[u8] = __BUFFER.as_ref();
|
||||
const __LEN: usize = __BYTES.len();
|
||||
|
||||
#used_attr
|
||||
#[unsafe(export_name = #export_name)]
|
||||
static __LINK_SECTION: [u8; __LEN] = #copy_bytes_fn(__BYTES);
|
||||
&__LINK_SECTION
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,8 +16,19 @@ keywords = ["assets"]
|
||||
const-serialize = { workspace = true }
|
||||
manganis-core = { workspace = true }
|
||||
manganis-macro = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
const-serialize-07 = { package = "const-serialize", version = "=0.7.2" }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
jni = "0.21"
|
||||
ndk-context = "0.1.1"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
objc2 = "0.6.3"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.6.3"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
dioxus = ["manganis-core/dioxus"]
|
||||
@@ -1,23 +1,17 @@
|
||||
# Manganis
|
||||
|
||||
The Manganis allows you to submit assets to any build tool that supports collecting assets. It makes it easy to self-host assets that are distributed throughout your libraries. Manganis also handles optimizing, converting, and fetching assets.
|
||||
Manganis is a tool for submitting assets and native source files to the program linker. It makes it easy to self-host assets and native plugins that are distributed throughout your libraries.
|
||||
|
||||
If you defined this in a component library:
|
||||
## Assets
|
||||
|
||||
Including assets is as simple as using the `asset!()` macro:
|
||||
|
||||
```rust
|
||||
use manganis::{Asset, asset};
|
||||
const AVIF_ASSET: Asset = manganis::asset!("/assets/image.png");
|
||||
const STYLE: Asset = asset!("/assets/style.css");
|
||||
```
|
||||
|
||||
AVIF_ASSET will be set to a new file name that will be served by some CLI. That file can be collected by any package that depends on the component library.
|
||||
|
||||
If you have assets that may not always be bundled, you can fall back gracefully with `option_asset!`:
|
||||
|
||||
```rust
|
||||
use manganis::{Asset, asset, option_asset};
|
||||
const REQUIRED: Asset = asset!("/assets/style.css");
|
||||
const OPTIONAL: Option<Asset> = option_asset!("/assets/missing.css");
|
||||
```
|
||||
After cargo builds your app, the asset path is embedded directly in the data section. A tool like the Dioxus CLI can extract this metadata and post-process these assets.
|
||||
|
||||
```rust
|
||||
use manganis::{ImageFormat, Asset, asset, ImageSize, AssetOptions};
|
||||
@@ -34,6 +28,16 @@ pub const RESIZED_PNG_ASSET: Asset =
|
||||
pub const AVIF_ASSET: Asset = asset!("/assets/image.png", AssetOptions::image().with_format(ImageFormat::Avif));
|
||||
```
|
||||
|
||||
## Adding Support to Your CLI
|
||||
## option_asset
|
||||
|
||||
To add support for your CLI, you need to integrate with the [manganis_cli_support](https://github.com/DioxusLabs/manganis/tree/main/cli-support) crate. This crate provides utilities to collect assets that integrate with the Manganis macro. It makes it easy to integrate an asset collection and optimization system into a build tool.
|
||||
If you have assets that may not always be bundled, you can fall back gracefully with `option_asset!`:
|
||||
|
||||
```rust
|
||||
use manganis::{Asset, asset, option_asset};
|
||||
const REQUIRED: Asset = asset!("/assets/style.css");
|
||||
const OPTIONAL: Option<Asset> = option_asset!("/assets/missing.css");
|
||||
```
|
||||
|
||||
## Native FFI Bindings
|
||||
|
||||
Manganis provides the `#[ffi]` attribute macro for generating direct FFI bindings between Rust and native platforms (Swift/Kotlin). See the `geolocation-native-plugin` example for usage.
|
||||
@@ -0,0 +1,63 @@
|
||||
use jni::{objects::JObject, JNIEnv, JavaVM};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Cached reference to the Android activity.
|
||||
static ACTIVITY: OnceLock<jni::objects::GlobalRef> = OnceLock::new();
|
||||
static JAVA_VM: OnceLock<JavaVM> = OnceLock::new();
|
||||
|
||||
/// Execute a JNI operation with a cached activity reference.
|
||||
///
|
||||
/// This function handles the boilerplate of getting the JavaVM and Activity
|
||||
/// references, caching them for subsequent calls. It's the foundation for
|
||||
/// most Android mobile API operations.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `f` - A closure that receives a mutable JNIEnv and the Activity JObject
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `Some(R)` if the operation succeeds, `None` if there's an error
|
||||
/// getting the VM or Activity references.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use dioxus_platform_bridge::android::with_activity;
|
||||
///
|
||||
/// let result = with_activity(|env, activity| {
|
||||
/// // Your JNI operations here
|
||||
/// Some(42)
|
||||
/// });
|
||||
/// ```
|
||||
pub fn with_activity<F, R>(f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut JNIEnv<'_>, &JObject<'_>) -> Option<R>,
|
||||
{
|
||||
let ctx = ndk_context::android_context();
|
||||
let vm = if let Some(vm) = JAVA_VM.get() {
|
||||
vm
|
||||
} else {
|
||||
let raw_vm = unsafe { JavaVM::from_raw(ctx.vm().cast()) }.ok()?;
|
||||
let _ = JAVA_VM.set(raw_vm);
|
||||
JAVA_VM.get()?
|
||||
};
|
||||
let mut env = vm.attach_current_thread().ok()?;
|
||||
|
||||
let activity = if let Some(activity) = ACTIVITY.get() {
|
||||
activity
|
||||
} else {
|
||||
let raw_activity = unsafe { JObject::from_raw(ctx.context() as jni::sys::jobject) };
|
||||
let global = env.new_global_ref(&raw_activity).ok()?;
|
||||
match ACTIVITY.set(global) {
|
||||
Ok(()) => ACTIVITY.get().unwrap(),
|
||||
Err(global) => {
|
||||
drop(global);
|
||||
ACTIVITY.get()?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let activity_obj = activity.as_obj();
|
||||
f(&mut env, &activity_obj)
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
use jni::{
|
||||
objects::{GlobalRef, JClass, JObject, JValue},
|
||||
sys::jlong,
|
||||
JNIEnv, NativeMethod,
|
||||
};
|
||||
|
||||
use crate::android::java::Result;
|
||||
|
||||
/// Generic callback system for loading DEX classes and registering native methods
|
||||
pub struct CallbackSystem {
|
||||
bytecode: &'static [u8],
|
||||
class_name: &'static str,
|
||||
callback_name: &'static str,
|
||||
callback_signature: &'static str,
|
||||
}
|
||||
|
||||
impl CallbackSystem {
|
||||
/// Create a new callback system
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `bytecode` - The compiled DEX bytecode
|
||||
/// * `class_name` - The fully qualified Java class name
|
||||
/// * `callback_name` - The name of the native callback method
|
||||
/// * `callback_signature` - The JNI signature of the callback method
|
||||
pub fn new(
|
||||
bytecode: &'static [u8],
|
||||
class_name: &'static str,
|
||||
callback_name: &'static str,
|
||||
callback_signature: &'static str,
|
||||
) -> Self {
|
||||
Self {
|
||||
bytecode,
|
||||
class_name,
|
||||
callback_name,
|
||||
callback_signature,
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the DEX class and register the native callback method
|
||||
///
|
||||
/// This function handles the boilerplate of:
|
||||
/// 1. Creating an InMemoryDexClassLoader
|
||||
/// 2. Loading the specified class
|
||||
/// 3. Registering the native callback method
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a `GlobalRef` to the loaded class, or an error if loading fails
|
||||
pub fn load_and_register(&self, env: &mut JNIEnv<'_>) -> Result<GlobalRef> {
|
||||
let callback_class = self.load_dex_class(env)?;
|
||||
self.register_native_callback(env, &callback_class)?;
|
||||
let global = env.new_global_ref(callback_class)?;
|
||||
Ok(global)
|
||||
}
|
||||
|
||||
/// Load the DEX class from bytecode
|
||||
fn load_dex_class<'a>(&self, env: &mut JNIEnv<'a>) -> Result<JClass<'a>> {
|
||||
const IN_MEMORY_LOADER: &str = "dalvik/system/InMemoryDexClassLoader";
|
||||
|
||||
// Create a ByteBuffer from our DEX bytecode
|
||||
let byte_buffer = unsafe {
|
||||
env.new_direct_byte_buffer(self.bytecode.as_ptr() as *mut u8, self.bytecode.len())
|
||||
}?;
|
||||
|
||||
// Create an InMemoryDexClassLoader with our DEX bytecode
|
||||
let dex_class_loader = env.new_object(
|
||||
IN_MEMORY_LOADER,
|
||||
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V",
|
||||
&[
|
||||
JValue::Object(&byte_buffer),
|
||||
JValue::Object(&JObject::null()),
|
||||
],
|
||||
)?;
|
||||
|
||||
// Load our class
|
||||
let class_name = env.new_string(self.class_name)?;
|
||||
let class = env
|
||||
.call_method(
|
||||
&dex_class_loader,
|
||||
"loadClass",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;",
|
||||
&[JValue::Object(&class_name)],
|
||||
)?
|
||||
.l()?;
|
||||
|
||||
Ok(class.into())
|
||||
}
|
||||
|
||||
/// Register the native callback method with the Java class
|
||||
fn register_native_callback<'a>(
|
||||
&self,
|
||||
env: &mut JNIEnv<'a>,
|
||||
callback_class: &JClass<'a>,
|
||||
) -> Result<()> {
|
||||
env.register_native_methods(
|
||||
callback_class,
|
||||
&[NativeMethod {
|
||||
name: self.callback_name.into(),
|
||||
sig: self.callback_signature.into(),
|
||||
fn_ptr: rust_callback as *mut _,
|
||||
}],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic native callback function called from Java
|
||||
///
|
||||
/// SAFETY: This function is called from Java and must maintain proper memory safety.
|
||||
/// The handler pointer is valid as long as the Manager exists (see Drop implementation).
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn rust_callback<'a>(
|
||||
mut env: JNIEnv<'a>,
|
||||
_class: JObject<'a>,
|
||||
handler_ptr_high: jlong,
|
||||
handler_ptr_low: jlong,
|
||||
location: JObject<'a>,
|
||||
) {
|
||||
// Reconstruct the pointer from two i64 values (for 64-bit pointers)
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
compile_error!("Only 64-bit Android targets are supported");
|
||||
|
||||
let handler_ptr_raw: usize =
|
||||
((handler_ptr_high as u64) << 32 | handler_ptr_low as u64) as usize;
|
||||
|
||||
// Convert to our callback function pointer
|
||||
let callback: fn(&mut JNIEnv, JObject) = unsafe { std::mem::transmute(handler_ptr_raw) };
|
||||
|
||||
// Use the local reference for this JNI frame; avoid leaking a global ref.
|
||||
callback(&mut env, location);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
use jni::{
|
||||
objects::{JClass, JObject, JObjectArray, JString, JValue, JValueOwned},
|
||||
JNIEnv,
|
||||
};
|
||||
|
||||
/// Result type for JNI operations
|
||||
pub type Result<T> = std::result::Result<T, jni::errors::Error>;
|
||||
|
||||
/// Helper functions for common JNI operations
|
||||
|
||||
/// Create a new Java string tied to the current JNI frame
|
||||
pub fn new_string<'env>(env: &mut JNIEnv<'env>, s: &str) -> Result<JString<'env>> {
|
||||
env.new_string(s)
|
||||
}
|
||||
|
||||
/// Create a new object array
|
||||
pub fn new_object_array<'env>(
|
||||
env: &mut JNIEnv<'env>,
|
||||
len: i32,
|
||||
element_class: &str,
|
||||
) -> Result<JObjectArray<'env>> {
|
||||
env.new_object_array(len, element_class, &JObject::null())
|
||||
}
|
||||
|
||||
/// Set an element in an object array
|
||||
pub fn set_object_array_element<'env>(
|
||||
env: &mut JNIEnv<'env>,
|
||||
array: &JObjectArray<'env>,
|
||||
index: i32,
|
||||
element: JString<'env>,
|
||||
) -> Result<()> {
|
||||
env.set_object_array_element(array, index, element)
|
||||
}
|
||||
|
||||
/// Call a static method on a class
|
||||
pub fn call_static_method<'env, 'obj>(
|
||||
env: &mut JNIEnv<'env>,
|
||||
class: &JClass<'env>,
|
||||
method_name: &str,
|
||||
signature: &str,
|
||||
args: &[JValue<'env, 'obj>],
|
||||
) -> Result<JValueOwned<'env>> {
|
||||
env.call_static_method(class, method_name, signature, args)
|
||||
}
|
||||
|
||||
/// Call an instance method on an object
|
||||
pub fn call_method<'env, 'obj>(
|
||||
env: &mut JNIEnv<'env>,
|
||||
obj: &JObject<'env>,
|
||||
method_name: &str,
|
||||
signature: &str,
|
||||
args: &[JValue<'env, 'obj>],
|
||||
) -> Result<JValueOwned<'env>> {
|
||||
env.call_method(obj, method_name, signature, args)
|
||||
}
|
||||
|
||||
/// Find a Java class by name
|
||||
pub fn find_class<'env>(env: &mut JNIEnv<'env>, class_name: &str) -> Result<JClass<'env>> {
|
||||
env.find_class(class_name)
|
||||
}
|
||||
|
||||
/// Create a new object instance
|
||||
pub fn new_object<'env, 'obj>(
|
||||
env: &mut JNIEnv<'env>,
|
||||
class_name: &str,
|
||||
signature: &str,
|
||||
args: &[JValue<'env, 'obj>],
|
||||
) -> Result<JObject<'env>> {
|
||||
env.new_object(class_name, signature, args)
|
||||
}
|
||||
|
||||
/// Check if a permission is granted (Activity.checkSelfPermission)
|
||||
pub fn check_self_permission(
|
||||
env: &mut JNIEnv,
|
||||
activity: &JObject,
|
||||
permission: &str,
|
||||
) -> Result<bool> {
|
||||
let permission_string = new_string(env, permission)?;
|
||||
let status = env.call_method(
|
||||
activity,
|
||||
"checkSelfPermission",
|
||||
"(Ljava/lang/String;)I",
|
||||
&[JValue::Object(&permission_string)],
|
||||
)?;
|
||||
|
||||
const PERMISSION_GRANTED: i32 = 0;
|
||||
Ok(status.i()? == PERMISSION_GRANTED)
|
||||
}
|
||||
|
||||
/// Request permissions via a helper class's static method
|
||||
///
|
||||
/// This uses PermissionsHelper.requestPermissionsOnUiThread(pattern)
|
||||
/// to request permissions on the UI thread.
|
||||
pub fn request_permissions_via_helper(
|
||||
env: &mut JNIEnv,
|
||||
helper_class: &JClass,
|
||||
activity: &JObject,
|
||||
permissions: JObjectArray,
|
||||
request_code: i32,
|
||||
) -> Result<()> {
|
||||
env.call_static_method(
|
||||
helper_class,
|
||||
"requestPermissionsOnUiThread",
|
||||
"(Landroid/app/Activity;[Ljava/lang/String;I)V",
|
||||
&[
|
||||
JValue::Object(activity),
|
||||
JValue::Object(&permissions.into()),
|
||||
JValue::Int(request_code),
|
||||
],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load a Java class from the APK's classloader
|
||||
pub fn load_class_from_classloader<'env>(
|
||||
env: &mut JNIEnv<'env>,
|
||||
class_name: &str,
|
||||
) -> Result<JClass<'env>> {
|
||||
let class_name_jstring = new_string(env, class_name)?;
|
||||
let class = env.call_static_method(
|
||||
"java/lang/Class",
|
||||
"forName",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;",
|
||||
&[JValue::Object(&class_name_jstring)],
|
||||
)?;
|
||||
Ok(class.l()?.into())
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//! Android metadata wrappers for linker-based collection.
|
||||
|
||||
pub use manganis_core::AndroidArtifactMetadata;
|
||||
|
||||
pub type AndroidMetadataBuffer = crate::macro_helpers::ConstVec<u8, 4096>;
|
||||
|
||||
pub const fn serialize_android_metadata(meta: &AndroidArtifactMetadata) -> AndroidMetadataBuffer {
|
||||
crate::macro_helpers::serialize_android_artifact(meta)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//! Android-specific utilities for mobile APIs
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod activity;
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod callback;
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod java;
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod metadata;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod macro_helpers {
|
||||
//! Helper functions for macro expansion
|
||||
//!
|
||||
//! These functions are used internally by the `android_plugin!()` macro
|
||||
//! and should not be used directly.
|
||||
|
||||
/// Copy a slice into a constant sized buffer at compile time
|
||||
pub const fn copy_bytes<const N: usize>(bytes: &[u8]) -> [u8; N] {
|
||||
let mut out = [0; N];
|
||||
let mut i = 0;
|
||||
while i < N {
|
||||
out[i] = bytes[i];
|
||||
i += 1;
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub use activity::*;
|
||||
#[cfg(target_os = "android")]
|
||||
pub use callback::*;
|
||||
#[cfg(target_os = "android")]
|
||||
pub use java::*;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub use metadata::*;
|
||||
@@ -0,0 +1,29 @@
|
||||
//! Darwin (iOS/macOS) shared utilities for objc2-based APIs
|
||||
//!
|
||||
//! This module provides shared utilities for both iOS and macOS platforms
|
||||
//! since they share the same Objective-C runtime and threading requirements
|
||||
//! through objc2.
|
||||
|
||||
/// Re-export MainThreadMarker for convenience
|
||||
pub use objc2::MainThreadMarker;
|
||||
|
||||
/// Macro helpers for FFI code generation
|
||||
pub mod macro_helpers {
|
||||
pub use crate::macro_helpers::copy_bytes;
|
||||
}
|
||||
|
||||
pub use manganis_core::SwiftPackageMetadata;
|
||||
|
||||
pub mod metadata {
|
||||
use manganis_core::SwiftPackageMetadata;
|
||||
|
||||
/// Buffer type for serialized Swift metadata
|
||||
#[doc(hidden)]
|
||||
pub type SwiftMetadataBuffer = crate::macro_helpers::ConstVec<u8, 4096>;
|
||||
|
||||
/// Serialize Swift package metadata for linker embedding
|
||||
#[doc(hidden)]
|
||||
pub const fn serialize_swift_metadata(meta: &SwiftPackageMetadata) -> SwiftMetadataBuffer {
|
||||
crate::macro_helpers::serialize_swift_package(meta)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod macro_helpers;
|
||||
@@ -8,6 +7,56 @@ pub use manganis_macro::css_module;
|
||||
pub use manganis_macro::option_asset;
|
||||
|
||||
pub use manganis_core::{
|
||||
Asset, AssetOptions, AssetVariant, BundledAsset, CssAssetOptions, CssModuleAssetOptions,
|
||||
FolderAssetOptions, ImageAssetOptions, ImageFormat, ImageSize, JsAssetOptions,
|
||||
// Core asset types
|
||||
Asset,
|
||||
AssetOptions,
|
||||
AssetVariant,
|
||||
BundledAsset,
|
||||
// Standard asset options
|
||||
CssAssetOptions,
|
||||
CssModuleAssetOptions,
|
||||
FolderAssetOptions,
|
||||
ImageAssetOptions,
|
||||
ImageFormat,
|
||||
ImageSize,
|
||||
JsAssetOptions,
|
||||
};
|
||||
|
||||
// Re-export metadata types for FFI and sidecar macros
|
||||
pub use manganis_core::SwiftPackageMetadata;
|
||||
pub use manganis_core::SymbolData;
|
||||
|
||||
// FFI utilities and plugin metadata for Dioxus mobile platform APIs
|
||||
//
|
||||
// This crate provides common patterns and utilities for implementing
|
||||
// mobile platform APIs in Dioxus applications. It handles the
|
||||
// boilerplate for JNI (Android) and objc2 (iOS/macOS) bindings, build scripts,
|
||||
// and platform-specific resource management.
|
||||
|
||||
/// Android platform utilities
|
||||
#[doc(hidden)]
|
||||
pub mod android;
|
||||
|
||||
/// Darwin (iOS/macOS) platform utilities
|
||||
#[doc(hidden)]
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
pub mod darwin;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub use android::*;
|
||||
|
||||
// Export darwin module for iOS, macOS, and when metadata feature is enabled (for FFI macro)
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
pub use darwin::*;
|
||||
|
||||
/// Re-export commonly used types for convenience
|
||||
#[cfg(target_os = "android")]
|
||||
pub use jni;
|
||||
|
||||
// Re-export objc2 for FFI macro generated code
|
||||
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
||||
pub use objc2;
|
||||
|
||||
/// Re-export the ffi attribute macro for native FFI bindings
|
||||
/// This macro generates direct FFI bindings between Rust and native platforms (Swift/Kotlin)
|
||||
pub use manganis_macro::ffi;
|
||||
@@ -1,11 +1,44 @@
|
||||
// Re-export const_serialize types for generated code.
|
||||
pub use const_serialize;
|
||||
use const_serialize::{serialize_const, ConstVec, SerializeConst};
|
||||
pub use const_serialize::{ConstStr, ConstVec, SerializeConst};
|
||||
pub use const_serialize_07;
|
||||
use const_serialize_07::{
|
||||
serialize_const as serialize_const_07, ConstVec as ConstVec07,
|
||||
SerializeConst as SerializeConst07,
|
||||
|
||||
use const_serialize_07::ConstVec as ConstVec07;
|
||||
use manganis_core::{
|
||||
AndroidArtifactMetadata, AssetOptions, BundledAsset, SwiftPackageMetadata, SymbolData,
|
||||
};
|
||||
use manganis_core::{AssetOptions, BundledAsset};
|
||||
|
||||
/// Copy a slice into a constant sized buffer at compile time
|
||||
///
|
||||
/// This is a generic utility that works with any byte slice and can be used
|
||||
/// in const contexts to create fixed-size arrays from dynamic slices.
|
||||
pub const fn copy_bytes<const N: usize>(bytes: &[u8]) -> [u8; N] {
|
||||
let mut out = [0; N];
|
||||
let mut i = 0;
|
||||
while i < N {
|
||||
out[i] = bytes[i];
|
||||
i += 1;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Serialize a SymbolData value into a const buffer
|
||||
///
|
||||
/// This is used by the widget!() macro and other symbol-based macros to embed
|
||||
/// metadata into the binary using the 4096-byte buffer format.
|
||||
pub const fn serialize_symbol_data(symbol_data: &SymbolData) -> ConstVec<u8, 4096> {
|
||||
dx_macro_helpers::serialize_to_const_with_max_padded::<4096>(symbol_data)
|
||||
}
|
||||
|
||||
/// Serialize Android artifact metadata (wrapped in `SymbolData::AndroidArtifact`).
|
||||
pub const fn serialize_android_artifact(meta: &AndroidArtifactMetadata) -> ConstVec<u8, 4096> {
|
||||
serialize_symbol_data(&SymbolData::AndroidArtifact(*meta))
|
||||
}
|
||||
|
||||
/// Serialize Swift package metadata (wrapped in `SymbolData::SwiftPackage`).
|
||||
pub const fn serialize_swift_package(meta: &SwiftPackageMetadata) -> ConstVec<u8, 4096> {
|
||||
serialize_symbol_data(&SymbolData::SwiftPackage(*meta))
|
||||
}
|
||||
|
||||
/// Create a bundled asset from the input path, the content hash, and the asset options
|
||||
pub const fn create_bundled_asset(input_path: &str, asset_config: AssetOptions) -> BundledAsset {
|
||||
@@ -26,25 +59,16 @@ pub const fn create_bundled_asset_relative(
|
||||
}
|
||||
|
||||
/// Serialize an asset to a const buffer
|
||||
pub const fn serialize_asset(asset: &BundledAsset) -> ConstVec<u8> {
|
||||
let data = ConstVec::new();
|
||||
let mut data = serialize_const(asset, data);
|
||||
// Reserve the maximum size of the asset
|
||||
while data.len() < <BundledAsset as SerializeConst>::MEMORY_LAYOUT.size() {
|
||||
data = data.push(0);
|
||||
}
|
||||
data
|
||||
///
|
||||
/// Serializes the asset directly (not wrapped in SymbolData) for simplicity.
|
||||
/// Uses a 4096-byte buffer and pads to the full size to match linker section size.
|
||||
pub const fn serialize_asset(asset: &BundledAsset) -> ConstVec<u8, 4096> {
|
||||
dx_macro_helpers::serialize_to_const_with_max_padded::<4096>(asset)
|
||||
}
|
||||
|
||||
/// Serialize an asset to a const buffer in the legacy 0.7 format
|
||||
pub const fn serialize_asset_07(asset: &BundledAsset) -> ConstVec07<u8> {
|
||||
let data = ConstVec07::new();
|
||||
let mut data = serialize_const_07(asset, data);
|
||||
// Reserve the maximum size of the asset
|
||||
while data.len() < <BundledAsset as SerializeConst07>::MEMORY_LAYOUT.size() {
|
||||
data = data.push(0);
|
||||
}
|
||||
data
|
||||
dx_macro_helpers::serialize_to_const_with_layout_padded_07(asset)
|
||||
}
|
||||
|
||||
/// Deserialize a const buffer into a BundledAsset
|
||||
@@ -55,13 +79,81 @@ pub const fn deserialize_asset(bytes: &[u8]) -> BundledAsset {
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy a slice into a constant sized buffer at compile time
|
||||
pub const fn copy_bytes<const N: usize>(bytes: &[u8]) -> [u8; N] {
|
||||
let mut out = [0; N];
|
||||
let mut i = 0;
|
||||
while i < N {
|
||||
out[i] = bytes[i];
|
||||
i += 1;
|
||||
pub mod dx_macro_helpers {
|
||||
use const_serialize::{ConstVec, SerializeConst};
|
||||
|
||||
/// Serialize a value to a const buffer, padding to the specified size
|
||||
///
|
||||
/// This is a generic helper that works with any type implementing `SerializeConst`.
|
||||
/// It serializes the value and then pads the buffer to the specified memory layout size.
|
||||
pub const fn serialize_to_const<T: SerializeConst>(
|
||||
value: &T,
|
||||
memory_layout_size: usize,
|
||||
) -> ConstVec<u8> {
|
||||
let data = ConstVec::new();
|
||||
let mut data = const_serialize::serialize_const(value, data);
|
||||
// Reserve the maximum size of the type
|
||||
while data.len() < memory_layout_size {
|
||||
data = data.push(0);
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
/// Serialize a value to a const buffer with a fixed maximum size, padding to the specified size
|
||||
///
|
||||
/// This variant uses a `ConstVec` with a fixed maximum size (e.g., `ConstVec<u8, 4096>`)
|
||||
/// and then pads to the specified memory layout size.
|
||||
///
|
||||
/// This function serializes directly into the larger buffer to avoid overflow issues
|
||||
/// when the serialized data exceeds the default 1024-byte buffer size.
|
||||
pub const fn serialize_to_const_with_max<const MAX_SIZE: usize>(
|
||||
value: &impl SerializeConst,
|
||||
memory_layout_size: usize,
|
||||
) -> ConstVec<u8, MAX_SIZE> {
|
||||
// Serialize using the default buffer, then copy into the larger buffer
|
||||
let serialized = const_serialize::serialize_const(value, ConstVec::new());
|
||||
let mut data: ConstVec<u8, MAX_SIZE> = ConstVec::new_with_max_size();
|
||||
data = data.extend(serialized.as_ref());
|
||||
// Reserve the maximum size of the type (pad to MEMORY_LAYOUT size)
|
||||
while data.len() < memory_layout_size {
|
||||
data = data.push(0);
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
/// Serialize a value to a const buffer and pad to the full buffer size
|
||||
///
|
||||
/// This is useful for linker section generation that expects a fixed-size buffer.
|
||||
pub const fn serialize_to_const_with_max_padded<const MAX_SIZE: usize>(
|
||||
value: &impl SerializeConst,
|
||||
) -> ConstVec<u8, MAX_SIZE> {
|
||||
let serialized = const_serialize::serialize_const(value, ConstVec::new());
|
||||
let mut data: ConstVec<u8, MAX_SIZE> = ConstVec::new_with_max_size();
|
||||
data = data.extend(serialized.as_ref());
|
||||
while data.len() < MAX_SIZE {
|
||||
data = data.push(0);
|
||||
}
|
||||
data
|
||||
}
|
||||
|
||||
/// Serialize a value using the legacy 0.7 const-serialize format and pad to layout size
|
||||
///
|
||||
/// Note: The legacy ConstVec has a 1024-byte limit. If MEMORY_LAYOUT.size() exceeds this,
|
||||
/// we pad only up to the buffer limit to avoid overflow.
|
||||
pub const fn serialize_to_const_with_layout_padded_07<T: const_serialize_07::SerializeConst>(
|
||||
value: &T,
|
||||
) -> const_serialize_07::ConstVec<u8> {
|
||||
let data = const_serialize_07::ConstVec::new();
|
||||
let mut data = const_serialize_07::serialize_const(value, data);
|
||||
// Pad to MEMORY_LAYOUT size, but cap at 1024 bytes (the legacy buffer limit)
|
||||
let target_size = if T::MEMORY_LAYOUT.size() > 1024 {
|
||||
1024
|
||||
} else {
|
||||
T::MEMORY_LAYOUT.size()
|
||||
};
|
||||
while data.len() < target_size {
|
||||
data = data.push(0);
|
||||
}
|
||||
data
|
||||
}
|
||||
out
|
||||
}
|
||||
Reference in new issue
Block a user