diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
deleted file mode 100644
index 9de05eb..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/.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
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/.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
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..c796c38
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,14 @@
+
+
+
+ true
+
+
+
+
+ $(MSBuildThisFileDirectory)artifacts\bin\$(MSBuildProjectName)\
+ $(MSBuildThisFileDirectory)artifacts\obj\$(MSBuildProjectName)\
+
+
diff --git a/README.md b/README.md
index 1a001e9..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
@@ -38,31 +47,49 @@ 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 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
+This produces a self-contained NativeAOT `rb.exe` (~6 MB) with no
runtime dependency.
## rbmanager MSI
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):
+(`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
@@ -85,8 +112,13 @@ 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
+- `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
+
+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..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 src/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,69 +25,30 @@ 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' }
-$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" }
- & $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 'src\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/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/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/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"
+ }
+}
diff --git a/rbmanager.sln b/rbmanager.sln
index 1305326..ed7b2c7 100644
--- a/rbmanager.sln
+++ b/rbmanager.sln
@@ -1,28 +1,32 @@
-
-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}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "rbmanager.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
+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
+ 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
+ {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/src/rbmanager.wxs b/src/rbmanager.Installer/Package.wxs
similarity index 73%
rename from src/rbmanager.wxs
rename to src/rbmanager.Installer/Package.wxs
index 37adbc2..9b2419d 100644
--- a/src/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
+
+
+
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 @@
-
+