build(release): package built-in engines instead of external plug-ins
Rectangles and Irregular now ship inside OpenNest.Engine.dll, so the Windows package no longer fetches, tests and bundles OpenNest-Engines at a pinned commit. Removes scripts/external-engines.json and the Engines/ folder, manifest and license from the package; build-info.json drops enginesCommit. ReleaseSmoke now checks the packaged registry: every built-in engine must instantiate from the packaged OpenNest.Engine.dll, the Opus55NestingEngine name must resolve to Irregular, and an unknown name must be rejected. Linux check against a published OpenNest.Engine: pass case exit 0; missing engine DLL exit 1. Full Windows packaging still needs the Windows runner.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user