From 3fcdc6bba846ce622f77d21905d3fde1e65df9d1 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 13 Jul 2026 20:22:33 +0900 Subject: [PATCH] Rename the toolchain commands to rb msvc enable/exec A top-level `rb exec` reads as "run under the selected ruby", the meaning exec has in version managers, not as MSVC build-environment activation. Namespace both surfaces under the toolset name and rename the Devkit class to Msvc to match; "devkit" is not a Visual Studio term. Co-Authored-By: Claude Fable 5 --- README.md | 20 ++--- docs/{devkit-enable.md => msvc-enable.md} | 80 +++++++++---------- docs/test-plan.md | 30 +++---- src/rbmanager/{Devkit.cs => Msvc.cs} | 28 ++++--- src/rbmanager/Program.cs | 20 ++--- tests/rbmanager.Tests/CliE2eTests.cs | 5 +- ...ivationTests.cs => MsvcActivationTests.cs} | 28 +++---- ...evkitHelperTests.cs => MsvcHelperTests.cs} | 40 +++++----- .../{DevkitVsTests.cs => MsvcVsTests.cs} | 10 +-- tests/rbmanager.Tests/SmokeTests.cs | 4 +- tests/rbmanager.Tests/Support/Serial.cs | 2 +- tests/rbmanager.Tests/Support/VsWhereScope.cs | 8 +- 12 files changed, 139 insertions(+), 136 deletions(-) rename docs/{devkit-enable.md => msvc-enable.md} (79%) rename src/rbmanager/{Devkit.cs => Msvc.cs} (89%) rename tests/rbmanager.Tests/{DevkitActivationTests.cs => MsvcActivationTests.cs} (89%) rename tests/rbmanager.Tests/{DevkitHelperTests.cs => MsvcHelperTests.cs} (51%) rename tests/rbmanager.Tests/{DevkitVsTests.cs => MsvcVsTests.cs} (82%) diff --git a/README.md b/README.md index 79657be..83df0a2 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ after the language (`%LOCALAPPDATA%\Ruby`, like `%LocalAppData%\Python`) rather than after the tool. rbmanager remains the product name. ``` -rb setup [--yes] copy rb onto PATH and set up the VC++ runtime -rb install install a ruby binary package -rb list list installed rubies -rb use switch the active ruby -rb uninstall remove an installed ruby -rb enable [shell] print C++ build env to eval (cmd|powershell) -rb exec run a command with the C++ build env applied +rb setup [--yes] copy rb onto PATH and set up the VC++ runtime +rb install install a ruby binary package +rb list list installed rubies +rb use switch the active ruby +rb uninstall remove an installed ruby +rb msvc enable [shell] print the MSVC build env to eval (cmd|powershell) +rb msvc exec run a command with the MSVC build env applied ``` rb is a bare exe; `setup` copies it to @@ -37,9 +37,9 @@ ship. It also checks for the VC++ 2015-2022 redistributable the official mswin packages depend on, and offers to download and install it (signature-verified, elevated); `--yes` skips the consent prompt. -`enable` and `exec` are the `ridk enable` equivalent for building -C extension gems with MSVC; see -[docs/devkit-enable.md](docs/devkit-enable.md). +`msvc enable` and `msvc exec` activate an installed Visual Studio (or +Build Tools) MSVC toolchain for building C extension gems; see +[docs/msvc-enable.md](docs/msvc-enable.md). The active ruby is exposed through an NTFS directory junction `%LOCALAPPDATA%\Ruby\current`, and `install` appends diff --git a/docs/devkit-enable.md b/docs/msvc-enable.md similarity index 79% rename from docs/devkit-enable.md rename to docs/msvc-enable.md index a3c8023..66d91e6 100644 --- a/docs/devkit-enable.md +++ b/docs/msvc-enable.md @@ -1,4 +1,4 @@ -# A `ridk enable` equivalent for the mswin packages +# Activating the MSVC build environment for the mswin packages ## Problem @@ -35,14 +35,14 @@ Studio. - `VsDevCmd.bat -arch=amd64 -host_arch=amd64` puts `cl`/`nmake`/`link` on PATH and sets `INCLUDE`/`LIB`/`LIBPATH`/`VCToolsRedistDir` (exit 0). -- Under the prototype `rb exec`, mkmf's `find_executable('cl')` +- Under the prototype `rb msvc exec`, mkmf's `find_executable('cl')` succeeds, and a trivial C extension compiles, links, and loads: `extconf.rb` -> `nmake` -> `require './hello.so'` returns a value from native code. -- `rb enable powershell | Invoke-Expression` puts `cl` on the current - session's PATH (ridk parity). +- `rb msvc enable powershell | Invoke-Expression` puts `cl` on the + current session's PATH. -The conclusion is that a compiler-only `rb enable`/`rb exec` is fully +The conclusion is that a compiler-only `rb msvc enable`/`rb msvc exec` is fully feasible and small. The interesting decisions are the command surface and how far to go on third-party dependency headers. @@ -53,40 +53,40 @@ environment. `ridk enable` only works because it is a shell function whose output is eval'd into the current shell. Any activation feature must work around this, and the two useful shapes are: -1. **`rb exec ` (primary).** Spawns a child process with the - toolchain and active ruby already applied. No parent mutation, so - nothing to eval and nothing to get wrong. `rb exec gem install - nokogiri` just works. This is the recommended path for the common - case (one build command) and for scripts/CI, and it is the surface - that is bulletproof by construction. +1. **`rb msvc exec ` (primary).** Spawns a child process + with the toolchain and active ruby already applied. No parent + mutation, so nothing to eval and nothing to get wrong. `rb msvc exec + gem install nokogiri` just works. This is the recommended path for + the common case (one build command) and for scripts/CI, and it is + the surface that is bulletproof by construction. -2. **`rb enable [cmd|powershell|pwsh]` (ridk parity).** Prints +2. **`rb msvc enable [cmd|powershell|pwsh]` (shell activation).** Prints environment assignments for the user to eval into the current shell, for interactive sessions where several build commands follow: ``` rem cmd - for /f "delims=" %L in ('rb enable cmd') do @%L + for /f "delims=" %L in ('rb msvc enable cmd') do @%L # PowerShell / pwsh - rb enable powershell | Invoke-Expression + rb msvc enable powershell | Invoke-Expression ``` - This mirrors `ridk enable` and is the escape hatch for users who want - a persistently activated shell rather than a per-command wrapper. + This is the escape hatch for users who want a persistently activated + shell rather than a per-command wrapper. -Recommend shipping both. `rb exec` is the headline; `rb enable` covers -the interactive workflow ridk users expect. A third option, writing a +Recommend shipping both. `rb msvc exec` is the headline; `rb msvc +enable` covers the interactive workflow. A third option, writing a dot-sourced activation script into `%LOCALAPPDATA%\Ruby`, adds a file to manage and a staleness problem (the resolved VS path is baked in) for no -gain over `rb enable`, so it is not recommended. +gain over `rb msvc enable`, so it is not recommended. -The parent-shell-mutation constraint is handled cleanly: `rb exec` +The parent-shell-mutation constraint is handled cleanly: `rb msvc exec` sidesteps it entirely by owning the child's environment; -`rb enable` respects it by making the caller responsible for the eval, -exactly as ridk does. +`rb msvc enable` respects it by making the caller responsible for the +eval. -### Shell selection for `rb enable` +### Shell selection for `rb msvc enable` The prototype takes the shell as an explicit argument and defaults to PowerShell (the common interactive shell on modern Windows). Auto- @@ -147,12 +147,12 @@ cmd /s /c "call "\Common7\Tools\VsDevCmd.bat" \ child inherits the calling shell's environment, so diffing the captured `set` output against rb's own environment yields exactly the variables VsDevCmd added or changed (PATH, INCLUDE, LIB, LIBPATH, -VCToolsRedistDir, and the VSCMD bookkeeping vars). `rb exec` applies -that delta to the child it spawns; `rb enable` prints it as `set +VCToolsRedistDir, and the VSCMD bookkeeping vars). `rb msvc exec` +applies that delta to the child it spawns; `rb msvc enable` prints it as `set "K=V"` (cmd) or `$env:K = '...'` (PowerShell, single-quoted literal with `'` doubled). -`rb exec` routes the user command through `cmd /s /c` so that `.cmd` +`rb msvc exec` routes the user command through `cmd /s /c` so that `.cmd` shims (`gem`, `bundle`) and PATHEXT resolve the way they would if the user had typed the command directly; a bare `CreateProcess` would not find `gem` (it is `gem.cmd`). @@ -166,8 +166,8 @@ name and the loader refuses to find it in the current directory, which surfaces as the same cryptic "install development tools first" error even though the compiler is present. -Both surfaces clear it for the activated environment: `rb exec` removes -the variable from the child's environment block, and `rb enable` emits +Both surfaces clear it for the activated environment: `rb msvc exec` +removes the variable from the child's environment block, and `rb msvc enable` emits the unset (`set "NoDefault...="` for cmd, `Remove-Item Env:\NoDefault...` for PowerShell). This is cheap insurance against a confusing failure and is recommended. @@ -194,7 +194,7 @@ and no opt-dir pointing at any such tree on the destination machine. ### Recommendation: phase 1 is compiler-only -Ship `rb exec`/`rb enable` as compiler-only first, and document the +Ship `rb msvc exec`/`rb msvc enable` as compiler-only first, and document the dependency-linking limitation. This unblocks the large class of pure-C gems immediately, is small and low-risk, and does not commit rbmanager to shipping or versioning a pile of vcpkg dev files whose provenance and @@ -241,20 +241,20 @@ should not depend on it.) ## Prototype -`src/rbmanager/Devkit.cs` implements both subcommands, wired into -`Program.cs`'s dispatch switch as `rb enable [shell]` and -`rb exec `. It is ~180 lines, marked as a prototype, and +`src/rbmanager/Msvc.cs` implements both subcommands, wired into +`Program.cs`'s dispatch switch as `rb msvc enable [shell]` and +`rb msvc exec `. It is ~180 lines, marked as a prototype, and covers VS discovery, VsDevCmd activation with env-diffing, the `NoDefaultCurrentDirectoryInExePath` clearing, and the per-shell output. It is compiler-only (phase 1). What was exercised: -- `rb enable powershell|cmd` prints correct assignments; the PowerShell - form activates a live session via `| Invoke-Expression`. -- `rb exec ruby -rmkmf -e "find_executable('cl')"` finds the compiler. -- `rb exec` drives a full `extconf.rb` -> `nmake` -> load of a native +- `rb msvc enable powershell|cmd` prints correct assignments; the + PowerShell form activates a live session via `| Invoke-Expression`. +- `rb msvc exec ruby -rmkmf -e "find_executable('cl')"` finds the compiler. +- `rb msvc exec` drives a full `extconf.rb` -> `nmake` -> load of a native extension. -A `gem install msgpack` under `rb exec` compiled several files (proving +A `gem install msgpack` under `rb msvc exec` compiled several files (proving the toolchain is live) before failing on an `RBIMPL_UNREACHABLE_RETURN`/`C2059` macro error in msgpack 1.8.3 against Ruby 4.0's headers. That is an upstream gem/source incompatibility, not @@ -263,14 +263,14 @@ appearing on the compile line. ## Open questions -1. Auto-detect the parent shell for `rb enable`, or keep the explicit +1. Auto-detect the parent shell for `rb msvc enable`, or keep the explicit argument with a default? (Prototype: explicit, default PowerShell.) -2. Should `rb exec`/`rb enable` also guarantee the active ruby's +2. Should `rb msvc exec`/`rb msvc enable` also guarantee the active ruby's `current\bin` is on PATH, or continue to rely on `install` having put it there? (Prototype relies on install.) 3. Phase 2 trigger: is dependency-linking demand high enough to justify bundling vcpkg dev files, and should the dev-file tree ship inside the mswin package or as a separate rbmanager-managed download? 4. Should a leaked/nonexistent `--with-opt-dir` in `configure_args` be - actively stripped or overridden by `rb enable` even in phase 1, to + actively stripped or overridden by `rb msvc enable` even in phase 1, to remove the confusing `-I` from every compile line? diff --git a/docs/test-plan.md b/docs/test-plan.md index 46124cd..e2e0af6 100644 --- a/docs/test-plan.md +++ b/docs/test-plan.md @@ -34,8 +34,8 @@ Command surface and contracts: | `rb list` | Installed names sorted, active one starred | 0 | | `rb use ` | Resolve query (exact or case-insensitive substring; must be unambiguous), recreate the `current` junction, ensure PATH | 0 / 1 | | `rb uninstall ` | Resolve; if active, delete the junction first and print a hint; delete the install dir recursively | 0 / 1 | -| `rb enable [shell]` | Locate VsDevCmd via vswhere, compute the env delta of activation, print per-shell assignments plus an unset of `NoDefaultCurrentDirectoryInExePath`. Shell defaults to PowerShell; `cmd`/`bat` selects cmd syntax. No toolchain: actionable warning on stderr | 0 / 1 | -| `rb exec ` | Same delta applied to a `cmd /s /c` child (so `.cmd` shims resolve); removes `NoDefaultCurrentDirectoryInExePath`; propagates the child's exit code | child / 1 | +| `rb msvc enable [shell]` | Locate VsDevCmd via vswhere, compute the env delta of activation, print per-shell assignments plus an unset of `NoDefaultCurrentDirectoryInExePath`. Shell defaults to PowerShell; `cmd`/`bat` selects cmd syntax. No toolchain: actionable warning on stderr | 0 / 1 | +| `rb msvc exec ` | Same delta applied to a `cmd /s /c` child (so `.cmd` shims resolve); removes `NoDefaultCurrentDirectoryInExePath`; propagates the child's exit code | child / 1 | | anything else | Usage text | 2 | Any thrown exception is caught in `Main`, printed as `rb: ` to @@ -63,7 +63,7 @@ stderr, exit 1. and deletes it in `Dispose`. Junction targets and junction points both live inside it. - Tests that redirect `Console.Out`/`Console.Error` or mutate the - process environment (`Program`/`Devkit` in-process tests) go in one + process environment (`Program`/`Msvc` in-process tests) go in one xUnit collection (`[Collection("process-global")]`) so they never run in parallel with each other. E2E tests spawn processes and can stay parallel because each gets its own root via the env seam. @@ -97,7 +97,7 @@ Keep this to the minimum below; each item is a mechanical change. an env var `RBMANAGER_ENV_KEY` naming an alternative HKCU-relative subkey (and suppress the broadcast when it is set) so a full `rb install` run never touches the real PATH. -3. `Devkit` seams. Make `VsWhere` an internal settable property (env +3. `Msvc` seams. Make `VsWhere` an internal settable property (env override `RBMANAGER_VSWHERE` for E2E), and make `ActivatedDelta`, `LocateVsDevCmd`, `ParseShell`, `Assignment`, `Unset`, `QuoteArg` internal instead of private. `ActivatedDelta(vsdevcmd)` already takes @@ -208,9 +208,9 @@ Drive the exe built by `dotnet build` (see section 5 for AOT). 34. No args → usage on stdout, exit 2. 35. Unknown command → usage, exit 2. -36. `install` with no argument, `use` with no argument, `exec` with no - command → usage, exit 2 (the `exec` pattern requires a non-empty - command). +36. `install` with no argument, `use` with no argument, `msvc` with no + subcommand, `msvc exec` with no command → usage, exit 2 (the + `msvc exec` pattern requires a non-empty command). 37. Failing command (e.g. `use nosuch`) → stderr starts with `rb: `, exit 1, stdout empty. 38. Full lifecycle: install A → list (A starred) → install B → list (B @@ -265,7 +265,7 @@ All against `HKCU\Software\rbmanager-tests\` with 55. Empty-string existing value → result is exactly the entry, no leading `;`. -### 4.8 Devkit: pure helpers — Unit +### 4.8 Msvc: pure helpers — Unit 56. `ParseShell`: `null`, `powershell`, `pwsh`, `ps` → PowerShell; `cmd`, `bat` → Cmd; `zsh` → throws `unknown shell 'zsh'`. (Note @@ -280,7 +280,7 @@ All against `HKCU\Software\rbmanager-tests\` with quotes; empty string → `""`; tab → quoted; embedded `"` → not escaped (pin as known limitation; see 6.7). -### 4.9 Devkit: activation with a stub VsDevCmd — Integration +### 4.9 Msvc: activation with a stub VsDevCmd — Integration Stub `.bat` fixture written per test, e.g. sets `RB_TEST_NEW=hello`, modifies `PATH` by prefixing a marker dir, sets a var whose value @@ -300,29 +300,29 @@ contains `=` and one containing non-ASCII, and `exit /b 0`. 66. `Enable`/`Exec` with the toolchain seam pointing nowhere and `VsWhere` set to a nonexistent path → stderr warning containing the winget hint, exit 1, stdout empty (the warning must not go to - stdout, since `rb enable | Invoke-Expression` would eval it). + stdout, since `rb msvc enable | Invoke-Expression` would eval it). 67. `LocateVsDevCmd` with `VsWhere` nonexistent → null (covered behaviorally by 66; also assert directly). 68. `Exec` with stub: run `cmd /c set` as the command, capture output → child sees the stub's variables and does not see `NoDefaultCurrentDirectoryInExePath` (set it in the test process first). -69. `Exec` exit-code propagation: `rb exec cmd /c exit 7` → 7. +69. `Exec` exit-code propagation: `rb msvc exec cmd /c exit 7` → 7. 70. `Exec` resolves `.cmd` shims: put a `hello.cmd` on the stub-added - PATH dir, `exec hello` → runs it (proves the `cmd /s /c` routing + PATH dir, `msvc exec hello` → runs it (proves the `cmd /s /c` routing and PATHEXT behavior). 71. `Exec` argument quoting: an argument with spaces survives to the child (child echoes `%1`-style or a tiny script writes its argv to a file). -### 4.10 Devkit against real Visual Studio — RequiresVS (opt-in) +### 4.10 Msvc against real Visual Studio — RequiresVS (opt-in) Skipped unless vswhere resolves an install (use a runtime skip, e.g. `Assert.Skip`/`SkippableFact`). 72. `LocateVsDevCmd` returns an existing `VsDevCmd.bat`. 73. `ActivatedDelta` includes `INCLUDE`, `LIB`, and a `PATH` change. -74. `rb exec cl` (E2E) exits 0 with cl's banner on stderr. +74. `rb msvc exec cl` (E2E) exits 0 with cl's banner on stderr. ### 4.11 AOT publish smoke — E2E (opt-in, slow) @@ -390,6 +390,6 @@ a comment. Each is a product decision to make separately. 5. Dangling `current` (target deleted out of band) has unpinned semantics in `CurrentTarget`/`Uninstall` (case 30 pins it). 6. `ParseShell` is case-sensitive (`PowerShell` is rejected). -7. `QuoteArg` does not escape embedded quotes; `rb exec` with an +7. `QuoteArg` does not escape embedded quotes; `rb msvc exec` with an argument containing `"` produces a broken cmd line (case 60 pins the helper's output only). diff --git a/src/rbmanager/Devkit.cs b/src/rbmanager/Msvc.cs similarity index 89% rename from src/rbmanager/Devkit.cs rename to src/rbmanager/Msvc.cs index a0fe7f4..f60d266 100644 --- a/src/rbmanager/Devkit.cs +++ b/src/rbmanager/Msvc.cs @@ -2,22 +2,24 @@ namespace RbManager; -// PROTOTYPE: a `ridk enable` equivalent for the mswin packages. +// PROTOTYPE: MSVC build-environment activation for the mswin packages. // -// The official mswin binary carries no devkit, so `gem install ` -// has no compiler on PATH and fails with mkmf's cryptic "install +// The official mswin binary ships no compiler, so `gem install ` +// has no toolchain on PATH and fails with mkmf's cryptic "install // development tools first". This locates an installed Visual Studio (or -// Build Tools) C++ toolchain, activates it the same way ruby/actions' -// mswin-build workflow does (VsDevCmd.bat), and exposes that environment -// two ways: +// Build Tools) MSVC toolchain, activates it the same way ruby/actions' +// mswin-build workflow does (VsDevCmd.bat, the script behind Visual +// Studio's Developer Command Prompt), and exposes that environment two +// ways: // -// rb enable [cmd|powershell|pwsh] print env assignments to eval in the -// current shell (ridk-parity) -// rb exec -- run one command with the toolchain -// already applied (no shell mutation) +// rb msvc enable [cmd|powershell|pwsh] print env assignments to eval +// in the current shell +// rb msvc exec run one command with the +// toolchain already applied +// (no shell mutation) // -// See docs/devkit-enable.md for the design rationale. -internal static class Devkit +// See docs/msvc-enable.md for the design rationale. +internal static class Msvc { // vswhere ships at a fixed, versionless path with the VS Installer and // is the only supported way to locate installs (including Build-Tools- @@ -154,7 +156,7 @@ public static int Exec(string[] command) // Fails fast with the setup steps instead of letting mkmf die later // with its cryptic "install development tools first". stderr only, so - // an eval'd `rb enable` pipeline never swallows it. + // an eval'd `rb msvc enable` pipeline never swallows it. private static int WarnMissingToolchain() { Console.Error.WriteLine(""" diff --git a/src/rbmanager/Program.cs b/src/rbmanager/Program.cs index 552b4ed..3b364ef 100644 --- a/src/rbmanager/Program.cs +++ b/src/rbmanager/Program.cs @@ -29,9 +29,9 @@ private static async Task Main(string[] args) ["list"] => List(), ["use", var name] => Use(name), ["uninstall", var name] => Uninstall(name), - ["enable"] => Devkit.Enable(null), - ["enable", var shell] => Devkit.Enable(shell), - ["exec", .. var command] when command.Length > 0 => Devkit.Exec(command), + ["msvc", "enable"] => Msvc.Enable(null), + ["msvc", "enable", var shell] => Msvc.Enable(shell), + ["msvc", "exec", .. var command] when command.Length > 0 => Msvc.Exec(command), _ => Usage(), }; } @@ -47,13 +47,13 @@ private static int Usage() Console.WriteLine(""" usage: rb - setup [--yes] copy rb onto PATH and set up the VC++ runtime - install install a ruby binary package from a zip file or URL - list list installed rubies - use switch the active ruby - uninstall remove an installed ruby - enable [shell] print C++ build env to eval (cmd|powershell) - exec run a command with the C++ build env applied + setup [--yes] copy rb onto PATH and set up the VC++ runtime + install install a ruby binary package from a zip file or URL + list list installed rubies + use switch the active ruby + uninstall remove an installed ruby + msvc enable [shell] print the MSVC build env to eval (cmd|powershell) + msvc exec run a command with the MSVC build env applied """); return 2; } diff --git a/tests/rbmanager.Tests/CliE2eTests.cs b/tests/rbmanager.Tests/CliE2eTests.cs index 2c8c29b..cd0394a 100644 --- a/tests/rbmanager.Tests/CliE2eTests.cs +++ b/tests/rbmanager.Tests/CliE2eTests.cs @@ -34,8 +34,9 @@ public void UnknownCommand_Usage_Exit2() [Theory] // case 36 [InlineData("install")] [InlineData("use")] - [InlineData("exec")] - public void MissingRequiredArgument_Usage_Exit2(string command) + [InlineData("msvc")] + [InlineData("msvc", "exec")] + public void MissingRequiredArgument_Usage_Exit2(params string[] command) { using var sb = new E2eSandbox(); RbResult r = sb.Run(command); diff --git a/tests/rbmanager.Tests/DevkitActivationTests.cs b/tests/rbmanager.Tests/MsvcActivationTests.cs similarity index 89% rename from tests/rbmanager.Tests/DevkitActivationTests.cs rename to tests/rbmanager.Tests/MsvcActivationTests.cs index 2a6e150..a24eb35 100644 --- a/tests/rbmanager.Tests/DevkitActivationTests.cs +++ b/tests/rbmanager.Tests/MsvcActivationTests.cs @@ -2,13 +2,13 @@ namespace RbManager.Tests; -// Plan 4.9: Devkit activation exercised with a stub VsDevCmd.bat, so no real -// Visual Studio is needed. Serial (env vars, console, the Devkit.VsWhere +// Plan 4.9: Msvc activation exercised with a stub VsDevCmd.bat, so no real +// Visual Studio is needed. Serial (env vars, console, the Msvc.VsWhere // static). The stub is invoked by ActivatedDelta as // `cmd /s /c "call "stub" -arch=... -no_logo && set"`. [Trait("Category", "Integration")] [Collection(Serial.Name)] -public class DevkitActivationTests +public class MsvcActivationTests { private const string NoDefault = "NoDefaultCurrentDirectoryInExePath"; private const string PwshUnset = @@ -23,7 +23,7 @@ private static string Bat(TempDir tmp, params string[] lines) } private static Dictionary Delta(string bat) => - Devkit.ActivatedDelta(bat) + Msvc.ActivatedDelta(bat) .ToDictionary(t => t.Item1, t => t.Item2, StringComparer.OrdinalIgnoreCase); [Fact] // case 61 @@ -66,7 +66,7 @@ public void ActivatedDelta_NonZeroExit_Throws() string bat = Bat(tmp, "exit /b 3"); var ex = Assert.Throws( - () => Devkit.ActivatedDelta(bat).ToList()); + () => Msvc.ActivatedDelta(bat).ToList()); Assert.Equal("VsDevCmd activation failed", ex.Message); } @@ -78,7 +78,7 @@ public void Enable_PowerShell_AssignmentsThenUnset() env.Set("RBMANAGER_VSDEVCMD", Bat(tmp, "set RB_TEST_NEW=hello")); using var cap = new ConsoleCapture(); - int rc = Devkit.Enable("powershell"); + int rc = Msvc.Enable("powershell"); Assert.Equal(0, rc); Assert.Contains("$env:RB_TEST_NEW = 'hello'", cap.OutLines); @@ -93,7 +93,7 @@ public void Enable_Cmd_AssignmentsThenUnset() env.Set("RBMANAGER_VSDEVCMD", Bat(tmp, "set RB_TEST_NEW=hello")); using var cap = new ConsoleCapture(); - int rc = Devkit.Enable("cmd"); + int rc = Msvc.Enable("cmd"); Assert.Equal(0, rc); Assert.Contains("set \"RB_TEST_NEW=hello\"", cap.OutLines); @@ -110,7 +110,7 @@ public void EnableAndExec_NoToolchain_WarnOnStderr() using (var cap = new ConsoleCapture()) { - int rc = Devkit.Enable("powershell"); + int rc = Msvc.Enable("powershell"); Assert.Equal(1, rc); Assert.Equal("", cap.Out); Assert.Contains("winget install Microsoft.VisualStudio.2022.BuildTools", cap.Err); @@ -118,7 +118,7 @@ public void EnableAndExec_NoToolchain_WarnOnStderr() using (var cap = new ConsoleCapture()) { - int rc = Devkit.Exec(["cmd", "/c", "echo", "x"]); + int rc = Msvc.Exec(["cmd", "/c", "echo", "x"]); Assert.Equal(1, rc); Assert.Equal("", cap.Out); Assert.Contains("no Visual Studio C++ toolchain found", cap.Err); @@ -133,7 +133,7 @@ public void LocateVsDevCmd_VsWhereMissing_Null() env.Set("RBMANAGER_VSDEVCMD", null); using var vsw = new VsWhereScope(tmp.At("no-vswhere.exe")); - Assert.Null(Devkit.LocateVsDevCmd()); + Assert.Null(Msvc.LocateVsDevCmd()); } [Fact] // case 68: Exec applies the delta and drops NoDefault... in the child @@ -146,7 +146,7 @@ public void Exec_ChildSeesDelta_AndNoDefaultRemoved() string dump = Bat(tmp, "set > \"%~1\""); string outFile = tmp.At("env.txt"); - int rc = Devkit.Exec([dump, outFile]); + int rc = Msvc.Exec([dump, outFile]); Assert.Equal(0, rc); string dumped = File.ReadAllText(outFile); @@ -161,7 +161,7 @@ public void Exec_PropagatesChildExitCode() using var env = new EnvScope(); env.Set("RBMANAGER_VSDEVCMD", Bat(tmp)); // no-op stub, exits 0 - int rc = Devkit.Exec(["cmd", "/c", "exit", "7"]); + int rc = Msvc.Exec(["cmd", "/c", "exit", "7"]); Assert.Equal(7, rc); } @@ -176,7 +176,7 @@ public void Exec_ResolvesCmdShimOnActivatedPath() using var env = new EnvScope(); env.Set("RBMANAGER_VSDEVCMD", Bat(tmp, $"set PATH={shimDir};%PATH%")); - int rc = Devkit.Exec(["hello"]); + int rc = Msvc.Exec(["hello"]); Assert.Equal(42, rc); } @@ -190,7 +190,7 @@ public void Exec_QuotesArgumentWithSpaces() string echo = Bat(tmp, ">\"%~2\" echo %~1"); string outFile = tmp.At("arg.txt"); - int rc = Devkit.Exec([echo, "a b c", outFile]); + int rc = Msvc.Exec([echo, "a b c", outFile]); Assert.Equal(0, rc); Assert.Equal("a b c", File.ReadAllText(outFile).Trim()); diff --git a/tests/rbmanager.Tests/DevkitHelperTests.cs b/tests/rbmanager.Tests/MsvcHelperTests.cs similarity index 51% rename from tests/rbmanager.Tests/DevkitHelperTests.cs rename to tests/rbmanager.Tests/MsvcHelperTests.cs index 8f394ff..01400ca 100644 --- a/tests/rbmanager.Tests/DevkitHelperTests.cs +++ b/tests/rbmanager.Tests/MsvcHelperTests.cs @@ -1,10 +1,10 @@ namespace RbManager.Tests; -// Plan 4.8: Devkit pure helpers — no VS, no process, no filesystem. Case +// Plan 4.8: Msvc pure helpers — no VS, no process, no filesystem. Case // notes pin known limitations (docs/test-plan.md 6.6, 6.7): ParseShell is // case-sensitive and neither Assignment nor QuoteArg escapes embedded quotes. [Trait("Category", "Unit")] -public class DevkitHelperTests +public class MsvcHelperTests { [Theory] // case 56 [InlineData(null)] @@ -12,62 +12,62 @@ public class DevkitHelperTests [InlineData("pwsh")] [InlineData("ps")] public void ParseShell_DefaultsAndAliases_PowerShell(string? shell) => - Assert.Equal(Devkit.Shell.PowerShell, Devkit.ParseShell(shell)); + Assert.Equal(Msvc.Shell.PowerShell, Msvc.ParseShell(shell)); [Theory] // case 56 [InlineData("cmd")] [InlineData("bat")] public void ParseShell_Cmd(string shell) => - Assert.Equal(Devkit.Shell.Cmd, Devkit.ParseShell(shell)); + Assert.Equal(Msvc.Shell.Cmd, Msvc.ParseShell(shell)); [Fact] // case 56 public void ParseShell_Unknown_Throws() { - var ex = Assert.Throws(() => Devkit.ParseShell("zsh")); + var ex = Assert.Throws(() => Msvc.ParseShell("zsh")); Assert.Equal("unknown shell 'zsh'", ex.Message); } [Fact] // case 56 (pin 6.6): ParseShell is case-sensitive public void ParseShell_IsCaseSensitive_Throws() => - Assert.Throws(() => Devkit.ParseShell("PowerShell")); + Assert.Throws(() => Msvc.ParseShell("PowerShell")); [Fact] // case 57 public void Assignment_Cmd() { - Assert.Equal("set \"K=V\"", Devkit.Assignment(Devkit.Shell.Cmd, "K", "V")); + Assert.Equal("set \"K=V\"", Msvc.Assignment(Msvc.Shell.Cmd, "K", "V")); Assert.Equal("set \"PATH=C:\\a b\\bin\"", - Devkit.Assignment(Devkit.Shell.Cmd, "PATH", @"C:\a b\bin")); - Assert.Equal("set \"K=a%B%c\"", Devkit.Assignment(Devkit.Shell.Cmd, "K", "a%B%c")); + Msvc.Assignment(Msvc.Shell.Cmd, "PATH", @"C:\a b\bin")); + Assert.Equal("set \"K=a%B%c\"", Msvc.Assignment(Msvc.Shell.Cmd, "K", "a%B%c")); // pin 6.7: embedded quote is not escaped - Assert.Equal("set \"K=a\"b\"", Devkit.Assignment(Devkit.Shell.Cmd, "K", "a\"b")); + Assert.Equal("set \"K=a\"b\"", Msvc.Assignment(Msvc.Shell.Cmd, "K", "a\"b")); } [Fact] // case 58 public void Assignment_PowerShell() { - Assert.Equal("$env:K = 'V'", Devkit.Assignment(Devkit.Shell.PowerShell, "K", "V")); + Assert.Equal("$env:K = 'V'", Msvc.Assignment(Msvc.Shell.PowerShell, "K", "V")); // single quote doubled - Assert.Equal("$env:K = 'a''b'", Devkit.Assignment(Devkit.Shell.PowerShell, "K", "a'b")); + Assert.Equal("$env:K = 'a''b'", Msvc.Assignment(Msvc.Shell.PowerShell, "K", "a'b")); // dollar stays literal (single-quoted PowerShell literal) - Assert.Equal("$env:K = 'a$b'", Devkit.Assignment(Devkit.Shell.PowerShell, "K", "a$b")); + Assert.Equal("$env:K = 'a$b'", Msvc.Assignment(Msvc.Shell.PowerShell, "K", "a$b")); } [Fact] // case 59 public void Unset_BothShells() { - Assert.Equal("set \"K=\"", Devkit.Unset(Devkit.Shell.Cmd, "K")); + Assert.Equal("set \"K=\"", Msvc.Unset(Msvc.Shell.Cmd, "K")); Assert.Equal("Remove-Item Env:\\K -ErrorAction SilentlyContinue", - Devkit.Unset(Devkit.Shell.PowerShell, "K")); + Msvc.Unset(Msvc.Shell.PowerShell, "K")); } [Fact] // case 60 public void QuoteArg() { - Assert.Equal("gem", Devkit.QuoteArg("gem")); - Assert.Equal("\"a b\"", Devkit.QuoteArg("a b")); - Assert.Equal("\"\"", Devkit.QuoteArg("")); - Assert.Equal("\"\t\"", Devkit.QuoteArg("\t")); + Assert.Equal("gem", Msvc.QuoteArg("gem")); + Assert.Equal("\"a b\"", Msvc.QuoteArg("a b")); + Assert.Equal("\"\"", Msvc.QuoteArg("")); + Assert.Equal("\"\t\"", Msvc.QuoteArg("\t")); // pin 6.7: embedded quote is not escaped, only wrapped when needed - Assert.Equal("a\"b", Devkit.QuoteArg("a\"b")); + Assert.Equal("a\"b", Msvc.QuoteArg("a\"b")); } } diff --git a/tests/rbmanager.Tests/DevkitVsTests.cs b/tests/rbmanager.Tests/MsvcVsTests.cs similarity index 82% rename from tests/rbmanager.Tests/DevkitVsTests.cs rename to tests/rbmanager.Tests/MsvcVsTests.cs index 0692906..7db723f 100644 --- a/tests/rbmanager.Tests/DevkitVsTests.cs +++ b/tests/rbmanager.Tests/MsvcVsTests.cs @@ -2,13 +2,13 @@ namespace RbManager.Tests; -// Plan 4.10: Devkit against a real Visual Studio install. Opt-in: each test +// Plan 4.10: Msvc against a real Visual Studio install. Opt-in: each test // skips (rather than fails) when no MSVC toolchain is discoverable, so the // suite is safe to run anywhere but only asserts on a VS machine. [Trait("Category", "RequiresVS")] -public class DevkitVsTests +public class MsvcVsTests { - private static string? RealVsDevCmd() => Devkit.LocateVsDevCmd(); + private static string? RealVsDevCmd() => Msvc.LocateVsDevCmd(); [SkippableFact] // case 72 public void LocateVsDevCmd_ReturnsExistingBat() @@ -24,7 +24,7 @@ public void ActivatedDelta_IncludesCompilerEnvironment() string? bat = RealVsDevCmd(); Skip.If(bat is null, "no Visual Studio C++ toolchain installed"); - var delta = Devkit.ActivatedDelta(bat!) + var delta = Msvc.ActivatedDelta(bat!) .ToDictionary(t => t.Item1, t => t.Item2, StringComparer.OrdinalIgnoreCase); Assert.True(delta.ContainsKey("INCLUDE")); @@ -38,7 +38,7 @@ public void Exec_Cl_RunsCompiler() Skip.If(RealVsDevCmd() is null, "no Visual Studio C++ toolchain installed"); using var sb = new E2eSandbox(); - RbResult r = sb.Run("exec", "cl"); + RbResult r = sb.Run("msvc", "exec", "cl"); // cl with no input files prints its version banner to stderr. Assert.Contains("Microsoft", r.Err); diff --git a/tests/rbmanager.Tests/SmokeTests.cs b/tests/rbmanager.Tests/SmokeTests.cs index a401fb1..f54296a 100644 --- a/tests/rbmanager.Tests/SmokeTests.cs +++ b/tests/rbmanager.Tests/SmokeTests.cs @@ -12,7 +12,7 @@ public class SmokeTests [Fact] public void InternalsAreVisible() { - Assert.Equal(Devkit.Shell.Cmd, Devkit.ParseShell("cmd")); - Assert.Equal(Devkit.Shell.PowerShell, Devkit.ParseShell(null)); + Assert.Equal(Msvc.Shell.Cmd, Msvc.ParseShell("cmd")); + Assert.Equal(Msvc.Shell.PowerShell, Msvc.ParseShell(null)); } } diff --git a/tests/rbmanager.Tests/Support/Serial.cs b/tests/rbmanager.Tests/Support/Serial.cs index 6ac2212..699f843 100644 --- a/tests/rbmanager.Tests/Support/Serial.cs +++ b/tests/rbmanager.Tests/Support/Serial.cs @@ -1,7 +1,7 @@ namespace RbManager.Tests.Support; // In-process tests that touch process-global state — environment variables -// (RBMANAGER_*), Console.Out/Error, and the Devkit.VsWhere static — must not +// (RBMANAGER_*), Console.Out/Error, and the Msvc.VsWhere static — must not // run concurrently with each other or with anything else. Classes tagged // [Collection(Serial.Name)] share this collection; DisableParallelization // keeps it from overlapping the parallel-safe collections too. diff --git a/tests/rbmanager.Tests/Support/VsWhereScope.cs b/tests/rbmanager.Tests/Support/VsWhereScope.cs index e9fee8a..d0c1861 100644 --- a/tests/rbmanager.Tests/Support/VsWhereScope.cs +++ b/tests/rbmanager.Tests/Support/VsWhereScope.cs @@ -1,12 +1,12 @@ namespace RbManager.Tests.Support; -// Temporarily overrides the Devkit.VsWhere static and restores it on Dispose. +// Temporarily overrides the Msvc.VsWhere static and restores it on Dispose. // Only safe inside the Serial collection. internal sealed class VsWhereScope : IDisposable { - private readonly string _prev = Devkit.VsWhere; + private readonly string _prev = Msvc.VsWhere; - public VsWhereScope(string value) => Devkit.VsWhere = value; + public VsWhereScope(string value) => Msvc.VsWhere = value; - public void Dispose() => Devkit.VsWhere = _prev; + public void Dispose() => Msvc.VsWhere = _prev; }