Skip to content

Commit 65f2784

Browse files
authored
Merge pull request #3 from ruby/claude/build-toolchain-wrapper-naming-0a30da
Rename the toolchain commands to rb msvc enable/exec
2 parents b42bcaf + 3fcdc6b commit 65f2784

12 files changed

Lines changed: 139 additions & 136 deletions

File tree

README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ after the language (`%LOCALAPPDATA%\Ruby`, like `%LocalAppData%\Python`)
2222
rather than after the tool. rbmanager remains the product name.
2323

2424
```
25-
rb setup [--yes] copy rb onto PATH and set up the VC++ runtime
26-
rb install <zip|url> install a ruby binary package
27-
rb list list installed rubies
28-
rb use <version> switch the active ruby
29-
rb uninstall <version> remove an installed ruby
30-
rb enable [shell] print C++ build env to eval (cmd|powershell)
31-
rb exec <command...> run a command with the C++ build env applied
25+
rb setup [--yes] copy rb onto PATH and set up the VC++ runtime
26+
rb install <zip|url> install a ruby binary package
27+
rb list list installed rubies
28+
rb use <version> switch the active ruby
29+
rb uninstall <version> remove an installed ruby
30+
rb msvc enable [shell] print the MSVC build env to eval (cmd|powershell)
31+
rb msvc exec <command...> run a command with the MSVC build env applied
3232
```
3333

3434
rb is a bare exe; `setup` copies it to
@@ -38,9 +38,9 @@ ship. It also checks for the VC++ 2015-2022 redistributable the
3838
official mswin packages depend on, and offers to download and install
3939
it (signature-verified, elevated); `--yes` skips the consent prompt.
4040

41-
`enable` and `exec` are the `ridk enable` equivalent for building
42-
C extension gems with MSVC; see
43-
[docs/devkit-enable.md](docs/devkit-enable.md).
41+
`msvc enable` and `msvc exec` activate an installed Visual Studio (or
42+
Build Tools) MSVC toolchain for building C extension gems; see
43+
[docs/msvc-enable.md](docs/msvc-enable.md).
4444

