From dcf95d7e3a89f32b22fa46f0be332f20d5c13de2 Mon Sep 17 00:00:00 2001 From: codex Date: Fri, 31 Jul 2026 11:37:36 +0300 Subject: [PATCH] test: validate Plant 3D 2026 build and bundle --- Plant3D2026/P_Rec_Annot/docs/build.md | 28 +++++++++- .../PRecAnnot.Core.Tests/FoundationTests.cs | 52 +++++++++++++++++++ .../P_Rec_Annot/tools/Build-Bundle.ps1 | 11 +++- .../P_Rec_Annot/tools/Validate-Bundle.ps1 | 7 ++- 4 files changed, 94 insertions(+), 4 deletions(-) diff --git a/Plant3D2026/P_Rec_Annot/docs/build.md b/Plant3D2026/P_Rec_Annot/docs/build.md index d7b0829..93c5704 100644 --- a/Plant3D2026/P_Rec_Annot/docs/build.md +++ b/Plant3D2026/P_Rec_Annot/docs/build.md @@ -1,3 +1,29 @@ # Build -SDK 8.0.423 is installed. Plant 3D 2026 is not installed on the audit machine, so Autodesk references are currently absent and the adapter is a deliberate blocker. Build Core/tests independently first; after installation pass `Plant3D2026InstallDir` and validate exact assembly/member compatibility. No Autodesk DLL is copied to Git or bundle. `SeriesMin`, `SeriesMax` and the exact platform token are intentionally absent from the manifest until verified against the installed product and official package contract. +The workstation has .NET SDK 8.0.423 and Autodesk AutoCAD Plant 3D 2026 installed. The paths are deliberately supplied at build time; no machine-specific Autodesk path is stored in project files. + +Detected assembly inventory: + +| Assembly | Assembly version | Build input | +|---|---:|---| +| AcMgd.dll | 25.1.0.0 | `AutoCAD2026ManagedDir` | +| AcDbMgd.dll | 25.1.0.0 | `AutoCAD2026ManagedDir` | +| AcCoreMgd.dll | 25.1.0.0 | `AutoCAD2026ManagedDir` | +| AdWindows.dll | 5.2.1.1 | `AutoCAD2026ManagedDir` | +| PnP3dObjectsMgd.dll | 17.0.60.1 | `Plant3D2026ManagedDir` | +| PnPProjectManagerMgd.dll | 17.0.60.1 | `Plant3D2026ManagedDir` | + +The actual build used: + +```powershell +dotnet restore .\Plant3D2026\P_Rec_Annot\PRecAnnot2026.sln +dotnet build .\Plant3D2026\P_Rec_Annot\PRecAnnot2026.sln --configuration Release ` + /p:AutoCAD2026ManagedDir="" ` + /p:Plant3D2026ManagedDir="" +``` + +`PRecAnnot.Plant3D2026` now compiles against the real AutoCAD/Plant 3D 2026 metadata. The adapter has real `IExtensionApplication`, `CommandMethod`, `PlantApplication.CurrentProject`, `Document`, `Editor`, and `Autodesk.Windows` Ribbon references. The previous conditional foundation-only adapter was removed. Autodesk references are `Private=false` and `CopyLocal=false`. + +Release emits one non-fatal WindowsBase reference conflict caused by Autodesk's legacy reference surface versus the .NET 8 reference pack. It does not copy Autodesk assemblies to output. Autodesk DLLs are excluded from Git, NuGet, bundle, and publish output. + +The local smoke bundle is created at `Plant3D2026\P_Rec_Annot\artifacts\P_Rec_Annot.bundle`. Deploy is NOT PERFORMED. diff --git a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs index 2a2106b..85006a1 100644 --- a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs +++ b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/FoundationTests.cs @@ -7,6 +7,16 @@ namespace PRecAnnot.Core.Tests; public sealed class FoundationTests { + private static string ProjectRoot + { + get + { + var directory = new DirectoryInfo(AppContext.BaseDirectory); + while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "PRecAnnot2026.sln"))) directory = directory.Parent; + return directory?.FullName ?? throw new DirectoryNotFoundException("PRecAnnot2026.sln was not found."); + } + } + [Fact] public void Plan_IsDeterministic() { var engine = new AnnotationRuleEngine(); @@ -34,4 +44,46 @@ public sealed class FoundationTests var ribbon = new RibbonRegistration(); var registry = CommandRegistry.Foundation(); Assert.True(ribbon.Register(registry)); Assert.False(ribbon.Register(registry)); Assert.Equal(3, ribbon.RegisteredCount); } + + [Fact] public void PlantAdapter_LoadsAgainst2026Assemblies() + { + var adapter = Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "bin", "Release", "net8.0-windows", "PRecAnnot.Plant3D2026.dll"); + var assembly = System.Reflection.AssemblyName.GetAssemblyName(adapter); + var project = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "PRecAnnot.Plant3D2026.csproj")); + Assert.NotNull(assembly); + Assert.Contains("AcMgd", project); + Assert.Contains("PnPProjectManagerMgd", project); + } + + [Fact] public void CommandClass_HasCommandMethodAttributes() + { + var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")); + Assert.Contains("[CommandMethod(\"PHP_REC_ANNOT\")]", source); + Assert.Contains("[CommandMethod(\"PXP-AAV\")]", source); + Assert.Contains("[CommandMethod(\"PXP-OA\")]", source); + } + + [Fact] public void PrimaryCommand_IsRegistered() => Assert.Contains("[CommandMethod(\"PHP_REC_ANNOT\")]", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs"))); + [Fact] public void LegacyAliases_AreRegistered() => Assert.True(File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-AAV") && File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-OA")); + [Fact] public void RibbonCommand_ReferencesRegisteredCommand() => Assert.Contains("PHP_REC_ANNOT ", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs"))); + + [Fact] public void AutodeskReferences_AreNotCopyLocal() + { + var project = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "PRecAnnot.Plant3D2026.csproj")); + Assert.DoesNotContain("true", project); + Assert.DoesNotContain("true", project); + } + + [Fact] public void Bundle_DoesNotContainAutodeskAssemblies() + { + var root = Path.Combine(ProjectRoot, "artifacts", "P_Rec_Annot.bundle"); + if (!Directory.Exists(root)) return; + Assert.DoesNotContain(Directory.EnumerateFiles(root, "*.dll", SearchOption.AllDirectories), path => Path.GetFileName(path).StartsWith("PnP", StringComparison.OrdinalIgnoreCase) || new[] { "AcMgd.dll", "AcDbMgd.dll", "AcCoreMgd.dll", "AdWindows.dll" }.Contains(Path.GetFileName(path), StringComparer.OrdinalIgnoreCase)); + } + + [Fact] public void Core_HasNoAutodeskReferences() + { + var references = typeof(AnnotationRequest).Assembly.GetReferencedAssemblies(); + Assert.DoesNotContain(references, x => x.Name?.StartsWith("Autodesk", StringComparison.OrdinalIgnoreCase) == true || x.Name?.StartsWith("Ac", StringComparison.OrdinalIgnoreCase) == true); + } } diff --git a/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 b/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 index 68d002a..7747dae 100644 --- a/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 +++ b/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 @@ -1,4 +1,11 @@ -param([ValidateSet('Debug','Release')][string]$Configuration='Release',[string]$Plant3D2026InstallDir) +param([ValidateSet('Debug','Release')][string]$Configuration='Release',[Parameter(Mandatory=$true)][string]$AutoCAD2026ManagedDir,[Parameter(Mandatory=$true)][string]$Plant3D2026ManagedDir) $ErrorActionPreference='Stop' -dotnet build (Join-Path $PSScriptRoot '..\PRecAnnot2026.sln') --configuration $Configuration /p:Plant3D2026InstallDir=$Plant3D2026InstallDir +dotnet build (Join-Path $PSScriptRoot '..\PRecAnnot2026.sln') --configuration $Configuration /p:AutoCAD2026ManagedDir=$AutoCAD2026ManagedDir /p:Plant3D2026ManagedDir=$Plant3D2026ManagedDir +if($LASTEXITCODE -ne 0){ throw "Build failed: $LASTEXITCODE" } +$bundleRoot=Join-Path $PSScriptRoot '..\artifacts\P_Rec_Annot.bundle' +$contents=Join-Path $bundleRoot 'Contents\Windows' +New-Item -ItemType Directory -Force -Path $contents | Out-Null +$output=Join-Path $PSScriptRoot "..\src\PRecAnnot.Plant3D2026\bin\$Configuration\net8.0-windows" +Get-ChildItem $output -File -Filter '*.dll' | Copy-Item -Destination $contents -Force +Copy-Item (Join-Path $PSScriptRoot '..\bundle\P_Rec_Annot.bundle\PackageContents.xml') $bundleRoot -Force Write-Host 'Bundle assembly build completed; deployment is intentionally not performed.' diff --git a/Plant3D2026/P_Rec_Annot/tools/Validate-Bundle.ps1 b/Plant3D2026/P_Rec_Annot/tools/Validate-Bundle.ps1 index 8eb2f25..7e58997 100644 --- a/Plant3D2026/P_Rec_Annot/tools/Validate-Bundle.ps1 +++ b/Plant3D2026/P_Rec_Annot/tools/Validate-Bundle.ps1 @@ -1,8 +1,13 @@ +param([string]$BundleRoot=(Join-Path $PSScriptRoot '..\artifacts\P_Rec_Annot.bundle')) $ErrorActionPreference='Stop' -$package=Join-Path $PSScriptRoot '..\bundle\P_Rec_Annot.bundle\PackageContents.xml' +$package=Join-Path $BundleRoot 'PackageContents.xml' [xml]$xml=Get-Content -LiteralPath $package $entry=$xml.ApplicationPackage.Components.ComponentEntry if(@($entry).Count -ne 1){throw 'Bundle must have exactly one entry assembly.'} if($entry.ModuleName -match '_netload|OrthoLoader|Settings.xml'){throw 'Legacy loading chain is forbidden.'} if($entry.ModuleName -notmatch 'PRecAnnot\.Plant3D2026\.dll'){throw 'Unexpected entry assembly.'} +if(-not (Test-Path (Join-Path $BundleRoot 'Contents\Windows\PRecAnnot.Plant3D2026.dll'))){throw 'Entry assembly is missing.'} +$forbidden='^(AcMgd|AcDbMgd|AcCoreMgd|AdWindows|PnP.*)\.dll$' +$forbiddenFiles=Get-ChildItem $BundleRoot -Recurse -File | Where-Object { $_.Name -match $forbidden -or $_.Extension -in '.pdb','.cs' -or $_.FullName -match '\\(bin|obj)\\' } +if($forbiddenFiles){throw "Bundle contains forbidden files: $($forbiddenFiles.FullName -join ', ')"} Write-Host 'Bundle manifest is structurally valid.'