---
name: Cargo and features
type: architecture-map-page
slice: 02-tauri-runtime
last_verified: 2026/05/09
---

# Cargo and features

> **Where you are:** [Architecture map](../README.md) → [Tauri runtime](README.md) → Cargo and features

**Plain English summary.** This page walks the Tauri crate's `Cargo.toml`, its `build.rs`, and its `.cargo/config.toml`. The crate is the binary entry for the desktop app and the library entry for the Android target. The `whisper` cargo feature is the kill-switch for the whisper.cpp backend. Per-target dependency blocks gate macOS objc2 bindings, Linux GTK / WebKitGTK, and Windows base64 (used by the TTS PowerShell shim). `build.rs` enforces the CSP regression guard documented in `docs/whisper-ecosystem/brief.md` item #2.

## At a glance

- Path: `src-tauri/Cargo.toml` (108 LOC), `src-tauri/build.rs` (81 LOC), `src-tauri/.cargo/config.toml` (3 LOC).
- Tauri commands exposed: none. Build-system files only.
- Events emitted: none.
- Depends on: every workspace crate (slices 03–05) plus the Tauri ecosystem.
- Called from frontend at: nothing direct. The build outputs land in the dev / packaged binary.

## What's in here

### `Cargo.toml`

Package metadata: `name = "lumotia"`, `version = "0.1.0"`, `description = "Lumotia — Think out loud"`, `authors = ["CORBEL Ltd"]`, `edition = "2021"`.

Lib stanza (`src-tauri/Cargo.toml:8`):

```
name       = "lumotia_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
```

`staticlib` and `cdylib` are needed for the Android target where Tauri builds an `.aar` from a `cdylib`. `rlib` makes it consumable from `main.rs`.

#### `[features]`

```
default = ["whisper"]
whisper = ["lumotia-transcription/whisper"]
```

The `whisper` feature transitively enables `lumotia-transcription/whisper`. The crate-level `default-features = false` on `lumotia-transcription` (`src-tauri/Cargo.toml:33`) means a `--no-default-features` workspace build drops `whisper-rs-sys` entirely; Parakeet still works. `commands::models::load_model_from_disk` returns a clear runtime error for `Engine::Whisper` when the feature is off.

#### `[build-dependencies]`

`tauri-build = "2"`, plus `serde_json = "1"` for the CSP regression guard in `build.rs`.

#### `[dependencies]` — workspace crates

```
lumotia-core
lumotia-audio
lumotia-transcription   { default-features = false }
lumotia-ai-formatting
lumotia-storage
lumotia-cloud-providers
lumotia-hotkey
lumotia-llm
```

The `cloud-providers` crate is referenced here even though no command file currently imports it. Likely reserved for the Phase-N upload flow that the diagnostic-report bundler hints at.

#### `[dependencies]` — Tauri

```
tauri                       = { version = "2" }
tauri-plugin-opener         = "2"
tauri-plugin-dialog         = "2"
tauri-plugin-notification   = "2"
```

These three plugins are unconditional. `notification` supports Android natively (Phase 6 nudges) so it stays out of the desktop-only block.

#### `[dependencies]` — runtime

```
serde       = { version = "1", features = ["derive"] }
serde_json  = "1"
tokio       = { version = "1", features = ["rt", "sync"] }
arboard     = "3.6.1"
sqlx        = { version = "0.8", default-features = false, features = ["runtime-tokio", "sqlite"] }
uuid        = { version = "1", features = ["v4"] }
```

The `sqlx` block has `default-features = false` and only opts into `runtime-tokio` and `sqlite`. `lumotia-storage` already pulls the macros / migrate / any / json features via its own re-export, so duplicating them here would just bloat compile time. `sqlx` is named directly because `AppState` types it as `SqlitePool`; naming a transitive dep type still requires the dep be listed.

#### `[dev-dependencies]`

`tempfile = "3"` for the Phase 9 `commands::fs::write_text_file_cmd` tests.

#### `[target.'cfg(not(target_os = "android"))'.dependencies]`

```
tauri                          = { version = "2", features = ["tray-icon"] }
tauri-plugin-global-shortcut   = "2"
tauri-plugin-window-state      = "2"
tauri-plugin-autostart         = "2"
```

