From d369dfc0d2b8f22a009939bad4ace77122ea3981 Mon Sep 17 00:00:00 2001 From: HAKAMIQ <167469500+HAKAMIQ@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:52:09 +0300 Subject: [PATCH] fix(release): repair version and native release gates --- scripts/Run-FinalReleaseGate.ps1 | 72 +++++-- scripts/Run-OfficialReleaseGate.ps1 | 211 ++++++++++++++++++--- scripts/Run-ReleaseGate.ps1 | 52 ++++- src/CsoKit.Core/Formats/Cso/CsoVerifier.cs | 2 +- 4 files changed, 285 insertions(+), 52 deletions(-) diff --git a/scripts/Run-FinalReleaseGate.ps1 b/scripts/Run-FinalReleaseGate.ps1 index 797d422..923e036 100644 --- a/scripts/Run-FinalReleaseGate.ps1 +++ b/scripts/Run-FinalReleaseGate.ps1 @@ -44,32 +44,63 @@ function Invoke-PowerShellScriptStep { } } -function Assert-ProjectVersion { +function Assert-CentralVersionProperties { param( - [string]$ProjectPath, - [string]$ExpectedVersion, - [string]$Name + [string]$PropsPath, + [string]$VersionFilePath, + [string]$ExpectedVersion + ) + + if (-not (Test-Path -LiteralPath $PropsPath -PathType Leaf)) { + throw "Directory.Build.props was not found: $PropsPath" + } + + if (-not (Test-Path -LiteralPath $VersionFilePath -PathType Leaf)) { + throw "VERSION file was not found: $VersionFilePath" + } + + [xml]$propsXml = Get-Content -LiteralPath $PropsPath + + $csoKitVersionNode = $propsXml.SelectSingleNode( + "/Project/PropertyGroup/CsoKitVersion" ) - if (-not (Test-Path $ProjectPath)) { - throw "$Name project file was not found: $ProjectPath" + if ($null -eq $csoKitVersionNode) { + throw "CsoKitVersion was not found in Directory.Build.props." } - [xml]$projectXml = Get-Content $ProjectPath + $expectedVersionExpression = '$([System.IO.File]::ReadAllText(''$(MSBuildThisFileDirectory)VERSION'').Trim())' + $actualVersionExpression = $csoKitVersionNode.InnerText.Trim() + + if ($actualVersionExpression -ne $expectedVersionExpression) { + throw "CsoKitVersion does not read the central VERSION file." + } - $version = $projectXml.Project.PropertyGroup.Version | Select-Object -First 1 - $packageVersion = $projectXml.Project.PropertyGroup.PackageVersion | Select-Object -First 1 - $informationalVersion = $projectXml.Project.PropertyGroup.InformationalVersion | Select-Object -First 1 + foreach ($propertyName in @( + "Version", + "PackageVersion", + "InformationalVersion" + )) { + $node = $propsXml.SelectSingleNode( + "/Project/PropertyGroup/$propertyName" + ) - foreach ($value in @($version, $packageVersion, $informationalVersion)) { - if ([string]::IsNullOrWhiteSpace($value)) { - throw "$Name project has an empty version field." + if ($null -eq $node) { + throw "$propertyName was not found in Directory.Build.props." } - if ($value -ne $ExpectedVersion) { - throw "$Name project version mismatch. Expected $ExpectedVersion, found $value." + if ($node.InnerText.Trim() -ne '$(CsoKitVersion)') { + throw "$propertyName does not reference CsoKitVersion." } } + + $actualVersion = ( + Get-Content -LiteralPath $VersionFilePath -Raw + ).Trim() + + if ($actualVersion -ne $ExpectedVersion) { + throw "Central version mismatch. Expected $ExpectedVersion, found $actualVersion." + } } function Assert-GitClean { @@ -174,9 +205,7 @@ $PublishedSmokeScript = Join-Path $PSScriptRoot "Run-PublishedExeSmoke.ps1" $PublishReleaseScript = Join-Path $PSScriptRoot "Publish-Release.ps1" $VerifyReleaseScript = Join-Path $PSScriptRoot "Verify-Release.ps1" $PublishSourceScript = Join-Path $PSScriptRoot "Publish-SourcePackage.ps1" -$CliProject = Join-Path $RepoRoot "src\CsoKit.Cli\CsoKit.Cli.csproj" -$CoreProject = Join-Path $RepoRoot "src\CsoKit.Core\CsoKit.Core.csproj" -$AppProject = Join-Path $RepoRoot "src\CsoKit.App\CsoKit.App.csproj" +$DirectoryBuildProps = Join-Path $RepoRoot "Directory.Build.props" $PublishDir = Join-Path (Join-Path $RepoRoot "artifacts\publish") $Runtime $ReleaseZip = Join-Path (Join-Path $RepoRoot "artifacts\release") "csokit-$Version-$Runtime.zip" $SourceZip = Join-Path (Join-Path $RepoRoot "artifacts\source") "csokit-$Version-source.zip" @@ -209,9 +238,10 @@ if (-not $AllowDirty) { } Assert-NoTrackedArtifacts -RepoRoot $RepoRoot -Assert-ProjectVersion -ProjectPath $CliProject -ExpectedVersion $Version -Name "CLI" -Assert-ProjectVersion -ProjectPath $CoreProject -ExpectedVersion $Version -Name "Core" -Assert-ProjectVersion -ProjectPath $AppProject -ExpectedVersion $Version -Name "App" +Assert-CentralVersionProperties ` + -PropsPath $DirectoryBuildProps ` + -VersionFilePath $versionFile ` + -ExpectedVersion $Version if (-not $SkipRealIsoGates) { if ([string]::IsNullOrWhiteSpace($InputIso)) { diff --git a/scripts/Run-OfficialReleaseGate.ps1 b/scripts/Run-OfficialReleaseGate.ps1 index 7b79180..76a2644 100644 --- a/scripts/Run-OfficialReleaseGate.ps1 +++ b/scripts/Run-OfficialReleaseGate.ps1 @@ -69,61 +69,173 @@ function Get-RelativePathCompat { return $relativePath.Replace('\', '/') } -function Assert-ProjectVersion { +function Assert-CentralVersionProperties { param( - [string]$ProjectPath, - [string]$ExpectedVersion, - [string]$Name + [string]$PropsPath, + [string]$VersionFilePath, + [string]$ExpectedVersion + ) + + if (-not (Test-Path -LiteralPath $PropsPath -PathType Leaf)) { + throw "Directory.Build.props was not found: $PropsPath" + } + + if (-not (Test-Path -LiteralPath $VersionFilePath -PathType Leaf)) { + throw "VERSION file was not found: $VersionFilePath" + } + + [xml]$propsXml = Get-Content -LiteralPath $PropsPath + + $csoKitVersionNode = $propsXml.SelectSingleNode( + "/Project/PropertyGroup/CsoKitVersion" ) - if (-not (Test-Path -LiteralPath $ProjectPath)) { - throw "$Name project file was not found: $ProjectPath" + if ($null -eq $csoKitVersionNode) { + throw "CsoKitVersion was not found in Directory.Build.props." } - [xml]$projectXml = Get-Content -LiteralPath $ProjectPath - $propertyGroups = @($projectXml.Project.PropertyGroup) - $version = ($propertyGroups | ForEach-Object { $_.Version } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1) - $packageVersion = ($propertyGroups | ForEach-Object { $_.PackageVersion } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1) - $informationalVersion = ($propertyGroups | ForEach-Object { $_.InformationalVersion } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1) + $expectedVersionExpression = '$([System.IO.File]::ReadAllText(''$(MSBuildThisFileDirectory)VERSION'').Trim())' + $actualVersionExpression = $csoKitVersionNode.InnerText.Trim() - foreach ($field in @(@("Version", $version), @("PackageVersion", $packageVersion), @("InformationalVersion", $informationalVersion))) { - if ($field[1] -ne $ExpectedVersion) { - throw "$Name $($field[0]) mismatch. Expected $ExpectedVersion, found $($field[1])." + if ($actualVersionExpression -ne $expectedVersionExpression) { + throw "CsoKitVersion does not read the central VERSION file." + } + + foreach ($propertyName in @( + "Version", + "PackageVersion", + "InformationalVersion" + )) { + $node = $propsXml.SelectSingleNode( + "/Project/PropertyGroup/$propertyName" + ) + + if ($null -eq $node) { + throw "$propertyName was not found in Directory.Build.props." + } + + if ($node.InnerText.Trim() -ne '$(CsoKitVersion)') { + throw "$propertyName does not reference CsoKitVersion." } } + + $actualVersion = ( + Get-Content -LiteralPath $VersionFilePath -Raw + ).Trim() + + if ($actualVersion -ne $ExpectedVersion) { + throw "Central version mismatch. Expected $ExpectedVersion, found $actualVersion." + } } function Assert-NativeVersion { param( [string]$NativeSourcePath, [string]$CMakePath, + [string]$HeaderTemplatePath, + [string]$VersionFilePath, [string]$ExpectedVersion ) - $parts = $ExpectedVersion.Split('-')[0].Split('.') + foreach ($requiredPath in @( + $NativeSourcePath, + $CMakePath, + $HeaderTemplatePath, + $VersionFilePath + )) { + if (-not (Test-Path -LiteralPath $requiredPath -PathType Leaf)) { + throw "Native version input was not found: $requiredPath" + } + } + + if ($ExpectedVersion -notmatch '^[0-9]+[.][0-9]+[.][0-9]+$') { + throw "Expected a stable semantic version: $ExpectedVersion" + } + + $actualVersion = ( + Get-Content -LiteralPath $VersionFilePath -Raw + ).Trim() - if ($parts.Count -ne 3) { - throw "Expected a semantic version with three numeric components: $ExpectedVersion" + if ($actualVersion -ne $ExpectedVersion) { + throw "Native VERSION mismatch. Expected $ExpectedVersion, found $actualVersion." } $sourceText = Get-Content -LiteralPath $NativeSourcePath -Raw $cmakeText = Get-Content -LiteralPath $CMakePath -Raw + $templateText = Get-Content -LiteralPath $HeaderTemplatePath -Raw - if ($cmakeText -notmatch [regex]::Escape("VERSION $($parts[0]).$($parts[1]).$($parts[2])")) { - throw "Native CMake version does not match $ExpectedVersion." + foreach ($requiredPattern in @( + 'file\s*\(\s*READ\s+"\$\{CMAKE_CURRENT_SOURCE_DIR\}/[.][.]/[.][.]/VERSION"\s+CSOKIT_VERSION\s*\)', + 'string\s*\(\s*STRIP\s+"\$\{CSOKIT_VERSION\}"\s+CSOKIT_VERSION\s*\)', + 'project\s*\(\s*CsoKit[.]Native\s+VERSION\s+\$\{CSOKIT_VERSION\}\s+LANGUAGES\s+C\s+CXX\s*\)' + )) { + if ($cmakeText -notmatch $requiredPattern) { + throw "Native CMake version wiring is invalid: $requiredPattern" + } + } + + foreach ($requiredPattern in @( + '#define\s+CSOKIT_VERSION_MAJOR\s+@PROJECT_VERSION_MAJOR@u', + '#define\s+CSOKIT_VERSION_MINOR\s+@PROJECT_VERSION_MINOR@u', + '#define\s+CSOKIT_VERSION_PATCH\s+@PROJECT_VERSION_PATCH@u' + )) { + if ($templateText -notmatch $requiredPattern) { + throw "Native version template is invalid: $requiredPattern" + } } - foreach ($pair in @( - @("major", $parts[0]), - @("minor", $parts[1]), - @("patch", $parts[2]) + foreach ($requiredPattern in @( + 'version->major\s*=\s*CSOKIT_VERSION_MAJOR\s*;', + 'version->minor\s*=\s*CSOKIT_VERSION_MINOR\s*;', + 'version->patch\s*=\s*CSOKIT_VERSION_PATCH\s*;' )) { - if ($sourceText -notmatch "version->$($pair[0])\s*=\s*$($pair[1]);") { - throw "Native source version->$($pair[0]) does not match $ExpectedVersion." + if ($sourceText -notmatch $requiredPattern) { + throw "Native source version wiring is invalid: $requiredPattern" } } } +function Copy-NativeRuntime { + param( + [string]$SourcePath, + [string]$DestinationPath, + [string]$Context + ) + + if (-not (Test-Path -LiteralPath $SourcePath -PathType Leaf)) { + throw "Native DLL was not found for $Context`: $SourcePath" + } + + $destinationDirectory = Split-Path -Parent $DestinationPath + + New-Item ` + -ItemType Directory ` + -Force ` + -Path $destinationDirectory | + Out-Null + + Copy-Item ` + -LiteralPath $SourcePath ` + -Destination $DestinationPath ` + -Force + + if (-not (Test-Path -LiteralPath $DestinationPath -PathType Leaf)) { + throw "Native DLL was not staged for $Context`: $DestinationPath" + } + + $sourceHash = ( + Get-FileHash -LiteralPath $SourcePath -Algorithm SHA256 + ).Hash + + $destinationHash = ( + Get-FileHash -LiteralPath $DestinationPath -Algorithm SHA256 + ).Hash + + if ($sourceHash -ne $destinationHash) { + throw "Native DLL hash mismatch for $Context." + } +} + function Assert-GitClean { param([string]$RepoRoot) @@ -309,11 +421,16 @@ if ([string]::IsNullOrWhiteSpace($Version)) { throw "VERSION is empty." } $SolutionPath = Join-Path $RepoRoot "CsoKit.slnx" +$DirectoryBuildProps = Join-Path $RepoRoot "Directory.Build.props" $CoreProject = Join-Path $RepoRoot "src\CsoKit.Core\CsoKit.Core.csproj" $CliProject = Join-Path $RepoRoot "src\CsoKit.Cli\CsoKit.Cli.csproj" $AppProject = Join-Path $RepoRoot "src\CsoKit.App\CsoKit.App.csproj" $NativeSource = Join-Path $RepoRoot "native\CsoKit.Native\src\csokit_native.cpp" $NativeCMake = Join-Path $RepoRoot "native\CsoKit.Native\CMakeLists.txt" +$NativeVersionTemplate = Join-Path $RepoRoot "native\CsoKit.Native\include\csokit_version.h.in" +$NativeBuildScript = Join-Path $RepoRoot "scripts\Build-Native.ps1" +$NativeDllPath = Join-Path $RepoRoot "artifacts\native-build\win-x64\Release\CsoKit.Native.dll" +$TestNativeDllPath = Join-Path $RepoRoot "tests\CsoKit.Tests\bin\Release\net10.0\CsoKit.Native.dll" $ArtifactsDir = Join-Path $RepoRoot "artifacts" $PublishRoot = Join-Path $ArtifactsDir "publish" $ReleaseRoot = Join-Path $ArtifactsDir "release" @@ -331,10 +448,17 @@ Write-Host "NuGetAudit: $(if ($SkipNuGetAudit) { 'disabled by request' } else { Write-Host "Real ISO: $(if (Test-ShouldRunRealIsoGate) { $InputIso } else { 'not provided; optional real-corpus smoke skipped' })" Write-Host "" -Assert-ProjectVersion -ProjectPath $CoreProject -ExpectedVersion $Version -Name "Core" -Assert-ProjectVersion -ProjectPath $CliProject -ExpectedVersion $Version -Name "CLI" -Assert-ProjectVersion -ProjectPath $AppProject -ExpectedVersion $Version -Name "App" -Assert-NativeVersion -NativeSourcePath $NativeSource -CMakePath $NativeCMake -ExpectedVersion $Version +Assert-CentralVersionProperties ` + -PropsPath $DirectoryBuildProps ` + -VersionFilePath $versionFile ` + -ExpectedVersion $Version + +Assert-NativeVersion ` + -NativeSourcePath $NativeSource ` + -CMakePath $NativeCMake ` + -HeaderTemplatePath $NativeVersionTemplate ` + -VersionFilePath $versionFile ` + -ExpectedVersion $Version if (-not $AllowDirty) { Assert-GitClean -RepoRoot $RepoRoot @@ -358,10 +482,29 @@ Invoke-Step "dotnet restore" { Invoke-DotNet -StepName "dotnet restore" -Arguments (@("restore", $SolutionPath) + $restoreAuditArgs) } +Invoke-Step "build native backend" { + & $NativeBuildScript -Configuration Release -Platform x64 + + if ($LASTEXITCODE -ne 0) { + throw "Native backend build failed with exit code $LASTEXITCODE." + } + + if (-not (Test-Path -LiteralPath $NativeDllPath -PathType Leaf)) { + throw "Native DLL was not produced: $NativeDllPath" + } +} + Invoke-Step "dotnet build Release" { Invoke-DotNet -StepName "dotnet build Release" -Arguments (@("build", $SolutionPath, "-c", "Release", "--no-restore") + $restoreAuditArgs) } +Invoke-Step "stage native backend for tests" { + Copy-NativeRuntime ` + -SourcePath $NativeDllPath ` + -DestinationPath $TestNativeDllPath ` + -Context "official release tests" +} + Invoke-Step "dotnet test Release" { Invoke-DotNet -StepName "dotnet test Release" -Arguments @("test", $SolutionPath, "-c", "Release", "--no-build") } @@ -376,6 +519,11 @@ Invoke-Step "publish CLI" { "-o", $CliPublishDir ) + $restoreAuditArgs) + Copy-NativeRuntime ` + -SourcePath $NativeDllPath ` + -DestinationPath (Join-Path $CliPublishDir "CsoKit.Native.dll") ` + -Context "CLI publish" + Copy-ReleaseDocuments -Destination $CliPublishDir New-Sha256Manifest -Directory $CliPublishDir Test-BlockedArtifacts -RootPath $CliPublishDir -Context "CLI publish" @@ -391,6 +539,11 @@ Invoke-Step "publish App" { "-o", $AppPublishDir ) + $restoreAuditArgs) + Copy-NativeRuntime ` + -SourcePath $NativeDllPath ` + -DestinationPath (Join-Path $AppPublishDir "CsoKit.Native.dll") ` + -Context "App publish" + Copy-ReleaseDocuments -Destination $AppPublishDir New-Sha256Manifest -Directory $AppPublishDir Test-BlockedArtifacts -RootPath $AppPublishDir -Context "App publish" diff --git a/scripts/Run-ReleaseGate.ps1 b/scripts/Run-ReleaseGate.ps1 index 10cdc2f..b9ba91a 100644 --- a/scripts/Run-ReleaseGate.ps1 +++ b/scripts/Run-ReleaseGate.ps1 @@ -132,7 +132,7 @@ function Test-HelpSmoke { foreach ($required in @( "csokit info ", - "csokit verify ", + "csokit verify ", "csokit repair ", "csokit analyze ", "csokit detect ", @@ -201,12 +201,55 @@ function Invoke-ScriptFile { } } +function Copy-NativeRuntime { + param( + [string]$SourcePath, + [string]$DestinationPath, + [string]$Context + ) + + if (-not (Test-Path -LiteralPath $SourcePath -PathType Leaf)) { + throw "Native DLL was not found for $Context`: $SourcePath" + } + + $destinationDirectory = Split-Path -Parent $DestinationPath + + New-Item ` + -ItemType Directory ` + -Force ` + -Path $destinationDirectory | + Out-Null + + Copy-Item ` + -LiteralPath $SourcePath ` + -Destination $DestinationPath ` + -Force + + if (-not (Test-Path -LiteralPath $DestinationPath -PathType Leaf)) { + throw "Native DLL was not staged for $Context`: $DestinationPath" + } + + $sourceHash = ( + Get-FileHash -LiteralPath $SourcePath -Algorithm SHA256 + ).Hash + + $destinationHash = ( + Get-FileHash -LiteralPath $DestinationPath -Algorithm SHA256 + ).Hash + + if ($sourceHash -ne $destinationHash) { + throw "Native DLL hash mismatch for $Context." + } +} + $RepoRoot = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path $SolutionPath = Join-Path $RepoRoot "CsoKit.slnx" $CliProject = Join-Path $RepoRoot "src\CsoKit.Cli\CsoKit.Cli.csproj" $RoundtripGateScript = Join-Path $RepoRoot "scripts\Run-RoundtripGate.ps1" $ProfileMatrixScript = Join-Path $RepoRoot "scripts\Run-ProfileRoundtripMatrix.ps1" $NativeBuildScript = Join-Path $RepoRoot "scripts\Build-Native.ps1" +$NativeDllPath = Join-Path $RepoRoot "artifacts\native-build\win-x64\Release\CsoKit.Native.dll" +$NativeTestDllPath = Join-Path $RepoRoot "tests\CsoKit.Tests\bin\$Configuration\net10.0\CsoKit.Native.dll" if (-not (Test-Path -LiteralPath $SolutionPath)) { throw "Solution file was not found: $SolutionPath" @@ -254,6 +297,13 @@ Invoke-GateStep -Name "dotnet build" -Action { ) } +Invoke-GateStep -Name "stage native backend for tests" -Action { + Copy-NativeRuntime ` + -SourcePath $NativeDllPath ` + -DestinationPath $NativeTestDllPath ` + -Context "$Configuration tests" +} + Invoke-GateStep -Name "dotnet test" -Action { Invoke-DotNet -StepName "dotnet test" -Arguments @( "test", diff --git a/src/CsoKit.Core/Formats/Cso/CsoVerifier.cs b/src/CsoKit.Core/Formats/Cso/CsoVerifier.cs index 94df57c..0af131c 100644 --- a/src/CsoKit.Core/Formats/Cso/CsoVerifier.cs +++ b/src/CsoKit.Core/Formats/Cso/CsoVerifier.cs @@ -179,7 +179,7 @@ private static List ValidateIndex( issues.Add(new CsoVerificationIssue( header.IsCsoV2 ? "CsoV2FinalSentinelHighBit" : "FinalIndexEntryHasFlag", header.IsCsoV2 - ? "CSO v2 final sentinel index entry must not have the high-bit flag set." + ? "CSO2 final sentinel index entry must not have the high-bit flag set." : "Final CSO index entry must not carry the stored-block flag.", entries.Count - 1) {