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:
@@ -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
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -147,7 +147,6 @@
|
||||
createReleaseCi
|
||||
rustToolchain
|
||||
pkgs.cargo-nextest
|
||||
pkgs.typeshare
|
||||
androidSdk
|
||||
pkgs.jdk17
|
||||
pkgs.just
|
||||
|
||||
@@ -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:
|
||||
|
||||
Generated
-23
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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]
|
||||
Reference in New Issue
Block a user