Rules & Targets
In Netsuke, a Rule defines how something is built, while a Target defines what is built. Understanding the relationship between these two concepts is key to mastering the build graph.
The Build Graph
Netsuke constructs a Directed Acyclic Graph (DAG) where nodes are targets and edges are dependencies. When you run netsuke build app, the system:
- Parses the manifest for the requested target.
- Identifies all transitive dependencies.
- Constructs the action graph (commands to run).
- Executes actions in parallel, topologically sorted.
Defining reusable rules
CoreA Netsuke rule is a reusable command or script template. Targets can reference the rule by name, add their own inputs, and keep duplicated shell out of the manifest.
rules:
- name: "compile"
command: "gcc -c {{ ins }} -o {{ outs }}"
description: "Compiling {{ outs }}"
- name: "link"
command: "gcc {{ ins }} -o {{ outs }}"
targets:
- name: "app"
rule: "link"
sources: ["main.o", "utils.o"]
Rule name
Targets refer to a rule by name. Keep names short and literal.
Recipe field
A rule carries exactly one recipe: command or script.
Target wiring
Targets supply the concrete sources, outputs, and optional per-target variables.
Target fields
Netsuke target references are plain names and paths. The important distinction is between normal dependencies, order-only dependencies, and phony or always-run targets.
| Field | Example | Meaning |
|---|---|---|
| sources | ["main.o", "utils.o"] | Build inputs whose changes should trigger a rebuild. |
| deps | ["build"] | Named dependencies that must be built first and do trigger rebuilds. |
| order_only_deps | ["build-dir"] | Dependencies that must run first but do not trigger rebuilds when they change. |
| phony / always | true / false | Control whether a target is logical only or should run every time. |
CLI Tip: Querying Targets
Use netsuke graph or netsuke generate to inspect the plan without running a full build.
Outputs & Determinism
Deterministic Output
Netsuke generates the same static plan from the same manifest and environment. Deterministic manifests make rebuild decisions easier to explain and verify.
- Consistent across machines
- Bit-for-bit identical builds
- Byte-for-byte stable
build.ninjaoutput
Validation boundaries
The reviewed docs emphasize validation, deterministic plan generation, and explicit shell escaping. Treat those as the documented safety story.
Read Security Model →Clean and incremental builds
Netsuke does not execute the build itself. It compiles the manifest into a static plan and hands that plan to Ninja, which decides what actually needs to run. How granular the rules and targets are therefore determines how much work an incremental build can skip.
Clean build
With no outputs on disk, every edge in the plan is out of date, so Ninja runs every command once. This is the upper bound on build work: the whole graph, in dependency order.
- Each target's recipe executes exactly once
- Ordering follows
sources,deps, andorder_only_deps
Incremental build
Ninja walks the same static graph and compares each edge's declared inputs against its outputs. Only the commands whose inputs changed are re-run, along with whatever depends on their results.
- Unchanged subgraphs are skipped, not re-checked command by command
-
order_only_depsconstrain ordering without forcing a rebuild - Targets marked
alwaysrun regardless of input state
Where the cost sits
All the dynamic work happens once, at generation time. Jinja expansion, foreach, when, and glob resolution are all evaluated before build.ninja is written. The plan Ninja receives contains no conditionals, no string manipulation, and no filesystem discovery, so the execution phase spends its time on dependency checking and on running commands.
Generation is deterministic: the same manifest and environment produce a byte-for-byte identical build.ninja. An unchanged manifest therefore yields an unchanged plan, and a build with nothing to do collapses to a dependency check with no recipes executed.