diff --git a/native/Cargo.lock b/native/Cargo.lock index d81bb933da..8b96e93400 100644 --- a/native/Cargo.lock +++ b/native/Cargo.lock @@ -235,7 +235,7 @@ dependencies = [ ] [[package]] -name = "immich_core_dart" +name = "immich_core_ffi" version = "0.1.0" dependencies = [ "cbindgen", diff --git a/native/Cargo.toml b/native/Cargo.toml index 3a0ce14dfb..a8dff395cf 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -2,14 +2,15 @@ resolver = "2" members = [ "crates/immich_core", - "crates/immich_core_dart", + "crates/immich_core_ffi", "crates/immich_core_napi", ] # shared logic lives in immich_core (no binding deps). each binding crate is a -# thin wrapper that picks its own crate-type: immich_core_dart -> cdylib/staticlib -# for dart:ffi (mobile), immich_core_napi -> cdylib (.node) for the node server. -# capabilities (hashing, exif, ...) are cargo features on immich_core so both +# thin wrapper that picks its own crate-type: immich_core_ffi -> cdylib/staticlib, +# the C ABI consumed by dart (ffigen), swift (C interop) and kotlin (JNI shim); +# immich_core_napi -> cdylib (.node) for the node server. +# capabilities (hashing, image, ...) are cargo features on immich_core so both # bindings opt into the same set. crate-type can't be feature-gated, which is why # the bindings are separate crates rather than one crate with feature flags. diff --git a/native/README.md b/native/README.md index c02a30886e..94db33affa 100644 --- a/native/README.md +++ b/native/README.md @@ -13,8 +13,9 @@ round-trips but is not wired into the server yet. ## Layout ``` crates/ - immich_core pure logic, no binding deps. capabilities = cargo features (hashing). - immich_core_dart cdylib/staticlib + cbindgen header for dart:ffi (mobile) + immich_core pure logic, no binding deps. capabilities = cargo features (hashing, image). + immich_core_ffi the hand-written C ABI + cbindgen header — consumed by dart (ffigen), + swift (native C interop) and kotlin (JNI shim) immich_core_napi cdylib (.node) via napi-rs (server, unwired) immich_native_core/ the Flutter package mobile depends on. build hook + ffigen @Native bindings. smoke/ host dart + node roundtrip scripts (no device) @@ -23,13 +24,13 @@ Bindings are separate crates (Cargo can't gate `crate-type` by feature). ## How the native lib is built (Flutter native assets — no prebuilt, no CI) `immich_native_core/hook/build.dart` (`native_toolchain_rust`) compiles -`crates/immich_core_dart` **from source on every app build** via rustup and bundles +`crates/immich_core_ffi` **from source on every app build** via rustup and bundles it as a Flutter *code asset*. The Dart side uses ffigen `@Native` externals bound to that asset — no `DynamicLibrary`, no prebuilt artifacts, no fetch/publish/separate-repo. Native assets is on by default on Flutter stable (3.38+), so a stock `flutter build` runs the hook. Each builder needs **rustup** (the hook auto-installs the pinned -toolchain + targets from `crates/immich_core_dart/rust-toolchain.toml`). +toolchain + targets from `crates/immich_core_ffi/rust-toolchain.toml`). ## Dev commands (mise) ``` @@ -43,7 +44,7 @@ mise run smoke Rust tests + host dart:ffi + host napi roundtrips ## Add a capability (end to end) 1. add the logic to `crates/immich_core` (behind a cargo feature if it pulls a dep). -2. expose a C entry in `crates/immich_core_dart/src/lib.rs` — `#[no_mangle] pub extern "C"`, +2. expose a C entry in `crates/immich_core_ffi/src/lib.rs` — `#[no_mangle] pub extern "C"`, wrap the body in `guard(...)` (panic at the boundary → null, never unwind into the host), validate pointers, return Rust-owned memory the caller frees via `immich_core_free_string`. 3. `mise run codegen` — regenerates the committed cbindgen header + ffigen `@Native` bindings. diff --git a/native/crates/immich_core/src/lib.rs b/native/crates/immich_core/src/lib.rs index c42950469f..9cd1c37cf7 100644 --- a/native/crates/immich_core/src/lib.rs +++ b/native/crates/immich_core/src/lib.rs @@ -1,7 +1,7 @@ //! immich_native_core — shared Rust core for the immich server (napi) and mobile (dart:ffi). //! //! Pure logic only: no binding or platform deps live here. Each binding crate -//! (`immich_core_dart`, `immich_core_napi`) is a thin wrapper. Capabilities are +//! (`immich_core_ffi`, `immich_core_napi`) is a thin wrapper. Capabilities are //! cargo features (`hashing`, `image`, ...) so every binding opts into the same set. #[cfg(feature = "hashing")] diff --git a/native/crates/immich_core_dart/Cargo.toml b/native/crates/immich_core_ffi/Cargo.toml similarity index 96% rename from native/crates/immich_core_dart/Cargo.toml rename to native/crates/immich_core_ffi/Cargo.toml index 1c6fca7e6d..1a52060d71 100644 --- a/native/crates/immich_core_dart/Cargo.toml +++ b/native/crates/immich_core_ffi/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "immich_core_dart" +name = "immich_core_ffi" version.workspace = true edition.workspace = true license.workspace = true diff --git a/native/crates/immich_core_dart/build.rs b/native/crates/immich_core_ffi/build.rs similarity index 100% rename from native/crates/immich_core_dart/build.rs rename to native/crates/immich_core_ffi/build.rs diff --git a/native/crates/immich_core_dart/cbindgen.toml b/native/crates/immich_core_ffi/cbindgen.toml similarity index 100% rename from native/crates/immich_core_dart/cbindgen.toml rename to native/crates/immich_core_ffi/cbindgen.toml diff --git a/native/crates/immich_core_dart/include/immich_core.h b/native/crates/immich_core_ffi/include/immich_core.h similarity index 100% rename from native/crates/immich_core_dart/include/immich_core.h rename to native/crates/immich_core_ffi/include/immich_core.h diff --git a/native/crates/immich_core_dart/rust-toolchain.toml b/native/crates/immich_core_ffi/rust-toolchain.toml similarity index 100% rename from native/crates/immich_core_dart/rust-toolchain.toml rename to native/crates/immich_core_ffi/rust-toolchain.toml diff --git a/native/crates/immich_core_dart/src/lib.rs b/native/crates/immich_core_ffi/src/lib.rs similarity index 100% rename from native/crates/immich_core_dart/src/lib.rs rename to native/crates/immich_core_ffi/src/lib.rs diff --git a/native/immich_native_core/README.md b/native/immich_native_core/README.md index 053a156713..3fc64a6e1d 100644 --- a/native/immich_native_core/README.md +++ b/native/immich_native_core/README.md @@ -30,7 +30,7 @@ Flutter bundles it as a code asset; the `@Native` bindings resolve against it. ## Layout -- `hook/build.dart` — builds `../crates/immich_core_dart` via `native_toolchain_rust`. +- `hook/build.dart` — builds `../crates/immich_core_ffi` via `native_toolchain_rust`. - `lib/immich_native_core.dart` — barrel, the public API. - `lib/src/{core,hashing,image}.dart` — thin wrappers, one file per Rust module. - `lib/src/ffi/bindings.g.dart` — ffigen `@Native` output (committed; do not edit). diff --git a/native/immich_native_core/ffigen.yaml b/native/immich_native_core/ffigen.yaml index e4127e1651..868e67ab1f 100644 --- a/native/immich_native_core/ffigen.yaml +++ b/native/immich_native_core/ffigen.yaml @@ -9,7 +9,7 @@ description: 'FFI bindings to immich_native_core — generated, do not edit.' output: 'lib/src/ffi/bindings.g.dart' headers: entry-points: - - '../crates/immich_core_dart/include/immich_core.h' + - '../crates/immich_core_ffi/include/immich_core.h' include-directives: - '**/immich_core.h' functions: diff --git a/native/immich_native_core/hook/build.dart b/native/immich_native_core/hook/build.dart index 026144b760..4c956a5f16 100644 --- a/native/immich_native_core/hook/build.dart +++ b/native/immich_native_core/hook/build.dart @@ -4,7 +4,7 @@ import 'package:code_assets/code_assets.dart'; import 'package:hooks/hooks.dart'; import 'package:native_toolchain_rust/native_toolchain_rust.dart'; -// Builds crates/immich_core_dart from source on every app build and bundles it as +// Builds crates/immich_core_ffi from source on every app build and bundles it as // a code asset. assetName must match the ffigen output (its package URI is the // @Native DefaultAsset id). The crate is a sibling, so point cratePath at it. void main(List args) async { @@ -12,7 +12,7 @@ void main(List args) async { await _ensureRustTarget(input); await RustBuilder( assetName: 'src/ffi/bindings.g.dart', - cratePath: '../crates/immich_core_dart', + cratePath: '../crates/immich_core_ffi', ).run(input: input, output: output); }); } @@ -50,7 +50,7 @@ Future _ensureRustTarget(BuildInput input) async { }; if (triple == null) return; final crate = input.packageRoot - .resolve('../crates/immich_core_dart/') + .resolve('../crates/immich_core_ffi/') .toFilePath(); // best effort — if rustup itself is broken the build below reports it properly await Process.run('rustup', [ diff --git a/native/mise.toml b/native/mise.toml index 5600ae6ee5..39b4cd81e8 100644 --- a/native/mise.toml +++ b/native/mise.toml @@ -22,17 +22,17 @@ run = "cargo clippy --workspace --all-targets -- -D warnings" alias = "codegen" description = "Generate the C header (cbindgen) + Dart @Native bindings (ffigen)" sources = [ - "crates/immich_core_dart/src/lib.rs", - "crates/immich_core_dart/cbindgen.toml", + "crates/immich_core_ffi/src/lib.rs", + "crates/immich_core_ffi/cbindgen.toml", "immich_native_core/ffigen.yaml", ] outputs = [ - "crates/immich_core_dart/include/immich_core.h", - "immich_native_core/lib/immich_native_core_bindings_generated.dart", + "crates/immich_core_ffi/include/immich_core.h", + "immich_native_core/lib/src/ffi/bindings.g.dart", ] run = [ - "cargo build -p immich_core_dart", - "cd immich_native_core && dart run ffigen --config ffigen.yaml && dart format lib/immich_native_core_bindings_generated.dart", + "cargo build -p immich_core_ffi", + "cd immich_native_core && dart run ffigen --config ffigen.yaml && dart format lib/src/ffi/bindings.g.dart", ] # Host FFI roundtrip through the real build hook — no device. Builds the Rust crate @@ -44,7 +44,7 @@ run = "flutter test" [tasks."build:dart"] description = "Build the dart:ffi cdylib directly (host, raw cargo)" -run = "cargo build -p immich_core_dart" +run = "cargo build -p immich_core_ffi" [tasks."build:napi"] description = "Build the node addon + stage a .node for require() (server, unwired)" @@ -56,7 +56,7 @@ run = [ [tasks."smoke:dart"] description = "Host dart:ffi ABI roundtrip (raw DynamicLibrary on the cdylib)" depends = ["build:dart"] -run = "dart run smoke/dart_smoke.dart target/debug/libimmich_core_dart.dylib" +run = "dart run smoke/dart_smoke.dart target/debug/libimmich_core_ffi.dylib" [tasks."smoke:node"] description = "Host napi roundtrip" diff --git a/native/smoke/dart_smoke.dart b/native/smoke/dart_smoke.dart index ca1d0ad796..5519f2edb7 100644 --- a/native/smoke/dart_smoke.dart +++ b/native/smoke/dart_smoke.dart @@ -1,7 +1,7 @@ // Mobile-side roundtrip: open the dart:ffi cdylib and call into the shared core. // Standalone script (no package:ffi dep) — reads the returned C string by hand. // -// dart run smoke/dart_smoke.dart target/debug/libimmich_core_dart.dylib +// dart run smoke/dart_smoke.dart target/debug/libimmich_core_ffi.dylib import 'dart:ffi'; @@ -18,7 +18,7 @@ String _readCString(Pointer p) { } void main(List args) { - final libPath = args.isNotEmpty ? args.first : 'target/debug/libimmich_core_dart.dylib'; + final libPath = args.isNotEmpty ? args.first : 'target/debug/libimmich_core_ffi.dylib'; final lib = DynamicLibrary.open(libPath); final version = lib.lookupFunction<_VersionNative, _VersionNative>('immich_core_version');