Building
The workspace roster, the feature lanes a default build never compiles, the one committed cargo config, and why the xtask convention is spelled differently here.
Vike Trader is a Cargo workspace of small crates. A plain cargo build --workspace is a real build
of most of it — but not of all of it, and the gap is the first thing worth understanding before you
change anything.
The roster is derived, never written down
CI does not read a hand-maintained list of crates. It computes one:
sorted(names - EXCLUDE_FROM_CI) over the workspace members cargo metadata reports. That is
xtask/src/ci/mod.rs's ci_crates, and the consequence is the point — a crate joins the merge
gate the moment it joins [workspace].members, with nothing to edit to let it in.
xtask/src/ci/tables.rs's EXCLUDE_FROM_CI is the whole of the exception, and each row carries its
reason: the vendored IBKR wire-protocol copy (exercised through its own feature lane); vike-desktop,
the egui/wgpu desktop binary, whose eframe + wgpu closure matches no other lane's cache and which
is instead checked, linted and unit-tested by its own dedicated job; and vike-backfill, which only
builds and tests under features. xtask itself is deliberately absent from that list — excluding
the tool that derives the merge gate would leave it as the one crate the merge gate never compiles.
A default build is not the whole tree
Large parts of the workspace are behind off-by-default Cargo features, and the reason is usually that a default build should not compile them: an order signer, a proprietary SDK shim, a remote control channel, a DataFusion dependency. Each of those configurations gets a feature lane.
Two files own the lanes between them, and they own different halves.
xtask/src/ci/tables.rs's FEATURE_SUITES decides which suite keys the plan may emit and which
crates trigger each one; the shell script CI runs decides, in its case arms, what each key
actually runs. xtask/src/lib.rs's header states that split, and records that moving the lane
commands into the crate would be a separate change with its own gate to rewrite. The features job
wires the first straight into the second, so the two key sets are one roster written twice — which
is why they are gated against each other. Nearly every row there carries a written argument for the
coverage hole it closes, and tables.rs states the rule for editing it: a row there is not a
preference, it is a claim about what CI would otherwise fail to test.
A lane is defined in one place, and this page is not it
What a lane compiles is decided by the case arms of the feature-suite shell script CI executes;
xtask/src/lib.rs's header records exactly that split. That script ships with the private build
tooling rather than the published tree, so no summary of it — this page included — can be checked
against it. What you can open is the other half: xtask/src/ci/tables.rs's FEATURE_SUITES names
every suite key and the crates that trigger it.
Treat a lane's crate list here as history, not as a spelling. Two lanes have already been widened,
and each widening is argued on its own trigger row in that table. The FXCM one twice: first when
the live mount arm landed behind vike-mount's own fxcm feature, then when the release grew an
FXCM-linked daemon asset. The IBKR one twice: first when the live-mount chain above the bridge
crate turned out to be compiled by nothing, then again when the daemon itself grew an ibkr
feature and the chain reached one crate further. Re-spelling a lane from a prose copy is how a
chain silently drops out of it.
The one committed cargo config
Two files configure the compiler for everyone who clones the repository: .cargo/config.toml and
the root Cargo.toml's profile tables. Neither is anybody's code, both are read on every build, and
a change to either arrives without a call site to review it — which is why
crates/vike-ops/tests/build_config_gate.rs exists.
It reads every file cargo would honour, not the one at the root. Cargo merges .cargo/config.toml
— and the pre-1.39 extensionless .cargo/config — out of every ancestor directory of wherever it was
invoked, closest-wins, so a config placed under crates/ outranks the repository root for the normal
inner loop. only_the_repository_root_carries_a_cargo_config refuses that shape outright: a setting
that applies depending on which directory you happen to be standing in cannot be reviewed by reading
a diff.
Two content rules follow. every_cargo_config_table_is_windows_scoped requires every table to be
scoped to x86_64-pc-windows-msvc, as an allowlist rather than a banned-table list, because a banned
list is a hand copy of cargo's vocabulary and would rot the day cargo adds a table. And
no_committed_cargo_setting_decides_which_program_runs refuses rustc-wrapper, rustc, rustdoc
and runner in every table, the Windows one included. linker is deliberately not in that set:
a linker receives object files and rustc's own link arguments, while rustc-wrapper and rustc
receive every source file in the tree, rustdoc runs the doctests, and runner receives the
compiled test binary — which would turn cargo test, the command every gate runs under, into
"execute a program of this file's choosing" before any assertion in any of them is reached.
The guarantee, exactly: a committed cargo setting in this repository may name the linker the Windows dev box runs, and no other program, on no other host.
Why just xtask, and not cargo xtask
The usual convention for a repository's build tool is a cargo xtask alias. There is deliberately
no such alias here. An alias lives in .cargo/config.toml; build_config_gate.rs requires every
leaf path in that file to sit under target.x86_64-pc-windows-msvc; and cargo does not allow
[alias] to be target-scoped. No spelling of the convention passes the gate that protects the Linux
boxes holding the credential store, so xtask/src/main.rs says so in its own header and a just
recipe is the entry point instead: just xtask <cmd>, which is cargo run -p xtask -- <cmd>.
That tool has four commands and one consumer each — and no fifth, because a command nobody consumes is a surface that rots unobserved, which is the defect the crate was written to remove:
| command | consumer | output |
|---|---|---|
crates | the local recipes' ci_crates := | one line, -p a -p b … |
affected | both workflows' plan step | seven key=value lines |
feature-lanes | a human, and the gates | key<TAB>trigger,trigger |
docs-bins | the release's bins.json docs-data asset | JSON: every workspace [[bin]] |
CI derivation explains how affected selects and how the local
recipes are held to the same spelling; Testing covers the tiers that run
over what you built.
Developer guide
How the workspace defends its own claims — what a gate is here, why prose that can rot is replaced by a test, and where each family of gates lives.
CI derivation
One implementation of what CI runs, its selection rules and diff-base ladder, and the five gates that hold the local recipes and CI to a single spelling.