4545
The official mswin packages deliberately do not bundle
4646
vcruntime140.dll (https://bugs.ruby-lang.org/issues/22180) and expect
Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# A `ridk enable` equivalent for the mswin packages
1+
# Activating the MSVC build environment for the mswin packages
22

33
## Problem
44

@@ -35,14 +35,14 @@ Studio.
3535
- `VsDevCmd.bat -arch=amd64 -host_arch=amd64` puts `cl`/`nmake`/`link`
3636
on PATH and sets `INCLUDE`/`LIB`/`LIBPATH`/`VCToolsRedistDir`
3737
(exit 0).
38-
- Under the prototype `rb exec`, mkmf's `find_executable('cl')`
38+
- Under the prototype `rb msvc exec`, mkmf's `find_executable('cl')`
3939
succeeds, and a trivial C extension compiles, links, and loads:
4040
`extconf.rb` -> `nmake` -> `require './hello.so'` returns a value from
4141
native code.
42-
- `rb enable powershell | Invoke-Expression` puts `cl` on the current
43-
session's PATH (ridk parity).
42+
- `rb msvc enable powershell | Invoke-Expression` puts `cl` on the
43+
current session's PATH.
4444

45-
The conclusion is that a compiler-only `rb enable`/`rb exec` is fully
45+
The conclusion is that a compiler-only `rb msvc enable`/`rb msvc exec` is fully
4646
feasible and small. The interesting decisions are the command surface
4747
and how far to go on third-party dependency headers.
4848

@@ -53,40 +53,40 @@ environment. `ridk enable` only works because it is a shell function
5353
whose output is eval'd into the current shell. Any activation feature
5454
must work around this, and the two useful shapes are:
5555

56-
1. **`rb exec <command...>` (primary).** Spawns a child process with the
57-
toolchain and active ruby already applied. No parent mutation, so
58-
nothing to eval and nothing to get wrong. `rb exec gem install
59-
nokogiri` just works. This is the recommended path for the common
60-
case (one build command) and for scripts/CI, and it is the surface
61-
that is bulletproof by construction.
56+
1. **`rb msvc exec <command...>` (primary).** Spawns a child process
57+
with the toolchain and active ruby already applied. No parent
58+
mutation, so nothing to eval and nothing to get wrong. `rb msvc exec
59+
gem install nokogiri` just works. This is the recommended path for
60+
the common case (one build command) and for scripts/CI, and it is
61+
the surface that is bulletproof by construction.
6262

63-
2. **`rb enable [cmd|powershell|pwsh]` (ridk parity).** Prints
63+
2. **`rb msvc enable [cmd|powershell|pwsh]` (shell activation).** Prints
6464
environment assignments for the user to eval into the current shell,
6565
for interactive sessions where several build commands follow:
6666

6767
```
6868
rem cmd
69-
for /f "delims=" %L in ('rb enable cmd') do @%L
69+
for /f "delims=" %L in ('rb msvc enable cmd') do @%L
7070
7171
# PowerShell / pwsh
72-
rb enable powershell | Invoke-Expression
72+
rb msvc enable powershell | Invoke-Expression
7373
```
7474

75-
This mirrors `ridk enable` and is the escape hatch for users who want
76-
a persistently activated shell rather than a per-command wrapper.
75+
This is the escape hatch for users who want a persistently activated
76+
shell rather than a per-command wrapper.
7777

78-
Recommend shipping both. `rb exec` is the headline; `rb enable` covers
79-
the interactive workflow ridk users expect. A third option, writing a
78+
Recommend shipping both. `rb msvc exec` is the headline; `rb msvc
79+
enable` covers the interactive workflow. A third option, writing a
8080
dot-sourced activation script into `%LOCALAPPDATA%\Ruby`, adds a file to
8181
manage and a staleness problem (the resolved VS path is baked in) for no
82-
gain over `rb enable`, so it is not recommended.
82+
gain over `rb msvc enable`, so it is not recommended.
8383

84-
The parent-shell-mutation constraint is handled cleanly: `rb exec`
84+
The parent-shell-mutation constraint is handled cleanly: `rb msvc exec`
8585
sidesteps it entirely by owning the child's environment;
86-
`rb enable` respects it by making the caller responsible for the eval,
87-
exactly as ridk does.
86+
`rb msvc enable` respects it by making the caller responsible for the
87+
eval.
8888

89-
### Shell selection for `rb enable`
89+
### Shell selection for `rb msvc enable`
9090

9191
The prototype takes the shell as an explicit argument and defaults to
9292
PowerShell (the common interactive shell on modern Windows). Auto-
@@ -147,12 +147,12 @@ cmd /s /c "call "<installationPath>\Common7\Tools\VsDevCmd.bat" \
147147
child inherits the calling shell's environment, so diffing the captured
148148
`set` output against rb's own environment yields exactly the variables
149149
VsDevCmd added or changed (PATH, INCLUDE, LIB, LIBPATH,
150-
VCToolsRedistDir, and the VSCMD bookkeeping vars). `rb exec` applies
151-
that delta to the child it spawns; `rb enable` prints it as `set
150+
VCToolsRedistDir, and the VSCMD bookkeeping vars). `rb msvc exec`
151+
applies that delta to the child it spawns; `rb msvc enable` prints it as `set
152152
"K=V"` (cmd) or `$env:K = '...'` (PowerShell, single-quoted literal
153153
with `'` doubled).
154154

155-
`rb exec` routes the user command through `cmd /s /c` so that `.cmd`
155+
`rb msvc exec` routes the user command through `cmd /s /c` so that `.cmd`
156156
shims (`gem`, `bundle`) and PATHEXT resolve the way they would if the
157157
user had typed the command directly; a bare `CreateProcess` would not
158158
find `gem` (it is `gem.cmd`).
@@ -166,8 +166,8 @@ name and the loader refuses to find it in the current directory, which
166166
surfaces as the same cryptic "install development tools first" error
167167
even though the compiler is present.
168168

169-
Both surfaces clear it for the activated environment: `rb exec` removes
170-
the variable from the child's environment block, and `rb enable` emits
169+
Both surfaces clear it for the activated environment: `rb msvc exec`
170+
removes the variable from the child's environment block, and `rb msvc enable` emits
171171
the unset (`set "NoDefault...="` for cmd, `Remove-Item Env:\NoDefault...`
172172
for PowerShell). This is cheap insurance against a confusing failure and
173173
is recommended.
@@ -194,7 +194,7 @@ and no opt-dir pointing at any such tree on the destination machine.
194194

195195
### Recommendation: phase 1 is compiler-only
196196

197-
Ship `rb exec`/`rb enable` as compiler-only first, and document the
197+
Ship `rb msvc exec`/`rb msvc enable` as compiler-only first, and document the
198198
dependency-linking limitation. This unblocks the large class of pure-C
199199
gems immediately, is small and low-risk, and does not commit rbmanager
200200
to shipping or versioning a pile of vcpkg dev files whose provenance and
@@ -241,20 +241,20 @@ should not depend on it.)
241241

