[CmdletBinding()] param( [switch]$Check ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" # Pinned protoc toolchain version used to generate the committed client descriptor set. # See docs/ToolchainLinks.md. Generating (committing) the canonical artifact must use this # exact version so the checked-in bytes are reproducible. The -Check comparison is made # tolerant of protoc-version encoding drift (chiefly source_code_info, see IPC-20) by # normalizing both the committed and the freshly built descriptor through the *same* protoc # with source info stripped, so it does not false-fail across protoc releases. $PinnedProtocVersion = "34.1" $repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") $protoRoot = Join-Path $repoRoot "src/ZB.MOM.WW.MxGateway.Contracts/Protos" $manifestPath = Join-Path $repoRoot "clients/proto/proto-inputs.json" $descriptorPath = Join-Path $repoRoot "clients/proto/descriptors/mxaccessgw-client-v1.protoset" $protoFiles = @("mxaccess_gateway.proto", "mxaccess_worker.proto", "galaxy_repository.proto") function Resolve-Protoc { # Prefer protoc on PATH (protoc on Linux/macOS, protoc.exe on Windows), then fall back to # the documented winget install location on Windows. foreach ($name in @("protoc", "protoc.exe")) { $pathCommand = Get-Command $name -ErrorAction SilentlyContinue if ($null -ne $pathCommand) { return $pathCommand.Source } } if ($env:LOCALAPPDATA) { $documentedPath = Join-Path $env:LOCALAPPDATA "Microsoft/WinGet/Packages/Google.Protobuf_Microsoft.Winget.Source_8wekyb3d8bbwe/bin/protoc.exe" if (Test-Path $documentedPath) { return $documentedPath } } throw "Could not find protoc on PATH. See docs/ToolchainLinks.md for the documented protobuf toolchain (protoc $PinnedProtocVersion)." } function Get-ProtocVersion { param([string]$Protoc) # `protoc --version` prints e.g. "libprotoc 34.1". $raw = (& $Protoc --version) | Select-Object -First 1 return ($raw -replace '^libprotoc\s+', '').Trim() } function Ensure-Directory { param([string]$Path) if (-not (Test-Path $Path)) { New-Item -ItemType Directory -Path $Path | Out-Null } } function New-SourceInfoFreeDescriptor { # Re-emits an existing descriptor set with source_code_info stripped, by round-tripping it # through protoc with --descriptor_set_in and *without* --include_source_info. Because both # the committed and freshly built descriptors are normalized through the same protoc binary, # any protoc-version-specific encoding cancels out and the byte comparison is stable. param( [string]$Protoc, [string]$InputDescriptor, [string]$OutputDescriptor ) & $Protoc ` "--descriptor_set_in=$InputDescriptor" ` "--include_imports" ` "--descriptor_set_out=$OutputDescriptor" ` @protoFiles if ($LASTEXITCODE -ne 0) { throw "protoc normalization (--descriptor_set_in) failed with exit code $LASTEXITCODE." } } function Test-FileBytesEqual { param( [string]$ExpectedPath, [string]$ActualPath ) if (-not (Test-Path $ExpectedPath)) { return $false } $expected = [System.IO.File]::ReadAllBytes($ExpectedPath) $actual = [System.IO.File]::ReadAllBytes($ActualPath) if ($expected.Length -ne $actual.Length) { return $false } for ($index = 0; $index -lt $expected.Length; $index++) { if ($expected[$index] -ne $actual[$index]) { return $false } } return $true } $manifest = Get-Content -Raw $manifestPath | ConvertFrom-Json Ensure-Directory (Split-Path $descriptorPath -Parent) foreach ($output in $manifest.generatedOutputs.PSObject.Properties.Value) { Ensure-Directory (Join-Path $repoRoot $output) } $protoc = Resolve-Protoc $protocVersion = Get-ProtocVersion $protoc if ($Check) { # Version drift no longer produces a false "stale" failure: the comparison is normalized # below. A mismatch is surfaced as a warning so it is visible without blocking the gate. if ($protocVersion -ne $PinnedProtocVersion) { Write-Warning "protoc version '$protocVersion' differs from the pinned '$PinnedProtocVersion'. The freshness comparison is normalized and tolerant of this, but regeneration must use the pinned version (see docs/ClientProtoGeneration.md)." } $freshDescriptor = Join-Path ([System.IO.Path]::GetTempPath()) ("mxaccessgw-client-v1-fresh-" + [System.Guid]::NewGuid().ToString("N") + ".protoset") $committedNormalized = Join-Path ([System.IO.Path]::GetTempPath()) ("mxaccessgw-client-v1-committed-" + [System.Guid]::NewGuid().ToString("N") + ".protoset") try { # Fresh descriptor from the current .proto sources, source info omitted (already normalized). & $protoc ` "--proto_path=$protoRoot" ` "--include_imports" ` "--descriptor_set_out=$freshDescriptor" ` @protoFiles if ($LASTEXITCODE -ne 0) { throw "protoc failed with exit code $LASTEXITCODE." } if (-not (Test-Path $descriptorPath)) { throw "Committed descriptor '$descriptorPath' does not exist. Run scripts/publish-client-proto-inputs.ps1 to create it." } # Normalize the committed descriptor (which carries source info) through the same protoc. New-SourceInfoFreeDescriptor -Protoc $protoc -InputDescriptor $descriptorPath -OutputDescriptor $committedNormalized if (-not (Test-FileBytesEqual -ExpectedPath $committedNormalized -ActualPath $freshDescriptor)) { throw "Client proto descriptor is stale. Run scripts/publish-client-proto-inputs.ps1 with the pinned protoc ($PinnedProtocVersion) and commit the updated descriptor." } Write-Host "Client proto descriptor is up to date." } finally { foreach ($temp in @($freshDescriptor, $committedNormalized)) { if (Test-Path $temp) { Remove-Item -LiteralPath $temp } } } } else { # Generating the canonical committed artifact must use the pinned protoc so the bytes are reproducible. if ($protocVersion -ne $PinnedProtocVersion) { throw "protoc version '$protocVersion' does not match the pinned toolchain version '$PinnedProtocVersion'. Install the pinned protoc (see docs/ToolchainLinks.md) before regenerating the committed descriptor, or run with -Check to only verify freshness." } & $protoc ` "--proto_path=$protoRoot" ` "--include_imports" ` "--include_source_info" ` "--descriptor_set_out=$descriptorPath" ` @protoFiles if ($LASTEXITCODE -ne 0) { throw "protoc failed with exit code $LASTEXITCODE." } Write-Host "Wrote client proto descriptor to $descriptorPath (protoc $protocVersion)." }