rsconstruct 0.9.132

Rust based fast build system
# Processor Types

Every processor in RSConstruct has a type that determines how it discovers inputs, produces outputs, and interacts with the cache. There are four types.

Run `rsconstruct processor types` to list them.

## Checker

A checker validates input files without producing any output. If the check passes, the result is cached — if the inputs haven't changed on the next build, the check is skipped entirely.

### How it works

1. Scans for files matching `src_extensions` in `src_dirs`
2. Creates one product per input file
3. Runs the tool on each file (or batch of files)
4. If the tool exits successfully, records a marker in the cache
5. On the next build, if inputs are unchanged, the check is skipped

### What gets cached

A marker entry — no files, no blobs. The marker's presence means "this check passed with these inputs."

### Examples

**Lint Python files with ruff:**

```toml
[processor.checker.ruff]
```

Scans for `.py` files, runs `ruff check` on each. No output files produced.

```text
src/main.py → (checker)
src/utils.py → (checker)
```

**Lint shell scripts:**

```toml
[processor.checker.shellcheck]
```

Scans for `.sh` and `.bash` files, runs `shellcheck` on each.

**Validate YAML files:**

```toml
[processor.checker.yamllint]
```

Scans for `.yml` and `.yaml` files, runs `yamllint` on each.

**Validate JSON files with jq:**

```toml
[processor.checker.jq]
```

Scans for `.json` files, validates each with `jq`.

**Spell check Markdown files:**

```toml
[processor.checker.zspell]
```

Scans for `.md` files, checks spelling with the built-in zspell engine.

### Built-in checkers

actionlint, ascii, aspell, biome, black, checkpatch, checkstyle, clang_tidy, clippy, cmake, cppcheck, cpplint, doctest, duplicate_files, encoding, eslint, hadolint, htmlhint, htmllint, iactionlint, icpplint, idockerfile, ijq, ijsonlint, iluacheck, isvglint, itaplo, itidy, ixmllint, iyamllint, iyamlschema, iyq, jq, jshint, jslint, json_schema, jsonlint, license_header, luacheck, make, markdownlint, marp_images, mdl, mypy, oxlint, perlcritic, php_lint, prettier, pylint, pyrefly, pytest, ruff, rumdl, script, selene, shellcheck, slidev, standard, stylelint, svglint, svgo, taplo, terms, tidy, xmllint, yamllint, yq, zspell

## Generator

A generator transforms each input file into one or more output files. It creates one product per input file (or one per input x format pair for multi-format generators like pandoc).

### How it works

1. Scans for files matching `src_extensions` in `src_dirs`
2. For each input file, computes the output path from the input path, output directory, and format
3. Creates one product per input x format pair
4. Runs the tool to produce the output file
5. Stores the output as a content-addressed blob in the cache

### What gets cached

One blob per output file. The blob is the raw file content, stored by its SHA-256 hash. On restore, the blob is hardlinked (or copied) to the output path.

### Examples

**Render Tera templates:**

```toml
[processor.generator.tera]
```

Scans `tera.templates/` for `.tera` files, renders each template. The output path is the template path with the `.tera` extension stripped:

```text
tera.templates/config.py.tera → config.py
tera.templates/README.md.tera → README.md
```

**Convert Marp slides to PDF:**

```toml
[processor.generator.marp]
```

Scans `marp/` for `.md` files, converts each to PDF (and optionally other formats):

```text
marp/slides.md → out/processor.generator.marp/slides.pdf
marp/intro.md → out/processor.generator.marp/intro.pdf
```

**Convert documents with pandoc (multi-format):**

```toml
[processor.generator.pandoc]
```

Scans `pandoc/` for `.md` files, converts each to PDF, HTML, and DOCX. Each format is a separate product with its own cache entry:

```text
pandoc/syllabus.md → out/processor.generator.pandoc/syllabus.pdf
pandoc/syllabus.md → out/processor.generator.pandoc/syllabus.html
pandoc/syllabus.md → out/processor.generator.pandoc/syllabus.docx
```

**Compile single-file C programs:**

```toml
[processor.generator.cc_single_file]
```

Scans `src/` for `.c` and `.cc` files, compiles each into an executable:

```text
src/main.c → out/processor.generator.cc_single_file/src/main.elf
src/test.c → out/processor.generator.cc_single_file/src/test.elf
```

**Convert Mermaid diagrams:**

```toml
[processor.generator.mermaid]
```

Scans for `.mmd` files, converts each to PNG (configurable formats):

```text
diagrams/flow.mmd → out/processor.generator.mermaid/diagrams/flow.png
```

**Compile SCSS to CSS:**

```toml
[processor.generator.sass]
```

Scans `sass/` for `.scss` and `.sass` files, compiles each to CSS:

```text
sass/styles.scss → out/processor.generator.sass/styles.css
```

