-
Notifications
You must be signed in to change notification settings - Fork 0
Use PowerShell for Windows recipes (#599) #607
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
05e19e7
4ed2c3f
8debf38
3dd5485
b9e969d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -298,7 +298,7 @@ offending key. | |
| A rule or target must provide exactly one recipe: | ||
|
|
||
| - `command`: one shell command, or an ordered list of commands. | ||
| - `script`: a multi-line POSIX shell script. | ||
| - `script`: a multi-line script for the selected legacy-recipe interpreter. | ||
| - `rule`: the name of another rule to use. | ||
|
|
||
| Rules may also provide `description`, text used for Ninja's progress display. | ||
|
|
@@ -319,8 +319,58 @@ same Jinja context, including `{{ ins }}` and `{{ outs }}`; those two | |
| placeholders are resolved later to the concrete target's shell-quoted input and | ||
| output paths. An empty command list is rejected when the manifest is parsed. | ||
|
|
||
| At execution time, each list entry is evaluated inside its own brace group and | ||
| the groups are joined with `&&`. The entry is passed to `eval` as a | ||
| ### Windows legacy recipe contract | ||
|
|
||
| On Windows, v0.1.x interprets every legacy `command` string, `command` list, | ||
| and `script` with **Windows PowerShell** (`powershell.exe`), not with the shell | ||
| that launched `netsuke`. Netsuke invokes it explicitly with an encoded, | ||
| non-interactive, no-profile command before Ninja executes a recipe. A build | ||
| started from PowerShell, `cmd.exe`, an IDE, or Git Bash therefore uses the same | ||
| recipe interpreter. This is a Windows PowerShell contract, not a PowerShell | ||
| Core (`pwsh`) contract. | ||
|
|
||
| Scalar commands and scripts each receive a fresh PowerShell process. A command | ||
| list receives one shared process: entries run in declaration order, later | ||
| entries see PowerShell variables, `$env:` assignments, and locations left by an | ||
| earlier entry, and Netsuke exits at the first native-program non-zero status. | ||
| PowerShell terminating errors also fail the recipe. State does not cross action | ||
| or target boundaries. | ||
|
|
||
| Use PowerShell syntax in the default route. `$name` is a PowerShell variable | ||
| and `$env:NAME` reads an environment variable; `${VAR:-default}` is POSIX | ||
| syntax and is not valid PowerShell. Recipe text is protected from Ninja dollar | ||
| expansion, so write ordinary PowerShell dollars rather than `$$`. The rendered | ||
| `{{ ins }}` and `{{ outs }}` paths use single-quoted PowerShell arguments, | ||
| including paths with spaces. Quote every other path and argument with | ||
| PowerShell syntax; arbitrary rendered Jinja text is not shell-quoted. | ||
|
|
||
| Ninja turns a failed recipe into its own non-zero result, and `netsuke` returns | ||
| failure after forwarding Ninja's output. The CLI contract distinguishes success | ||
| from failure; it does not promise to return the recipe's exact child value. | ||
|
|
||
| To retain POSIX interpretation on Windows, explicitly select a Git | ||
| Bash-compatible runtime: | ||
|
|
||
| <!-- tested-example: guide-windows-bash-compatibility --> | ||
| ```powershell | ||
| choco install git --yes --no-progress | ||
| $env:PATH = "C:\Program Files\Git\bin;$env:PATH" | ||
| $env:NETSUKE_WINDOWS_SHELL = "bash" | ||
| netsuke build | ||
| ``` | ||
|
|
||
| MSYS2 Bash is also supported when `bash.exe` is on `PATH`. Before `build` or | ||
| Ninja-tool execution, Netsuke checks this selection. If `bash.exe --version` | ||
| cannot run, it stops with instructions to install Git for Windows or MSYS2, add | ||
| Bash to `PATH`, or unset `NETSUKE_WINDOWS_SHELL`. `generate` and `help targets` | ||
| do not execute recipes, so they do not require Bash. In CI, install Git | ||
| explicitly, prepend its `bin` directory to `PATH`, set | ||
| `NETSUKE_WINDOWS_SHELL=bash`, and launch Netsuke normally from a `pwsh` step; | ||
| do not rely on a workflow-wide `shell: bash` setting. | ||
|
|
||
| For the Unix default and the explicit Bash route, each list entry is evaluated | ||
| inside its own brace group and the groups are joined with `&&`. The entry is | ||
| passed to `eval` as a | ||
| shell-quoted payload, so an inline `#` comment or a trailing control operator | ||
| such as `&` cannot consume the generated group's closing boundary. Brace groups | ||
| run in the current shell rather than a subshell: a changed working directory, | ||
|
|
@@ -368,9 +418,12 @@ Prefer a `command` list for a short, ordered sequence of distinct commands. | |
| Prefer `script` when the logic needs multi-line structure or shell constructs | ||
| such as loops, conditionals, or variable assignment. | ||
|
|
||
| The v0.1.0-beta2 `script` implementation invokes `/bin/sh -e`; it is not | ||
| currently a portable PowerShell abstraction. Prefer `command` or | ||
| platform-selected actions when a manifest must work on Windows. | ||
| Legacy recipes remain shell strings in v0.1.x. The structured command blocks | ||
| and argv templates proposed | ||
| in [RFC: structured command blocks and argv templates #573](https://github.com/leynos/netsuke/pull/573) | ||
| for v0.2.0 are intended to remove this shell-selection, quoting, path, | ||
| variable, and exit-semantics ambiguity. They do not change the v0.1.x contract | ||
| described here. | ||
|
|
||
| ### Targets, inputs, and dependencies | ||
|
|
||
|
|
@@ -1352,7 +1405,9 @@ Netsuke reduces some common quoting mistakes, but it is not a sandbox: | |
| - `{{ ins }}` and `{{ outs }}` are quoted as path arguments. | ||
| - Arbitrary Jinja values in `command` and `script` are not automatically | ||
| shell-quoted. | ||
| - `script` uses `/bin/sh -e` in v0.1.0-beta2. | ||
| - On Windows, legacy recipes use the PowerShell contract above unless | ||
| `NETSUKE_WINDOWS_SHELL=bash` selects the explicit Bash compatibility route. | ||
| On Unix, scripts use `/bin/sh -e`. | ||
|
Comment on lines
+1408
to
+1410
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Scope the POSIX-only safety rules. The Windows PowerShell route skips POSIX command-list validation, but the following safety text describes brace groups, 🤖 Prompt for AI Agents |
||
| - `shell`, `grep`, `fetch`, filesystem helpers, and ordinary recipes interact | ||
| with the host. | ||
| - `glob` restricts its filesystem metadata access to a capability handle | ||
|
|
@@ -1381,11 +1436,11 @@ Netsuke reduces some common quoting mistakes, but it is not a sandbox: | |
| structured or nested `exec` forms are rejected during Ninja generation. | ||
| Failure diagnostics include the action fingerprint and one-based entry | ||
| position when Netsuke can attribute the failed list entry. | ||
| - Write shell dollar expressions normally: `$PATH`, `$RUSTFLAGS`, and | ||
| `${CARGO:-cargo}` reach the child shell unchanged. Netsuke performs the | ||
| required Ninja escaping after it lowers `$in`, `$out`, `{{ ins }}`, and | ||
| `{{ outs }}`. A `$in` or `$out` token inside backticks is rejected because | ||
| Netsuke cannot safely lower it there. | ||
| - Write shell dollar expressions normally. `$PATH`, `$RUSTFLAGS`, and | ||
| `${CARGO:-cargo}` reach POSIX routes unchanged; PowerShell routes use `$name` | ||
| or `$env:NAME`. Netsuke performs the required Ninja escaping after it lowers | ||
| `$in`, `$out`, `{{ ins }}`, and `{{ outs }}`. A `$in` or `$out` token inside | ||
| backticks is rejected because Netsuke cannot safely lower it there. | ||
|
Comment on lines
+1439
to
+1443
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- target section ---'
sed -n '1400,1475p' docs/users-guide.md
printf '%s\n' '--- occurrences of dollar escaping and migration wording ---'
rg -n -C 3 '\$\$|PowerShell|POSIX|process identifier|migration|shell dollar' docs/users-guide.md src .github 2>/dev/null | head -240
printf '%s\n' '--- applicable repository guidance and learnings ---'
find /tmp/coderabbit-repo-knowledge/leynos-netsuke-ecaa667f -maxdepth 2 -type f -name '*.md' -print 2>/dev/null | sort | while read -r f; do
case "$f" in
*learnings/*|*conventions/*|*guidelines/*) printf '\n### %s\n' "$f"; head -120 "$f";;
esac
doneRepository: leynos/netsuke Length of output: 50372 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- relevant source declarations and call sites ---'
sed -n '1,130p' src/ninja_gen_escape.rs
sed -n '1,220p' src/runner/recipe_shell.rs
rg -n -C 4 'lower|interpolat|escape_ninja_value|RecipeShell::(PowerShell|Bash)|powershell|bash|NETSUKE_WINDOWS_SHELL' src tests docs --glob '*.rs' --glob '*.md' --glob '*.feature' | head -260
printf '%s\n' '--- focused repository diff ---'
git diff -- docs/users-guide.md | sed -n '1,180p'
printf '%s\n' '--- focused $$ references ---'
rg -n -C 3 '\$\$' --glob '!target/**' --glob '!*.lock' . | head -220Repository: leynos/netsuke Length of output: 41451 🌐 Web query:
💡 Result: According to official Microsoft PowerShell documentation, the automatic variable $$ contains the last token in the last line received by the session [1][2]. It is one of several reserved automatic variables created and maintained by PowerShell to store state information [1][3]. Citations:
Scope the 🤖 Prompt for AI Agents |
||
| - **Migration:** replace the historical manifest spelling `$$PATH` with | ||
| `$PATH`. Keeping the extra dollar now asks the shell to interpret `$$` as | ||
| its process identifier and can change the command's result. Existing script | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
Repository: leynos/netsuke
Length of output: 50373
🏁 Script executed:
Repository: leynos/netsuke
Length of output: 21145
🏁 Script executed:
Repository: leynos/netsuke
Length of output: 23471
🌐 Web query:
Microsoft PowerShell about automatic variables LASTEXITCODE value last native program💡 Result:
The
$LASTEXITCODEautomatic variable in PowerShell contains the exit code of the last native program or PowerShell script that ran [1][2]. Key details regarding its behavior: General Functionality - It serves as the PowerShell equivalent to the %ERRORLEVEL% environment variable found in cmd.exe [3]. - When a native command (an external executable) is executed, PowerShell updates$LASTEXITCODEwith the exit code returned by that program [1][3]. - PowerShell also uses this variable to track the exit status of scripts that explicitly use the exit keyword [1][4]. - Related to this is the$?automatic variable, which contains a Boolean value indicating the success ($true) or failure ($false) of the last operation [3]. For native commands,$?is set to $true if$LASTEXITCODEis 0, and $false if it is any other value [2][5]. Behavior in PowerShell Scripts The value of$LASTEXITCODEwhen running scripts depends on how they are invoked and whether the exit keyword is used [1][2]: - Using the exit keyword: If a script uses the exit keyword,$LASTEXITCODEis set to the value specified by that keyword [1]. - Direct invocation (e.g.,./Test.ps1 or &./Test.ps1): The value is generally not changed unless the script specifically calls a native command, calls another script that uses the exit keyword, or uses the exit keyword itself [1][2]. - Invocation via pwsh with the -File parameter: - Set to 1 if the script terminates due to an exception [1]. - Set to the value specified by the exit keyword if one is used [1]. - Set to 0 if the script completes successfully [1]. - Invocation via pwsh with the -Command parameter: - Set to 1 if the script terminates due to an exception or if the last command set$?to $false [1]. - Set to 0 if the script completes successfully and the last command set$?to $true [1].Citations:
Make PowerShell list status checks fail fast per native command.
RecipeShell::command_list_scriptchecks$LASTEXITCODEonly after each complete entry. A later successful native command can overwrite an earlier failure within the same entry, so later entries can run. Align the renderer and the guarantees documented indocs/users-guide.md,docs/v0-1-0-migration-guide.md, anddocs/developers-guide.md.📍 Affects 3 files
docs/users-guide.md#L332-L337(this comment)docs/v0-1-0-migration-guide.md#L88-L94docs/developers-guide.md#L299-L303🤖 Prompt for AI Agents