param( [string]$Revision, [switch]$Force, [switch]$SkipBuild ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest function Invoke-CommandChecked { param( [Parameter(Mandatory = $true)] [string]$Executable, [Parameter(Mandatory = $true)] [string[]]$Arguments ) & $Executable @Arguments if ($LASTEXITCODE -ne 0) { throw "$Executable failed with exit code $LASTEXITCODE." } } function Get-PomProperty { param( [Parameter(Mandatory = $true)] [string]$PomText, [Parameter(Mandatory = $true)] [string]$Name ) $escapedName = [Regex]::Escape($Name) $match = [Regex]::Match($PomText, "<$escapedName>([^<]+)") if (-not $match.Success) { throw "Cannot find Maven property '$Name'." } return $match.Groups[1].Value.Trim() } function Sync-ProtoDirectory { param( [Parameter(Mandatory = $true)] [string]$Source, [Parameter(Mandatory = $true)] [string]$Destination ) if (-not (Test-Path -LiteralPath $Source -PathType Container)) { throw "Upstream proto directory does not exist: $Source" } New-Item -ItemType Directory -Path $Destination -Force | Out-Null $sourceFiles = @{} foreach ($sourceFile in Get-ChildItem -LiteralPath $Source -Filter '*.proto' -File -Recurse) { $relativePath = $sourceFile.FullName.Substring($Source.Length).TrimStart('\', '/') $sourceFiles[$relativePath] = $true $destinationFile = Join-Path $Destination $relativePath $destinationDirectory = Split-Path -Parent $destinationFile New-Item -ItemType Directory -Path $destinationDirectory -Force | Out-Null Copy-Item -LiteralPath $sourceFile.FullName -Destination $destinationFile -Force } foreach ($destinationFile in Get-ChildItem -LiteralPath $Destination -Filter '*.proto' -File -Recurse) { $relativePath = $destinationFile.FullName.Substring($Destination.Length).TrimStart('\', '/') if (-not $sourceFiles.ContainsKey($relativePath)) { Remove-Item -LiteralPath $destinationFile.FullName -Force } } } if (-not (Get-Command git -ErrorAction SilentlyContinue)) { throw 'Git was not found on PATH.' } $rootDirectory = Split-Path -Parent $PSScriptRoot $moduleRelativePath = 'cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-lib' $moduleDirectory = Join-Path $rootDirectory $moduleRelativePath $pomPath = Join-Path $moduleDirectory 'pom.xml' $protoRelativePath = "$moduleRelativePath/src/main/proto/cmvr" $protoDirectory = Join-Path $rootDirectory $protoRelativePath $checkoutDirectory = Join-Path $moduleDirectory 'target/cmvr-es' $pomText = [IO.File]::ReadAllText($pomPath) $repository = Get-PomProperty -PomText $pomText -Name 'cmvr.es.repository' $branch = Get-PomProperty -PomText $pomText -Name 'cmvr.es.proto.branch' $localProtoChanges = @(& git -C $rootDirectory status --porcelain -- $protoRelativePath) if ($LASTEXITCODE -ne 0) { throw 'Unable to inspect local proto changes.' } if ($localProtoChanges.Count -gt 0 -and -not $Force) { throw "Local proto files have uncommitted changes. Commit/stash them first, or rerun with -Force.`n$($localProtoChanges -join "`n")" } if ([string]::IsNullOrWhiteSpace($Revision)) { Write-Host "Looking up the latest '$branch' revision..." $remoteLine = @(& git ls-remote $repository "refs/heads/$branch") if ($LASTEXITCODE -ne 0) { throw 'Unable to read the upstream branch. Check the network and Git credentials.' } if ($remoteLine.Count -ne 1) { throw "Upstream branch '$branch' was not found or was ambiguous." } $Revision = ($remoteLine[0] -split '\s+')[0] } if ($Revision -notmatch '^[0-9a-fA-F]{40}$') { throw "Revision must be a full 40-character Git commit SHA: $Revision" } $Revision = $Revision.ToLowerInvariant() $checkoutParent = Split-Path -Parent $checkoutDirectory New-Item -ItemType Directory -Path $checkoutParent -Force | Out-Null if (Test-Path -LiteralPath (Join-Path $checkoutDirectory '.git') -PathType Container) { Write-Host 'Refreshing the existing upstream checkout...' Invoke-CommandChecked git @('-C', $checkoutDirectory, 'remote', 'set-url', 'origin', $repository) Invoke-CommandChecked git @('-C', $checkoutDirectory, 'fetch', '--filter=blob:none', 'origin', $branch) } else { if (Test-Path -LiteralPath $checkoutDirectory) { $expectedPath = [IO.Path]::GetFullPath($checkoutDirectory).TrimEnd('\') $actualPath = (Resolve-Path -LiteralPath $checkoutDirectory).Path.TrimEnd('\') if ($actualPath -ne $expectedPath -or (Split-Path -Parent $actualPath) -ne [IO.Path]::GetFullPath($checkoutParent).TrimEnd('\')) { throw "Refusing to replace unexpected checkout path: $actualPath" } Remove-Item -LiteralPath $checkoutDirectory -Recurse -Force } Write-Host 'Cloning the upstream proto repository...' Invoke-CommandChecked git @( 'clone', '--filter=blob:none', '--sparse', '--single-branch', '--branch', $branch, $repository, $checkoutDirectory ) } Invoke-CommandChecked git @( '-C', $checkoutDirectory, 'sparse-checkout', 'set', 'protos/cmvr/api', 'protos/cmvr/msgs', 'protos/cmvr/common' ) Invoke-CommandChecked git @('-C', $checkoutDirectory, 'checkout', '--detach', $Revision) $actualRevision = (& git -C $checkoutDirectory rev-parse HEAD).Trim() if ($LASTEXITCODE -ne 0 -or $actualRevision -ne $Revision) { throw "Expected revision $Revision, but checked out $actualRevision." } Write-Host "Syncing proto files from revision $Revision..." foreach ($group in @('api', 'msgs', 'common')) { Sync-ProtoDirectory ` -Source (Join-Path $checkoutDirectory "protos/cmvr/$group") ` -Destination (Join-Path $protoDirectory $group) } $revisionPattern = '()[^<]+()' $revisionRegex = [Regex]::new($revisionPattern) $updatedPomText = $revisionRegex.Replace($pomText, "`${1}$Revision`${2}", 1) if ($updatedPomText -eq $pomText -and (Get-PomProperty -PomText $pomText -Name 'cmvr.es.proto.revision') -ne $Revision) { throw 'Unable to update cmvr.es.proto.revision in pom.xml.' } if ($updatedPomText -ne $pomText) { [IO.File]::WriteAllText($pomPath, $updatedPomText, [Text.UTF8Encoding]::new($false)) } Write-Host '' Write-Host 'Updated files:' & git -C $rootDirectory status --short -- $protoRelativePath "$moduleRelativePath/pom.xml" if ($LASTEXITCODE -ne 0) { throw 'Unable to show the update summary.' } if (-not $SkipBuild) { if (-not (Get-Command mvn -ErrorAction SilentlyContinue)) { throw 'Maven was not found on PATH. Proto files were updated, but the verification build was not run.' } Write-Host '' Write-Host 'Running the gRPC verification build...' Push-Location $rootDirectory try { Invoke-CommandChecked mvn @( '-pl', 'cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client', '-am', 'clean', 'package', '-DskipTests' ) } finally { Pop-Location } } Write-Host '' Write-Host "CMVR-ES proto update completed: $Revision" Write-Host 'Review and commit the proto files and pom.xml together.'