Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

CratePurpose
openstack-keystone-identity-driver-sqlSQL backend for identity
openstack-keystone-k8s-auth-driver-sqlSQL backend for Kubernetes auth
openstack-keystone-k8s-auth-driver-raftRaft backed backend for Kubernetes auth
openstack-keystone-spiffe-driver-raftRaft 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.rs discovers it automatically.
  • Compile-time enforcement: The build fails if any openstack-keystone-* dependency is missing pub 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.rs code generation, include!()).

Negative

  • Every openstack-keystone-* crate has a one-line no-op function. This is negligible overhead.
  • build.rs adds 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 generation
  • crates/keystone/src/lib.rsinclude!() of generated anchors
  • crates/cli-manage/src/db.rs — binary-side _INVENTORY_LINK reference