a simple cli to lint and fix recurrent issues in rust workspaces surrounding 'features'. it can be installed with cargo install zepter and is documented here. it is most prominently used in the polkadot-sdk but can be used in any rust repo.

motivation

missing feature configuration can result in unexpected build errors that are very difficult to debug. especially when the dependency tree is deep.
we also kept getting external bug reports that our code would not build in downstream repos. resolving of these issues would take me about an hour; surely this was an unacceptable state.

now there are sadly many ways to mis-configure features in rust. zepter prevents about the three most common ones. it turns out that this was enough to cut the bug report rate down to zero. internal developers thanked me as well that this was a much welcomed change to reduce development time.

architecture

zepter extracts all relevant data from a rust workspace by utilizing cargo metadata. as user input it accepts a set of rules that must not be violated. it then creates a graph (directed-acyclic-graph) and tries to find violations of each rule. if that fails, the program succeeds.

usage

running the cli is enough to detect and fix mostly all issues in our code base. this is possible since polkadot-sdk has a config file that defines the default behaviour.

$ zepter 
[INFO] Running workflow 'default'
[INFO] 1/2 lint propagate-feature
[INFO] 2/2 format features

helpful error messages are provided in the ci through custom config:

Polkadot-SDK uses the Zepter CLI to detect abnormalities in the feature configuration.
It looks like one more more checks failed; please check the console output. You can try to automatically address them by running `zepter`.
Otherwise please ask directly in the Merge Request, GitHub Discussions or on Matrix Chat, thank you.

performance

the dag code is reasonably fast that a run in our codebase (488 crates) with multiple lints takes about 2 seconds. in general there can still be a lot of optimization, especially caching and not re-computing things multiple times. the code was just written very quickly (=optimized for dev speed :P).

the slowest part is the exhaustive path search in the dag, luckily this is embarrassingly parallel and could be optimized. the most memory intense operations are the generation of transitive closures of the dag. in most cases it is enough to build the transitive closure of a subset, which reduces memory consumption.