docs, flake: document tailored builds and name the flake features
flake.nix first: its native package passed a bare --no-default-features, which used to mean "everything but web-ui" and now means *no providers at all*. It names its set explicitly (all-providers,opus,spectrum,notifications); the aarch64 cross build keeps the full defaults and its staged wasm bundle. docs/src/build-features.md: the feature table with what each one costs to lose, why fs takes /crabidy, /orphans, queue persistence and scan with it, the opus/libopus build note, two worked examples, what is deliberately not gated, and check-features. Linked from SUMMARY.md, and config.md now says the providers list can only offer what the binary was built with. README gains a short pointer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a03e3de84e
commit
cd3a16f95c
18
README.md
18
README.md
|
|
@ -45,6 +45,24 @@ cargo run -p cbd # server + TUI in one process
|
||||||
Or run the halves separately: `cargo run -p crabidy-server` and, in
|
Or run the halves separately: `cargo run -p crabidy-server` and, in
|
||||||
another terminal, `cargo run -p cbd-tui`.
|
another terminal, `cargo run -p cbd-tui`.
|
||||||
|
|
||||||
|
### Tailored builds
|
||||||
|
|
||||||
|
Every provider — plus Opus decoding, the spectrum bars, the embedded web
|
||||||
|
UI, and the TUI's desktop notifications — sits behind a Cargo feature,
|
||||||
|
all on by default. Drop what you do not need and the dependencies go with
|
||||||
|
it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# a local-files appliance: no network providers, no web UI, no FFT
|
||||||
|
cargo build --release -p crabidy-server --no-default-features --features fs,opus
|
||||||
|
```
|
||||||
|
|
||||||
|
`crabidy-server features` prints what a binary was built with. See
|
||||||
|
[docs/src/build-features.md](docs/src/build-features.md) for the full
|
||||||
|
table (and what `fs` takes with it), and
|
||||||
|
[architecture/build-features.md](architecture/build-features.md) for the
|
||||||
|
design.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
All configuration lives in `~/.config/crabidy/` (the platform config
|
All configuration lives in `~/.config/crabidy/` (the platform config
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,5 @@
|
||||||
- [The cbd bundle](./clients/cbd.md)
|
- [The cbd bundle](./clients/cbd.md)
|
||||||
- [Command line](./clients/cli.md)
|
- [Command line](./clients/cli.md)
|
||||||
- [Configuration](./config.md)
|
- [Configuration](./config.md)
|
||||||
|
- [Tailored builds](./build-features.md)
|
||||||
- [Roles and authorization](./auth.md)
|
- [Roles and authorization](./auth.md)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
# Tailored builds
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
Every provider, Opus decoding, the spectrum bars, the embedded web UI and
|
||||||
|
the TUI's desktop notifications sit behind a Cargo **feature**. All of them
|
||||||
|
are **on by default**, so a plain `cargo build` gives you the full player.
|
||||||
|
Turning some off gives you a smaller binary that pulls fewer dependencies —
|
||||||
|
useful for a single-purpose box (a Raspberry Pi playing a local flac
|
||||||
|
collection) or a build environment you want to keep lean.
|
||||||
|
|
||||||
|
This is the *compile-time* half of provider selection. The runtime half —
|
||||||
|
the `providers` list in `crabidy-server.toml` — still works exactly as
|
||||||
|
before; see [Configuration](./config.md#enabling-and-disabling-providers).
|
||||||
|
The two compose in one direction: **a provider that is not compiled in
|
||||||
|
cannot be enabled in the config**, and if you name one anyway the server
|
||||||
|
logs a warning and carries on.
|
||||||
|
|
||||||
|
## What this build has
|
||||||
|
|
||||||
|
```sh
|
||||||
|
crabidy-server features # or: cbd features
|
||||||
|
```
|
||||||
|
|
||||||
|
prints one feature per line — the providers it can mount, then the extras.
|
||||||
|
The same list goes into the server's startup log, so a support question
|
||||||
|
("why is `/tidal` missing?") is answerable from the binary and its log.
|
||||||
|
|
||||||
|
## The features
|
||||||
|
|
||||||
|
| Feature | Turning it off drops |
|
||||||
|
| ------------ | ----------------------------------------------------------- |
|
||||||
|
| `tidal` | the `/tidal` provider (`tidaldy`) |
|
||||||
|
| `youtube` | the `/youtube` provider (`ytdy`, and with it `rustypipe`) |
|
||||||
|
| `fyyd` | the `/fyyd` podcast provider |
|
||||||
|
| `abs` | the `/abs` audiobookshelf provider |
|
||||||
|
| `soundcloud` | the `/soundcloud` provider |
|
||||||
|
| `jamendo` | the `/jamendo` provider |
|
||||||
|
| `fs` | local files **and persistent state** — see below |
|
||||||
|
| `opus` | Ogg-Opus decoding (`symphonia` + a bundled libopus C build) |
|
||||||
|
| `spectrum` | the server-side FFT feeding clients' spectrum bars |
|
||||||
|
| `web-ui` | the embedded web client (`tonic-web` + the wasm bundle) |
|
||||||
|
|
||||||
|
Plus two conveniences: `all-providers` enables the seven provider features
|
||||||
|
at once, and `cbd` (the bundle) mirrors every feature above and adds
|
||||||
|
`notifications` for the TUI's desktop "now playing" popups (`notify-rust`,
|
||||||
|
which on Linux pulls a D-Bus stack).
|
||||||
|
|
||||||
|
### `fs` is more than `/fs`
|
||||||
|
|
||||||
|
The `/fs` provider, the content store, and everything built on it are all
|
||||||
|
written against the same crate, so they share one feature. With `fs` off you
|
||||||
|
lose:
|
||||||
|
|
||||||
|
- the `/fs` mount (your local music folder);
|
||||||
|
- `/crabidy` — saved queues, bookmarks (`w`) and captures (`W`);
|
||||||
|
- `/orphans`, which is a view over the store;
|
||||||
|
- **queue persistence**: the queue lives in memory, so a restart starts
|
||||||
|
empty;
|
||||||
|
- the `scan` command (it fails with a message naming the feature);
|
||||||
|
- captured-track markers in library listings.
|
||||||
|
|
||||||
|
The capture and save-queue RPCs answer `Unimplemented` on such a server, so
|
||||||
|
clients report an ordinary error instead of hanging.
|
||||||
|
|
||||||
|
If you want `/fs` but not `/crabidy`, keep the feature and prune the
|
||||||
|
`providers` list at runtime instead — that is what it is for.
|
||||||
|
|
||||||
|
### `opus` and the libopus build
|
||||||
|
|
||||||
|
`symphonia` (and so rodio) has no Opus decoder, so Ogg-Opus files are
|
||||||
|
decoded through a bundled libopus, which needs `cmake` at build time.
|
||||||
|
Turning `opus` off removes both the crates and that build requirement.
|
||||||
|
|
||||||
|
The feature also decides whether `scan` treats `.opus` files as playable at
|
||||||
|
all, so a build that cannot decode Opus will not index Opus files either. An
|
||||||
|
Opus file that reaches such a build fails to decode with a message naming
|
||||||
|
the missing feature and is skipped, exactly like any unplayable file.
|
||||||
|
|
||||||
|
### `spectrum` and `web-ui`
|
||||||
|
|
||||||
|
`spectrum` only affects the *server*: with it off no frames are computed or
|
||||||
|
broadcast, and clients simply show no bars — nothing else changes, and no
|
||||||
|
client needs rebuilding. `web-ui` drops the embedded browser client; the
|
||||||
|
gRPC service on port 50051 still serves `cbd-tui` and the CLI.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
A local-files appliance — no network providers, no web UI, no FFT:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build --release -p crabidy-server \
|
||||||
|
--no-default-features --features fs,opus
|
||||||
|
```
|
||||||
|
|
||||||
|
A streaming box with the browser client and the bars, no local library:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build --release -p crabidy-server \
|
||||||
|
--no-default-features --features tidal,web-ui,opus,spectrum
|
||||||
|
```
|
||||||
|
|
||||||
|
The bundle, tailored the same way (its features forward to the server):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build --release -p cbd --no-default-features --features fs,opus
|
||||||
|
```
|
||||||
|
|
||||||
|
A terminal client with no D-Bus dependency:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build --release -p cbd-tui --no-default-features
|
||||||
|
```
|
||||||
|
|
||||||
|
`--no-default-features` on its own is legal and compiles: you get a server
|
||||||
|
that starts, serves an empty library and plays nothing. It is the base case
|
||||||
|
the feature matrix checks, not a useful deployment.
|
||||||
|
|
||||||
|
## What is *not* behind a feature
|
||||||
|
|
||||||
|
- **Authorization.** `[auth]` and its password hashing always ship. A build
|
||||||
|
that ignored configured role hashes would silently run an
|
||||||
|
intended-to-be-locked server open — a fail-open hole not worth a small
|
||||||
|
dependency.
|
||||||
|
- **Audio output.** The server is the player; a server without audio has no
|
||||||
|
purpose.
|
||||||
|
- **HLS streaming and the spectrum tap.** They bring no dependencies of
|
||||||
|
their own, so gating them would add build complexity and save nothing.
|
||||||
|
|
||||||
|
## Checking combinations
|
||||||
|
|
||||||
|
`devenv shell -- check-features` runs the curated matrix — defaults, no
|
||||||
|
features, each provider alone, each extra dropped, both examples above, and
|
||||||
|
the client crates — and requires every one to be clippy-clean. Run it after
|
||||||
|
changing anything that sits behind a feature.
|
||||||
|
|
@ -47,6 +47,13 @@ left unread. Deleting the whole `providers` line re-enables everything (the
|
||||||
same as a fresh install with no file). Because `orphans` is a view over the
|
same as a fresh install with no file). Because `orphans` is a view over the
|
||||||
store, disabling `crabidy` disables `orphans` too.
|
store, disabling `crabidy` disables `orphans` too.
|
||||||
|
|
||||||
|
The list can only offer what the binary was **built** with: providers are
|
||||||
|
also selectable at compile time (see [Tailored
|
||||||
|
builds](./build-features.md)). The default list written on first start
|
||||||
|
therefore names only the providers this build has, and if you add one it
|
||||||
|
does not have, the server logs a warning at startup and ignores it. Run
|
||||||
|
`crabidy-server features` to see what a binary contains.
|
||||||
|
|
||||||
## Client config: `cbd-tui.toml` and `cbd.toml`
|
## Client config: `cbd-tui.toml` and `cbd.toml`
|
||||||
|
|
||||||
`cbd-tui` (the standalone terminal client) reads `cbd-tui.toml`; `cbd`
|
`cbd-tui` (the standalone terminal client) reads `cbd-tui.toml`; `cbd`
|
||||||
|
|
|
||||||
15
flake.nix
15
flake.nix
|
|
@ -54,11 +54,18 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# Only the real binary crates; never build cbd-web (a wasm crate) for a
|
# Only the real binary crates; never build cbd-web (a wasm crate) for a
|
||||||
# host or aarch64 target. Headless: --no-default-features drops the
|
# host or aarch64 target.
|
||||||
# embedded web UI. The aarch64 server (below) re-enables it and stages
|
|
||||||
# the wasm bundle.
|
|
||||||
workspaceBins = "-p crabidy-server -p cbd -p cbd-tui";
|
workspaceBins = "-p crabidy-server -p cbd -p cbd-tui";
|
||||||
|
|
||||||
|
# Headless: everything except the embedded web UI. Spelled out rather
|
||||||
|
# than "--no-default-features" alone, which since
|
||||||
|
# architecture/build-features.md means *no providers at all* — the
|
||||||
|
# features must be named or this package ships an empty server. The
|
||||||
|
# aarch64 server (below) takes the full defaults and stages the wasm
|
||||||
|
# bundle. `notifications` is cbd-tui's own default and needs no
|
||||||
|
# mention here.
|
||||||
|
headlessFeatures = "all-providers,opus,spectrum,notifications";
|
||||||
|
|
||||||
# --- Web UI (wasm) bundle ---------------------------------------------
|
# --- Web UI (wasm) bundle ---------------------------------------------
|
||||||
# cbd-web is compiled to wasm by trunk; the output dist/ is what
|
# cbd-web is compiled to wasm by trunk; the output dist/ is what
|
||||||
# crabidy-server's build.rs embeds (feature `web-ui`). The bundle is
|
# crabidy-server's build.rs embeds (feature `web-ui`). The bundle is
|
||||||
|
|
@ -130,7 +137,7 @@
|
||||||
nativeArgs = {
|
nativeArgs = {
|
||||||
inherit src;
|
inherit src;
|
||||||
strictDeps = true;
|
strictDeps = true;
|
||||||
cargoExtraArgs = "--locked --no-default-features ${workspaceBins}";
|
cargoExtraArgs = "--locked --no-default-features --features ${headlessFeatures} ${workspaceBins}";
|
||||||
# protoc + pkg-config are build-host tools (run during the build);
|
# protoc + pkg-config are build-host tools (run during the build);
|
||||||
# with strictDeps they must be nativeBuildInputs or alsa-sys cannot
|
# with strictDeps they must be nativeBuildInputs or alsa-sys cannot
|
||||||
# find pkg-config. alsa-lib is the actual linked library.
|
# find pkg-config. alsa-lib is the actual linked library.
|
||||||
|
|
|
||||||
|
|
@ -13,24 +13,24 @@ devenv shell -- bash -lc 'CARGO_TARGET_DIR="$(pwd)/target-claude" cargo … '
|
||||||
|
|
||||||
## A — Mount registry (behaviour-preserving refactor)
|
## A — Mount registry (behaviour-preserving refactor)
|
||||||
|
|
||||||
- [ ] **A1 — `Mount` + registry types.** In `provider.rs`: `struct Mount {
|
- [x] **A1 — `Mount` + registry types.** In `provider.rs`: `struct Mount {
|
||||||
root: &'static str, name: &'static str, client: Arc<dyn ProviderClient> }`,
|
root: &'static str, name: &'static str, client: Arc<dyn ProviderClient> }`,
|
||||||
`Mount::new(root, name, Arc<impl ProviderClient>) -> Mount` (the `Arc`
|
`Mount::new(root, name, Arc<impl ProviderClient>) -> Mount` (the `Arc`
|
||||||
coerces at the call site), and `Mount::owns(&self, path) -> bool` matching
|
coerces at the call site), and `Mount::owns(&self, path) -> bool` matching
|
||||||
`path == root || path.starts_with("<root>/")` — so `/fsx` is not `/fs`.
|
`path == root || path.starts_with("<root>/")` — so `/fsx` is not `/fs`.
|
||||||
*Verifies:* `mount_owns_its_root_and_children_only` (G10).
|
*Verifies:* `mount_owns_its_root_and_children_only` (G10).
|
||||||
- [ ] **A2 — `ProviderOrchestrator` holds `mounts: Vec<Mount>`.** Replace the
|
- [x] **A2 — `ProviderOrchestrator` holds `mounts: Vec<Mount>`.** Replace the
|
||||||
nine `*_client` fields (keep `crabidy_store`). Add
|
nine `*_client` fields (keep `crabidy_store`). Add
|
||||||
`from_mounts(Vec<Mount>) -> Self` (sorts the mounts crabidy-first /
|
`from_mounts(Vec<Mount>) -> Self` (sorts the mounts crabidy-first /
|
||||||
orphans-last / rest alphabetical, and creates the bounded channel), used by
|
orphans-last / rest alphabetical, and creates the bounded channel), used by
|
||||||
`build` and the tests. *Verifies:* `root_lists_mounted_providers_in_order`
|
`build` and the tests. *Verifies:* `root_lists_mounted_providers_in_order`
|
||||||
(G12).
|
(G12).
|
||||||
- [ ] **A3 — One dispatch helper.** `owner(&self, path) -> Option<&dyn
|
- [x] **A3 — One dispatch helper.** `owner(&self, path) -> Option<&dyn
|
||||||
ProviderClient>` plus `owner_or(&self, path, err)` that warns with the same
|
ProviderClient>` plus `owner_or(&self, path, err)` that warns with the same
|
||||||
message and returns the typed error. Delete the nine `*_owns` functions and
|
message and returns the typed error. Delete the nine `*_owns` functions and
|
||||||
the nine `*_provider` accessors. *Verifies:* `unowned_paths_are_typed_errors`
|
the nine `*_provider` accessors. *Verifies:* `unowned_paths_are_typed_errors`
|
||||||
(G11).
|
(G11).
|
||||||
- [ ] **A4 — Rewrite the eight dispatch methods** (`is_track_path`,
|
- [x] **A4 — Rewrite the eight dispatch methods** (`is_track_path`,
|
||||||
`get_urls_for_track`, `get_metadata_for_track`, `get_lib_node`,
|
`get_urls_for_track`, `get_metadata_for_track`, `get_lib_node`,
|
||||||
`create_lib_node`, `rename_lib_node`, `delete_lib_node`,
|
`create_lib_node`, `rename_lib_node`, `delete_lib_node`,
|
||||||
`resolve_tracks_into`) as a single lookup each, preserving: the synthetic
|
`resolve_tracks_into`) as a single lookup each, preserving: the synthetic
|
||||||
|
|
@ -39,35 +39,35 @@ devenv shell -- bash -lc 'CARGO_TARGET_DIR="$(pwd)/target-claude" cargo … '
|
||||||
`warn!`s. *Verifies:* `dispatch_reaches_the_owning_mount`,
|
`warn!`s. *Verifies:* `dispatch_reaches_the_owning_mount`,
|
||||||
`overridden_resolve_tracks_into_is_dispatched`,
|
`overridden_resolve_tracks_into_is_dispatched`,
|
||||||
`no_mounts_serves_an_empty_root` (G10, G11, G13).
|
`no_mounts_serves_an_empty_root` (G10, G11, G13).
|
||||||
- [ ] **A5 — `get_lib_root` maps the sorted mounts** to `LibraryNodeChild::new`
|
- [x] **A5 — `get_lib_root` maps the sorted mounts** to `LibraryNodeChild::new`
|
||||||
(no per-provider block, no re-sort). *Verifies:*
|
(no per-provider block, no re-sort). *Verifies:*
|
||||||
`root_lists_mounted_providers_in_order` (G12).
|
`root_lists_mounted_providers_in_order` (G12).
|
||||||
- [ ] **A6 — `build()` registers mounts.** Each provider's init block stays as
|
- [x] **A6 — `build()` registers mounts.** Each provider's init block stays as
|
||||||
is; on success it pushes a `Mount` instead of assigning a field. `/crabidy`
|
is; on success it pushes a `Mount` instead of assigning a field. `/crabidy`
|
||||||
and `/orphans` push after the store exists. *Verifies:* full test suite +
|
and `/orphans` push after the store exists. *Verifies:* full test suite +
|
||||||
`cargo clippy -- -D warnings` (G2).
|
`cargo clippy -- -D warnings` (G2).
|
||||||
- [ ] **A7 — Commit A.** `cargo test -p crabidy-server`, clippy, fmt all clean;
|
- [x] **A7 — Commit A.** `cargo test -p crabidy-server`, clippy, fmt all clean;
|
||||||
no `Cargo.toml` change in this commit (G1/G2 hold trivially).
|
no `Cargo.toml` change in this commit (G1/G2 hold trivially).
|
||||||
|
|
||||||
## B — The features
|
## B — The features
|
||||||
|
|
||||||
- [ ] **B1 — Manifests** (done in api-design; re-verify): `crabidy-server`
|
- [x] **B1 — Manifests** (done in api-design; re-verify): `crabidy-server`
|
||||||
(`default = all-providers + opus + spectrum + web-ui`, gated deps optional),
|
(`default = all-providers + opus + spectrum + web-ui`, gated deps optional),
|
||||||
`audio-player` (`opus`), `cbd-tui` (`notifications`), `cbd` (pass-through of
|
`audio-player` (`opus`), `cbd-tui` (`notifications`), `cbd` (pass-through of
|
||||||
all of them). *Verifies:* G4, G5; `cargo tree -p crabidy-server` at defaults
|
all of them). *Verifies:* G4, G5; `cargo tree -p crabidy-server` at defaults
|
||||||
matches the pre-change tree (G1).
|
matches the pre-change tree (G1).
|
||||||
- [ ] **B2 — `settings`: `BUILT_IN_PROVIDERS`, `provider_enabled` bounded by
|
- [x] **B2 — `settings`: `BUILT_IN_PROVIDERS`, `provider_enabled` bounded by
|
||||||
it, `unavailable_providers()`, `ensure_default` writing only built-ins.**
|
it, `unavailable_providers()`, `ensure_default` writing only built-ins.**
|
||||||
Replace the `todo!()`. *Verifies:*
|
Replace the `todo!()`. *Verifies:*
|
||||||
`an_absent_providers_key_enables_every_built_in_provider`,
|
`an_absent_providers_key_enables_every_built_in_provider`,
|
||||||
`unavailable_providers_reports_built_out_and_unknown_names`,
|
`unavailable_providers_reports_built_out_and_unknown_names`,
|
||||||
`ensure_default_lists_only_built_in_providers`,
|
`ensure_default_lists_only_built_in_providers`,
|
||||||
`a_built_out_provider_cannot_be_enabled` (G4, G25, G26).
|
`a_built_out_provider_cannot_be_enabled` (G4, G25, G26).
|
||||||
- [ ] **B3 — Per-provider `#[cfg]` in `build()` only.** Wrap each provider's
|
- [x] **B3 — Per-provider `#[cfg]` in `build()` only.** Wrap each provider's
|
||||||
init+registration block in `#[cfg(feature = "…")]`; the `enabled.*` toggle
|
init+registration block in `#[cfg(feature = "…")]`; the `enabled.*` toggle
|
||||||
read stays inside the block. Provider `use`/imports move to the same gate.
|
read stays inside the block. Provider `use`/imports move to the same gate.
|
||||||
*Verifies:* the matrix (B12) compiles each provider alone; G9 by reading.
|
*Verifies:* the matrix (B12) compiles each provider alone; G9 by reading.
|
||||||
- [ ] **B4 — `fs`: gate the store half.** `#[cfg(feature = "fs")]` on the
|
- [x] **B4 — `fs`: gate the store half.** `#[cfg(feature = "fs")]` on the
|
||||||
`crabidy_store`, `capture`, and `orphans` modules (`lib.rs`), on
|
`crabidy_store`, `capture`, and `orphans` modules (`lib.rs`), on
|
||||||
`ProviderOrchestrator::crabidy_store`/its field, on `Playback`'s store field
|
`ProviderOrchestrator::crabidy_store`/its field, on `Playback`'s store field
|
||||||
and `restore_current` and the persister, and on the `/fs`, `/crabidy`,
|
and `restore_current` and the persister, and on the `/fs`, `/crabidy`,
|
||||||
|
|
@ -75,66 +75,66 @@ devenv shell -- bash -lc 'CARGO_TARGET_DIR="$(pwd)/target-claude" cargo … '
|
||||||
parameter becomes `#[cfg]`-free by threading `Option<Arc<CrabidyStore>>`
|
parameter becomes `#[cfg]`-free by threading `Option<Arc<CrabidyStore>>`
|
||||||
only under the feature (adjust the two call sites in `lib.rs` and the
|
only under the feature (adjust the two call sites in `lib.rs` and the
|
||||||
playback tests). *Verifies:* G14, G17; `--no-default-features` build (B12).
|
playback tests). *Verifies:* G14, G17; `--no-default-features` build (B12).
|
||||||
- [ ] **B5 — `fs`: RPC degradation.** Without `fs`, `CaptureLibraryNode` and
|
- [x] **B5 — `fs`: RPC degradation.** Without `fs`, `CaptureLibraryNode` and
|
||||||
`SaveQueue` return `Status::unimplemented` naming the feature; with `fs`,
|
`SaveQueue` return `Status::unimplemented` naming the feature; with `fs`,
|
||||||
unchanged. No panic, no hang. *Verifies:* G15 by reading + a
|
unchanged. No panic, no hang. *Verifies:* G15 by reading + a
|
||||||
`--no-default-features` smoke run.
|
`--no-default-features` smoke run.
|
||||||
- [ ] **B6 — `fs`: the `scan` command.** Gate `AUDIO_EXTENSIONS`,
|
- [x] **B6 — `fs`: the `scan` command.** Gate `AUDIO_EXTENSIONS`,
|
||||||
`is_audio_file`, `scan`, `scan_dir`, `scan_file` and their tests behind
|
`is_audio_file`, `scan`, `scan_dir`, `scan_file` and their tests behind
|
||||||
`fs`; without it the dispatcher in `crabidy-server/src/main.rs` and
|
`fs`; without it the dispatcher in `crabidy-server/src/main.rs` and
|
||||||
`cbd/src/main.rs` prints "this binary was built without the `fs` feature"
|
`cbd/src/main.rs` prints "this binary was built without the `fs` feature"
|
||||||
and exits non-zero. The clap surface does not change. *Verifies:* G16, G9.
|
and exits non-zero. The clap surface does not change. *Verifies:* G16, G9.
|
||||||
- [ ] **B7 — `opus` in `audio-player`.** Gate `mod opus_source` and the
|
- [x] **B7 — `opus` in `audio-player`.** Gate `mod opus_source` and the
|
||||||
`is_ogg_opus` sniff in `player_engine::build_source`; without the feature an
|
`is_ogg_opus` sniff in `player_engine::build_source`; without the feature an
|
||||||
Ogg-Opus header produces `Err` naming the missing feature (the existing
|
Ogg-Opus header produces `Err` naming the missing feature (the existing
|
||||||
"failed to decode" path), never a panic. *Verifies:* G18, G19 (read + the
|
"failed to decode" path), never a panic. *Verifies:* G18, G19 (read + the
|
||||||
`opus`-off matrix entry); `opus_source`'s own tests stay under the feature.
|
`opus`-off matrix entry); `opus_source`'s own tests stay under the feature.
|
||||||
- [ ] **B8 — `opus` in `scan`.** `AUDIO_EXTENSIONS` includes `"opus"` only
|
- [x] **B8 — `opus` in `scan`.** `AUDIO_EXTENSIONS` includes `"opus"` only
|
||||||
under the feature. *Verifies:* `opus_is_scannable_only_with_the_decoder`,
|
under the feature. *Verifies:* `opus_is_scannable_only_with_the_decoder`,
|
||||||
`scan_walks_past_opus_files_it_cannot_play` (G20).
|
`scan_walks_past_opus_files_it_cannot_play` (G20).
|
||||||
- [ ] **B9 — `spectrum`.** Gate `mod spectrum`, `spawn_spectrum_task`, and its
|
- [x] **B9 — `spectrum`.** Gate `mod spectrum`, `spawn_spectrum_task`, and its
|
||||||
call; `realfft` optional. `Player::spectrum_tap()` unchanged. *Verifies:*
|
call; `realfft` optional. `Player::spectrum_tap()` unchanged. *Verifies:*
|
||||||
G21; the `spectrum`-off matrix entry.
|
G21; the `spectrum`-off matrix entry.
|
||||||
- [ ] **B10 — `notifications` in `cbd-tui`.** Gate the `notify_rust` import and
|
- [x] **B10 — `notifications` in `cbd-tui`.** Gate the `notify_rust` import and
|
||||||
the notify call in `app/now_playing.rs`; the config key stays accepted so an
|
the notify call in `app/now_playing.rs`; the config key stays accepted so an
|
||||||
existing `cbd-tui.toml` still parses. *Verifies:* G23; `cbd-tui
|
existing `cbd-tui.toml` still parses. *Verifies:* G23; `cbd-tui
|
||||||
--no-default-features` build + `cargo test -p cbd-tui`.
|
--no-default-features` build + `cargo test -p cbd-tui`.
|
||||||
- [ ] **B11 — `features` command.** `cli::build_features()` +
|
- [x] **B11 — `features` command.** `cli::build_features()` +
|
||||||
`cli::features()` (replace the `todo!()`s), wired into
|
`cli::features()` (replace the `todo!()`s), wired into
|
||||||
`ServerCommand::Features` and `CbdCommand::Features`; plus one `info!` line
|
`ServerCommand::Features` and `CbdCommand::Features`; plus one `info!` line
|
||||||
at startup listing the
|
at startup listing the
|
||||||
build, and one `warn!` per `unavailable_providers()` entry. *Verifies:*
|
build, and one `warn!` per `unavailable_providers()` entry. *Verifies:*
|
||||||
`build_features_lists_providers_then_extras` (G24, G25).
|
`build_features_lists_providers_then_extras` (G24, G25).
|
||||||
- [ ] **B12 — The build matrix as a devenv script.** `check-features` runs
|
- [x] **B12 — The build matrix as a devenv script.** `check-features` runs
|
||||||
`cargo clippy -- -D warnings` (and `cargo test` where meaningful) over:
|
`cargo clippy -- -D warnings` (and `cargo test` where meaningful) over:
|
||||||
defaults · `--no-default-features` · each of the seven providers alone ·
|
defaults · `--no-default-features` · each of the seven providers alone ·
|
||||||
defaults-minus-`opus` · minus-`spectrum` · minus-`web-ui` · `-p cbd-tui
|
defaults-minus-`opus` · minus-`spectrum` · minus-`web-ui` · `-p cbd-tui
|
||||||
--no-default-features` · `-p audio-player --no-default-features`. Add
|
--no-default-features` · `-p audio-player --no-default-features`. Add
|
||||||
`cargo-hack` to devenv for ad-hoc deeper sweeps. *Verifies:* G6, G7.
|
`cargo-hack` to devenv for ad-hoc deeper sweeps. *Verifies:* G6, G7.
|
||||||
- [ ] **B13 — Commit B** once the whole matrix is green and no
|
- [x] **B13 — Commit B** once the whole matrix is green and no
|
||||||
`todo!()`/`unimplemented!()` from the stub stage remains (G28).
|
`todo!()`/`unimplemented!()` from the stub stage remains (G28).
|
||||||
|
|
||||||
## C — Packaging and docs
|
## C — Packaging and docs
|
||||||
|
|
||||||
- [ ] **C1 — `flake.nix`.** Native build: replace the bare
|
- [x] **C1 — `flake.nix`.** Native build: replace the bare
|
||||||
`--no-default-features` with an explicit
|
`--no-default-features` with an explicit
|
||||||
`--no-default-features --features all-providers,opus,spectrum`; leave the
|
`--no-default-features --features all-providers,opus,spectrum`; leave the
|
||||||
aarch64 cross build on defaults (`web-ui` on). *Verifies:* G8;
|
aarch64 cross build on defaults (`web-ui` on). *Verifies:* G8;
|
||||||
`nix build .#crabidy` if the sandbox allows, otherwise read + the equivalent
|
`nix build .#crabidy` if the sandbox allows, otherwise read + the equivalent
|
||||||
cargo invocation.
|
cargo invocation.
|
||||||
- [ ] **C2 — `docs/src/build-features.md`** — the feature table, what each one
|
- [x] **C2 — `docs/src/build-features.md`** — the feature table, what each one
|
||||||
costs to lose, the two worked examples (local-files appliance:
|
costs to lose, the two worked examples (local-files appliance:
|
||||||
`--no-default-features --features fs,opus`; streaming box:
|
`--no-default-features --features fs,opus`; streaming box:
|
||||||
`--no-default-features --features tidal,web-ui,opus,spectrum`), the
|
`--no-default-features --features tidal,web-ui,opus,spectrum`), the
|
||||||
compile-time vs runtime distinction, and the `features` command. Linked from
|
compile-time vs runtime distinction, and the `features` command. Linked from
|
||||||
`SUMMARY.md`; `docs/src/config.md` cross-references it from the `providers`
|
`SUMMARY.md`; `docs/src/config.md` cross-references it from the `providers`
|
||||||
key. *Verifies:* G27 + `mdbook build`.
|
key. *Verifies:* G27 + `mdbook build`.
|
||||||
- [ ] **C3 — `README.md`** gains a short "tailored builds" pointer.
|
- [x] **C3 — `README.md`** gains a short "tailored builds" pointer.
|
||||||
*Verifies:* G27.
|
*Verifies:* G27.
|
||||||
- [ ] **C4 — `plan/summary.md`** entry describing what shipped, the decisions
|
- [x] **C4 — `plan/summary.md`** entry describing what shipped, the decisions
|
||||||
taken autonomously, and what stayed out (auth, HLS, spectrum tap — with the
|
taken autonomously, and what stayed out (auth, HLS, spectrum tap — with the
|
||||||
reason). *Verifies:* dev-flow convention.
|
reason). *Verifies:* dev-flow convention.
|
||||||
- [ ] **C5 — Commit C.**
|
- [x] **C5 — Commit C.**
|
||||||
|
|
||||||
## Deferred / not done (recorded, not silently dropped)
|
## Deferred / not done (recorded, not silently dropped)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1201,3 +1201,81 @@ covering enter/step/jump/back-sweep/exit/Esc/non-move-exit/node-focus-exit/
|
||||||
is_queable/filter); clippy `-D warnings` and fmt clean; markdownlint clean. Not
|
is_queable/filter); clippy `-D warnings` and fmt clean; markdownlint clean. Not
|
||||||
exercised: live in-terminal keypresses (no TTY here) — the pure dispatch/table
|
exercised: live in-terminal keypresses (no TTY here) — the pure dispatch/table
|
||||||
logic is fully unit-tested and the render path compiles.
|
logic is fully unit-tested and the render path compiles.
|
||||||
|
|
||||||
|
## build features (2026-07-25)
|
||||||
|
|
||||||
|
Guarded each provider, Opus decoding, the spectrum, and desktop
|
||||||
|
notifications behind Cargo features (all default-on) so people can build a
|
||||||
|
non-bloated binary, with the dependency graph aligned to the flags. Ran the
|
||||||
|
full dev-flow: `architecture/build-features.md` (D1–D11),
|
||||||
|
`quality/build-features.md` (G1–G30), `plan/build-features.md` (A/B/C).
|
||||||
|
Three commits.
|
||||||
|
|
||||||
|
**A — mount registry** (`crabidy-server/src/provider.rs`). The orchestrator
|
||||||
|
held nine `Option<Arc<ConcreteClient>>` fields and repeated the same
|
||||||
|
`if …_owns(path)` chain across eight `ProviderClient` methods.
|
||||||
|
`ProviderClient` is dyn-compatible (`init` carries `Self: Sized`), so it now
|
||||||
|
holds `mounts: Vec<Mount>` of `Arc<dyn ProviderClient>` and every method is
|
||||||
|
one `owner(path)` lookup — ~600 lines of repetition gone, and a provider is
|
||||||
|
named in exactly one place, which is what made the feature work tractable.
|
||||||
|
Ordering (crabidy first, orphans last, rest alphabetical) moved to
|
||||||
|
`from_mounts`; the five config-file providers share a `mount_from_config`
|
||||||
|
helper. Behaviour-preserving, landed on its own with 6 new unit tests
|
||||||
|
(ownership boundaries, dispatch, typed errors for unowned paths, ordering,
|
||||||
|
the empty registry, dyn dispatch of an overridden `resolve_tracks_into`).
|
||||||
|
|
||||||
|
**B — the features.** `crabidy-server`: `tidal`, `youtube`, `fyyd`, `abs`,
|
||||||
|
`soundcloud`, `jamendo`, `fs`, `opus`, `spectrum`, `web-ui`, plus
|
||||||
|
`all-providers`; `cbd` forwards all of them and adds `notifications`;
|
||||||
|
`cbd-tui` owns `notifications`; `audio-player` owns `opus`. Gated deps are
|
||||||
|
`optional = true`, and the three feature-carrying local crates get
|
||||||
|
`default-features = false` in `[workspace.dependencies]` (a member cannot
|
||||||
|
drop an inherited default).
|
||||||
|
|
||||||
|
**Answers to the two questions in the request:** `scan` *did* already treat
|
||||||
|
`.opus` as playable; that entry is now conditional on `opus`, so a build
|
||||||
|
that cannot decode Opus will not index it either. And `fs` off does take
|
||||||
|
`/crabidy` and `/orphans` with it.
|
||||||
|
|
||||||
|
Decisions taken autonomously (dev-flow, no questions):
|
||||||
|
|
||||||
|
- **A feature must pay for itself in dependencies.** That is why `crabidy`
|
||||||
|
and `orphans` are *not* separate features (no dependency of their own —
|
||||||
|
`crabidy_store`/`capture`/`orphans` are written against `fsdy`), and why
|
||||||
|
`hls.rs`, `windowed_http.rs`, and `spectrum_tap.rs` stay unconditional.
|
||||||
|
Finer-grained control already exists at runtime in the `providers` list.
|
||||||
|
- **`fs` is one coherent unit**: `/fs`, the content store, `/crabidy`,
|
||||||
|
`/orphans`, bookmarks/captures, queue persistence, and `scan`. Without it
|
||||||
|
`CaptureLibraryNode`/`SaveQueue` answer `Unimplemented` and `scan` reports
|
||||||
|
the missing feature — no panics, no hangs.
|
||||||
|
- **`[auth]`/argon2 is deliberately not gated** (D9): a build that ignored
|
||||||
|
configured role hashes would run an intended-to-be-locked server open.
|
||||||
|
Refused as a fail-open hole for a small dependency.
|
||||||
|
- **The compile-time set bounds the runtime one**: `BUILT_IN_PROVIDERS`
|
||||||
|
filters `provider_enabled`, the auto-written default config lists only
|
||||||
|
built-ins, and a `providers` entry this binary lacks logs one warning
|
||||||
|
(fail-open, unlike auth). `crabidy-server features` / `cbd features` print
|
||||||
|
the compiled set and startup logs it.
|
||||||
|
- **An internal `_any-provider` marker feature** (every provider enables it)
|
||||||
|
expresses "is any provider compiled in?", which Cargo features cannot;
|
||||||
|
it keeps the legal-but-degenerate zero-provider build warning-free without
|
||||||
|
blanket `allow`s.
|
||||||
|
- **`flake.nix` had to change in the same breath**: its native package
|
||||||
|
passed a bare `--no-default-features` (meaning "all but `web-ui`"), which
|
||||||
|
after this work would have shipped a provider-*less* binary. Now
|
||||||
|
`--features all-providers,opus,spectrum,notifications`.
|
||||||
|
|
||||||
|
Verification: `devenv shell -- check-features` (new script) runs the curated
|
||||||
|
matrix — defaults, `--no-default-features`, each of the seven providers
|
||||||
|
alone, each extra dropped, the two documented examples, `audio-player`,
|
||||||
|
`cbd-tui`, `cbd` — all clippy-clean under `-D warnings`, with tests at both
|
||||||
|
extremes. Full default suite green (88 server + 90 tui + the rest). **G1
|
||||||
|
verified mechanically**: the default `cargo tree -p crabidy-server` set is
|
||||||
|
identical to the pre-change tree. Confirmed by inspection of `cargo tree`
|
||||||
|
that `--no-default-features` drops every gated crate, `opus`-off drops
|
||||||
|
`opusic-sys`/`symphonia-adapter-libopus`, and `cbd-tui
|
||||||
|
--no-default-features` drops `notify-rust`. Smoke-ran both the
|
||||||
|
provider-less and the `fs,opus` binaries under isolated XDG dirs: startup
|
||||||
|
logs the feature list, writes a default config listing only built-ins, and
|
||||||
|
warns once per unavailable name. Not exercised: `nix build` (not run here)
|
||||||
|
and playback of an Opus file in an `opus`-less build (no audio device).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue