rpm-spec-tool
Pretty-printer and static analyzer CLI for RPM .spec files.
Built on top of the rpm-spec parser and a
visitor-based analyzer that ships with 24 built-in distribution profiles
(generic, RHEL 8/9/10, Fedora-derived families, SUSE, ALT Linux variants).
Features
- Static analysis — lint rules over the parsed AST with rich
codespan-style diagnostics. Output as human-readable text, JSON, or SARIF for GitHub Code Scanning. - Auto-fixes —
lint --fixapplies machine-applicable rewrites in place;--fix-suggestedalso commits "maybe-incorrect" rewrites. - Severity overrides —
--deny,--warn,--allowon the CLI; per-lint defaults in.rpmspec.toml. - Pretty-printer —
formatfor canonical reformat (with--check,--in-place,--diffmodes for CI / pre-commit);prettystreams the reformatted text to stdout with ANSI syntax highlighting. - Distribution profiles — 24 built-in profiles select the right macro
registry, rpmlib feature set, and family-gated lints. Pick one with
--profile <name>or via.rpmspec.toml. --definelikerpmbuild—-D 'NAME VALUE'injects macros at lint time; CLI defines outrank profile / config defaults. Repeatable.- Shellcheck integration — optional. The analyzer invokes
shellcheckon%prep,%build,%installscript sections and surfaces its findings; missingshellcheckin$PATHis reported as a separate diagnostic, never crashes the lint. Configurable via[shellcheck]in.rpmspec.toml. - AST dump —
astemits the parsed AST as JSON or YAML for downstream tooling.
Quick start
# Lint, with a chosen distribution profile:
# Apply auto-fixes, then reformat in place:
# CI: one-shot lint + format check (exits non-zero on any issue):
# SARIF output for GitHub Code Scanning:
# Inspect a built-in profile:
# Compare a macro across profiles (note: macro name without `%`):
Every spec-taking subcommand reads from stdin when the path is - or omitted.
Configuration
Drop a .rpmspec.toml next to your specs (or anywhere up the directory tree —
the tool walks upward from each input). Minimal example:
# Pick the active distribution profile. CLI `--profile` overrides this.
= "rhel-9-x86_64"
# Per-lint severity overrides. CLI flags --deny/--warn/--allow win.
# Rule IDs below are illustrative — see the lint reference for the full list.
[]
= "warn"
= "allow"
# Tune shellcheck. Set `shellcheck = "allow"` at the top level to
# disable shellcheck entirely.
[]
= "shellcheck"
= 10
# Define macros at lint time (also available via CLI: -D 'name value').
[]
= "production"
Subcommand options that override the config:
| Flag | Available in | Purpose |
|---|---|---|
--config <PATH> |
lint, check, format, pretty, profile * |
explicit .rpmspec.toml path |
--profile <NAME> |
lint, check |
pick the distribution profile (in profile show the name is a positional argument) |
-D, --define 'NAME VALUE' |
lint, check, profile * |
inject a macro (rpmbuild-style); repeatable |
--deny <LINT> / --warn / --allow |
lint |
override severity for a single rule; repeatable |
--fix / --fix-suggested |
lint |
apply machine-applicable (and optionally suggested) rewrites |
--format human|json|sarif |
lint |
diagnostics output format |
--check / --in-place / --diff |
format |
dry-run / overwrite / unified-diff modes |
--indent N |
format, pretty |
indent %if blocks for readability (rpm rejects indented %if — display only) |
--format json|yaml / --pretty |
ast |
AST dump format |
--color auto|always|never |
top-level and every subcommand | ANSI colour control |
Exit codes
| Command | Code | Meaning |
|---|---|---|
lint |
0 |
no errors (warnings allowed) |
lint |
1 |
one or more error-severity diagnostics |
format --check |
0 |
input is already canonical |
format --check |
1 |
input would be rewritten |
check |
1 |
either lint or format-check failed |
profile macro <NAME> |
2 |
single-profile lookup with undefined macro |
| any | non-zero | parse error, I/O failure, or invalid CLI |
Lint rules
The full catalogue of built-in rules — IDs, default severities,
categories, one-line descriptions — lives in
doc/lints-list.md. That file is regenerated from
the registry by the lints subcommand:
--category and --severity are repeatable. Values inside one flag
OR-combine; distinct flags AND-combine — for example
--category correctness --severity warn lists only warn-severity rules
in the correctness category.
The distribution-profile model is documented in
doc/profiles.md.
Installation
Pre-built packages
Each tag vX.Y.Z publishes .tar.gz, .deb, and .rpm for Linux x86_64
and aarch64 (plus a SHA256SUMS file) at
github.com/johnlepikhin/rpm-spec-tool/releases.
# Debian / Ubuntu derivatives:
# RHEL / Fedora derivatives:
shellcheck is declared as a Recommends / Suggests dependency — install
it if you want the shell-section lints to run.
From source
Requires Rust 1.88 or newer.
# Latest from git:
# From a local clone:
Shell completions
After installation, generate a completion script for your shell with the
completions subcommand:
# Bash (system-wide):
|
# Zsh (per-user; ensure the target dir is on $fpath):
# Fish (per-user):
# PowerShell:
Supported shells: bash, zsh, fish, powershell, elvish.
Pre-commit hook (contributors)
A versioned git pre-commit hook in .githooks/ keeps
doc/lints-list.md in sync with the rule registry.
Enable it once per clone:
The hook fires only when staged files touch crates/analyzer/src/rules/**
or the registry, and auto-stages the regenerated markdown. CI runs the
same regeneration as a sanity check (lints-doc-check job), so PRs
that bypass the hook still get caught.
License
Dual-licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.