Features
Cabin features are public, additive, named-boolean capabilities the
user (or a downstream consumer) selects at build time. Features may
imply other features and may enable optional dependencies on the
same package. They are declared in the [features] table of
cabin.toml.
Manifest syntax
[package]
name = "demo"
version = "0.1.0"
[features]
default = ["simd"]
simd = []
ssl = []
full = ["simd", "ssl"]
Identifier grammar
Feature names are:
- non-empty;
- ASCII letters, digits,
_,-; - no whitespace,
/,., or:.
This keeps ordinary feature identifiers separate from the feature-entry syntax described below.
Rules
- The reserved
defaultkey holds the list of features enabled when the user does not pass--no-default-features. It may be omitted. - A feature value is a list of feature entries, each of which is one of:
"feature_name"— enables another local feature on the same package (transitive feature implication)."dep:dependency_name"— enables an optional Cabin package dependency declared by this package's[dependencies]table."dependency_name/feature_name"— requests a feature on a Cabin package dependency. If the dependency is optional this form also enables it.- The on-disk shape stays a list of strings; the typed
FeatureEntryview is produced lazily by the feature resolver. - Local feature references must point at another declared feature in the same package. Unknown local references are rejected with a clear error.
- Cycles between local features are rejected with a clear
feature definitions contain a cycle: a -> b -> aerror. - Declaring a normal feature called
defaultis rejected (the key is reserved for the default group). - Feature entries may only use ASCII letters, digits,
_,-,., plus the leadingdep:prefix or a single/separator. Anything else is rejected with a clear error.
Optional dependencies and the feature resolver
Features can also turn Cabin package dependencies on and off, and can request features on dependency packages that are present. This layers cleanly on top of the dependency-kind model:
[dependencies]
fmt = { version = "^10", features = ["compile"], default-features = false }
openssl = { version = "^3", optional = true }
[features]
default = []
ssl = ["dep:openssl"]
full = ["ssl", "openssl/vendored"]
optional = truedeclares an optional Cabin package dependency (supported in[dependencies]; rejected in[dev-dependencies]and`system = truedeps`).features = ["..."]requests features on the dependency package; entries must be feature names declared by that dependency.default-features = falsedrops this edge's request for the dependency'sdefaultfeature. It does not globally disable the dependency's defaults — if any other edge requests defaults for the same dependency, the unified result still includes them.
The cross-package feature resolver lives in the cabin-feature
crate. Given a typed PackageGraph, the selected root indices, and
a RootFeatureRequest built from the CLI flags, it computes the
additive closure of:
- enabled features per package;
- enabled optional dependencies per package;
- per-edge feature requests applied to the depended-on package.
Resolution is deterministic (sorted iteration, fixed-point
worklist) and never touches the network. Errors are explicit and
testable: unknown root features, dep: on non-optional
dependencies, and requests for features the depended-on package
does not declare all surface with stable wording.
Effects on commands:
cabin resolve/update/fetchfilter disabled optional dependencies declared on local (workspace / path) packages out of the resolver / fetch / lockfile inputs. Optional dependencies declared on registry packages are skipped regardless of feature state. A feature request on a registry package does not enable that registry package's own optional dependencies. Dev dependencies remain excluded by default; system dependencies remain declaration-only.cabin buildsees the same filtered dep set, so a disabled optional dependency never enters the build graph or links into ordinary C++ targets.cabin packagepreservesoptional,features, anddefault-featuresper dependency in the canonical metadata document. Bare entries without overrides serialize as plain version-requirement strings so older readers stay happy.
CLI selection
cabin build / cabin resolve / cabin metadata accept the
same selection flag bundle:
--features <names> # repeatable; each value may be comma-separated
--all-features # enable every declared feature
--no-default-features # drop the [features].default set
cabin tree and the graph-only cabin explain subcommands
(package, target, source, and feature) also run the
feature-selection part of this bundle so unknown features and
invalid dep: feature entries surface consistently.
Default behavior:
- without
--no-default-features, the names listed under[features].defaultare enabled and expanded transitively; --all-featuresoverrides everything else and enables every declared feature;- CLI selections apply to the selected root / primary packages. Dependency feature requests declared on edges are then resolved additively through the graph.
Errors are validated up-front and reported with a clear message:
$ cabin build --features missing
unknown feature "missing" for package "demo"
cabin metadata
cabin metadata --format json includes a per-package features
block (omitted when empty so older consumers see the same JSON
shape they always have) and a resolved configuration block
whenever the package declared features:
{
"configuration": {
"features": ["simd"],
"fingerprint": "<64 hex chars>"
}
}
Selection flags passed to cabin metadata flow into the
configuration block exactly the way they do for cabin build.
Package metadata and registry preservation
cabin package writes feature declarations into the
<name>-<version>.json document next to the archive. cabin
publish --registry-dir <path> carries them through the registry's
per-package index file. cabin-index (local file index) and
cabin-index-http (sparse HTTP index) parse the optional
features field on every version entry and preserve it on
VersionMetadata. Older registry entries that omit the field
continue to load.
The resolver consults package features and dependency feature
requests when deciding whether optional package dependencies
declared on local (workspace / path) packages are active.
Optional dependencies declared on registry packages are
conservatively skipped: their per-edge features /
default-features requests round-trip through registry metadata,
but transitive feature state for registry packages does not
gate them on. The lockfile records resolved registry versions
only; per-BuildConfiguration state lives in the run-time
fingerprint and is not persisted to cabin.lock.