docs: README aimed at kernel contributors
States the scaffold status up front, documents what each verb shells out to and why those tools come from your tree rather than being vendored, and ends with a map of the 20 stubs grouped by how much they unblock.
This commit is contained in:
@@ -1,3 +1,226 @@
|
|||||||
# spectral
|
# spectral
|
||||||
|
|
||||||
|
A monolithic kernel work wrapper, to make it easy.
|
||||||
|
|
||||||
|
> **Status: scaffold.** The command surface parses, every verb is wired
|
||||||
|
> through `main` to its module, and the paths resolve — but the bodies are
|
||||||
|
> `todo!()`. Twenty of them. Nothing builds a kernel, boots qemu, scrapes
|
||||||
|
> bugzilla, or sends mail yet. What exists is the shape of the thing and the
|
||||||
|
> seams to fill in, which is exactly what the *Where to pitch in* section
|
||||||
|
> below is for.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The kernel patch lifecycle is a dozen commands you retype every time. Find
|
||||||
|
something to fix, write it, run checkpatch, run it again after `--fix`,
|
||||||
|
commit with the right trailer, build, boot it under qemu, diff against
|
||||||
|
master, work out who the maintainers are for those files, send. Then a week
|
||||||
|
later do it all again as v2, remembering to thread it off the original.
|
||||||
|
|
||||||
|
None of those steps are hard. They are just easy to get subtly wrong, and
|
||||||
|
the subtle mistakes — a missing `Signed-off-by`, a v2 that doesn't thread,
|
||||||
|
a missing CC — are the ones that get a patch ignored rather than reviewed.
|
||||||
|
|
||||||
|
spectral is a thin wrapper over that loop. It is deliberately *thin*: every
|
||||||
|
verb shells out to a tool you already have and already trust.
|
||||||
|
|
||||||
|
## What it wraps
|
||||||
|
|
||||||
|
Nothing here reimplements kernel tooling, and nothing vendors it:
|
||||||
|
|
||||||
|
| spectral | actually runs |
|
||||||
|
|---|---|
|
||||||
|
| `patch check` / `patch format` | `scripts/checkpatch.pl` **from your tree** |
|
||||||
|
| `patch commit` | `git commit` |
|
||||||
|
| `patch create` | `git diff <base>` |
|
||||||
|
| `patch submit` | `scripts/get_maintainer.pl` **from your tree**, then `git send-email` |
|
||||||
|
| `kernel test` | `make`, then `qemu-system-x86_64` |
|
||||||
|
| `kernel quest` | HTTP against `bugzilla.kernel.org` |
|
||||||
|
|
||||||
|
The two scripts are called out because it matters: they come from the tree
|
||||||
|
you are working in, so spectral cannot have a stale copy of the kernel's own
|
||||||
|
style rules or maintainer map. Upgrade your tree, get the new rules.
|
||||||
|
|
||||||
|
And because it is thin, you can always drop the wrapper. `patch submit
|
||||||
|
--dry-run` prints the recipients and the exact `git send-email` command line
|
||||||
|
instead of sending, so you can check the plumbing or just run it yourself.
|
||||||
|
|
||||||
|
## The loop
|
||||||
|
|
||||||
|
What the finished tool should feel like — none of this runs yet:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ spectral kernel quest
|
||||||
|
12345 usb: xhci: device does not enumerate after resume
|
||||||
|
https://bugzilla.kernel.org/show_bug.cgi?id=12345
|
||||||
|
|
||||||
|
# ... go and fix it, in the tree ...
|
||||||
|
|
||||||
|
$ spectral patch check
|
||||||
|
$ spectral patch format # checkpatch --fix, then check again
|
||||||
|
$ spectral patch commit "usb: xhci: re-arm the port after resume" --signoff
|
||||||
|
$ spectral kernel test # build, then boot it under qemu
|
||||||
|
$ spectral patch create 000-xhci-port-rearm
|
||||||
|
~/.spectral/patches/000-xhci-port-rearm.patch
|
||||||
|
|
||||||
|
$ spectral patch submit 000-xhci-port-rearm.patch --dry-run
|
||||||
|
# happy? drop --dry-run and it goes out
|
||||||
|
|
||||||
|
# ... wait for review ...
|
||||||
|
|
||||||
|
$ spectral patch update 000-xhci-port-rearm.patch -v 2
|
||||||
|
~/.spectral/patches/v2-000-xhci-port-rearm.patch
|
||||||
|
$ spectral patch submit v2-000-xhci-port-rearm.patch --in-reply-to '<msg-id-of-v1>'
|
||||||
|
```
|
||||||
|
|
||||||
|
One deviation from the obvious design: `patch update` is a single subcommand
|
||||||
|
taking `-v N` rather than a generated `update-v2` / `update-v3`. clap
|
||||||
|
subcommands are static, and a flag beats a subcommand you cannot tab-complete.
|
||||||
|
Leave `-v` off and it infers the next revision from the file's current `vN-`
|
||||||
|
prefix.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Needs a recent stable Rust — edition 2024, so **1.85 or newer**; developed
|
||||||
|
against 1.98.1.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ git clone https://git.spectoria.dev/huntedbytheirs/spectral.git
|
||||||
|
$ cd spectral
|
||||||
|
$ cargo build --release
|
||||||
|
$ install -Dm755 target/release/spectral ~/.local/bin/spectral
|
||||||
|
```
|
||||||
|
|
||||||
|
Not on crates.io, so `cargo install spectral` will get you something else.
|
||||||
|
`cargo install --path .` from a clone works too.
|
||||||
|
|
||||||
|
Beyond Rust you want:
|
||||||
|
|
||||||
|
- **a kernel tree** — for `scripts/checkpatch.pl` and
|
||||||
|
`scripts/get_maintainer.pl`. Both ship with the kernel; spectral does not
|
||||||
|
carry its own.
|
||||||
|
- **git with `send-email` configured** — `git send-email` must work from a
|
||||||
|
plain shell first. If it does not, `patch submit` cannot make it work, and
|
||||||
|
is not meant to.
|
||||||
|
- **qemu** — `qemu-system-x86_64` on `$PATH`, for `kernel test`.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
There is no config file yet. Two paths matter, and both come from the
|
||||||
|
environment with `$HOME`-relative defaults:
|
||||||
|
|
||||||
|
| what | resolution |
|
||||||
|
|---|---|
|
||||||
|
| kernel tree | `$SPECTRAL_KERNEL`, else `~/.spectral/linux` |
|
||||||
|
| patch dir | `~/.spectral/patches` |
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ export SPECTRAL_KERNEL=$HOME/src/linux
|
||||||
|
```
|
||||||
|
|
||||||
|
A tree is accepted only if it has `scripts/checkpatch.pl`; otherwise you get
|
||||||
|
`not a kernel source tree` rather than a confusing failure three steps later.
|
||||||
|
A missing tree reports the path and the variable that would have set it. The
|
||||||
|
tree is not required for `kernel quest`, which only needs the network.
|
||||||
|
|
||||||
|
`~/.config/spectral/config.toml` and a `spectral init` to clone the tree are
|
||||||
|
planned, not built — `src/config.rs` is the only file that will have to change
|
||||||
|
for either.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ spectral --help
|
||||||
A monolithic kernel work wrapper to make it easy.
|
A monolithic kernel work wrapper to make it easy.
|
||||||
|
|
||||||
|
Usage: spectral <COMMAND>
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
kernel Find work, build it, boot it
|
||||||
|
patch Carry a change from working tree to mailing list
|
||||||
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
```
|
||||||
|
|
||||||
|
`spectral kernel quest` · `kernel test`
|
||||||
|
|
||||||
|
`spectral patch check` · `format` · `commit` · `create` · `submit` · `update`
|
||||||
|
|
||||||
|
Every one of them has `--help` that says more than this README does — and,
|
||||||
|
for now, a body that panics with a description of what it is supposed to do.
|
||||||
|
|
||||||
|
## Where to pitch in
|
||||||
|
|
||||||
|
Every stub is a small, self-contained function with its signature and doc
|
||||||
|
comment already written, its caller already wired, and a `todo!()` naming the
|
||||||
|
command it should run. Pick one and the blast radius is that file. No stub
|
||||||
|
needs you to have read the rest of the crate.
|
||||||
|
|
||||||
|
Roughly in order of how much they unblock:
|
||||||
|
|
||||||
|
**`patch submit` — the most self-contained win.** Needs a kernel tree but no
|
||||||
|
network and no scraper.
|
||||||
|
|
||||||
|
- `patch/maintainers.rs` · `lookup` — run `get_maintainer.pl --git` over the
|
||||||
|
patch, split the output into `To:` and `Cc:`, and collect the files the
|
||||||
|
patch touches
|
||||||
|
- `patch/maintainers.rs` · `add_cc` — fold `--cc` flags in without duplicates
|
||||||
|
- `patch/mod.rs` · `submit` — the above plus `git send-email`, with
|
||||||
|
`--dry-run` stopping one step short
|
||||||
|
|
||||||
|
**`patch check` / `format` / `commit` / `create` — the everyday verbs.**
|
||||||
|
`patch/checkpatch.rs` · `run` already defines the `Target` enum (working
|
||||||
|
tree, a revision, or a patch file) and `Report` with an `is_clean`, so what
|
||||||
|
is missing is the process call and parsing the error/warning counts out of
|
||||||
|
its output.
|
||||||
|
|
||||||
|
**`patch update` — small, and worth doing with a test.** `patch/mod.rs` ·
|
||||||
|
`reroll_path` is the naming rule in one pure function: strip one leading
|
||||||
|
`vN-`, prepend `v<revision>-`. Re-running it at the same revision should be a
|
||||||
|
no-op, which is easier to assert than to describe.
|
||||||
|
|
||||||
|
**`kernel quest` — the fun one, if you like HTML.** `kernel/quest.rs` ·
|
||||||
|
`Bugzilla::fetch_open` is the only place `reqwest` and `scraper` earn their
|
||||||
|
place in `Cargo.toml`; `run` then applies `--filter` and picks one. The
|
||||||
|
`QuestSource` trait is the seam for swapping bugzilla for syzbot or a lore
|
||||||
|
thread later.
|
||||||
|
|
||||||
|
**`kernel test` — needs a machine you are willing to boot kernels on.**
|
||||||
|
`kernel/qemu.rs` · `build` then `boot`. Streaming serial output as it arrives
|
||||||
|
beats buffering it until qemu exits, which is what makes a boot hang
|
||||||
|
diagnosable.
|
||||||
|
|
||||||
|
**`git.rs` — the four plumbing calls.** `current_branch`, `diff_against`,
|
||||||
|
`commit`, `rev_parse`, each a few lines over the `run` that is already
|
||||||
|
written. Take these if you want to warm up on something tiny.
|
||||||
|
|
||||||
|
There are no tests yet. The naming rule in `reroll_path` and the recipient
|
||||||
|
split in `lookup` are the two that most want one.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ cargo fmt --all
|
||||||
|
$ cargo clippy --all-targets -- -D warnings
|
||||||
|
$ cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
All three are clean on `main` and are the bar for a change.
|
||||||
|
|
||||||
|
A few conventions, in the spirit of keeping the crate reviewable:
|
||||||
|
|
||||||
|
- `src/cli.rs` holds the whole command surface. It is one file on purpose —
|
||||||
|
the CLI is the specification, and reading it top to bottom should tell you
|
||||||
|
what the tool does.
|
||||||
|
- Command modules return what they produced; `main` prints it. Output policy,
|
||||||
|
formatting and all, lives in one place.
|
||||||
|
- Errors go through the one `Error` enum and `?`. The command paths have no
|
||||||
|
`unwrap`.
|
||||||
|
- A new stub gets a `todo!()` that names the command it will end up running.
|
||||||
|
If a whole module is unreachable until its caller exists, it carries a
|
||||||
|
single `#[allow(dead_code)]` with the reason attached — there are no
|
||||||
|
crate-wide allows, so the warnings come back as the stubs get filled in.
|
||||||
|
- No `unsafe`, no FFI.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT — see `LICENSE`, © 2026 huntedbytheirs.
|
||||||
|
|||||||
Reference in New Issue
Block a user