Makefiles, built right.
Fast, trustworthy linting and formatting for Makefiles.
Rumk is the Makefile sibling of Rumdl.
Its CLI, configuration model, diagnostics, fixing behavior, and exit codes intentionally follow Rumdl so existing users can reuse their workflow.
Features
- Lints individual Makefiles or entire directory trees
- Fixes recipe indentation and long static
.PHONYdeclarations without changing what Make does, and offers style-aware missing.PHONYdeclarations and$(MAKE)spellings behind--unsafe-fixes - Parses continued logical statements and nested
$(...)/${...}expressions - Models GNU Make assignment flavors, static patterns, target-specific variables, includes,
conditionals,
defineblocks, custom recipe prefixes, and.ONESHELL - Builds semantic indexes for variables, references, targets, dependencies, and includes
- Safely evaluates statically knowable variables and conditionals without running recipes, shell commands, or side-effecting Make functions
- Supports GNU substitution references plus common word, path, list, and lazy logical functions
- Resolves expanded include graphs and reports cross-file findings at their real source paths
- Preserves LF/CRLF line endings and final newlines during fixes
- Uses Rumdl-style
check,fmt,rule,config,init, andexplaincommands, withfmtresponsible for layout alone andcheckfor what Make does with the file - Discovers
.rumk.tomlupward through the project tree - Respects
.gitignoreby default - Supports rule selection, file globs, per-file ignores, severities, fix allowlists, and safe-versus-unsafe fix selection
- Emits text, flat JSON, and GitHub Actions annotations
Installation
cargo install rumk --locked --version 0.0.8
Or install the native executable from PyPI with a Python tool manager:
uv tool install rumk==0.0.8
# or
pipx install rumk==0.0.8
Rumk is alpha-stage 0.0.x software, so installation names the version explicitly. Release
archives and Python wheels cover Linux, macOS, and Windows. GitHub release assets include SHA-256
checksums.
GitHub Actions
The repository is also a composite Action that installs a checksum-verified native Rumk release,
runs it, and leaves rumk on PATH for later steps:
steps:
- uses: actions/checkout@v4
- uses: rvben/rumk@v0.0.8
with:
version: 0.0.8
path: .
report-type: annotations
Pin both the Action ref and version while Rumk is alpha. The moving v0 Action tag is available
for users who prefer automatic 0.x Action updates. Supported commands are check, fmt-check,
and fmt; install-only: true only installs Rumk. The Action also accepts config, args,
fail-on-error, and output-file, and exposes rumk-version and rumk-path outputs.
Editor buffers and pipelines
Use - to read a Makefile from standard input. Give unsaved buffers their actual
filename so Rumk can find the nearest configuration, resolve relative includes,
and apply per-file rule settings:
rumk check - --stdin-filename project/Makefile --output-format json < buffer.mk
rumk fmt - --stdin-filename project/Makefile < buffer.mk > formatted.mk
fmt - writes only the complete formatted buffer to stdout, even when unchanged
or when --quiet/--silent is set. It preserves a UTF-8 BOM, line endings, and
significant whitespace. The named file and its includes are never written.
fmt - --check returns 1 when formatting is needed; --diff previews changes.
Checking retains the normal diagnostic formats and --fail-on policy, without
a summary. Each JSON fix contains one byte range and a replacement covering all
of that fix's edits. Apply it atomically to the exact checked buffer and honor its
applicability (safe or unsafe).
Without --stdin-filename, the buffer is treated as Makefile in the current
directory. Explicit --config and --no-config override automatic discovery.
Stdin must be valid UTF-8 and cannot be mixed with filesystem paths.
check --fix - is rejected: use JSON edits for lint fixes or fmt - for layout.
This is a command-line integration interface, not a language server.
SARIF reports
Export diagnostics for code-scanning consumers:
rumk check . --output-format sarif --fail-on never > /tmp/rumk.sarif
The SARIF 2.1.0 report includes stable rule IDs, documentation links, severities, include-file locations, URI-encoded paths, and explicit Unicode column units. Stdin checking supports this format too. It reports fix availability and safety; use JSON output for edit payloads. Run from the repository root when preparing reports for upload. Rumk creates the report; uploading it is a separate CI step.
The GitHub upload-sarif action can populate missing fingerprints from the
checkout. Direct API uploads may need caller-supplied fingerprints to avoid
duplicate alerts; see GitHub's SARIF support documentation.
Pre-commit
The repository provides rumk-fmt and rumk-check hooks. Pre-commit builds the
native executable from the selected repository revision using its Rust backend.
Pin the release tag to keep hook behavior reproducible.
repos:
- repo: https://github.com/rvben/rumk
rev: v0.0.8
hooks:
- id: rumk-fmt
- id: rumk-check
Formatting runs on changed Makefiles. Checking scans the project when a Makefile
or Rumk configuration changes, so included fragments are analyzed with their
entry Makefiles. This also means existing violations elsewhere can fail the
check; use configuration exclusions and per-file ignores for gradual adoption.
Neither hook enables unsafe fixes. Use args: [--check] on rumk-fmt for a
read-only formatting gate. Hooks match Makefile, makefile, GNUmakefile,
*.mk, and *.make, including paths with spaces.
Quick start
# Check Makefiles below the current directory
rumk check
# Check specific files or directories
rumk check Makefile build/
# Apply safe fixes, then fail only if violations remain
rumk check --fix
# Also apply the fixes that can change what Make does
rumk check --fix --unsafe-fixes
# Lay files out, leaving what Make does with them alone
rumk fmt
# Preview formatting changes
rumk fmt --diff
# Fail when formatting changes are required
rumk fmt --check
# Inspect rules and effective configuration
rumk rule
rumk rule MK101
rumk config
rumk config get MK101.line-length
rumk config file
Run rumk --help or rumk <command> --help for all options.
Configuration
Create a .rumk.toml file manually or run rumk init:
[global]
dialect = "gnu"
respect-gitignore = true
exclude = ["vendor/**", "generated/**"]
disable = ["MK101"]
fixable = ["MK001"]
unsafe-fixes = false
include-paths = ["mk"]
predefined-variables = { FROM_CLI = "yes" }
entry-targets = ["all"]
[MK101]
enabled = true
severity = "warning"
line-length = 100
ignore-comments = true
ignore-recipes = true
[MK102]
enabled = true
style = "upper-case"
[MK201]
placement = "auto"
[per-file-ignores]
"vendor/**/*.mk" = ["MK202"]
For configuration completion and validation in editors that support inline TOML schemas, put this comment at the top of the file:
#:schema https://raw.githubusercontent.com/rvben/rumk/main/rumk.schema.json
The versioned schema is also included in Cargo packages and native release archives.
Configuration discovery checks .rumk.toml, rumk.toml, and .config/rumk.toml while walking
upward, stopping at a Git project boundary. Use --config <PATH> for an explicit file or
--no-config/--isolated for built-in defaults.
Configurations can inherit another file with extends = "../.rumk.toml"; nested tables are
merged, child values win, relative paths resolve from the extending file, and cycles are rejected.
Rules can be suppressed in Make comments without changing project configuration:
# rumk-disable MK202
INSTALL_PREFIX := /usr/local
# rumk-enable MK202
# rumk-disable-next-line MK201
clean:
rm -rf build
rumk-disable, rumk-enable, rumk-disable-line, and rumk-disable-next-line are supported.
Recipe shell comments are not interpreted as Rumk directives.
The original Rumk [rules] and [ignore] configuration sections remain accepted for migration.
include-paths models GNU Make's -I search directories and resolves relative entries from the
configuration directory. predefined-variables supplies command-line-style values to safe
evaluation and names expected by opt-in rule MK208.
The MK208.external-variables option accepts exact caller-supplied names
without inventing their values; typos and local read-before-definition warnings
remain visible. That rule intentionally ignores references
inside recipes and deferred macro bodies, where command-line parameters and shell values are
normal. entry-targets supplies the roots for opt-in reachability rule MK209; the rule stays
silent without explicit roots because any Make target may be invoked directly from the command
line.
Rule and file selection
Rumk follows Rumdl's selection vocabulary:
rumk check --enable MK001,MK002 .
rumk check --disable MK101 .
rumk check --extend-enable MK202 .
rumk check --exclude "vendor/**,generated/**" .
rumk check --include "src/**" .
rumk check --respect-gitignore=false .
rumk fmt --fixable MK001 .
Path patterns are globs against the path relative to the project: * and ? stay within one
path segment, ** crosses segments, and a leading **/ matches in the project root as well as
below it, so **/vendor/** covers vendor/a.mk and sub/vendor/a.mk alike.
Fix safety
A fix is safe when Make reads the fixed file the way it read the original, or when the
original was not a file Make would read at all and the fix is the only reading that makes it one.
Correcting recipe indentation (MK001)
is safe: the file did not build before, and a tab is what Make needs to see there.
A fix is unsafe when applying it can change what Make does. Declaring a target .PHONY
(MK201) makes its recipe run where Make
would have called the target up to date; spelling a sub-make $(MAKE)
(MK203) passes the parent's options and
jobserver down. Both are the right change to make, and both are a change a person should agree to.
rumk check --fix applies only safe fixes and reports how many it withheld. Ask for the rest with
--unsafe-fixes, refuse them explicitly with --no-unsafe-fixes, or set unsafe-fixes under
[global] to choose for a project. rumk rule MK201 says which kind a rule offers. Under
--diff the withheld count goes to stderr, leaving stdout a patch other tools can read.
rumk check --fix . # safe fixes only
rumk check --fix --unsafe-fixes . # every fix the enabled rules offer
Rumdl has no equivalent setting; this is a Rumk addition, modeled on Ruff's --unsafe-fixes.
Formatting
rumk fmt is responsible for how a Makefile is laid out, and leaves what Make does with it to
rumk check. By default, only these layout rules run:
MK001 indents recipes with the prefix
Make expects there, and MK101 wraps a
long static .PHONY declaration. A path that cannot be read is still reported as
MK007, because a file that was never
read was never formatted either. rumk rule <ID> says which commands run a rule.
So a formatting run neither reports nor rewrites a missing .PHONY declaration, a hardcoded
path, or a bare make in a recipe: those are lint findings, they belong to rumk check, and a
project that formats on save should not have them appear as formatting noise. This also makes
rumk fmt --check usable as a CI gate on its own, since it fails only when a file's layout is
not the one Rumk writes.
Opt into assignment operator spacing by enabling MK105 in the config
(or rumk fmt --enable MK001,MK101,MK105). This preserves variable values and
trailing whitespace, and leaves continuations, dynamic names, define bodies,
and target-specific assignments alone. See MK105.
Every layout fix is safe, so rumk fmt has no unsafe fix to withhold and --unsafe-fixes makes
no difference to it.
Exit codes
0: success, or all selected violations were fixed1: lint violations,fmt --checkfound required changes, or a path could not be read2: configuration error, a path that does not exist, a failed write, or another tool error
rumk check fails on any diagnostic by default. Use --fail-on warning, --fail-on error, or
--fail-on never to change that policy. rumk fmt exits successfully after formatting even if
a layout diagnostic it has no fix for remains. A Makefile that cannot be read, or a directory that cannot be
searched for Makefiles, is reported as
MK007 and fails every command regardless
of --fail-on; the other files are still checked.
Output
Text diagnostics follow Rumdl's familiar form:
Makefile:2:1: [MK001] Recipe must be indented with tab, not spaces [*]
JSON output is a flat array collected across all files:
rumk check --output-format json .
[
{
"file": "Makefile",
"line": 2,
"column": 1,
"end_line": 2,
"end_column": 1,
"rule": "MK001",
"message": "Recipe must be indented with tab, not spaces",
"severity": "error",
"fixable": true,
"fix": {
"applicability": "safe",
"range": { "start": 7, "end": 11 },
"replacement": "\t"
}
}
]
fixable says whether this run would apply the fix; fix.applicability says why. A diagnostic
with "fixable": false and an "unsafe" fix is one Rumk withheld, and an editor can still offer
it as an explicit action.
The legacy --format spelling remains an alias for --output-format.
Rules
Rules marked default run without configuration. Each rule page documents its behavior, configuration, fixes, edge cases, and the GNU Make, POSIX, or Rumk convention on which it is based.
Syntax
MK001- Recipes must use tab indentation (default, safe fix, run byfmt)MK002- Invalid variable syntax (default)MK003- Malformed conditional structure (default)MK004- Targets must not mix single- and double-colon declarations (default)MK005- GNU Make special targets must stand alone (default)MK006- Statement is not valid GNU Make syntax: missing separators, recipes before the first target, unterminated references, empty variable names, and unbalanceddefineblocks (default)MK007- Path could not be read: an unreadable file or directory is an error and a file that is not valid UTF-8 is linted with the invalid bytes replaced and fixes disabled, without stopping the run (default)
Style
-
MK101- Declarative line exceeds the configured maximum length; comments and recipes are ignored by default, and static.PHONYdeclarations can be wrapped safely (default, partially fixable, safe fix, run byfmt) -
MK102- Variable naming convention -
MK103- Target naming convention -
MK104 - Configurable logical recipe length (opt-in)
-
MK105 - Assignment operator spacing (opt-in, safe fix, run by
fmt)
Best practices
-
MK201- Conventional non-file targets should be.PHONY; fixes consolidate canonical groups, preserve per-section style, and wrap long declarations (default, unsafe fix) -
MK202- Avoid hardcoded absolute paths (opt-in) -
MK203- Recursive Make invocations should use$(MAKE)(default, unsafe fix) -
MK204- Concrete targets should not declare multiple single-colon recipes (default) -
MK205- Explicit target dependencies must not form cycles (default) -
MK206- Required static includes must resolve (default) -
MK207- Static Makefile includes must not form cycles (default) -
MK208- Static graph-level variable references must resolve, and by the time Make reads them (opt-in) -
MK209- Targets must be reachable from explicitly configured entries (opt-in) -
MK210- Explain include expressions blocked by safe evaluation (opt-in) -
MK211- Variable references longer than one character need parentheses (default) -
MK212- A recipe line must not end with thecdthe lines after it need (default) -
MK213-$(shell ...)belongs in a variable Make expands once (opt-in) -
MK214 - Global
.IGNOREsuppresses recipe failures (default) -
MK215 - Required project targets must be defined and phony (opt-in)
-
MK216 - Missing static prerequisites with no visible producer (opt-in)
Development
cargo test --all-targets --all-features
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --all -- --check
cargo build --release
make check-gnu-fixtures
Run make benchmark (Python 3.9+) for deterministic generated workloads: a
1,002-file include graph, a 20,000-target Makefile, and 10,000 indentation fixes.
The runner uses temporary files, restores fix inputs before each run, and reports
medians after two warmups and seven measured runs. Timings include process startup,
file access, linting, JSON output, and writes for the fix workload; generation and
output comparison are excluded. These are warm-cache measurements.
Compare release binaries and save raw timings outside the repository:
python3 scripts/benchmark.py --baseline /tmp/rumk-before \
--binary target/release/rumk --output /tmp/rumk-benchmarks.json
The runner alternates binary order and requires identical diagnostics and fixed
contents. Use --scale 1 --runs 1 --warmups 0 for a quick smoke check. Keep compiler
settings and machine load comparable; use raw samples to assess noise rather than
enforcing a universal timing threshold. The include graph caps at 1,002 files to
stay below Rumk's default project limit.
The shared comparison corpus verifies
new rules against exact expectations and supports reproducible runs of pinned
checkmake, unmake, and mbake executables. Run make check-comparison for its
offline regression checks. scripts/compare-linters.py records cross-tool
observations separately; generated results are local working material.
The product-level compatibility contract is documented in
docs/rumdl-compatibility.md.
Contributing
See CONTRIBUTING.md.
License
MIT. See LICENSE.
Metadata
Release files for rumk 0.0.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rumk-0.0.8.tar.gz | 268.5 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| rumk-0.0.8-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| rumk-0.0.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| rumk-0.0.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| rumk-0.0.8-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| rumk-0.0.8-py3-none-macosx_10_12_x86_64.whl | Python 3 | none | macOS 10.12+ x86-64 | Details |
Total release size: 8.8 MB
Release files / rumk-0.0.8.tar.gz
| Download URL | rumk-0.0.8.tar.gz |
|---|---|
| Size | 268.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
64b891dd35a1a9eefa5e24399e9666ce9f0b0088409e7844741419e0222867bc
|
|
BLAKE2b-256 checksum How to use checksums |
8525ba11f5fc3edd7eb29bb13a6dcf366195748cbe69f9c924c2e157d6283362
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logRelease files / rumk-0.0.8-py3-none-win_amd64.whl
| Download URL | rumk-0.0.8-py3-none-win_amd64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
5215159d5b2dbe5beae6292a6eb0fb9e19bc1b929e4fe4316c157187b49cfdb1
|
|
BLAKE2b-256 checksum How to use checksums |
2f89357d7768e4357f1830829d5450e2b699decce3ee6bfffa2229d8655ed866
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logRelease files / rumk-0.0.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | rumk-0.0.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
eb3852d7c1b69a24fd7e079f847b6b79eb290145d10537bc5e4138344382212a
|
|
BLAKE2b-256 checksum How to use checksums |
ba2adffc121e1a26a247e6efac57e1b97335adafb16941a26cebfae175869545
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logRelease files / rumk-0.0.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | rumk-0.0.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 1.6 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
18734fa9a16ca2f6398b485aac6ff7f37a9e9f8d5270f0324f6d06cd8c1e0c85
|
|
BLAKE2b-256 checksum How to use checksums |
bd351846d77a9e39d8dde2d50c47ece55a4c9d5d2c9808cdb14695a3c1bd883d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logRelease files / rumk-0.0.8-py3-none-macosx_11_0_arm64.whl
| Download URL | rumk-0.0.8-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.6 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
12431f84d11749377522c2f8e7f708280ee4e3af537c6686d9d5c2f997de031b
|
|
BLAKE2b-256 checksum How to use checksums |
e56852d3be6d641ebc92ba66d9442d80ac24a916dac633ea16f5af16cac3e1cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logRelease files / rumk-0.0.8-py3-none-macosx_10_12_x86_64.whl
| Download URL | rumk-0.0.8-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
f189d6a52d950a2985bff0113fca1560b7d1ca6dd776efcec997a4ac8bd2a745
|
|
BLAKE2b-256 checksum How to use checksums |
498062c5ed6ae7cc930ec758e88b4fcf2ef641502529718b0c4cd15225e42958
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency log