diff --git a/AGENTS.md b/AGENTS.md index c0fea37b..a7dc8980 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -134,9 +134,9 @@ project: ## Markdown Guidance - Validate Markdown files using `make markdownlint`. -- Run `make fmt` after any documentation changes to format all Markdown - files and fix table markup. -- Validate Markdown Mermaid diagrams using by running `make nixie`. +- Run `make fmt` after any documentation changes to format all Markdown files + and fix table markup. +- Validate Markdown Mermaid diagrams by running `make nixie`. - Markdown paragraphs and bullet points must be wrapped at 80 columns. - Code blocks must be wrapped at 120 columns. - Tables and headings must not be wrapped. diff --git a/docs/netsuke-design.md b/docs/netsuke-design.md index bcbe3028..e9289375 100644 --- a/docs/netsuke-design.md +++ b/docs/netsuke-design.md @@ -62,10 +62,12 @@ before execution, a critical requirement for compatibility with Ninja. The AST is traversed to construct a canonical, fully resolved Intermediate Representation (IR) of the build. This IR represents the build as a static - dependency graph, with all file paths, commands, and dependencies explicitly + dependency graph with all file paths, commands, and dependencies explicitly defined. During this transformation, Netsuke performs critical validation - checks, such as verifying the existence of defined rules, detecting circular - dependencies, and ensuring all required inputs are accounted for. + checks. It verifies the existence of referenced rules, ensures each rule has + exactly one of `command` or `script`, and ensures every target specifies + exactly one of `rule`, `command`, or `script`. Circular dependencies and + missing inputs are also detected at this stage. 5. Stage 5: Ninja Synthesis & Execution @@ -162,13 +164,24 @@ top-level keys. ### 2.3 Defining `rules` -Each entry in the `rules` list is a mapping that defines a command template. +Each entry in the `rules` list is a mapping that defines a reusable action. - `name`: A unique string identifier for the rule. -- `command`: The command string to be executed. This string uses placeholders - like `{ins}` and `{outs}` for input and output files. Netsuke's IR-to-Ninja - generator will translate these into Ninja's native `$in` and `$out` variables. +- `command`: A single command string to be executed. This string uses + placeholders like `{ins}` and `{outs}` for input and output files. Netsuke's + IR-to-Ninja generator will translate these into Ninja's native `$in` and + `$out` variables. + +- `script`: A multi-line script declared with the YAML `|` block style. The + entire block is passed to an interpreter (currently `/bin/sh`). + + Exactly one of `command` or `script` must be provided. The manifest parser + enforces this rule to prevent invalid states. + + Internally, these options deserialise into a shared `Recipe` enum tagged with + a `kind` field. Serde aliases ensure manifests that omit the tag continue to + load correctly. - `description`: An optional, user-friendly string that is printed to the console when the rule is executed. This maps to Ninja's `description` field @@ -190,6 +203,18 @@ Each entry in `targets` defines a build edge; placing a target in the optional - `rule`: The name of the rule (from the `rules` section) to use for building this target. +- `command`: A single command string to run directly for this target. + +- `script`: A multi-line script passed to the interpreter. When present, it is + defined using the YAML `|` block style. + + Only one of `rule`, `command`, or `script` may be specified. The parser + validates this exclusivity during deserialisation. + + This union deserialises into the same `Recipe` enum used for rules. The parser + enforces that only one variant is present, maintaining backward compatibility + through serde aliases when `kind` is omitted. + - `sources`: The input files required by the command. This can be a single string or a list of strings. @@ -324,18 +349,29 @@ pub struct NetsukeManifest { #[serde(deny_unknown_fields)] pub struct Rule { pub name: String, - pub command: String, + pub recipe: Recipe, pub description: Option, pub deps: Option, // Additional fields like 'pool' or 'restat' can be added here // to map to more advanced Ninja features. } +/// A union of execution styles for both rules and targets. +#[serde(tag = "kind", rename_all = "lowercase")] +pub enum Recipe { + #[serde(alias = "command")] + Command { command: String }, + #[serde(alias = "script")] + Script { script: String }, + #[serde(alias = "rule")] + Rule { rule: String }, +} + /// Represents a single build target or edge in the dependency graph. #[serde(deny_unknown_fields)] pub struct Target { pub name: StringOrList, - pub rule: String, + pub recipe: Recipe, #[serde(default)] pub sources: StringOrList, @@ -358,6 +394,7 @@ pub struct Target { pub always: bool, } + /// An enum to handle fields that can be either a single string or a list of strings. #[serde(untagged)] pub enum StringOrList { @@ -626,7 +663,7 @@ pub struct BuildGraph { /// Represents a reusable command, analogous to a Ninja 'rule'. pub struct Action { - pub command: String, + pub recipe: Recipe, pub description: Option, pub depfile: Option, // Template for the.d file path, e.g., "$out.d" pub deps_format: Option, // "gcc" or "msvc" @@ -705,6 +742,10 @@ structures to the Ninja file syntax. file. The command placeholders (`{ins}`, `{outs}`) are replaced with Ninja's variables (`$in`, `$out`). + When an action's `recipe` is a script, the generated rule wraps the script in + an invocation of `/bin/sh -e -c` so that multi-line scripts execute + consistently across platforms. + Code snippet ````ninja