From 5d0de7a6823a1c6a9b1d236cc8d02b7ea2354722 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 18 Aug 2026 15:54:10 -0400 Subject: [PATCH] android: the binding generator gets its own crate, free of the app's deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third Android run got further than either before it — all four ABIs cross-compiled, vendored OpenSSL and all — then the generator died: error: failed to run custom build command for `openssl-sys v0.9.117` That is the HOST build. The generator was a [[bin]] inside thoughtsync-ffi, so building it compiled that crate and therefore the core, reqwest, native-tls and openssl-sys for linux. The vendored-OpenSSL block is scoped to `cfg(target_os = "android")`, so the host build went looking for a system OpenSSL that ci-rust-android has no reason to carry. Adding libssl-dev to the image would have fixed it and been wrong: a code generator has no business linking the app's TLS stack to emit Kotlin. Splitting it into thoughtsync-uniffi-bindgen, whose only dependency is uniffi, removes the entire chain. Verified from the dependency graph rather than from a build that happened to succeed — `cargo tree -p thoughtsync-uniffi-bindgen` contains none of openssl-sys, native-tls, reqwest, thoughtsync-core or rusqlite. It stays a WORKSPACE MEMBER on purpose. Sharing one lockfile is what keeps uniffi here and uniffi linked into the .so at one version; they are two halves of one ABI, and a separate lockfile is precisely how they would drift apart. The cost is that the desktop lane now compiles ~15 generator crates it never runs — cheap next to Tauri, and better than leaving the crate unlinted. Drops the `bindgen` feature and required-features bin from thoughtsync-ffi, which existed only to keep those crates off the desktop lane and now have nothing to gate. Local fmt + clippy + test all green before pushing (107 tests). Co-Authored-By: Claude Opus 5 (1M context) --- Cargo.lock | 7 +++++++ Cargo.toml | 2 +- android/app/build.gradle.kts | 6 ++---- android/bindgen/Cargo.toml | 25 +++++++++++++++++++++++++ android/bindgen/src/main.rs | 16 ++++++++++++++++ android/ffi/Cargo.toml | 18 ------------------ android/ffi/src/bin/uniffi-bindgen.rs | 14 -------------- 7 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 android/bindgen/Cargo.toml create mode 100644 android/bindgen/src/main.rs delete mode 100644 android/ffi/src/bin/uniffi-bindgen.rs diff --git a/Cargo.lock b/Cargo.lock index e0d09b8..223732e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4213,6 +4213,13 @@ dependencies = [ "uniffi", ] +[[package]] +name = "thoughtsync-uniffi-bindgen" +version = "0.1.0" +dependencies = [ + "uniffi", +] + [[package]] name = "time" version = "0.3.55" diff --git a/Cargo.toml b/Cargo.toml index 1118a78..01c7023 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ # module inside the desktop app (Scribe note 2730). [workspace] resolver = "2" -members = ["core", "desktop/src-tauri", "android/ffi"] +members = ["core", "desktop/src-tauri", "android/ffi", "android/bindgen"] # Shared pins, so two consumers of the core cannot drift onto different versions of # the same dependency and resolve differently. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index ecf686c..5b9f679 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -92,10 +92,8 @@ abstract class UniffiBindgen : DefaultTask() { "cargo", "run", "--locked", - "--features", - "bindgen", - "--bin", - "uniffi-bindgen", + "-p", + "thoughtsync-uniffi-bindgen", "--", "generate", "--library", diff --git a/android/bindgen/Cargo.toml b/android/bindgen/Cargo.toml new file mode 100644 index 0000000..b843e06 --- /dev/null +++ b/android/bindgen/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "thoughtsync-uniffi-bindgen" +version = "0.1.0" +description = "Generates the Kotlin bindings for thoughtsync-ffi" +authors = ["bvandeusen"] +edition = "2021" + +# A crate whose ONLY dependency is uniffi itself. +# +# This started life as a `[[bin]]` inside thoughtsync-ffi, which failed: building +# it compiled that crate and therefore the core, reqwest, native-tls and +# openssl-sys — for the HOST. The vendored-OpenSSL block in core/Cargo.toml is +# scoped to `cfg(target_os = "android")`, so a host build looks for a system +# OpenSSL that ci-rust-android has no reason to carry, and the generator died +# with "failed to run custom build command for openssl-sys". +# +# Adding libssl-dev to the image would have worked and been wrong: a code +# generator should not link the app's TLS stack to emit Kotlin. Splitting it out +# means the generator compiles ~15 small crates and nothing else. +# +# Still a WORKSPACE MEMBER, deliberately. That is what keeps `uniffi` here and +# `uniffi` linked into the .so on one version from one lockfile — they are two +# halves of one ABI, and a separate lockfile is exactly how they would drift. +[dependencies] +uniffi = { version = "0.32", features = ["cli"] } diff --git a/android/bindgen/src/main.rs b/android/bindgen/src/main.rs new file mode 100644 index 0000000..23a5ca0 --- /dev/null +++ b/android/bindgen/src/main.rs @@ -0,0 +1,16 @@ +//! The Kotlin generator. +//! +//! Invoked by Gradle (see android/app/build.gradle.kts) as: +//! +//! ```text +//! cargo run --locked -p thoughtsync-uniffi-bindgen -- \ +//! generate --library \ +//! --language kotlin --out-dir +//! ``` +//! +//! `--library` mode reads uniffi's metadata straight out of the compiled artifact, +//! so the generated bindings can never describe a different version of the Rust +//! than the one being packaged. +fn main() { + uniffi::uniffi_bindgen_main() +} diff --git a/android/ffi/Cargo.toml b/android/ffi/Cargo.toml index 9b315f3..526e5ef 100644 --- a/android/ffi/Cargo.toml +++ b/android/ffi/Cargo.toml @@ -29,21 +29,3 @@ tokio = { version = "1", features = ["rt-multi-thread"] } # Display + Error impls for the error enum uniffi turns into a Kotlin exception. thiserror = "2" -[features] -# The Kotlin generator, off by default. -# -# uniffi's `cli` feature drags in clap, askama and goblin — ~15 crates that exist -# only to serve a three-line binary. Until the Android lane lands, this crate is -# compiled on every DESKTOP push (it is a workspace member, so `cargo clippy -# --all-targets` picks it up), and paying for a code generator on a lane that never -# runs one is the wrong trade. `required-features` on the bin means -# `--all-targets` skips it rather than failing. -# -# Generate bindings with: -# cargo run --features bindgen --bin uniffi-bindgen -- generate ... -bindgen = ["uniffi/cli"] - -[[bin]] -name = "uniffi-bindgen" -path = "src/bin/uniffi-bindgen.rs" -required-features = ["bindgen"] diff --git a/android/ffi/src/bin/uniffi-bindgen.rs b/android/ffi/src/bin/uniffi-bindgen.rs deleted file mode 100644 index e2fbbbc..0000000 --- a/android/ffi/src/bin/uniffi-bindgen.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! The Kotlin generator, as a binary in THIS workspace. -//! -//! uniffi's generated bindings and the `uniffi` runtime crate linked into the `.so` -//! have to be the same version — they are two halves of one ABI. Running the -//! generator from here guarantees that by construction, because it compiles against -//! the very same dependency. A `cargo install uniffi-bindgen` in the CI image would -//! instead be a second version that has to be kept in step by hand, which is why -//! ci-rust-android deliberately doesn't ship one. -//! -//! Invoked as: cargo run --bin uniffi-bindgen -- generate --library \ -//! --language kotlin --out-dir -fn main() { - uniffi::uniffi_bindgen_main() -}