build: remove the dead typeshare type-generation pipeline

The typeshare-generated GeneratedTypes.kt was gitignored and never
actually used (no #[typeshare] annotations remain in the Rust sources),
having been superseded by the uniffi bindings. Remove the whole pipeline:

- Gradle: drop the generateKotlinTypes task, the findTypeshareExecutable
  helper, the dead commented-out compile hook, and the now-unused java.io
  imports.
- justfile: drop the generate-types recipe.
- Rust: drop the unused typeshare workspace/lib dependency (and its
  Cargo.lock entries) and the typeshare.toml config.
- android/.gitignore: drop the GeneratedTypes.kt entry.
- flake.nix: drop pkgs.typeshare from the dev shell.
- docs: delete the obsolete TYPE_GENERATION.md, drop typeshare from
  BUILD.md and the PDF plan, and remove the stale build TODO.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Greg Shuflin
2026-05-31 02:50:22 -07:00
parent 6828d7dfaa
commit c12ca31aa1
13 changed files with 4 additions and 361 deletions
-1
View File
@@ -215,7 +215,6 @@ view should use this.~~
### Improve Build Process
- **Description**: Streamline the build process between Rust library and Android app
- **Details**: Consider using cargo-ndk or similar tools for better Android build integration
- find a way to make sure typeshare is installed before attempting a build
- investigate https://github.com/mozilla/uniffi-rs and see if there's any way to use this to make the build process better
### Make relay/DNS infrastructure configurable and self-hostable
-3
View File
@@ -13,6 +13,3 @@
.externalNativeBuild
.cxx
local.properties
# Auto-generated typeshare types - regenerated on each build
app/src/main/java/com/gregshuflin/synchronicity/data/GeneratedTypes.kt
+1 -90
View File
@@ -1,7 +1,3 @@
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
@@ -171,90 +167,6 @@ tasks.register<Exec>("generateUniffiBindings") {
// Make the Rust build task run before Java compilation
tasks.named("preBuild") { dependsOn("buildRustLib", "generateUniffiBindings", "copySharedAssets") }
// Helper function to find typeshare executable
fun findTypeshareExecutable(): String? {
// Strategy 1: Check in ~/.cargo/bin
val home = System.getenv("HOME")
val cargoBin = home?.let { "$it/.cargo/bin/typeshare" }
if (cargoBin != null && File(cargoBin).exists()) {
return cargoBin
}
// Strategy 2: Check if typeshare is available in PATH
try {
val result = Runtime.getRuntime().exec(arrayOf("which", "typeshare"))
result.waitFor()
if (result.exitValue() == 0) {
val reader = BufferedReader(InputStreamReader(result.inputStream))
val path = reader.readLine()
if (path != null && File(path).exists()) {
return path
}
}
} catch (e: Exception) {
// which command failed, continue to next strategy
}
// Strategy 3: Try running typeshare directly (assumes it's in PATH)
try {
val result = Runtime.getRuntime().exec(arrayOf("typeshare", "--version"))
result.waitFor()
if (result.exitValue() == 0) {
return "typeshare"
}
} catch (e: Exception) {
// typeshare command failed
}
return null
}
// Task to generate Kotlin types from Rust using typeshare
tasks.register<Exec>("generateKotlinTypes") {
description = "Generate Kotlin types from Rust definitions using typeshare"
group = "build"
workingDir = file("../../rust")
doFirst {
// Try multiple locations to find typeshare
val typesharePath = findTypeshareExecutable()
if (typesharePath == null) {
throw GradleException(
"""
typeshare binary not found. Please install it with:
cargo install typeshare-cli
Then ensure ~/.cargo/bin is in your PATH, or run this task from a shell where cargo-installed binaries are accessible.
"""
.trimIndent()
)
}
println("Using typeshare at: $typesharePath")
executable = typesharePath
}
args(
".",
"--lang=kotlin",
"--output-file=../android/app/src/main/java/com/gregshuflin/synchronicity/data/GeneratedTypes.kt",
)
// Make sure this runs before compiling Kotlin
outputs.file("src/main/java/com/gregshuflin/synchronicity/data/GeneratedTypes.kt")
}
// Make sure the generate task runs before compiling
// Temporarily disabled due to Gradle compatibility issues
// tasks.whenTaskAdded {
// if (name == "compileDebugKotlin" || name == "compileReleaseKotlin") {
// dependsOn("generateKotlinTypes")
// }
// }
dependencies {
// JNA — required by uniffi-generated Kotlin bindings (5.16+ for 16 KB page size / Android 15+)
implementation("net.java.dev.jna:jna:5.18.1@aar")
@@ -285,8 +197,7 @@ dependencies {
// Retrofit for API calls
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// Using kotlinx.serialization instead of Gson for better type safety and typeshare
// compatibility
// Using kotlinx.serialization instead of Gson for better type safety
implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")
// Kotlinx Serialization for JSON handling
+1 -2
View File
@@ -8,7 +8,6 @@ Requires:
- JDK 17 (or Android Studio's bundled JBR at `/opt/android-studio/jbr`)
- Android SDK 35, NDK 28.0.13004108
- Rust stable with `aarch64-linux-android` target
- `typeshare-cli` (`cargo install typeshare-cli`)
Set `JAVA_HOME` and run:
```sh
@@ -25,7 +24,7 @@ just fmt-kotlin-check # check Kotlin formatting
### With Nix
The repo includes a `flake.nix` that provides a dev shell with all dependencies pinned (Android SDK, NDK, Rust + cross target, typeshare, JDK 17).
The repo includes a `flake.nix` that provides a dev shell with all dependencies pinned (Android SDK, NDK, Rust + cross target, JDK 17).
```sh
nix develop
+1 -3
View File
@@ -326,7 +326,6 @@ Add to appropriate Rust module:
```rust
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(feature = "typeshare", derive(typeshare::TypeShare))]
pub struct PdfReadingState {
pub document_hash: String,
pub document_name: String,
@@ -344,7 +343,6 @@ pub struct PdfReadingState {
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(feature = "typeshare", derive(typeshare::TypeShare))]
pub struct PdfBookmark {
pub id: String,
pub page: u32,
@@ -356,7 +354,7 @@ pub struct PdfBookmark {
### 5.2 FFI Integration
- [ ] Add Rust functions for storing/retrieving PDF state
- [ ] Generate Kotlin types via typeshare
- [ ] Expose the types across the FFI (uniffi)
- [ ] Create JNI bindings
- [ ] Test round-trip serialization
-192
View File
@@ -1,192 +0,0 @@
# Type Generation System
This document explains how we use the `typeshare` library to keep Rust and Kotlin types synchronized automatically.
## Problem
Previously, the Rust `Document` schema and Kotlin `Document` data class were maintained separately, leading to runtime failures when the Rust schema changed but the Kotlin types weren't updated to match.
The specific error was:
```
Failed to load podcasts from database
java.lang.RuntimeException: Failed to invoke private com.gregshuflin.synchronicity.data.ArtifactType() with no args
```
This occurred because Gson couldn't deserialize the `ArtifactType` sealed class from the JSON format that Rust produces.
## Solution
We implemented an automatic type generation system using the `typeshare` library where Rust is the source of truth. **Important**: This system requires using kotlinx.serialization instead of Gson due to fundamental incompatibilities.
### 1. Typeshare Annotations
In `rust/lib/src/documents.rs`, we annotated our core types with `#[typeshare]`:
- `DocumentID`
- `ArtifactID`
- `Document`
- `DocumentType`
- `DocumentRelationships`
- `DocumentMetadata`
- `Artifact`
- `ArtifactType`
- `ArtifactKind`
- `AudioFormat`
- `ValidationError`
```rust
use typeshare::typeshare;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(PartialEq))]
#[typeshare]
pub struct DocumentID(#[serde(with = "uuid::serde::compact")] Uuid);
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
#[serde(tag = "type", content = "content")]
#[typeshare]
pub enum ArtifactType {
// ... variants
}
```
### 2. Serde Configuration for Enums
For enums with data (like `ArtifactType` and `ValidationError`), we use `#[serde(tag = "type", content = "content")]` to ensure typeshare can properly generate the corresponding Kotlin sealed classes.
### 3. Type Mappings Configuration
The `typeshare.toml` file maps Rust types to Kotlin types:
```toml
[kotlin.type_mappings]
"uuid::Uuid" = "String"
"chrono::DateTime<chrono::Utc>" = "String"
"u8" = "Byte"
"u16" = "Short"
"u32" = "Int"
"u64" = "Long"
"u128" = "String"
"i8" = "Byte"
"i16" = "Short"
"i32" = "Int"
"i64" = "Long"
"i128" = "String"
```
### 4. Automatic Code Generation
The `typeshare` CLI generates Kotlin data classes matching the Rust schema. This creates `GeneratedTypes.kt` with:
- Type aliases for UUIDs and DateTimes as strings for kotlinx.serialization compatibility
- Sealed classes for Rust enums
- Data classes for Rust structs
- Proper kotlinx.serialization annotations (`@Serializable`, `@SerialName`)
### 5. Build System Integration
The Android build system (`android/app/build.gradle.kts`) includes a typeshare task that can be run manually:
```kotlin
tasks.register<Exec>("generateKotlinTypes") {
description = "Generate Kotlin types from Rust definitions using typeshare"
group = "build"
workingDir = file("../../rust")
commandLine("typeshare", ".", "--lang=kotlin", "--output-file=../android/app/src/main/java/com/gregshuflin/synchronicity/data/GeneratedTypes.kt")
outputs.file("src/main/java/com/gregshuflin/synchronicity/data/GeneratedTypes.kt")
}
```
**Note**: The automatic integration is currently disabled due to Gradle compatibility issues, but the task can be run manually.
## Current State
**Working**:
- Typeshare generates kotlinx.serialization-compatible Kotlin types from Rust
- Generated types compile successfully in Android project
- Type mappings for Rust primitives (Uuid, DateTime, etc.) work correctly
- Android project builds successfully with generated types
⚠️ **Known Issues**:
- Automatic typeshare execution during build is disabled due to Gradle compatibility
- Manual execution required: `./gradlew generateKotlinTypes`
## Usage
### For Development
1. **Modify Rust types**: Make changes to the Rust types in `rust/lib/src/documents.rs`
2. **Add typeshare annotations**: If you add new types, annotate them with `#[typeshare]`
3. **Configure serde for enums**: For enums with data, add `#[serde(tag = "type", content = "content")]`
4. **Regenerate Kotlin types**: Run `./gradlew generateKotlinTypes` from the `android/` directory
5. **Build Android app**: The generated types should now compile successfully
### Repository Setup
The `PodcastRepository` uses the generated types with kotlinx.serialization:
```kotlin
// Using generated types to ensure consistency with Rust
import com.gregshuflin.synchronicity.data.Document
import com.gregshuflin.synchronicity.data.ArtifactType
import com.gregshuflin.synchronicity.data.DocumentType
```
### Generated vs Manual Types
- **Use generated types**: For core data structures that cross the FFI boundary (`Document`, `Artifact`, etc.)
- **Use manual types**: For Android-specific types that don't correspond to Rust types (`Podcast`, UI models, etc.)
## Benefits
1. **Single source of truth**: Rust types are the canonical definition
2. **No schema drift**: Kotlin types are automatically updated when Rust changes
3. **Compile-time safety**: Mismatches cause build failures instead of runtime crashes
4. **Industry standard**: Uses the well-maintained `typeshare` library instead of custom code
5. **Cross-platform**: typeshare supports multiple target languages if needed in the future
6. **Modern serialization**: Uses kotlinx.serialization instead of Gson for better type safety
## File Overview
- `rust/lib/src/documents.rs` - Core type definitions (source of truth) with `#[typeshare]` annotations
- `rust/typeshare.toml` - Configuration for type mappings and package settings
- `android/app/src/main/java/com/gregshuflin/synchronicity/data/GeneratedTypes.kt` - Generated Kotlin types (auto-generated, do not edit)
- `android/app/build.gradle.kts` - Build configuration with typeshare task
## Testing
Test the type generation:
```bash
cd android && ./gradlew generateKotlinTypes
```
Test that the Rust code still compiles:
```bash
cd rust && cargo check
```
Test that the Android app builds with generated types:
```bash
cd android && ./gradlew assembleDebug
```
## Future Improvements
1. **Fix Gradle integration**: Resolve the compatibility issue to enable automatic typeshare execution during build
2. **More types**: Add typeshare annotations to more Rust types as they're used across the FFI boundary
3. **Automated testing**: Add tests that verify serialization compatibility between Rust and Kotlin
4. **CI integration**: Ensure type generation runs in CI to catch drift early
## Migration from Gson
The project has been migrated from Gson to kotlinx.serialization to support typeshare:
- **Removed**: Gson dependencies and type adapters
- **Added**: kotlinx.serialization dependencies
- **Updated**: All code to use kotlinx.serialization annotations
- **Benefit**: Better type safety and automatic type generation compatibility
-1
View File
@@ -147,7 +147,6 @@
createReleaseCi
rustToolchain
pkgs.cargo-nextest
pkgs.typeshare
androidSdk
pkgs.jdk17
pkgs.just
-7
View File
@@ -23,13 +23,6 @@ build-rust:
slint-preview path:
slint-viewer --auto-reload {{path}}
# Generate Kotlin types from Rust using Typeshare (via Gradle)
[group: "build"]
generate-types:
#!/usr/bin/env bash
export JAVA_HOME=/opt/android-studio/jbr
cd android && ./gradlew generateKotlinTypes
# Generate Kotlin types using uniffi
[group: "build"]
uniffi-bindgen-generate:
-23
View File
@@ -6716,7 +6716,6 @@ dependencies = [
"thiserror 2.0.18",
"tokio",
"tokio-stream",
"typeshare",
"typst",
"typst-pdf",
"uniffi",
@@ -7365,28 +7364,6 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "typeshare"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da1bf9fe204f358ffea7f8f779b53923a20278b3ab8e8d97962c5e1b3a54edb7"
dependencies = [
"chrono",
"serde",
"serde_json",
"typeshare-annotation",
]
[[package]]
name = "typeshare-annotation"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "621963e302416b389a1ec177397e9e62de849a78bd8205d428608553def75350"
dependencies = [
"quote",
"syn 2.0.117",
]
[[package]]
name = "typst"
version = "0.14.2"
-1
View File
@@ -38,7 +38,6 @@ tokio = { version = "1.49", features = ["full"] }
tempfile = "3.27"
once_cell = "1.21"
strum = { version = "0.28", features = ["derive"] }
typeshare = "1.0.4"
jni-sys = "0.4"
ndk-context = "0.1"
iroh = "0.98"
-1
View File
@@ -34,7 +34,6 @@ tokio = { workspace = true }
tempfile = { workspace = true }
once_cell = { workspace = true }
strum = { workspace = true }
typeshare = { workspace = true }
iroh = { workspace = true }
iroh-gossip = { workspace = true }
tokio-stream = { workspace = true, features = ["sync"] }
+1 -1
View File
@@ -21,7 +21,7 @@ use crate::stores::Store;
use crate::vault::{DEFAULT_VAULT_ID, VaultKey, decrypt_vault_key};
/// A 32-byte Ed25519 public key identifying a node.
/// Serializes as a hex string for JSON/typeshare interop.
/// Serializes as a hex string for JSON interop.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NodePubkey(pub [u8; 32]);
-36
View File
@@ -1,36 +0,0 @@
[swift]
prefix = ""
default_decorators = []
default_generic_constraints = []
codablevoid_constraints = []
[swift.type_mappings]
[typescript.type_mappings]
[kotlin]
package = "com.gregshuflin.synchronicity.data"
module_name = ""
prefix = ""
[kotlin.type_mappings]
"Uuid" = "String"
"DateTime" = "String"
"DateTime<Utc>" = "String"
"u8" = "Byte"
"u16" = "Short"
"u32" = "Int"
"u64" = "Long"
"u128" = "String"
"i8" = "Byte"
"i16" = "Short"
"i32" = "Int"
"i64" = "Long"
"i128" = "String"
"NodePubkey" = "String"
[scala]
package = ""
module_name = ""
[scala.type_mappings]