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 @@ - +