242242
## Prototype
243243

244-
`src/rbmanager/Devkit.cs` implements both subcommands, wired into
245-
`Program.cs`'s dispatch switch as `rb enable [shell]` and
246-
`rb exec <command...>`. It is ~180 lines, marked as a prototype, and
244+
`src/rbmanager/Msvc.cs` implements both subcommands, wired into
245+
`Program.cs`'s dispatch switch as `rb msvc enable [shell]` and
246+
`rb msvc exec <command...>`. It is ~180 lines, marked as a prototype, and
247247
covers VS discovery, VsDevCmd activation with env-diffing, the
248248
`NoDefaultCurrentDirectoryInExePath` clearing, and the per-shell output.
249249
It is compiler-only (phase 1). What was exercised:
250250

251-
- `rb enable powershell|cmd` prints correct assignments; the PowerShell
252-
form activates a live session via `| Invoke-Expression`.
253-
- `rb exec ruby -rmkmf -e "find_executable('cl')"` finds the compiler.
254-
- `rb exec` drives a full `extconf.rb` -> `nmake` -> load of a native
251+
- `rb msvc enable powershell|cmd` prints correct assignments; the
252+
PowerShell form activates a live session via `| Invoke-Expression`.
253+
- `rb msvc exec ruby -rmkmf -e "find_executable('cl')"` finds the compiler.
254+
- `rb msvc exec` drives a full `extconf.rb` -> `nmake` -> load of a native
255255
extension.
256256

