18. Plugin Linking — Anchor Convention and build.rs
Date: 2026-05-28
Status
Accepted
Context
Keystone uses inventory for plugin
registration. Driver crates (SQL, Raft, etc.) call inventory::submit! to
register their SqlDriverRegistration items into a global table discovered at
runtime by inventory::iter.
The problem is that the Rust compiler and linker treat inventory::submit!
items as dead code when no strong symbol from the contributing crate is
referenced by the binary. The linker skips the .rlib archive members entirely,
causing inventory::iter to silently yield no results — drivers are present in
the dependency tree but invisible at runtime.
Decision
Crate Naming Convention
All driver crates follow the naming scheme:
openstack-keystone-<PROVIDER>-driver-<DRIVER_TYPE>
Where <PROVIDER> identifies the subsystem (e.g. identity, role,
federation) and <DRIVER_TYPE> identifies the backend technology (e.g. sql,
raft).
Examples:
| Crate | Purpose |
|---|---|
openstack-keystone-identity-driver-sql | SQL backend for identity |
openstack-keystone-k8s-auth-driver-sql | SQL backend for Kubernetes auth |
openstack-keystone-k8s-auth-driver-raft | Raft backed backend for Kubernetes auth |
openstack-keystone-spiffe-driver-raft | Raft backed backend for SPIFFE |
Infrastructure crates (api-types, config, core, core-types,
distributed-storage, token-fernet) do not use this naming convention —
they are not driver crates.
pub fn anchor() Convention
Every openstack-keystone-* crate exports a no-op function:
#![allow(unused)]
fn main() {
/// Linkage anchor — see ADR-0018. Referenced by `keystone` build.rs so the
/// linker extracts this crate's `.rlib` members, keeping `inventory::submit!`
/// sections visible at runtime.
#[allow(dead_code)]
pub fn anchor() {}
}
This is a compile-time convention enforced by the build script. Any crate in the
dependency tree that does not provide pub fn anchor() will cause a compile
error in the binary crate.
build.rs Auto-Discovery
crates/keystone/build.rs parses Cargo.toml at build time, discovers all
dependencies whose name starts with openstack-keystone-, and generates a
sidecar file (OUT_DIR/inventory_anchors.rs) containing a #[used] static that
references each crate’s anchor() function:
#![allow(unused)]
fn main() {
#[used]
pub static _ANCHORS: &[fn()] = &[
openstack_keystone_appcred_driver_sql::anchor,
openstack_keystone_assignment_driver_sql::anchor,
// ... (auto-discovered, sorted)
];
}
Only deps with driver in the name are collected (the build.rs filter),
ensuring only actual driver crates are anchored.
The generated file is included into lib.rs via include!(), creating a strong
reference chain:
keystone-manage (binary crate)
└─ openstack_keystone (keystone crate)
├─ #[used] static _ANCHORS (generated by build.rs)
│ ├─ appcred_driver_sql::anchor ── forces .rlib extraction
│ ├─ identity_driver_sql::anchor ── forces .rlib extraction
│ └─ ... (all *_driver_* crates)
└─ (transitive deps carry inventory::submit! items)
Binary Crate Linkage
Binary crates (e.g. cli-manage) reference the generated static from the
keystone crate to create the linkage chain:
#![allow(unused)]
fn main() {
#[used]
static _INVENTORY_LINK: &[fn()] = openstack_keystone::_ANCHORS;
}
This prevents the linker from stripping the keystone crate members, which in
turn prevents stripping of all driver crate members.
Consequences
Positive
- Zero manual maintenance: Adding a new driver crate only requires adding it
as a dependency of
crates/keystone.build.rsdiscovers it automatically. - Compile-time enforcement: The build fails if any
openstack-keystone-*dependency is missingpub fn anchor(), preventing accidental breakage. - Naming conveys intent: The
openstack-keystone-<PROVIDER>-driver-<DRIVER_TYPE>suffix makes it immediately clear what crate provides. - No special tooling: The solution uses standard Rust features (
#[used]static,build.rscode generation,include!()).
Negative
- Every
openstack-keystone-*crate has a one-line no-op function. This is negligible overhead. build.rsadds a trivial compilation step (~0.01s in practice, parses a single small text file).
Migration Notes
Existing driver crates were renamed in a single commit. All references
(Cargo.toml workspace members, Cargo.toml dependencies, plugin_manager.rs,
test code, Cargo.toml of test crates) were updated via the subagent.
See Also
crates/keystone/build.rs— auto-discovery and anchor generationcrates/keystone/src/lib.rs—include!()of generated anchorscrates/cli-manage/src/db.rs— binary-side_INVENTORY_LINKreference