### Built-in generators

a2x, cc_single_file, chromium, drawio, generic, ijinja2, imarkdown2html, ipdfunite, iprotobuf, isass, jinja2, libreoffice, mako, markdown2html, marp, mermaid, objdump, pandoc, pdflatex, pdfunite, protobuf, requirements, rust_single_file, sass, tags, tera, yaml2json

## Explicit

An explicit processor aggregates many inputs into (possibly) many output files and/or directories. Unlike other types which create one product per discovered file, explicit creates a single product with all declared inputs and outputs.

### How it works

1. Inputs are listed explicitly via `inputs` and `input_globs` in the config
2. Creates a single product with all inputs and all outputs
3. Runs the command, passing `--inputs` and `--outputs` on the command line
4. Stores each output file as a content-addressed blob

### What gets cached

One blob per output file (like generator).

### Examples

**Build a static site from generated HTML:**

```toml
[processor.explicit.generic.site]
command = "python3"
args = ["build_site.py"]
input_globs = ["out/processor.generator.pandoc/*.html", "templates/*.html"]
inputs = ["site.yaml"]
outputs = ["out/site/index.html", "out/site/style.css"]
```

Waits for pandoc to produce HTML files, then combines them with templates into a site. All inputs are aggregated into one product:

```text
out/processor.generator.pandoc/page1.html, out/processor.generator.pandoc/page2.html, templates/base.html, site.yaml → out/site/index.html, out/site/style.css
```

**Merge PDFs into a course bundle:**

```toml
[processor.explicit.generic.course]
command = "pdfunite"
input_globs = ["out/processor.generator.pdflatex/*.pdf"]
outputs = ["out/course/full-course.pdf"]
```

Aggregates all PDF outputs from pdflatex into a single merged PDF.

### Built-in explicit processors

generic

## Mass Generator

A mass generator runs a tool that produces many files in one invocation and enumerates them in advance. Every planned file becomes a product of its own with its own inputs and blob cache entry, while the underlying build tool runs at most once per build.

### How it works

1. At graph-build time, computes the output plan (natively or via a planner command)
2. Creates one product per planned file: `inputs` = the entry's `sources`, `outputs` = its `path`
3. Classifies each product individually (skip, restore, or build)
4. The first product that needs building runs `command`; the rest find the tool already run
5. Compares the files the tool wrote against the plan — a missing predicted file or an off-plan write fails the build. Files another product declares, as `output_files` or under its `output_dirs`, are that product's and are not reported even when it writes them during the tool's run
6. Stores each predicted file as a content-addressed blob

### What gets cached

One blob per predicted file (like generator). When every product is clean the tool does not run at all; when some are, the tool runs once and every dirty product caches its own file.

### Examples

**A static site generator with a plan mode:**

```toml
[processor.mass_generator.generic.site]
command         = "rssite"
args            = ["build"]
predict_command = "rssite"
predict_args    = ["plan"]
output_dirs     = ["_site"]
```

`rssite plan` prints which `_site/*.html` files `rssite build` will write and which sources each depends on. Changing `docs/about.md` dirties only `_site/about/index.html`; a linter scanning `_site/` depends on each page individually.

```text
docs/index.md, templates/default.html → _site/index.html
docs/about.md, templates/default.html → _site/about/index.html
```

**Build documentation with Sphinx:**

```toml
[processor.mass_generator.sphinx]
dep_inputs = ["src/mypkg/**/*.py", "pyproject.toml", "uv.lock"]
```

Asks Sphinx itself which files `sphinx-build -b html` will write (a planner runs Sphinx's read phase, nothing more), builds `sphinx/` into `docs/` through a staging directory, and caches every page, static file and index as a product of its own.

```text
sphinx/*, src/mypkg/**/*.py → docs/index.html, docs/mypkg.html, docs/_static/basic.css, …
```

### Built-in mass generators

cargo, cc, gem, generic, jekyll, linux_module, mdbook, npm, pip, sphinx, zola

Any tool that honors the manifest contract plugs into the config-driven `generic` mass generator via `[processor.mass_generator.generic.NAME]`; see [Mass Generator](processor/mass_generator/generic.md).

## Comparison

| | Checker | Generator | Explicit | Mass Generator |
|---|---|---|---|---|
| **Purpose** | Validate | Transform | Aggregate | Transform, many at once |
| **Inputs** | Scanned | Scanned | Declared in config | Declared by the tool's plan |
| **Products** | One per input | One per input (x format) | One total | One per predicted file |
| **Outputs** | None | Derived from input path | Declared files | Predicted files |
| **Cache type** | Marker | Blob | Blob | Blob |
| **Runs in** | Project root | Project root | Project root | Project root / anchor dir |
| **Command args** | Input files | Input + output | `--inputs` + `--outputs` | User-defined args |