[workspace] resolver = "2" members = [ "core/dr-types", "core/dr-catalog", "core/dr-thumbs", "core/dr-decode", "core/dr-denoise", "core/dr-export", "core/dr-face", "core/dr-film", "core/dr-inference-engine", "core/dr-ingest", "core/dr-gpu", "core/dr-lens", "core/dr-pano", "core/dr-pipeline", "core/dr-preset-xmp", "core/dr-segment", "core/dr-sync", "core/dr-sync-folder", "core/dr-sync-nextcloud", "core/dr-xmp", "platform/dr-plat", "ui/dr-ui", "apps/darkroom-desktop", "apps/darkroom-android", "tools/bench", "tools/traceability", ] # Patched copies of upstream crates, not our code: see third_party/README.md. # Excluded so `--workspace` does not test, lint or format them as ours. exclude = ["third_party"] [workspace.package] version = "0.24.0" edition = "2021" rust-version = "1.92" license = "GPL-3.0-or-later" repository = "https://github.com/dtourolle/DarkRoom" [workspace.dependencies] # Internal dr-types = { path = "core/dr-types" } dr-catalog = { path = "core/dr-catalog" } dr-thumbs = { path = "core/dr-thumbs" } dr-decode = { path = "core/dr-decode" } dr-denoise = { path = "core/dr-denoise" } dr-export = { path = "core/dr-export" } # Stated explicitly for the same reason as `dr-segment` below: no dependant # should drag in an ONNX runtime by accident. Members opt in with # `features = ["inference"]`. dr-face = { path = "core/dr-face", default-features = false } dr-film = { path = "core/dr-film" } # `tract` on by default so a test binary can open a session with nothing # installed; the apps add `native` to look for a runtime file (docs/dev/inference.md §3). dr-inference-engine = { path = "core/dr-inference-engine" } dr-ingest = { path = "core/dr-ingest" } dr-gpu = { path = "core/dr-gpu" } dr-lens = { path = "core/dr-lens" } # Optional runtime, like `dr-segment`: the geometry never needs a model. dr-pano = { path = "core/dr-pano", default-features = false } dr-pipeline = { path = "core/dr-pipeline" } dr-preset-xmp = { path = "core/dr-preset-xmp" } # `default-features = false` belongs *here*, not on each dependant: a member # inheriting a workspace dependency cannot turn its default features off, so # writing it below would silently do nothing and every crate touching # `dr-segment` would drag in tract and 11 MB of weights. Members opt in with # `features = ["semantic", "embedded-model"]` instead. dr-segment = { path = "core/dr-segment", default-features = false } dr-plat = { path = "platform/dr-plat" } dr-sync = { path = "core/dr-sync" } dr-sync-folder = { path = "core/dr-sync-folder" } dr-sync-nextcloud = { path = "core/dr-sync-nextcloud" } dr-xmp = { path = "core/dr-xmp" } dr-ui = { path = "ui/dr-ui" } # GPU + UI # # The wgpu version is not a free choice: it is dictated by Slint. Importing a # texture into the scene (ARCH §6.1, spike S1) requires it to come from the # *same* `wgpu::Device` Slint renders with, and Slint will only hand out a # device of the version it was compiled against. Slint 1.17 offers # `unstable-wgpu-28` and `unstable-wgpu-29` and nothing older, so 29 it is — # pinned to the same `29.0.4` floor Slint itself requires, because two # semver-compatible-but-different wgpu crates in one tree are two *types*, and # the device would not typecheck across them. # # Consequently: bumping Slint may force a wgpu bump, and wgpu cannot be bumped # on its own. They move together or not at all. wgpu = "29.0.4" slint = { version = "1.17", default-features = false } slint-build = "1.17" # UI token codegen (S2): style.yaml -> theme.slint. serde_yaml was deprecated # by its maintainer in 2024 and serde_yml, the first fork, has since been # deprecated too; serde_norway is the fork still receiving releases. Its # mappings preserve insertion order, which is what lets the generated Slint # keep the token ordering the YAML author chose. serde_norway = "0.9" # Foundations anyhow = "1" thiserror = "2" log = "0.4" env_logger = "0.11" pollster = "0.4" # Networking — no mature Nextcloud crate exists; the connector is hand-rolled # over reqwest (D7). reqwest_dav was evaluated and is too thin to build on. # `rustls-no-provider` rather than `rustls`: the latter defaults to the # aws-lc-rs crypto provider, whose aws-lc-sys crate is C and fails to # cross-compile for Android — precisely the NDK pain D1 chose Rust to avoid. # ring is pure Rust apart from a small asm core that does build under the NDK. # # `rustls-tls-webpki-roots-no-provider` rather than plain `rustls-no-provider`: # the latter verifies against rustls-platform-verifier, which reaches the # Android trust store over JNI and panics mid-handshake unless initialised from # Java first — the crash D7 predicted and spike S3 exists to resolve properly. # The panic surfaces inside tokio, which catches task panics itself, so it # reaches the UI as a worker that stopped rather than as an error. # # webpki-roots is the escape hatch D7 records: a root store compiled into the # binary, no JNI, identical on both platforms. The trade is real and belongs in # S3's scope — user-installed and enterprise CAs are not consulted, and the # roots go stale with the release rather than with the OS. reqwest = { version = "0.13", default-features = false, features = ["rustls-no-provider", "webpki-roots", "stream", "json"] } rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } quick-xml = "0.41" tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"] } url = "2.5" async-trait = "0.1" serde = { version = "1", features = ["derive"] } serde_json = "1" # The manual's HTML rendering (tools/traceability). Already in the tree as # Slint's Markdown parser, so this adds a dependency edge and no crate; only # the HTML writer is needed, not the command-line front end. pulldown-cmark = { version = "0.13", default-features = false, features = ["html"] } # The verdict-writer check (tools/traceability, FR-CULL-13) reads Rust as Rust: # a text scan cannot tell a call from a comment, a test module from shipped # code, or which callback closure a call sits in. Both already in the tree as # every proc macro's parser; `span-locations` gives a problem its line. syn = { version = "2", default-features = false, features = ["full", "parsing", "visit", "printing"] } proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] } base64 = "0.23" # Display-server clients, for FR-DSP-8's per-display profile acquisition. # # Neither is a new cost: winit already builds both, so the versions are the # ones Slint's backend has resolved to and pinning anything else here would # compile a second copy. Both are pure Rust — x11rb speaks the X11 wire # protocol itself rather than binding libxcb, and wayland-client binds # libwayland only under a feature that is off — which keeps the Android # cross-compile a plain Rust dependency graph, the same criterion as the TLS # and SQLite choices above. They are declared under a target predicate that # excludes Android, where neither display server exists. # # `staging` on wayland-protocols is what carries `wp_color_manager_v1`: the # colour-management extension is still staging upstream, which is the # protocol-level statement of the thing FR-DSP-8 anticipates when it says # Wayland's colour management "is not universally available". x11rb = { version = "0.13", features = ["randr"] } wayland-client = "0.31" wayland-protocols = { version = "0.32", features = ["client", "staging"] } # Platform secure storage: Secret Service on Linux, Keystore on Android # (FR-NC-2). Credentials never touch the catalog or a plain file. # keyring 4 restructured its features: `v1` is the default set and brings # the zbus Secret Service backend, which is what GNOME Keyring and KWallet # (via ksecretd) both speak. keyring = { version = "4", features = ["v1"] } # The Android half of the same project: a keyring-core CredentialStore backed # by AndroidKeyStore AES-GCM over SharedPreferences (FR-PLAT-AND-1). It reads # the JavaVM and Context from ndk-context, which android-activity populates # before `android_main` runs, so no Kotlin shim of our own is needed. # # This is the keyring-core API, not the v1 `Entry` API the Linux path uses; # the two impls are deliberately separate rather than sharing a code path. android-native-keyring-store = "1.0.0" keyring-core = "1" # Decode. rawler is the pure-Rust decoder (D2); zune-jpeg decodes the # embedded previews rawler extracts. # Catalog. `bundled` compiles SQLite from source rather than linking the # system library — the same cross-compilation reasoning as the TLS choice # above: no system dependency to satisfy under the Android NDK. # # `backup` is not optional in practice: it is what takes a consistent snapshot # of a live WAL database for upload. A filesystem copy of `catalog.sqlite` # while a `-wal` exists beside it uploads a torn file. rusqlite = { version = "0.40", features = ["bundled", "backup"] } rawler = "0.7" zune-jpeg = "0.4.21" # Thumbnails are stored encoded, not as raw RGBA: a 256px RGBA buffer is # ~256 KB against ~20 KB as JPEG, and the store syncs to Nextcloud where that # 13× is transfer cost on every client. Pure Rust, no C dependency — the same # criterion behind the TLS and SQLite choices above. jpeg-encoder = "0.7" bytemuck = { version = "1", features = ["derive"] } # Lens correction profiles. A pure-Rust port of Lensfun rather than a binding # to the C library, for the same cross-compilation reason as the TLS and # SQLite choices above: liblensfun would be a third C dependency to satisfy # under the Android NDK. # # The database ships *inside* the crate — 56 XML files, gzipped at build time # and decompressed on first lookup. That matters beyond convenience: Android # gives us no filesystem path (ARCH §6.9), so a database loaded from a # system directory would have nowhere to live there. # # Licence: LGPL-3.0-or-later, which upgrades cleanly into our GPLv3 (D8). # The upstream Lensfun *database* is CC-BY-SA and is redistributed by the # crate; attribution belongs in the about screen. # # Caveat worth remembering: this is a third-party port at 0.7.0, not upstream # Lensfun. Verified working against the bundled database (interpolation # between calibration points, and an unknown lens returning empty rather than # panicking), but the pipeline talks to it through its own profile types so # swapping it out is not a pipeline change. lensfun = "0.7" # Neural inference for semantic segmentation (S15 arm B, D14). # # D13 framed this as a choice between `ort` (fast, best operator coverage, and # a C++ dependency to cross-compile under the NDK) and a pure-Rust runtime # (policy-compliant, unproven coverage). That framing turned out to be a false # choice: `ort` 2.0's `alternative-backend` feature *disables the linking # entirely* and lets a different engine supply the `OrtApi`, and `ort-tract` — # same authors, MIT/Apache — supplies it from `tract`, which is pure Rust. # # So we get `ort`'s API with no C at all. `download-binaries` and `tls-native` # are off with `default-features = false`, which is the point: nothing is # fetched at build time and nothing is linked, so the Android cross-compile # sees an ordinary Rust dependency graph. That is the same reasoning as rustls # over aws-lc-rs and bundled SQLite, applied to inference — D13's largest # tolerated exception turns out not to be needed. # # The trade is real and belongs on the record: tract is slower than the C++ # runtime and covers fewer operators. Both were measured rather than assumed # before this landed — yolo26n-seg loads with **zero unsupported operators** # and runs 640x640 in ~470 ms on the reference desktop's CPU. That is fine for # a once-per-image precompute off the frame path (ARCH §6.1) and would not be # fine for anything per-frame, which is a constraint on what may be built on # top rather than on this choice. # # Pinned to an rc: `ort` 2.0 has been in rc for a long while and `ort-tract` # exists only against it. Worth revisiting at 2.0 final. ort = { version = "2.0.0-rc.13", default-features = false, features = ["alternative-backend", "ndarray", "std"] } ort-tract = "0.4" # Not a free choice: it is the version `ort` exposes its tensors through, so # two semver-incompatible ndarrays would not typecheck across the boundary — # the same coupling wgpu has with Slint above. ndarray = "0.17" [profile.dev] # Dev builds are tuned for how fast they *compile*, not for how fast they run. # Optimisation is a release concern; `[profile.release]` below is where it # belongs. # # This deliberately reverses an earlier choice. Dependencies used to be built # at `opt-level = 2` here, because wgpu and image decoding are slow without it. # That is still true, and it is the price: a debug run of the app, and the # decode- and GPU-heavy tests, are slower than they were. What it buys is that # nothing has to be optimised before it can be compiled — which is the cost # paid on every edit, by every worktree, rather than only when something is # actually run. # # If a particular crate turns out to be the one that makes a test unbearable, # raise it alone rather than restoring the blanket rule: # # [profile.dev.package.zune-jpeg] # opt-level = 2 opt-level = 0 [profile.release] lto = "thin" codegen-units = 1 # Except dr-ui. Slint expands the `.slint` files into ~27 MB of Rust # (`out/app.rs`), and at one codegen unit LLVM optimises all of it on a single # thread: 13.5 minutes of a release build with the other cores idle. The code # it holds is UI glue — property bindings and callbacks — not the image work, # which lives in the crates above that keep the single unit. [profile.release.package.dr-ui] codegen-units = 16 # A release build that can say where it panicked: line tables, so a crash # record's backtrace (`dr_plat::crash`) reads `file.rs:123` rather than bare # addresses. The macOS build uses it (docs/dev/macos.md) — no one here can # reproduce a Mac bug, so its reports carry what a debugger would have — at # the price of a larger binary and no slower code. On macOS the tables land # in a `.dSYM` beside the executable (rustc's default `packed`), and the # bundle must carry that directory next to the binary for the backtrace to # find it. [profile.diagnostic] inherits = "release" debug = "line-tables-only" # Three upstream crates carry a local patch: wgpu-hal and Slint's Skia # renderer so that the Android build can draw with wgpu on a rotated display # (technical-debt.md TD-1), and rawler so that a linear DNG wider than 16 700 # pixels decodes. Each is an exact copy of the version the lockfile already # resolves, plus its patch; third_party/README.md says what was changed and # how to carry it forward when Slint, wgpu or rawler moves. [patch.crates-io] wgpu-hal = { path = "third_party/wgpu-hal-29.0.4" } i-slint-renderer-skia = { path = "third_party/i-slint-renderer-skia-1.17.1" } rawler = { path = "third_party/rawler-0.7.2" }