An explicit, cached subprocess.run for ad-hoc build pipelines.
Reforge is a tiny build helper: you write your pipeline as a plain Python
script that calls run(...) for each step, and reforge skips steps whose
declared inputs haven't changed. No DAG, no rules file, no DSL — just
subprocess.run with a content-addressed cache.
pip install reforge-build
# or
uv add reforge-buildThe PyPI distribution name is reforge-build (the bare reforge name was
already taken on PyPI). The import name is still reforge.
from reforge import input, output, run
# Inputs and outputs must be declared explicitly.
run(
"pandoc",
input("docs/index.md"),
"-o",
output("build/index.html"),
)
# stdout/stderr can be captured to declared outputs.
run(
"python",
input("scripts/list_things.py"),
stdout=output("build/things.tsv"),
)
# When an output is a directory containing a known sentinel file,
# pass the sentinel as the second argument so reforge can check it.
run(
"iqtree2",
"-s", input("data/aln.fasta"),
"--prefix", output("build/iqtree/output", ".log"),
)run(...)executes immediately.- Inputs must be wrapped with
input(...); missing inputs raiseFileNotFoundError. - Outputs must be wrapped with
output(...); their parent directories are auto-created. - A step is skipped only when all declared outputs exist and the fingerprint (command + content hash of every input) matches the cached fingerprint.
- A step with no inputs always runs — there is no way to fingerprint it safely.
- Commands run in a fresh temporary working directory, so they can't accidentally read or write relative paths in your tree.
- Cache metadata lives in
.reforge/at the current working directory.
The module exports input and output, which shadow the Python builtin
input() when star-imported. If you need the builtin, import it explicitly:
from reforge import input as rf_input, output, runMIT