Rewrite the README and the source comments in a plainer register. The README had picked up the usual generated-prose tics: seventeen em dashes, inline bold headers over every work item, signposting lines like 'the two scripts are called out because it matters', and a two-word 'Twenty of them.' dropped in for emphasis. Replaced with commas, real section headings, and sentences that carry their own subject. Also fixes the two rustdoc warnings cargo doc was emitting: <NAME> and <N> in the subcommand doc comments were being parsed as unclosed HTML tags. cargo doc is clean now.
238 lines
8.4 KiB
Markdown
238 lines
8.4 KiB
Markdown
# 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. Every body is a
|
|
> `todo!()` stub. Nothing builds a kernel, boots qemu, scrapes bugzilla, or
|
|
> sends mail yet. What exists is the shape of the tool and the seams to fill
|
|
> in, and *Where to pitch in* below is the list.
|
|
|
|
## 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 maintains 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 ones that get a patch ignored rather than reviewed are the boring
|
|
details: a missing `Signed-off-by`, a v2 that does not thread, a maintainer
|
|
who never got CC'd.
|
|
|
|
spectral is a thin wrapper over that loop, and the thinness is the point.
|
|
Every verb shells out to a tool you already have and already trust.
|
|
|
|
## What it wraps
|
|
|
|
What each verb actually ends up running:
|
|
|
|
| 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` |
|
|
|
|
checkpatch.pl and get_maintainer.pl are worth calling out. They come from
|
|
the tree you are working in, so spectral cannot drift out of date with the
|
|
kernel's own style rules or maintainer map. Upgrade your tree and the new
|
|
rules apply.
|
|
|
|
Because it is thin, you can drop the wrapper at any point. `patch submit
|
|
--dry-run` prints the recipients and the exact `git send-email` command line
|
|
instead of sending anything. Read it, then either drop `--dry-run` or run
|
|
the command 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.
|
|
|
|
You also need:
|
|
|
|
- a kernel tree, for `scripts/checkpatch.pl` and
|
|
`scripts/get_maintainer.pl`. Both ship with the kernel, so spectral does
|
|
not carry its own.
|
|
- git with `send-email` configured. `git send-email` has to work from a plain
|
|
shell first. If it does not, spectral cannot fix it for you and is not
|
|
meant to try.
|
|
- qemu, so that `qemu-system-x86_64` is 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 needed for `kernel quest`, which only wants 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 has to change to
|
|
add either.
|
|
|
|
## Commands
|
|
|
|
```console
|
|
$ spectral --help
|
|
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 function that already has its signature, its doc
|
|
comment, and its caller in `main`. Pick one and you only need to read the
|
|
file it lives in.
|
|
|
|
Roughly in order of how much they unblock.
|
|
|
|
### patch submit
|
|
|
|
The most self-contained win, and the only one that needs no network. It does
|
|
need a kernel tree and a working `git send-email`.
|
|
|
|
- `patch/maintainers.rs` · `lookup` runs `get_maintainer.pl --git` over the
|
|
patch, splits the output into `To:` and `Cc:`, and collects the files the
|
|
patch touches.
|
|
- `patch/maintainers.rs` · `add_cc` folds `--cc` flags in without duplicates.
|
|
- `patch/mod.rs` · `submit` ties those two together with `git send-email`,
|
|
with `--dry-run` stopping one step short.
|
|
|
|
### patch check, format, commit, create
|
|
|
|
The everyday verbs. `patch/checkpatch.rs` · `run` already has the `Target`
|
|
enum (working tree, a revision, or a patch file) and `Report` with
|
|
`is_clean`. What is missing is the process call and pulling the error and
|
|
warning counts out of checkpatch's output.
|
|
|
|
### patch update
|
|
|
|
Small, and worth writing a test for. `patch/mod.rs` · `reroll_path` holds the
|
|
naming rule in one pure function: strip one leading `vN-`, then prepend
|
|
`v<revision>-`. Running it twice at the same revision should do nothing,
|
|
which is easier to assert than to explain.
|
|
|
|
### 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`, and
|
|
`run` then applies `--filter` and picks one issue. The `QuestSource` trait is
|
|
where you would add syzbot or a lore.kernel.org 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, and it is the difference between a boot hang you can
|
|
diagnose and one you cannot.
|
|
|
|
### git plumbing
|
|
|
|
`git.rs` · `current_branch`, `diff_against`, `commit`, `rev_parse`. Each is a
|
|
few lines over the `run` that is already written, which makes these the
|
|
easiest place to start.
|
|
|
|
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:
|
|
|
|
- `src/cli.rs` holds the whole command surface. 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 and `main` prints it, so output
|
|
formatting lives in one place.
|
|
- Errors go through the one `Error` enum and `?`. Nothing in the command
|
|
paths calls `unwrap`.
|
|
- A new stub gets a `todo!()` naming the command it will end up running. A
|
|
module that is unreachable until its caller exists 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` and no FFI.
|
|
|
|
## License
|
|
|
|
MIT. See `LICENSE`, © 2026 huntedbytheirs.
|