Each is justified inline in `src-tauri/Cargo.toml:74`. Tray icons, global shortcuts, multi-window state, and autostart all either fail to compile against the Android NDK or are structurally meaningless on a single-window mobile activity. The Cargo gate matches the `#[cfg(not(target_os = "android"))]` blocks in `lib.rs` and `commands/windows.rs`.

#### Per-OS dependency blocks

```
linux:   webkit2gtk = "2.0", gtk = "0.18", gdk = "0.18"
macos:   objc2 = "0.6.4", objc2-foundation = { ... features = ["std", "NSString", "NSProcessInfo"] }
windows: base64 = "0.22"
```

- `webkit2gtk`/`gtk`/`gdk` are used in `lib.rs` (auto-grant audio capture) and `commands/windows.rs` (set GTK `WindowTypeHint::Utility` on the preview overlay). Versions track what `webkit2gtk` 2.0 transitively depends on.
- `objc2` + `objc2-foundation` power the macOS App Nap power assertion (`commands/power.rs::objc_bridge`).
- `base64` is the Windows TTS encoder. PowerShell `-EncodedCommand` requires UTF-16-LE base64.

### `build.rs`

Two responsibilities:

1. **Linker workaround.** `--allow-multiple-definition` is passed to GNU ld / lld on Linux because `llama-cpp-sys-2` and `whisper-rs-sys` both statically link their own copy of `ggml`, leading to duplicate symbols (`src-tauri/build.rs:8`). Documented as INTERIM; the intended fix is system-ggml shared-lib linking. Only emitted on Linux.
2. **CSP regression guard.** `assert_loopback_llm_csp` (`src-tauri/build.rs:30`) parses `tauri.conf.json` with `serde_json`, finds the `connect-src` directive (full-name match, not prefix), and asserts the four required tokens: `http://127.0.0.1:*` and `ws://127.0.0.1:*` must be present, `http://localhost:*` and `ws://localhost:*` must be absent. Fails compilation with a brief-item-#2 reference if any of those break. Re-runs when `tauri.conf.json` changes.

`tauri_build::build()` is called last. Order matters: the CSP guard fails fast, before tauri-build does any of its own work.

### `.cargo/config.toml`

```
[env]
LIBCLANG_PATH = "C:\\Program Files\\LLVM\\bin"
```

Hard-coded Windows path so `bindgen` (used by Tauri 2's macros and a couple of dependencies) can find clang during a Windows build. Harmless on non-Windows hosts — the env var is just unused. Foot-gun: a Windows developer with Clang in a non-default path will have to override or remove this.

## Data flow

- `cargo build` reads `Cargo.toml`, runs `build.rs`, then compiles `src/lib.rs` and `src/main.rs`. The CSP regression guard fires before any source compiles.
- The default features include `whisper`. A workspace-wide `cargo build --no-default-features` drops both whisper.cpp and the Tauri default features (which would also drop tray-icon, etc., so this is a niche build).
- Plugin crates land in the dependency graph and contribute permission manifests that `gen/schemas/` is regenerated from at build time.

## Watch-outs

- Adding a workspace crate dependency here also requires that crate to be in the root `Cargo.toml`'s workspace members (or referenced by `path =` here, which is what is done now). All eight workspace crates appear in this `Cargo.toml`.
- The `--allow-multiple-definition` link arg is the kind of thing that hides bugs. If both ggml copies ever drift, the binary picks "the first definition" and the second copy's slightly-different symbol is silently shadowed. Track the `whisper-rs-sys` and `llama-cpp-sys-2` ggml pins together.
- Bumping any of `objc2`, `objc2-foundation`, `webkit2gtk`, `gtk`, `gdk` is an ABI move. Test on each platform.
- The hard-coded `LIBCLANG_PATH` should ideally come from an environment variable or a build-script probe. Until then, document it in `docs/dev-setup.md` for Windows contributors.

## See also

- [App lifecycle](app-lifecycle.md) — `lib.rs::run` is what consumes everything declared here.
- [Tauri config](tauri-config.md) — `build.rs` reads `tauri.conf.json` and pins the CSP shape.
- [Tests](tests.md) — runtime regression for the same CSP property.
- [Capabilities and ACL](capabilities-and-acl.md) — `gen/schemas/` files are regenerated when a plugin in this Cargo manifest changes.
