From f517de438435485ad69803b9561fd2cc8ade03ef Mon Sep 17 00:00:00 2001 From: Leynos Date: Sat, 12 Jul 2025 20:31:41 +0100 Subject: [PATCH 1/4] Document command and script options --- docs/netsuke-design.md | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/netsuke-design.md b/docs/netsuke-design.md index bcbe3028..5dfb49e1 100644 --- a/docs/netsuke-design.md +++ b/docs/netsuke-design.md @@ -162,13 +162,18 @@ 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. - `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 +195,13 @@ 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. + - `sources`: The input files required by the command. This can be a single string or a list of strings. @@ -324,7 +336,8 @@ pub struct NetsukeManifest { #[serde(deny_unknown_fields)] pub struct Rule { pub name: String, - pub command: String, + pub command: Option, + pub script: Option, pub description: Option, pub deps: Option, // Additional fields like 'pool' or 'restat' can be added here @@ -335,7 +348,9 @@ pub struct Rule { #[serde(deny_unknown_fields)] pub struct Target { pub name: StringOrList, - pub rule: String, + pub rule: Option, + pub command: Option, + pub script: Option, #[serde(default)] pub sources: StringOrList, @@ -626,7 +641,8 @@ pub struct BuildGraph { /// Represents a reusable command, analogous to a Ninja 'rule'. pub struct Action { - pub command: String, + pub command: Option, + pub script: Option, pub description: Option, pub depfile: Option, // Template for the.d file path, e.g., "$out.d" pub deps_format: Option, // "gcc" or "msvc" From 74cd27ee0f4fd6118db43a853b8816ae8dfef17b Mon Sep 17 00:00:00 2001 From: Leynos Date: Sun, 13 Jul 2025 09:33:13 +0100 Subject: [PATCH 2/4] Refine design for rule and target scripts --- docs/netsuke-design.md | 50 ++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/docs/netsuke-design.md b/docs/netsuke-design.md index 5dfb49e1..ef06bf98 100644 --- a/docs/netsuke-design.md +++ b/docs/netsuke-design.md @@ -64,8 +64,10 @@ before execution, a critical requirement for compatibility with Ninja. Representation (IR) of the build. This IR represents the build as a static 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 that 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 @@ -172,8 +174,13 @@ Each entry in the `rules` list is a mapping that defines a reusable action. `$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. + 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 an enum `Recipe` to ensure the + exclusivity is encoded at the type level. - `description`: An optional, user-friendly string that is printed to the console when the rule is executed. This maps to Ninja's `description` field @@ -200,7 +207,11 @@ Each entry in `targets` defines a build edge; placing a target in the optional - `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. + Only one of `rule`, `command`, or `script` may be specified. The parser + validates this exclusivity during deserialisation. + + This union deserialises into an enum `TargetRecipe` so that the chosen + execution method is explicit in the AST. - `sources`: The input files required by the command. This can be a single string or a list of strings. @@ -336,21 +347,25 @@ pub struct NetsukeManifest { #[serde(deny_unknown_fields)] pub struct Rule { pub name: String, - pub command: Option, - pub script: Option, + 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 a rule. +#[serde(untagged)] +pub enum Recipe { + Command { command: String }, + Script { script: String }, +} + /// Represents a single build target or edge in the dependency graph. #[serde(deny_unknown_fields)] pub struct Target { pub name: StringOrList, - pub rule: Option, - pub command: Option, - pub script: Option, + pub recipe: TargetRecipe, #[serde(default)] pub sources: StringOrList, @@ -373,6 +388,14 @@ pub struct Target { pub always: bool, } +/// Specifies how a target is built. +#[serde(untagged)] +pub enum TargetRecipe { + Rule { rule: String }, + Command { command: String }, + Script { script: String }, +} + /// An enum to handle fields that can be either a single string or a list of strings. #[serde(untagged)] pub enum StringOrList { @@ -641,8 +664,7 @@ pub struct BuildGraph { /// Represents a reusable command, analogous to a Ninja 'rule'. pub struct Action { - pub command: Option, - pub script: Option, + 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" @@ -721,6 +743,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 From b286aac44f9799f8edacafdbb55fda06eac05816 Mon Sep 17 00:00:00 2001 From: Leynos Date: Sun, 13 Jul 2025 10:09:16 +0100 Subject: [PATCH 3/4] Clarify recipe enums and wrap lines --- AGENTS.md | 4 ++-- docs/netsuke-design.md | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c0fea37b..41f5fd64 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -134,8 +134,8 @@ 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. +- 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`. - Markdown paragraphs and bullet points must be wrapped at 80 columns. - Code blocks must be wrapped at 120 columns. diff --git a/docs/netsuke-design.md b/docs/netsuke-design.md index ef06bf98..ced837c0 100644 --- a/docs/netsuke-design.md +++ b/docs/netsuke-design.md @@ -62,12 +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. It verifies the existence of referenced rules, ensures each rule has - exactly one of `command` or `script`, and that every target specifies exactly - one of `rule`, `command`, or `script`. Circular dependencies and missing - inputs are also detected at this stage. + 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 @@ -179,8 +179,8 @@ Each entry in the `rules` list is a mapping that defines a reusable action. Exactly one of `command` or `script` must be provided. The manifest parser enforces this rule to prevent invalid states. - Internally, these options deserialise into an enum `Recipe` to ensure the - exclusivity is encoded at the type level. + Internally, these options deserialise into a tagged enum `Recipe` using a + `kind` field to encode the exclusivity at the type level. - `description`: An optional, user-friendly string that is printed to the console when the rule is executed. This maps to Ninja's `description` field @@ -210,7 +210,7 @@ Each entry in `targets` defines a build edge; placing a target in the optional Only one of `rule`, `command`, or `script` may be specified. The parser validates this exclusivity during deserialisation. - This union deserialises into an enum `TargetRecipe` so that the chosen + This union deserialises into a tagged enum `TargetRecipe` so that the chosen execution method is explicit in the AST. - `sources`: The input files required by the command. This can be a single @@ -355,7 +355,7 @@ pub struct Rule { } /// A union of execution styles for a rule. -#[serde(untagged)] +#[serde(tag = "kind", rename_all = "lowercase")] pub enum Recipe { Command { command: String }, Script { script: String }, @@ -389,7 +389,7 @@ pub struct Target { } /// Specifies how a target is built. -#[serde(untagged)] +#[serde(tag = "kind", rename_all = "lowercase")] pub enum TargetRecipe { Rule { rule: String }, Command { command: String }, From 0da50c5a610d1a267b1e6d6516901cd2e631b8ad Mon Sep 17 00:00:00 2001 From: Leynos Date: Sun, 13 Jul 2025 10:23:45 +0100 Subject: [PATCH 4/4] Fix docs --- AGENTS.md | 2 +- docs/netsuke-design.md | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 41f5fd64..a7dc8980 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -136,7 +136,7 @@ project: - 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`. +- 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 ced837c0..e9289375 100644 --- a/docs/netsuke-design.md +++ b/docs/netsuke-design.md @@ -179,8 +179,9 @@ Each entry in the `rules` list is a mapping that defines a reusable action. 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 tagged enum `Recipe` using a - `kind` field to encode the exclusivity at the type level. + 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 @@ -210,8 +211,9 @@ Each entry in `targets` defines a build edge; placing a target in the optional Only one of `rule`, `command`, or `script` may be specified. The parser validates this exclusivity during deserialisation. - This union deserialises into a tagged enum `TargetRecipe` so that the chosen - execution method is explicit in the AST. + 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. @@ -354,18 +356,22 @@ pub struct Rule { // to map to more advanced Ninja features. } -/// A union of execution styles for a rule. +/// 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 recipe: TargetRecipe, + pub recipe: Recipe, #[serde(default)] pub sources: StringOrList, @@ -388,13 +394,6 @@ pub struct Target { pub always: bool, } -/// Specifies how a target is built. -#[serde(tag = "kind", rename_all = "lowercase")] -pub enum TargetRecipe { - Rule { rule: String }, - Command { command: String }, - Script { script: String }, -} /// An enum to handle fields that can be either a single string or a list of strings. #[serde(untagged)]