diff --git a/scripts/Publish-Windows.ps1 b/scripts/Publish-Windows.ps1 index 4603fb6..2d740aa 100644 --- a/scripts/Publish-Windows.ps1 +++ b/scripts/Publish-Windows.ps1 @@ -38,41 +38,12 @@ try { # Gravograph's serial-port runtime is not in the desktop project's dependency graph. if ($name -eq 'GravographIS') { Copy-Item "$staging/System.IO.Ports.dll" $package } } - $engineManifestPath = Join-Path $PSScriptRoot 'external-engines.json' - $engineManifest = Get-Content $engineManifestPath -Raw | ConvertFrom-Json - if ($engineManifest.commit -notmatch '^[0-9a-f]{40}$') { throw 'Engines must be pinned to a full commit SHA.' } - $engineSource = Join-Path $output 'external-engine-source' - git init $engineSource - if ($LASTEXITCODE -ne 0) { throw 'Cannot initialize engine checkout.' } - git -C $engineSource fetch --depth 1 $engineManifest.repository $engineManifest.commit - if ($LASTEXITCODE -ne 0) { throw 'Cannot fetch pinned engines.' } - git -C $engineSource checkout --detach FETCH_HEAD - if ($LASTEXITCODE -ne 0) { throw 'Cannot check out pinned engines.' } - $engineCommit = (git -C $engineSource rev-parse HEAD).Trim() - if ($LASTEXITCODE -ne 0 -or $engineCommit -ne $engineManifest.commit) { throw 'Engine source revision mismatch.' } - $engineDirectory = Join-Path $package 'Engines' - New-Item -ItemType Directory -Path $engineDirectory | Out-Null - # A global MSBuild property cannot be normalized by Directory.Build.props: retain the trailing slash. - $hostRoot = $root.Replace('\', '/') + '/' - foreach ($engine in $engineManifest.engines) { - $project = $engine.project - $projectDirectory = Join-Path $engineSource $project - dotnet test "$projectDirectory/tests/$project.Tests.csproj" -c Release "-p:OpenNestRoot=$hostRoot" "-p:Version=$Version" --logger "trx;LogFileName=$project.trx" --results-directory (Join-Path $root 'TestResults') - if ($LASTEXITCODE -ne 0) { throw "$project tests failed." } - $staging = Join-Path $output "engine-build/$project" - dotnet publish "$projectDirectory/$project.csproj" @common --self-contained false "-p:OpenNestRoot=$hostRoot" -o $staging - if ($LASTEXITCODE -ne 0) { throw "$project publish failed." } - Copy-Item "$staging/$project.dll" $engineDirectory - } - Copy-Item $engineManifestPath (Join-Path $engineDirectory 'manifest.json') - Copy-Item (Join-Path $engineSource 'LICENSE') (Join-Path $engineDirectory 'LICENSE.txt') New-Item -ItemType Directory -Path (Join-Path $package 'Schemes') -Force | Out-Null Copy-Item LICENSE $package @{ version = $Version sourceCommit = $commit - enginesCommit = $engineCommit runtime = 'win-x64' selfContained = $true } | ConvertTo-Json | Set-Content (Join-Path $package 'build-info.json') -Encoding utf8NoBOM @@ -83,10 +54,8 @@ try { 'onnxruntime.dll', 'System.IO.Ports.dll', 'Configurations/PipeFlangeShape.json', 'Posts/OpenNest.Posts.Cincinnati.dll', 'Posts/OpenNest.Posts.Cincinnati.json', 'Posts/OpenNest.Posts.CincinnatiCIFiber.dll', 'Posts/OpenNest.Posts.CincinnatiCIFiber.json', - 'Posts/OpenNest.Posts.GravographIS.dll', 'LICENSE', 'build-info.json', - 'Engines/manifest.json', 'Engines/LICENSE.txt' + 'Posts/OpenNest.Posts.GravographIS.dll', 'LICENSE', 'build-info.json' ) - $required += $engineManifest.engines | ForEach-Object { "Engines/$($_.project).dll" } foreach ($file in $required) { if (-not (Test-Path (Join-Path $package $file) -PathType Leaf)) { throw "Package missing $file" } } @@ -106,19 +75,12 @@ try { foreach ($file in $required) { if (-not (Test-Path (Join-Path $expanded $file) -PathType Leaf)) { throw "ZIP missing $file" } } + # Built-in engines ship inside OpenNest.Engine.dll: check the packaged registry, not the build tree. $smokeProject = Join-Path $PSScriptRoot 'ReleaseSmoke/ReleaseSmoke.csproj' dotnet build $smokeProject -c Release - if ($LASTEXITCODE -ne 0) { throw 'Engine discovery checker build failed.' } - # Verify silent plugin-load failure cannot be mistaken for a successful package. - $probe = Join-Path $expanded "Engines/$($engineManifest.engines[0].project).dll" - Move-Item $probe "$probe.disabled" - try { - dotnet run --project $smokeProject -c Release --no-build -- $expanded - if ($LASTEXITCODE -eq 0) { throw 'Missing-engine failure case was not detected.' } - } - finally { Move-Item "$probe.disabled" $probe } + if ($LASTEXITCODE -ne 0) { throw 'Engine registry checker build failed.' } dotnet run --project $smokeProject -c Release --no-build -- $expanded - if ($LASTEXITCODE -ne 0) { throw 'Packaged engine discovery failed.' } + if ($LASTEXITCODE -ne 0) { throw 'Packaged engine registry check failed.' } $process = Start-Process (Join-Path $expanded 'OpenNest.exe') -WorkingDirectory $expanded -PassThru try { diff --git a/scripts/ReleaseSmoke/Program.cs b/scripts/ReleaseSmoke/Program.cs index c024a50..06906f5 100644 --- a/scripts/ReleaseSmoke/Program.cs +++ b/scripts/ReleaseSmoke/Program.cs @@ -1,6 +1,5 @@ using System.Reflection; using System.Runtime.Loader; -using System.Text.Json; try { @@ -15,21 +14,36 @@ try return File.Exists(path) ? AssemblyLoadContext.Default.LoadFromAssemblyPath(path) : null; }; - var engineAssembly = Assembly.LoadFrom(Path.Combine(package, "OpenNest.Engine.dll")); + var engineAssemblyPath = Path.Combine(package, "OpenNest.Engine.dll"); + var engineAssembly = Assembly.LoadFrom(engineAssemblyPath); var registry = engineAssembly.GetType("OpenNest.Engine.Jobs.NestingEngineRegistry", true)!; - var engineDirectory = Path.Combine(package, "Engines"); - registry.GetMethod("LoadPlugins")!.Invoke(null, [engineDirectory]); - using var manifest = JsonDocument.Parse(File.ReadAllText(Path.Combine(engineDirectory, "manifest.json"))); - foreach (var entry in manifest.RootElement.GetProperty("engines").EnumerateArray()) + var create = registry.GetMethod("Create")!; + + // Every built-in engine the desktop offers must instantiate from the packaged assembly. + string[] expected = ["Rectangles", "Irregular", "StockLadder", "Default", "Strip", "Vertical Remnant", "Horizontal Remnant"]; + foreach (var name in expected) { - var name = entry.GetProperty("registryName").GetString()!; - var expected = Path.Combine(engineDirectory, entry.GetProperty("project").GetString() + ".dll"); - if (!File.Exists(expected)) - throw new FileNotFoundException("Required engine missing", expected); - var engine = registry.GetMethod("Create")!.Invoke(null, [name])!; - if (!string.Equals(engine.GetType().Assembly.Location, expected, StringComparison.OrdinalIgnoreCase)) + var engine = create.Invoke(null, [name])!; + if (!string.Equals(engine.GetType().Assembly.Location, engineAssemblyPath, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException($"Engine {name} was not loaded from the release package."); - Console.WriteLine($"PASS: {name} loaded and instantiated from {expected}"); + Console.WriteLine($"PASS: {name} instantiated from {engineAssemblyPath}"); + } + + // A selection saved before the engines were renamed must still resolve. + var resolved = registry.GetMethod("ResolveName")!.Invoke(null, ["Opus55NestingEngine"]); + if (!Equals(resolved, "Irregular")) + throw new InvalidOperationException($"Legacy selection Opus55NestingEngine resolved to '{resolved}', expected Irregular."); + Console.WriteLine("PASS: legacy selection Opus55NestingEngine resolves to Irregular"); + + // Failure case: an unknown engine must be rejected, so a broken registry cannot pass silently. + try + { + create.Invoke(null, ["ReleaseSmokeMissingEngine"]); + throw new InvalidOperationException("Unknown engine name was accepted."); + } + catch (TargetInvocationException exception) when (exception.InnerException is NotSupportedException) + { + Console.WriteLine("PASS: unknown engine name rejected"); } return 0; } diff --git a/scripts/external-engines.json b/scripts/external-engines.json deleted file mode 100644 index 7ecdabc..0000000 --- a/scripts/external-engines.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "repository": "https://git.thecozycat.net/aj/OpenNest-Engines.git", - "commit": "33cc2ee810a9408a4e720eecd5be30271f87d720", - "engines": [ - { "project": "OpenNest.Engine.Gpt6Astra", "registryName": "Gpt6AstraNestingEngine" }, - { "project": "OpenNest.Engine.Opus55", "registryName": "Opus55NestingEngine" }, - { "project": "OpenNest.Engine.Qwen38FlashNext", "registryName": "Qwen38FlashNextNestingEngine" } - ] -}