257-
A `gem install msgpack` under `rb exec` compiled several files (proving
257+
A `gem install msgpack` under `rb msvc exec` compiled several files (proving
258258
the toolchain is live) before failing on an
259259
`RBIMPL_UNREACHABLE_RETURN`/`C2059` macro error in msgpack 1.8.3 against
260260
Ruby 4.0's headers. That is an upstream gem/source incompatibility, not
@@ -263,14 +263,14 @@ appearing on the compile line.
263263

264264
## Open questions
265265

266-
1. Auto-detect the parent shell for `rb enable`, or keep the explicit
266+
1. Auto-detect the parent shell for `rb msvc enable`, or keep the explicit
267267
argument with a default? (Prototype: explicit, default PowerShell.)
268-
2. Should `rb exec`/`rb enable` also guarantee the active ruby's
268+
2. Should `rb msvc exec`/`rb msvc enable` also guarantee the active ruby's
269269
`current\bin` is on PATH, or continue to rely on `install` having put
270270
it there? (Prototype relies on install.)
271271
3. Phase 2 trigger: is dependency-linking demand high enough to justify
272272
bundling vcpkg dev files, and should the dev-file tree ship inside
273273
the mswin package or as a separate rbmanager-managed download?
274274
4. Should a leaked/nonexistent `--with-opt-dir` in `configure_args` be
275-
actively stripped or overridden by `rb enable` even in phase 1, to
275+
actively stripped or overridden by `rb msvc enable` even in phase 1, to
276276
remove the confusing `-I<nonexistent>` from every compile line?

docs/test-plan.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ Command surface and contracts:
3434
| `rb list` | Installed names sorted, active one starred | 0 |
3535
| `rb use <query>` | Resolve query (exact or case-insensitive substring; must be unambiguous), recreate the `current` junction, ensure PATH | 0 / 1 |
3636
| `rb uninstall <query>` | Resolve; if active, delete the junction first and print a hint; delete the install dir recursively | 0 / 1 |
37-
| `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 |
38-
| `rb exec <cmd...>` | Same delta applied to a `cmd /s /c` child (so `.cmd` shims resolve); removes `NoDefaultCurrentDirectoryInExePath`; propagates the child's exit code | child / 1 |
37+
| `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 |
38+
| `rb msvc exec <cmd...>` | Same delta applied to a `cmd /s /c` child (so `.cmd` shims resolve); removes `NoDefaultCurrentDirectoryInExePath`; propagates the child's exit code | child / 1 |
3939
| anything else | Usage text | 2 |
4040

4141
Any thrown exception is caught in `Main`, printed as `rb: <message>` to
@@ -63,7 +63,7 @@ stderr, exit 1.
6363
and deletes it in `Dispose`. Junction targets and junction points
6464
both live inside it.
6565
- Tests that redirect `Console.Out`/`Console.Error` or mutate the
66-
process environment (`Program`/`Devkit` in-process tests) go in one
66+
process environment (`Program`/`Msvc` in-process tests) go in one
6767
xUnit collection (`[Collection("process-global")]`) so they never
6868
run in parallel with each other. E2E tests spawn processes and can
6969
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.
9797
an env var `RBMANAGER_ENV_KEY` naming an alternative HKCU-relative
9898
subkey (and suppress the broadcast when it is set) so a full
9999
`rb install` run never touches the real PATH.
100-
3. `Devkit` seams. Make `VsWhere` an internal settable property (env
100+
3. `Msvc` seams. Make `VsWhere` an internal settable property (env
101101
override `RBMANAGER_VSWHERE` for E2E), and make `ActivatedDelta`,
102102
`LocateVsDevCmd`, `ParseShell`, `Assignment`, `Unset`, `QuoteArg`
103103
internal instead of private. `ActivatedDelta(vsdevcmd)` already takes
@@ -208,9 +208,9 @@ Drive the exe built by `dotnet build` (see section 5 for AOT).
208208

209209
34. No args → usage on stdout, exit 2.
210210
35. Unknown command → usage, exit 2.
211-
36. `install` with no argument, `use` with no argument, `exec` with no
212-
command → usage, exit 2 (the `exec` pattern requires a non-empty
213-
command).
211+
36. `install` with no argument, `use` with no argument, `msvc` with no
212+
subcommand, `msvc exec` with no command → usage, exit 2 (the
213+
`msvc exec` pattern requires a non-empty command).
214214
37. Failing command (e.g. `use nosuch`) → stderr starts with `rb: `,
215215
exit 1, stdout empty.
216216
38. Full lifecycle: install A → list (A starred) → install B → list (B
@@ -265,7 +265,7 @@ All against `HKCU\Software\rbmanager-tests\<guid>` with
265265
55. Empty-string existing value → result is exactly the entry, no
266266
leading `;`.
267267

268-
### 4.8 Devkit: pure helpers — Unit
268+
### 4.8 Msvc: pure helpers — Unit
269269

270270
56. `ParseShell`: `null`, `powershell`, `pwsh`, `ps` → PowerShell;
271271
`cmd`, `bat` → Cmd; `zsh` → throws `unknown shell 'zsh'`. (Note
@@ -280,7 +280,7 @@ All against `HKCU\Software\rbmanager-tests\<guid>` with
280280
quotes; empty string → `""`; tab → quoted; embedded `"` → not
281281
escaped (pin as known limitation; see 6.7).
282282

283-
### 4.9 Devkit: activation with a stub VsDevCmd — Integration
283+
### 4.9 Msvc: activation with a stub VsDevCmd — Integration
284284

285285
Stub `.bat` fixture written per test, e.g. sets `RB_TEST_NEW=hello`,
286286
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`.
300300
66. `Enable`/`Exec` with the toolchain seam pointing nowhere and
301301
`VsWhere` set to a nonexistent path → stderr warning containing the
302302
winget hint, exit 1, stdout empty (the warning must not go to
303-
stdout, since `rb enable | Invoke-Expression` would eval it).
303+
stdout, since `rb msvc enable | Invoke-Expression` would eval it).
304304
67. `LocateVsDevCmd` with `VsWhere` nonexistent → null (covered
305305
behaviorally by 66; also assert directly).
306306
68. `Exec` with stub: run `cmd /c set` as the command, capture output →
307307
child sees the stub's variables and does not see
308308
`NoDefaultCurrentDirectoryInExePath` (set it in the test process
309309
first).
310-
69. `Exec` exit-code propagation: `rb exec cmd /c exit 7` → 7.
310+
69. `Exec` exit-code propagation: `rb msvc exec cmd /c exit 7` → 7.
311311
70. `Exec` resolves `.cmd` shims: put a `hello.cmd` on the stub-added
312-
PATH dir, `exec hello` → runs it (proves the `cmd /s /c` routing
312+
PATH dir, `msvc exec hello` → runs it (proves the `cmd /s /c` routing
313313
and PATHEXT behavior).
314314
71. `Exec` argument quoting: an argument with spaces survives to the
315315
child (child echoes `%1`-style or a tiny script writes its argv to
316316
a file).
317317

318-
### 4.10 Devkit against real Visual Studio — RequiresVS (opt-in)
318+
### 4.10 Msvc against real Visual Studio — RequiresVS (opt-in)
319319

320320
Skipped unless vswhere resolves an install (use a runtime skip, e.g.
321321
`Assert.Skip`/`SkippableFact`).
322322

323323
72. `LocateVsDevCmd` returns an existing `VsDevCmd.bat`.
324324
73. `ActivatedDelta` includes `INCLUDE`, `LIB`, and a `PATH` change.
325-
74. `rb exec cl` (E2E) exits 0 with cl's banner on stderr.
325+
74. `rb msvc exec cl` (E2E) exits 0 with cl's banner on stderr.
326326

327327
### 4.11 AOT publish smoke — E2E (opt-in, slow)
328328

@@ -390,6 +390,6 @@ a comment. Each is a product decision to make separately.
390390
5. Dangling `current` (target deleted out of band) has unpinned
391391
semantics in `CurrentTarget`/`Uninstall` (case 30 pins it).
392392
6. `ParseShell` is case-sensitive (`PowerShell` is rejected).
393-
7. `QuoteArg` does not escape embedded quotes; `rb exec` with an
393+
7. `QuoteArg` does not escape embedded quotes; `rb msvc exec` with an
394394
argument containing `"` produces a broken cmd line (case 60 pins
395395
the helper's output only).
Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,24 @@
22

33
namespace RbManager;
44

5-
// PROTOTYPE: a `ridk enable` equivalent for the mswin packages.
5+
// PROTOTYPE: MSVC build-environment activation for the mswin packages.
66
//
7-
// The official mswin binary carries no devkit, so `gem install <native>`
8-
// has no compiler on PATH and fails with mkmf's cryptic "install
7+
// The official mswin binary ships no compiler, so `gem install <native>`
8+
// has no toolchain on PATH and fails with mkmf's cryptic "install
99
// development tools first". This locates an installed Visual Studio (or
10-
// Build Tools) C++ toolchain, activates it the same way ruby/actions'
11-
// mswin-build workflow does (VsDevCmd.bat), and exposes that environment
12-
// two ways:
10+
// Build Tools) MSVC toolchain, activates it the same way ruby/actions'
11+
// mswin-build workflow does (VsDevCmd.bat, the script behind Visual
12+
// Studio's Developer Command Prompt), and exposes that environment two
13+
// ways:
1314
//
14-
// rb enable [cmd|powershell|pwsh] print env assignments to eval in the
15-
// current shell (ridk-parity)
16-
// rb exec -- <command...> run one command with the toolchain
17-
// already applied (no shell mutation)
15+
// rb msvc enable [cmd|powershell|pwsh] print env assignments to eval
16+
// in the current shell
17+
// rb msvc exec <command...> run one command with the
18+
// toolchain already applied
19+
// (no shell mutation)
1820
//
19-
// See docs/devkit-enable.md for the design rationale.
20-
internal static class Devkit
21+
// See docs/msvc-enable.md for the design rationale.
22+
internal static class Msvc
2123
{
2224
// vswhere ships at a fixed, versionless path with the VS Installer and
2325
// is the only supported way to locate installs (including Build-Tools-
@@ -154,7 +156,7 @@ public static int Exec(string[] command)
154156

155157
// Fails fast with the setup steps instead of letting mkmf die later
156158
// with its cryptic "install development tools first". stderr only, so
157-
// an eval'd `rb enable` pipeline never swallows it.
159+
// an eval'd `rb msvc enable` pipeline never swallows it.
158160
private static int WarnMissingToolchain()
159161
{
160162
Console.Error.WriteLine("""

0 commit comments

Comments
 (0)