Migration

Coming from GNU make

Hypermake keeps what made Make durable — rule headers, prerequisites, tab-indented commands, automatic variables — and removes the macro language layered on top. There are no variables, no conditionals, no includes, no $(shell). Each removed idiom has a graph-native replacement, and the parser teaches it to you per line rather than failing silently.

What carries over unchanged

  • target: prerequisites rule headers with tab-indented command lines.
  • Order-only prerequisites after | — and order-only directories (trailing /) are created for you.
  • Automatic variables $@, $<, $^ — here expanded shell-safely to resolved absolute paths. Other automatics ($*, $?, $%) do not exist.
  • Command prefixes - (ignore failure), @ and + (accepted, stripped).
  • # comments; blank line ends a rule.

The migration table

GNU idiom to Hypermake shape

The teaching column quotes hypermake doctor's per-line lint (recipe.gnu_make_idiom findings), anchored on concept://recipe-doctrine.

GNU makeHypermakeWhy / teaching
%.css: %.scss{name}.css: {name}.scss% wildcards are not patterns in Hypermake (they match literally); use named captures such as {path} or {name}. Captures also type ({width} matches digits) and bind in commands.
.PHONY: build.VIRTUAL: targetGNU special targets are not supported. .VIRTUAL means no file is required and the command output becomes the target's captured value; add .DYNAMIC (refresh on direct request) or .ALWAYS (refresh on every visit) for re-run semantics.
.SILENT, .SUFFIXES, .DELETE_ON_ERROR, .ONESHELL, …The whole special-target family lints as special-target; the behaviors are either defaults or graph facts declared per target.
CC:= gcc, SRCS += …Make variables are not supported; graph facts live in rule headers, paths in capture variables such as {path}. Recipe-side values come from captures and template variables ({root}, {targetDir}, …).
ifeq/ifdef/define/exportConditionals, defines, and exports are not supported; graph facts live in rule headers, behavior in commands. Variant outputs are separate targets with their own recipes.
include deps.mk.d.make producer targetsinclude has no Hypermake equivalent; dynamic edges are .d.make graph targets consumed as prerequisites — inspectable with hypermake value TARGET.d, never globally included.
$(shell find …).d.make producer targets$(shell …) is not supported; discovery belongs in a .d.make dynamic dependency producer with its own declared matcher surface.
$(wildcard src/*.css)src/*.css in the prerequisite listGlobs are legal prerequisites directly: they become fingerprinted discovery matchers, and a newly created matching file invalidates the target.
a.min.js b.min.js: build.shone rule per target, one file per ruleMulti-target rules are not supported — that header parses as one unbuildable target named "a.min.js b.min.js". Separately, hypermake doctor flags files containing several rules (recipe.multiple_targets_per_make): locality doctrine wants one target family per .make file.
deps: a \ (continuation lines)one-line rule headersBackslash continuations are not supported in authored .make files — the \ becomes a literal prerequisite. (Materialized .d manifests may use them.)
VPATH / vpathprotocol addressesSearch paths are implicit state; Hypermake replaces them with explicit protocol mappings — src://, cache://, public:// addresses are valid targets and prerequisites.
make all aggregatesaggregate targets + ~ on-demandAn aggregate is an ordinary rule listing its prerequisites (this site's public). Targets prefixed ~ are excluded from aggregate traversal and built only when directly requested — reachability is a projection choice, not a side effect of linking.

The linter teaches at the moment of the mistake

Drop a legacy Makefile into a recipe root and hypermake doctor answers with per-line translations instead of a parse mystery. This transcript is from exactly that experiment — a three-rule GNU Makefile in a scratch project:

doctor on a legacy Makefile hypermake doctor
warning recipe.gnu_make_idiom 1x
  Recipe file uses GNU Make idioms: src/gnu/legacy.make
  edit src/gnu/legacy.make
    line 1: variable-assignment -> Make variables are not supported; graph facts live in rule headers, paths in capture variables such as {path}
    line 2: special-target -> GNU special targets are not supported; use .VIRTUAL for non-file targets and .DYNAMIC for refresh-on-request targets
    percent-pattern -> % wildcards are not patterns in Hypermake (they match literally); use named captures such as {path} or {campaign}
  reason recipe.gnu_make_idiom
		

Files that fail outright (a rule without commands, a header without the colon-space) surface as recipe.parse_failed with the offending file named; dropped lines carry invalid_rule_header or skipped_dot_target findings, and the legacy body directive @always lints as legacy_always. A .make file is never silently ignored.

Verify this yourself shell
hypermake explain concept://recipe-doctrine
hypermake doctor
hypermake match rendered/public/docs/coming-from-make.html --json
hypermake scaffold recipe cache/example/first.txt