From 9f02413fe3db8239bced37a743ffe78101862984 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 10 Jul 2026 14:58:40 +0900 Subject: [PATCH 1/8] Adopt the standard src/tests repository layout Move the product to src/rbmanager, the test suite to tests/rbmanager.Tests, and the WiX source to installer/. Enable the SDK artifacts output layout (UseArtifactsOutput) so build output goes to artifacts/ instead of per-project bin/obj, which the test helpers' path resolution now relies on. Co-Authored-By: Claude Fable 5 --- .gitignore | 6 +----- Directory.Build.props | 7 +++++++ README.md | 12 ++++++++---- build-installer.ps1 | 8 ++++---- docs/devkit-enable.md | 2 +- {src => installer}/rbmanager.wxs | 0 rbmanager.sln | 4 ++-- {rbmanager => src/rbmanager}/Devkit.cs | 0 {rbmanager => src/rbmanager}/Junction.cs | 0 {rbmanager => src/rbmanager}/Program.cs | 0 {rbmanager => src/rbmanager}/UserPath.cs | 0 {rbmanager => src/rbmanager}/VcRedist.cs | 0 {rbmanager => src/rbmanager}/rbmanager.csproj | 2 +- .../rbmanager.Tests}/CliE2eTests.cs | 0 .../rbmanager.Tests}/CurrentTargetTests.cs | 0 .../rbmanager.Tests}/DevkitActivationTests.cs | 0 .../rbmanager.Tests}/DevkitHelperTests.cs | 0 .../rbmanager.Tests}/DevkitVsTests.cs | 0 .../rbmanager.Tests}/JunctionTests.cs | 0 .../rbmanager.Tests}/ProgramLifecycleTests.cs | 0 .../rbmanager.Tests}/PublishE2eTests.cs | 0 .../rbmanager.Tests}/ResolveTests.cs | 0 .../rbmanager.Tests}/SetupE2eTests.cs | 0 .../rbmanager.Tests}/SingleRootDirectoryTests.cs | 0 .../rbmanager.Tests}/SmokeTests.cs | 0 .../rbmanager.Tests}/Support/ConsoleCapture.cs | 0 .../rbmanager.Tests}/Support/E2eSandbox.cs | 0 .../rbmanager.Tests}/Support/EnvScope.cs | 0 .../rbmanager.Tests}/Support/LoopbackZipServer.cs | 0 .../rbmanager.Tests}/Support/PublishFixture.cs | 7 ++++--- .../rbmanager.Tests}/Support/Rb.cs | 7 ++++--- .../rbmanager.Tests}/Support/RbSandbox.cs | 0 .../rbmanager.Tests}/Support/ScratchRegistryKey.cs | 0 .../rbmanager.Tests}/Support/Serial.cs | 0 .../rbmanager.Tests}/Support/TempDir.cs | 0 .../rbmanager.Tests}/Support/VsWhereScope.cs | 0 .../rbmanager.Tests}/Support/Zips.cs | 0 .../rbmanager.Tests}/UserPathTests.cs | 0 .../rbmanager.Tests}/VcRedistTests.cs | 0 .../rbmanager.Tests}/rbmanager.Tests.csproj | 2 +- 40 files changed, 33 insertions(+), 24 deletions(-) create mode 100644 Directory.Build.props rename {src => installer}/rbmanager.wxs (100%) rename {rbmanager => src/rbmanager}/Devkit.cs (100%) rename {rbmanager => src/rbmanager}/Junction.cs (100%) rename {rbmanager => src/rbmanager}/Program.cs (100%) rename {rbmanager => src/rbmanager}/UserPath.cs (100%) rename {rbmanager => src/rbmanager}/VcRedist.cs (100%) rename {rbmanager => src/rbmanager}/rbmanager.csproj (91%) rename {rbmanager.Tests => tests/rbmanager.Tests}/CliE2eTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/CurrentTargetTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/DevkitActivationTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/DevkitHelperTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/DevkitVsTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/JunctionTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/ProgramLifecycleTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/PublishE2eTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/ResolveTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/SetupE2eTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/SingleRootDirectoryTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/SmokeTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/ConsoleCapture.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/E2eSandbox.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/EnvScope.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/LoopbackZipServer.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/PublishFixture.cs (88%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/Rb.cs (88%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/RbSandbox.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/ScratchRegistryKey.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/Serial.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/TempDir.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/VsWhereScope.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/Support/Zips.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/UserPathTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/VcRedistTests.cs (100%) rename {rbmanager.Tests => tests/rbmanager.Tests}/rbmanager.Tests.csproj (93%) diff --git a/.gitignore b/.gitignore index fce3594..7eb65e7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,5 @@ dist/ *.msi *.wixpdb *.cab -rbmanager/bin/ -rbmanager/obj/ -rbmanager/publish/ -rbmanager.Tests/bin/ -rbmanager.Tests/obj/ +artifacts/ TestResults/ diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..257c20b --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,7 @@ + + + + true + + diff --git a/README.md b/README.md index 1a001e9..1490f21 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ links because they need no privilege and no Developer Mode. Build (requires the .NET 8 SDK and MSVC link.exe): ``` -dotnet publish rbmanager -r win-x64 -c Release -o rbmanager\publish +dotnet publish src\rbmanager -r win-x64 -c Release -o artifacts\publish\rbmanager ``` This produces a self-contained NativeAOT `rb.exe` (~5 MB) with no @@ -51,7 +51,7 @@ runtime dependency. For channels that want a real installer instead of the bare exe, `build-installer.ps1` wraps rb.exe in a per-user MSI -(`src/rbmanager.wxs`, WiX v5): +(`installer/rbmanager.wxs`, WiX v5): ``` .\build-installer.ps1 -Validate @@ -85,8 +85,12 @@ failure modes all degrade to the previous behavior. ## Layout -- `rbmanager/` — the version manager (C#, NativeAOT) +- `src/rbmanager/` — the version manager (C#, NativeAOT) +- `tests/rbmanager.Tests/` — the xUnit test suite - `overlay/` — files layered onto every installed runtime -- `src/rbmanager.wxs`, `build-installer.ps1` — the rbmanager MSI +- `installer/rbmanager.wxs`, `build-installer.ps1` — the rbmanager MSI - `docs/` — design notes - `tools/query.ps1` — dump MSI tables via the WindowsInstaller COM API + +Build output goes to `artifacts/` (`UseArtifactsOutput` in +`Directory.Build.props`), never into the project directories. diff --git a/build-installer.ps1 b/build-installer.ps1 index 2ac3a16..dcba062 100644 --- a/build-installer.ps1 +++ b/build-installer.ps1 @@ -3,7 +3,7 @@ Build the rbmanager MSI (rb.exe installer). .DESCRIPTION - Publishes rb.exe with NativeAOT and compiles src/rbmanager.wxs with + Publishes rb.exe with NativeAOT and compiles installer/rbmanager.wxs with WiX v5 into a per-user MSI under dist/. Code signing is a separate, later step; the output is unsigned. @@ -53,14 +53,14 @@ $pathGuid = New-UuidV5 $NamespaceGuid 'ruby-windows-installer:path-component:rbm $dotnet = Get-Command dotnet -ErrorAction SilentlyContinue $dotnet = if ($dotnet) { $dotnet.Source } else { 'C:\Program Files\dotnet\dotnet.exe' } -$publishDir = Join-Path $PSScriptRoot 'rbmanager\publish' +$publishDir = Join-Path $PSScriptRoot 'artifacts\publish\rbmanager' $rbExe = Join-Path $publishDir 'rb.exe' Push-Location $PSScriptRoot try { if (-not $SkipPublish) { Write-Host "Publishing rb.exe ($Arch) ..." - & $dotnet publish rbmanager -r "win-$Arch" -c Release -o $publishDir + & $dotnet publish src\rbmanager -r "win-$Arch" -c Release -o $publishDir if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed" } } if (-not (Test-Path $rbExe)) { throw "$rbExe not found" } @@ -72,7 +72,7 @@ try { $msi = Join-Path $OutDir "rbmanager-$Version-$Arch.msi" Write-Host "Building $msi ..." - & $dotnet wix build (Join-Path $PSScriptRoot 'src\rbmanager.wxs') ` + & $dotnet wix build (Join-Path $PSScriptRoot 'installer\rbmanager.wxs') ` -arch $Arch ` -d "Version=$Version" ` -d "UpgradeCode=$upgradeCode" ` diff --git a/docs/devkit-enable.md b/docs/devkit-enable.md index ae1269e..a3c8023 100644 --- a/docs/devkit-enable.md +++ b/docs/devkit-enable.md @@ -241,7 +241,7 @@ should not depend on it.) ## Prototype -`rbmanager/Devkit.cs` implements both subcommands, wired into +`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 covers VS discovery, VsDevCmd activation with env-diffing, the diff --git a/src/rbmanager.wxs b/installer/rbmanager.wxs similarity index 100% rename from src/rbmanager.wxs rename to installer/rbmanager.wxs diff --git a/rbmanager.sln b/rbmanager.sln index 1305326..a174d4f 100644 --- a/rbmanager.sln +++ b/rbmanager.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rbmanager", "rbmanager\rbmanager.csproj", "{375503E8-4DA4-40AD-AE41-82FCE6D6742C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rbmanager", "src\rbmanager\rbmanager.csproj", "{375503E8-4DA4-40AD-AE41-82FCE6D6742C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rbmanager.Tests", "rbmanager.Tests\rbmanager.Tests.csproj", "{22625437-7DC7-421A-8896-A994C22A8E7A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rbmanager.Tests", "tests\rbmanager.Tests\rbmanager.Tests.csproj", "{22625437-7DC7-421A-8896-A994C22A8E7A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/rbmanager/Devkit.cs b/src/rbmanager/Devkit.cs similarity index 100% rename from rbmanager/Devkit.cs rename to src/rbmanager/Devkit.cs diff --git a/rbmanager/Junction.cs b/src/rbmanager/Junction.cs similarity index 100% rename from rbmanager/Junction.cs rename to src/rbmanager/Junction.cs diff --git a/rbmanager/Program.cs b/src/rbmanager/Program.cs similarity index 100% rename from rbmanager/Program.cs rename to src/rbmanager/Program.cs diff --git a/rbmanager/UserPath.cs b/src/rbmanager/UserPath.cs similarity index 100% rename from rbmanager/UserPath.cs rename to src/rbmanager/UserPath.cs diff --git a/rbmanager/VcRedist.cs b/src/rbmanager/VcRedist.cs similarity index 100% rename from rbmanager/VcRedist.cs rename to src/rbmanager/VcRedist.cs diff --git a/rbmanager/rbmanager.csproj b/src/rbmanager/rbmanager.csproj similarity index 91% rename from rbmanager/rbmanager.csproj rename to src/rbmanager/rbmanager.csproj index 3a1bb55..1ccd5f5 100644 --- a/rbmanager/rbmanager.csproj +++ b/src/rbmanager/rbmanager.csproj @@ -18,7 +18,7 @@ - diff --git a/rbmanager.Tests/CliE2eTests.cs b/tests/rbmanager.Tests/CliE2eTests.cs similarity index 100% rename from rbmanager.Tests/CliE2eTests.cs rename to tests/rbmanager.Tests/CliE2eTests.cs diff --git a/rbmanager.Tests/CurrentTargetTests.cs b/tests/rbmanager.Tests/CurrentTargetTests.cs similarity index 100% rename from rbmanager.Tests/CurrentTargetTests.cs rename to tests/rbmanager.Tests/CurrentTargetTests.cs diff --git a/rbmanager.Tests/DevkitActivationTests.cs b/tests/rbmanager.Tests/DevkitActivationTests.cs similarity index 100% rename from rbmanager.Tests/DevkitActivationTests.cs rename to tests/rbmanager.Tests/DevkitActivationTests.cs diff --git a/rbmanager.Tests/DevkitHelperTests.cs b/tests/rbmanager.Tests/DevkitHelperTests.cs similarity index 100% rename from rbmanager.Tests/DevkitHelperTests.cs rename to tests/rbmanager.Tests/DevkitHelperTests.cs diff --git a/rbmanager.Tests/DevkitVsTests.cs b/tests/rbmanager.Tests/DevkitVsTests.cs similarity index 100% rename from rbmanager.Tests/DevkitVsTests.cs rename to tests/rbmanager.Tests/DevkitVsTests.cs diff --git a/rbmanager.Tests/JunctionTests.cs b/tests/rbmanager.Tests/JunctionTests.cs similarity index 100% rename from rbmanager.Tests/JunctionTests.cs rename to tests/rbmanager.Tests/JunctionTests.cs diff --git a/rbmanager.Tests/ProgramLifecycleTests.cs b/tests/rbmanager.Tests/ProgramLifecycleTests.cs similarity index 100% rename from rbmanager.Tests/ProgramLifecycleTests.cs rename to tests/rbmanager.Tests/ProgramLifecycleTests.cs diff --git a/rbmanager.Tests/PublishE2eTests.cs b/tests/rbmanager.Tests/PublishE2eTests.cs similarity index 100% rename from rbmanager.Tests/PublishE2eTests.cs rename to tests/rbmanager.Tests/PublishE2eTests.cs diff --git a/rbmanager.Tests/ResolveTests.cs b/tests/rbmanager.Tests/ResolveTests.cs similarity index 100% rename from rbmanager.Tests/ResolveTests.cs rename to tests/rbmanager.Tests/ResolveTests.cs diff --git a/rbmanager.Tests/SetupE2eTests.cs b/tests/rbmanager.Tests/SetupE2eTests.cs similarity index 100% rename from rbmanager.Tests/SetupE2eTests.cs rename to tests/rbmanager.Tests/SetupE2eTests.cs diff --git a/rbmanager.Tests/SingleRootDirectoryTests.cs b/tests/rbmanager.Tests/SingleRootDirectoryTests.cs similarity index 100% rename from rbmanager.Tests/SingleRootDirectoryTests.cs rename to tests/rbmanager.Tests/SingleRootDirectoryTests.cs diff --git a/rbmanager.Tests/SmokeTests.cs b/tests/rbmanager.Tests/SmokeTests.cs similarity index 100% rename from rbmanager.Tests/SmokeTests.cs rename to tests/rbmanager.Tests/SmokeTests.cs diff --git a/rbmanager.Tests/Support/ConsoleCapture.cs b/tests/rbmanager.Tests/Support/ConsoleCapture.cs similarity index 100% rename from rbmanager.Tests/Support/ConsoleCapture.cs rename to tests/rbmanager.Tests/Support/ConsoleCapture.cs diff --git a/rbmanager.Tests/Support/E2eSandbox.cs b/tests/rbmanager.Tests/Support/E2eSandbox.cs similarity index 100% rename from rbmanager.Tests/Support/E2eSandbox.cs rename to tests/rbmanager.Tests/Support/E2eSandbox.cs diff --git a/rbmanager.Tests/Support/EnvScope.cs b/tests/rbmanager.Tests/Support/EnvScope.cs similarity index 100% rename from rbmanager.Tests/Support/EnvScope.cs rename to tests/rbmanager.Tests/Support/EnvScope.cs diff --git a/rbmanager.Tests/Support/LoopbackZipServer.cs b/tests/rbmanager.Tests/Support/LoopbackZipServer.cs similarity index 100% rename from rbmanager.Tests/Support/LoopbackZipServer.cs rename to tests/rbmanager.Tests/Support/LoopbackZipServer.cs diff --git a/rbmanager.Tests/Support/PublishFixture.cs b/tests/rbmanager.Tests/Support/PublishFixture.cs similarity index 88% rename from rbmanager.Tests/Support/PublishFixture.cs rename to tests/rbmanager.Tests/Support/PublishFixture.cs index 4c51c70..866ca0a 100644 --- a/rbmanager.Tests/Support/PublishFixture.cs +++ b/tests/rbmanager.Tests/Support/PublishFixture.cs @@ -58,14 +58,15 @@ public PublishFixture() } } - // Repo root is the path segment before \rbmanager.Tests\. + // Repo root is the path segment before \artifacts\ (artifacts output + // layout, rooted next to Directory.Build.props). private static string? LocateCsproj() { - string marker = $"{Path.DirectorySeparatorChar}rbmanager.Tests{Path.DirectorySeparatorChar}"; + string marker = $"{Path.DirectorySeparatorChar}artifacts{Path.DirectorySeparatorChar}"; int idx = AppContext.BaseDirectory.IndexOf(marker, StringComparison.Ordinal); if (idx < 0) return null; string repoRoot = AppContext.BaseDirectory[..idx]; - string csproj = Path.Combine(repoRoot, "rbmanager", "rbmanager.csproj"); + string csproj = Path.Combine(repoRoot, "src", "rbmanager", "rbmanager.csproj"); return File.Exists(csproj) ? csproj : null; } diff --git a/rbmanager.Tests/Support/Rb.cs b/tests/rbmanager.Tests/Support/Rb.cs similarity index 88% rename from rbmanager.Tests/Support/Rb.cs rename to tests/rbmanager.Tests/Support/Rb.cs index eb5e7d9..4d44f2f 100644 --- a/rbmanager.Tests/Support/Rb.cs +++ b/tests/rbmanager.Tests/Support/Rb.cs @@ -42,13 +42,14 @@ public static RbResult RunExe(string exe, string root, string? envKey, return new RbResult(proc.ExitCode, outTask.Result, errTask.Result); } - // The test assembly runs from ...\rbmanager.Tests\bin\\\; the - // product's apphost sits at the sibling ...\rbmanager\bin\\\. + // With the artifacts output layout the test assembly runs from + // ...\artifacts\bin\rbmanager.Tests\\; the product's apphost sits + // at the sibling ...\artifacts\bin\rbmanager\\. private static string ResolveExe() { string sep = Path.DirectorySeparatorChar.ToString(); string productDir = AppContext.BaseDirectory.Replace( - $"{sep}rbmanager.Tests{sep}bin{sep}", $"{sep}rbmanager{sep}bin{sep}"); + $"{sep}bin{sep}rbmanager.Tests{sep}", $"{sep}bin{sep}rbmanager{sep}"); string exe = Path.Combine(productDir, "rb.exe"); if (!File.Exists(exe)) throw new FileNotFoundException($"rb.exe not found at {exe}"); diff --git a/rbmanager.Tests/Support/RbSandbox.cs b/tests/rbmanager.Tests/Support/RbSandbox.cs similarity index 100% rename from rbmanager.Tests/Support/RbSandbox.cs rename to tests/rbmanager.Tests/Support/RbSandbox.cs diff --git a/rbmanager.Tests/Support/ScratchRegistryKey.cs b/tests/rbmanager.Tests/Support/ScratchRegistryKey.cs similarity index 100% rename from rbmanager.Tests/Support/ScratchRegistryKey.cs rename to tests/rbmanager.Tests/Support/ScratchRegistryKey.cs diff --git a/rbmanager.Tests/Support/Serial.cs b/tests/rbmanager.Tests/Support/Serial.cs similarity index 100% rename from rbmanager.Tests/Support/Serial.cs rename to tests/rbmanager.Tests/Support/Serial.cs diff --git a/rbmanager.Tests/Support/TempDir.cs b/tests/rbmanager.Tests/Support/TempDir.cs similarity index 100% rename from rbmanager.Tests/Support/TempDir.cs rename to tests/rbmanager.Tests/Support/TempDir.cs diff --git a/rbmanager.Tests/Support/VsWhereScope.cs b/tests/rbmanager.Tests/Support/VsWhereScope.cs similarity index 100% rename from rbmanager.Tests/Support/VsWhereScope.cs rename to tests/rbmanager.Tests/Support/VsWhereScope.cs diff --git a/rbmanager.Tests/Support/Zips.cs b/tests/rbmanager.Tests/Support/Zips.cs similarity index 100% rename from rbmanager.Tests/Support/Zips.cs rename to tests/rbmanager.Tests/Support/Zips.cs diff --git a/rbmanager.Tests/UserPathTests.cs b/tests/rbmanager.Tests/UserPathTests.cs similarity index 100% rename from rbmanager.Tests/UserPathTests.cs rename to tests/rbmanager.Tests/UserPathTests.cs diff --git a/rbmanager.Tests/VcRedistTests.cs b/tests/rbmanager.Tests/VcRedistTests.cs similarity index 100% rename from rbmanager.Tests/VcRedistTests.cs rename to tests/rbmanager.Tests/VcRedistTests.cs diff --git a/rbmanager.Tests/rbmanager.Tests.csproj b/tests/rbmanager.Tests/rbmanager.Tests.csproj similarity index 93% rename from rbmanager.Tests/rbmanager.Tests.csproj rename to tests/rbmanager.Tests/rbmanager.Tests.csproj index 57b9c4b..af6f1b4 100644 --- a/rbmanager.Tests/rbmanager.Tests.csproj +++ b/tests/rbmanager.Tests/rbmanager.Tests.csproj @@ -21,7 +21,7 @@ - + From a5bbf1ff3d334831ced70cfd66172f6e576de43f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 11 Jul 2026 05:53:03 +0900 Subject: [PATCH 2/8] Add .gitattributes Normalize text line endings in the object store and renormalize the two files that had CRLF committed. Windows script types stay CRLF in the working tree. Co-Authored-By: Claude Fable 5 --- .config/dotnet-tools.json | 24 ++++++++--------- .gitattributes | 11 ++++++++ rbmanager.sln | 56 +++++++++++++++++++-------------------- 3 files changed, 51 insertions(+), 40 deletions(-) create mode 100644 .gitattributes diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 9de05eb..ca9071b 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -1,13 +1,13 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "wix": { - "version": "5.0.2", - "commands": [ - "wix" - ], - "rollForward": false - } - } +{ + "version": 1, + "isRoot": true, + "tools": { + "wix": { + "version": "5.0.2", + "commands": [ + "wix" + ], + "rollForward": false + } + } } \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..69db5f8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Normalize line endings; keep Windows script types CRLF on checkout. +* text=auto + +*.ps1 text eol=crlf +*.cmd text eol=crlf +*.bat text eol=crlf +*.sh text eol=lf + +*.exe binary +*.msi binary +*.zip binary diff --git a/rbmanager.sln b/rbmanager.sln index a174d4f..1768c4b 100644 --- a/rbmanager.sln +++ b/rbmanager.sln @@ -1,28 +1,28 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rbmanager", "src\rbmanager\rbmanager.csproj", "{375503E8-4DA4-40AD-AE41-82FCE6D6742C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rbmanager.Tests", "tests\rbmanager.Tests\rbmanager.Tests.csproj", "{22625437-7DC7-421A-8896-A994C22A8E7A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {375503E8-4DA4-40AD-AE41-82FCE6D6742C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {375503E8-4DA4-40AD-AE41-82FCE6D6742C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {375503E8-4DA4-40AD-AE41-82FCE6D6742C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {375503E8-4DA4-40AD-AE41-82FCE6D6742C}.Release|Any CPU.Build.0 = Release|Any CPU - {22625437-7DC7-421A-8896-A994C22A8E7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {22625437-7DC7-421A-8896-A994C22A8E7A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22625437-7DC7-421A-8896-A994C22A8E7A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {22625437-7DC7-421A-8896-A994C22A8E7A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rbmanager", "src\rbmanager\rbmanager.csproj", "{375503E8-4DA4-40AD-AE41-82FCE6D6742C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rbmanager.Tests", "tests\rbmanager.Tests\rbmanager.Tests.csproj", "{22625437-7DC7-421A-8896-A994C22A8E7A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {375503E8-4DA4-40AD-AE41-82FCE6D6742C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {375503E8-4DA4-40AD-AE41-82FCE6D6742C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {375503E8-4DA4-40AD-AE41-82FCE6D6742C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {375503E8-4DA4-40AD-AE41-82FCE6D6742C}.Release|Any CPU.Build.0 = Release|Any CPU + {22625437-7DC7-421A-8896-A994C22A8E7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22625437-7DC7-421A-8896-A994C22A8E7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22625437-7DC7-421A-8896-A994C22A8E7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22625437-7DC7-421A-8896-A994C22A8E7A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From e8c9b8e117360f67c65ef07489f5695ae576b026 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 11 Jul 2026 05:53:26 +0900 Subject: [PATCH 3/8] Add .editorconfig Whitespace and charset basics plus the file-scoped namespace style the codebase already uses, so editors and dotnet format agree. Co-Authored-By: Claude Fable 5 --- .editorconfig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..12b16d6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.cs] +csharp_style_namespace_declarations = file_scoped:suggestion + +[*.{csproj,props,targets,wxs,sln,json,yaml,yml}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false From 4a572ae04ad6bfcb58243bad188870b052733eec Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 11 Jul 2026 05:53:57 +0900 Subject: [PATCH 4/8] Add global.json Pin the SDK to the 8.0.4xx feature band. The artifacts output layout assumes SDK 8+, so this also makes the floor explicit. Co-Authored-By: Claude Fable 5 --- global.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 global.json diff --git a/global.json b/global.json new file mode 100644 index 0000000..ae9f38c --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.400", + "rollForward": "latestFeature" + } +} From a17e4d514032b8c0ab4c2917bbf108e4ad0bbcb9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 11 Jul 2026 13:54:58 +0900 Subject: [PATCH 5/8] Convert the MSI build to a WiX SDK-style project The authoring moves to src/rbmanager.Installer (WixToolset.Sdk 5.0.2) and builds with dotnet build, ICE validation included, replacing the wix CLI tool manifest. The UpgradeCode and PATH component GUID never change, so they are baked in as constants with the derivation kept in docs/upgrade-code.md. Solution builds skip the project because it packages the published NativeAOT rb.exe. Co-Authored-By: Claude Fable 5 --- .config/dotnet-tools.json | 13 ---- Directory.Build.props | 7 +++ README.md | 11 ++-- build-installer.ps1 | 61 ++++--------------- docs/upgrade-code.md | 35 ++++++++--- rbmanager.sln | 4 ++ .../rbmanager.Installer/Package.wxs | 19 +++--- .../rbmanager.Installer.wixproj | 16 +++++ 8 files changed, 81 insertions(+), 85 deletions(-) delete mode 100644 .config/dotnet-tools.json rename installer/rbmanager.wxs => src/rbmanager.Installer/Package.wxs (73%) create mode 100644 src/rbmanager.Installer/rbmanager.Installer.wixproj diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json deleted file mode 100644 index ca9071b..0000000 --- a/.config/dotnet-tools.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "wix": { - "version": "5.0.2", - "commands": [ - "wix" - ], - "rollForward": false - } - } -} \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 257c20b..c796c38 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,4 +4,11 @@ instead of per-project bin/ and obj/. --> true + + + + $(MSBuildThisFileDirectory)artifacts\bin\$(MSBuildProjectName)\ + $(MSBuildThisFileDirectory)artifacts\obj\$(MSBuildProjectName)\ + diff --git a/README.md b/README.md index 1490f21..742ee68 100644 --- a/README.md +++ b/README.md @@ -51,18 +51,19 @@ runtime dependency. For channels that want a real installer instead of the bare exe, `build-installer.ps1` wraps rb.exe in a per-user MSI -(`installer/rbmanager.wxs`, WiX v5): +(`src/rbmanager.Installer/`, a WiX v5 SDK-style project): ``` -.\build-installer.ps1 -Validate +.\build-installer.ps1 ``` The MSI installs `rb.exe` into `%LOCALAPPDATA%\Ruby\bin` and puts that directory on the user PATH, producing exactly the layout `rb setup` creates, and removes both again on uninstall. rbmanager is a single MSI product line; see [docs/upgrade-code.md](docs/upgrade-code.md). WiX -5.0.2 is pinned as a dotnet local tool in `.config/dotnet-tools.json`. -The output is unsigned; code signing is tracked separately. +5.0.2 comes in through the `WixToolset.Sdk` NuGet package, and ICE +validation runs as part of the build. The output is unsigned; code +signing is tracked separately. An earlier iteration packaged each Ruby version as its own MSI. That direction was dropped in favor of rbmanager; see the git history for @@ -88,7 +89,7 @@ failure modes all degrade to the previous behavior. - `src/rbmanager/` — the version manager (C#, NativeAOT) - `tests/rbmanager.Tests/` — the xUnit test suite - `overlay/` — files layered onto every installed runtime -- `installer/rbmanager.wxs`, `build-installer.ps1` — the rbmanager MSI +- `src/rbmanager.Installer/`, `build-installer.ps1` — the rbmanager MSI - `docs/` — design notes - `tools/query.ps1` — dump MSI tables via the WindowsInstaller COM API diff --git a/build-installer.ps1 b/build-installer.ps1 index dcba062..7427361 100644 --- a/build-installer.ps1 +++ b/build-installer.ps1 @@ -3,19 +3,19 @@ Build the rbmanager MSI (rb.exe installer). .DESCRIPTION - Publishes rb.exe with NativeAOT and compiles installer/rbmanager.wxs with - WiX v5 into a per-user MSI under dist/. Code signing is a separate, - later step; the output is unsigned. + Publishes rb.exe with NativeAOT and builds src/rbmanager.Installer + (WiX v5, SDK-style) into a per-user MSI under dist/. ICE validation + runs as part of the build. Code signing is a separate, later step; + the output is unsigned. .EXAMPLE .\build-installer.ps1 - .\build-installer.ps1 -Version 0.1.0 -Validate + .\build-installer.ps1 -Version 0.1.0 #> param( [string]$Version = '0.1.0', [ValidateSet('x64', 'arm64')][string]$Arch = 'x64', [string]$OutDir = (Join-Path $PSScriptRoot 'dist'), - [switch]$Validate, [switch]$SkipPublish ) @@ -25,31 +25,6 @@ if ($Version -notmatch '^\d+\.\d+\.\d+$') { throw "Version must be numeric x.y.z for MSI ProductVersion, got '$Version'" } -# UUIDv5 derivation; see docs/upgrade-code.md. rbmanager is a single -# product line, so the arch is not part of its identity. -$NamespaceGuid = [guid]'E2C11F7E-A84E-4363-A0EB-D0A93E07E3CF' - -function New-UuidV5([guid]$Namespace, [string]$Name) { - $ns = $Namespace.ToByteArray() - # GUID byte layout is little-endian in the first three fields; RFC 4122 - # hashing requires network byte order. - [Array]::Reverse($ns, 0, 4); [Array]::Reverse($ns, 4, 2); [Array]::Reverse($ns, 6, 2) - $sha1 = [System.Security.Cryptography.SHA1]::Create() - try { - $hash = $sha1.ComputeHash($ns + [System.Text.Encoding]::UTF8.GetBytes($Name)) - } finally { - $sha1.Dispose() - } - $b = $hash[0..15] - $b[6] = ($b[6] -band 0x0F) -bor 0x50 # version 5 - $b[8] = ($b[8] -band 0x3F) -bor 0x80 # RFC 4122 variant - [Array]::Reverse($b, 0, 4); [Array]::Reverse($b, 4, 2); [Array]::Reverse($b, 6, 2) - [guid][byte[]]$b -} - -$upgradeCode = New-UuidV5 $NamespaceGuid 'ruby-windows-installer:upgrade-code:rbmanager' -$pathGuid = New-UuidV5 $NamespaceGuid 'ruby-windows-installer:path-component:rbmanager:perUser' - $dotnet = Get-Command dotnet -ErrorAction SilentlyContinue $dotnet = if ($dotnet) { $dotnet.Source } else { 'C:\Program Files\dotnet\dotnet.exe' } @@ -65,29 +40,15 @@ try { } if (-not (Test-Path $rbExe)) { throw "$rbExe not found" } - & $dotnet tool restore | Out-Null - if ($LASTEXITCODE -ne 0) { throw "dotnet tool restore failed" } + Write-Host "Building the MSI ..." + & $dotnet build src\rbmanager.Installer -c Release ` + "-p:ProductVersion=$Version" "-p:InstallerPlatform=$Arch" "-p:RbExe=$rbExe" + if ($LASTEXITCODE -ne 0) { throw "MSI build failed" } New-Item -ItemType Directory -Force $OutDir | Out-Null $msi = Join-Path $OutDir "rbmanager-$Version-$Arch.msi" - - Write-Host "Building $msi ..." - & $dotnet wix build (Join-Path $PSScriptRoot 'installer\rbmanager.wxs') ` - -arch $Arch ` - -d "Version=$Version" ` - -d "UpgradeCode=$upgradeCode" ` - -d "PathComponentGuid=$pathGuid" ` - -d "RbExe=$rbExe" ` - -o $msi - if ($LASTEXITCODE -ne 0) { throw "wix build failed" } - - if ($Validate) { - Write-Host "Validating $msi ..." - # Per-user packages installing into the user profile trip ICE38/ICE64/ - # ICE91 by design; those rules target per-machine packages. - & $dotnet wix msi validate '-sice' 'ICE38' '-sice' 'ICE64' '-sice' 'ICE91' $msi - if ($LASTEXITCODE -ne 0) { throw "wix msi validate failed" } - } + Copy-Item (Join-Path $PSScriptRoot "artifacts\bin\rbmanager.Installer\Release\rbmanager-$Version-$Arch.msi") ` + $msi -Force } finally { Pop-Location } diff --git a/docs/upgrade-code.md b/docs/upgrade-code.md index d091934..8b6978e 100644 --- a/docs/upgrade-code.md +++ b/docs/upgrade-code.md @@ -14,9 +14,9 @@ requires. ## Derivation -UpgradeCodes are never allocated by hand. build-installer.ps1 derives -them as UUIDv5 (RFC 4122, SHA-1) from a fixed namespace GUID and a name -string: +UpgradeCodes are never allocated by hand. They are derived as UUIDv5 +(RFC 4122, SHA-1) from a fixed namespace GUID and a name string, and +baked into `src/rbmanager.Installer/Package.wxs` as constants: namespace: E2C11F7E-A84E-4363-A0EB-D0A93E07E3CF name: ruby-windows-installer:upgrade-code:rbmanager @@ -28,16 +28,33 @@ the repository rename and is frozen with the rest. Changing either would silently break in-place upgrades, because new packages would stop finding the installed ones. -Derived values for reference (reproducible with `New-UuidV5` in -build-installer.ps1): +Derived values for reference: -| name | UpgradeCode | -|------|-------------| +| name | GUID | +|------|------| | ruby-windows-installer:upgrade-code:rbmanager | CAB95EEE-B5A6-52EA-94B3-B4413CE23855 | +| ruby-windows-installer:path-component:rbmanager:perUser | F786646B-2533-5C48-AD8F-9CCDD8AE4D16 | -The PATH environment component needs a stable GUID for the same reason: +The second entry is the PATH environment component, which needs a +stable GUID for the same reason. Both are reproducible with: - name: ruby-windows-installer:path-component:rbmanager:perUser +```powershell +function New-UuidV5([guid]$Namespace, [string]$Name) { + $ns = $Namespace.ToByteArray() + # GUID byte layout is little-endian in the first three fields; RFC 4122 + # hashing requires network byte order. + [Array]::Reverse($ns, 0, 4); [Array]::Reverse($ns, 4, 2); [Array]::Reverse($ns, 6, 2) + $hash = [System.Security.Cryptography.SHA1]::HashData( + $ns + [System.Text.Encoding]::UTF8.GetBytes($Name)) + $b = $hash[0..15] + $b[6] = ($b[6] -band 0x0F) -bor 0x50 # version 5 + $b[8] = ($b[8] -band 0x3F) -bor 0x80 # RFC 4122 variant + [Array]::Reverse($b, 0, 4); [Array]::Reverse($b, 4, 2); [Array]::Reverse($b, 6, 2) + [guid][byte[]]$b +} +New-UuidV5 'E2C11F7E-A84E-4363-A0EB-D0A93E07E3CF' ` + 'ruby-windows-installer:upgrade-code:rbmanager' +``` The retired per-version Ruby MSI used the same scheme with one product line per (series, arch) pair; its name formats and derived codes are in diff --git a/rbmanager.sln b/rbmanager.sln index 1768c4b..ed7b2c7 100644 --- a/rbmanager.sln +++ b/rbmanager.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rbmanager", "src\rbmanager\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rbmanager.Tests", "tests\rbmanager.Tests\rbmanager.Tests.csproj", "{22625437-7DC7-421A-8896-A994C22A8E7A}" EndProject +Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "rbmanager.Installer", "src\rbmanager.Installer\rbmanager.Installer.wixproj", "{8B9A5A63-3F0A-4E4B-9D0C-6E3A7D9B21C4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,7 @@ Global {22625437-7DC7-421A-8896-A994C22A8E7A}.Debug|Any CPU.Build.0 = Debug|Any CPU {22625437-7DC7-421A-8896-A994C22A8E7A}.Release|Any CPU.ActiveCfg = Release|Any CPU {22625437-7DC7-421A-8896-A994C22A8E7A}.Release|Any CPU.Build.0 = Release|Any CPU + {8B9A5A63-3F0A-4E4B-9D0C-6E3A7D9B21C4}.Debug|Any CPU.ActiveCfg = Debug|x64 + {8B9A5A63-3F0A-4E4B-9D0C-6E3A7D9B21C4}.Release|Any CPU.ActiveCfg = Release|x64 EndGlobalSection EndGlobal diff --git a/installer/rbmanager.wxs b/src/rbmanager.Installer/Package.wxs similarity index 73% rename from installer/rbmanager.wxs rename to src/rbmanager.Installer/Package.wxs index 37adbc2..9b2419d 100644 --- a/installer/rbmanager.wxs +++ b/src/rbmanager.Installer/Package.wxs @@ -7,18 +7,21 @@ uses) and puts that directory on the user PATH, so the MSI and the bare exe channel produce identical layouts. Per-user only; no elevation. - All variable inputs are supplied by build-installer.ps1 as preprocessor - variables: - Version numeric x.y.z ProductVersion - UpgradeCode UUIDv5, see docs/upgrade-code.md - PathComponentGuid UUIDv5, see docs/upgrade-code.md - RbExe path to the published rb.exe + Variable inputs are supplied by rbmanager.Installer.wixproj as + preprocessor variables: + Version numeric x.y.z ProductVersion + RbExe path to the published rb.exe + + The UpgradeCode and the PATH component GUID are UUIDv5 values derived + from fixed inputs, so they are constants baked in below; the + derivation is documented in docs/upgrade-code.md and must never + change. --> @@ -42,7 +45,7 @@ - + diff --git a/src/rbmanager.Installer/rbmanager.Installer.wixproj b/src/rbmanager.Installer/rbmanager.Installer.wixproj new file mode 100644 index 0000000..102e890 --- /dev/null +++ b/src/rbmanager.Installer/rbmanager.Installer.wixproj @@ -0,0 +1,16 @@ + + + + x64 + 0.1.0 + + $(MSBuildThisFileDirectory)..\..\artifacts\publish\rbmanager\rb.exe + $(DefineConstants);Version=$(ProductVersion);RbExe=$(RbExe) + rbmanager-$(ProductVersion)-$(InstallerPlatform) + + ICE38;ICE64;ICE91 + + + From 712c925648498ef8794b1010d89c0fdf45a32739 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 11 Jul 2026 13:58:14 +0900 Subject: [PATCH 6/8] Add GitHub Actions CI Build the solution and run the full test suite on windows-latest, where the hosted image has VS with MSVC so the RequiresVS and Publish suites run for real instead of skipping. Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..889029d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: windows-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + - run: dotnet build rbmanager.sln + # The hosted image has VS with MSVC, so the RequiresVS and Publish + # suites run for real instead of skipping. + - run: dotnet test rbmanager.sln --no-build From 3285b0f1c28b2e1182acc1729e57e9a478de2d2e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 11 Jul 2026 14:00:21 +0900 Subject: [PATCH 7/8] README: reflect the solution-based build and test entry points Co-Authored-By: Claude Fable 5 --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 742ee68..353792b 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,24 @@ The active ruby is exposed through an NTFS directory junction versions only re-points the junction. Junctions rather than symbolic links because they need no privilege and no Developer Mode. -Build (requires the .NET 8 SDK and MSVC link.exe): +## Build and test + +Regular development needs only the .NET 8 SDK (the feature band is +pinned by `global.json`): + +``` +dotnet build rbmanager.sln +dotnet test rbmanager.sln +``` + +Suites that need more than the SDK (Visual Studio, network access) +skip themselves when the environment lacks it; the category filters +for running them selectively are listed in +[docs/test-plan.md](docs/test-plan.md). CI +(`.github/workflows/ci.yml`) runs the same build and the full suite +on windows-latest, where VS is present. + +Publishing the shipping rb.exe additionally requires MSVC link.exe: ``` dotnet publish src\rbmanager -r win-x64 -c Release -o artifacts\publish\rbmanager @@ -90,6 +107,7 @@ failure modes all degrade to the previous behavior. - `tests/rbmanager.Tests/` — the xUnit test suite - `overlay/` — files layered onto every installed runtime - `src/rbmanager.Installer/`, `build-installer.ps1` — the rbmanager MSI +- `winget/` — draft winget manifests - `docs/` — design notes - `tools/query.ps1` — dump MSI tables via the WindowsInstaller COM API From 916b5483c114a6765795c886c755c35fc1c14dcc Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Sat, 11 Jul 2026 14:04:13 +0900 Subject: [PATCH 8/8] README: sync the command list with the implementation rb enable/exec and the VC++ redistributable handling in rb setup were missing, and the published exe is ~6 MB now. Co-Authored-By: Claude Fable 5 --- README.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 353792b..79657be 100644 --- a/README.md +++ b/README.md @@ -21,16 +21,25 @@ after the language (`%LOCALAPPDATA%\Ruby`, like `%LocalAppData%\Python`) rather than after the tool. rbmanager remains the product name. ``` -rb setup copy rb itself onto PATH -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 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 is a bare exe; `setup` copies it to `%LOCALAPPDATA%\Ruby\bin` and puts that directory on the user PATH, -which stands in for an installer until a winget manifest exists. +which stands in for an installer until the winget and MSI channels +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). The active ruby is exposed through an NTFS directory junction `%LOCALAPPDATA%\Ruby\current`, and `install` appends @@ -61,7 +70,7 @@ Publishing the shipping rb.exe additionally requires MSVC link.exe: dotnet publish src\rbmanager -r win-x64 -c Release -o artifacts\publish\rbmanager ``` -This produces a self-contained NativeAOT `rb.exe` (~5 MB) with no +This produces a self-contained NativeAOT `rb.exe` (~6 MB) with no runtime dependency. ## rbmanager MSI