Modules/businessdev.ALbuild.Apps/Public/Get-BcCallGraphBaseline.ps1
|
function Get-BcCallGraphBaseline { <# .SYNOPSIS Downloads the call graph of the last successful build of a branch, as the baseline to compare with. .DESCRIPTION Looks up the most recent completed build of the pipeline definition on the branch whose result is succeeded or partially succeeded, skipping the current build, and downloads its call graph artifact. Older builds are tried when the newest one has no such artifact (e.g. it ran before the call graph was switched on). Returns $null when there is no baseline, which is normal for a first build. In a pull request build pass the target branch: the change is then measured against what it will be merged into. .PARAMETER CollectionUri Organization URL, e.g. https://dev.azure.com/contoso/ (System.CollectionUri). .PARAMETER Project Team project name or id (System.TeamProject). .PARAMETER DefinitionId Pipeline definition id (System.DefinitionId). .PARAMETER Branch Branch to take the baseline from, e.g. refs/heads/master. .PARAMETER CurrentBuildId The running build, never used as its own baseline. .PARAMETER AccessToken System.AccessToken (Bearer) or a PAT (with -AuthScheme Basic). .PARAMETER AuthScheme Bearer (default, for System.AccessToken) or Basic (for a PAT). .PARAMETER ArtifactName Name of the build artifact holding the call graph. Default 'callgraph'. .PARAMETER Destination Folder to extract the baseline into. .PARAMETER MaxBuilds How many recent builds to try. Default 5. .EXAMPLE Get-BcCallGraphBaseline -CollectionUri $env:SYSTEM_COLLECTIONURI -Project $env:SYSTEM_TEAMPROJECT ` -DefinitionId $env:SYSTEM_DEFINITIONID -Branch 'refs/heads/master' -AccessToken $env:SYSTEM_ACCESSTOKEN ` -Destination "$env:AGENT_TEMPDIRECTORY/callgraph-baseline" .OUTPUTS PSCustomObject: Path (the folder with graph.jsonl), BuildId, BuildNumber, Branch, SourceVersion; or $null. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [string] $CollectionUri, [Parameter(Mandatory)] [string] $Project, [Parameter(Mandatory)] [int] $DefinitionId, [Parameter(Mandatory)] [string] $Branch, [int] $CurrentBuildId = 0, [Parameter(Mandatory)] [string] $AccessToken, [ValidateSet('Bearer', 'Basic')] [string] $AuthScheme = 'Bearer', [string] $ArtifactName = 'callgraph', [Parameter(Mandatory)] [string] $Destination, [ValidateRange(1, 50)] [int] $MaxBuilds = 5 ) if (-not $Branch.StartsWith('refs/')) { $Branch = "refs/heads/$Branch" } $base = "$($CollectionUri.TrimEnd('/'))/$([uri]::EscapeDataString($Project))/_apis/build" $headers = @{ Authorization = if ($AuthScheme -eq 'Basic') { 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$AccessToken")) } else { "Bearer $AccessToken" } } $query = "definitions=$DefinitionId&branchName=$([uri]::EscapeDataString($Branch))&statusFilter=completed&resultFilter=succeeded,partiallySucceeded&queryOrder=finishTimeDescending&`$top=$($MaxBuilds + 1)&api-version=7.1" try { $builds = @((Invoke-RestMethod -Uri "$base/builds?$query" -Headers $headers -Method Get -ErrorAction Stop).value) } catch { throw "Could not list the builds of definition $DefinitionId on '$Branch': $($_.Exception.Message)" } $tried = 0 foreach ($build in $builds) { if ($build.id -eq $CurrentBuildId) { continue } if (++$tried -gt $MaxBuilds) { break } $artifact = $null try { $artifact = Invoke-RestMethod -Uri "$base/builds/$($build.id)/artifacts?artifactName=$([uri]::EscapeDataString($ArtifactName))&api-version=7.1" -Headers $headers -Method Get -ErrorAction Stop } catch { Write-ALbuildLog -Level Verbose "Build $($build.buildNumber) has no '$ArtifactName' artifact ($($_.Exception.Message))."; continue } $resource = if ($artifact) { $artifact.PSObject.Properties['resource'] } else { $null } $url = if ($resource -and $resource.Value -and $resource.Value.PSObject.Properties['downloadUrl']) { $resource.Value.downloadUrl } else { $null } if (-not $url) { continue } if (Test-Path -LiteralPath $Destination) { Remove-Item -LiteralPath $Destination -Recurse -Force } New-Item -ItemType Directory -Force -Path $Destination | Out-Null $zip = Join-Path $Destination 'baseline.zip' Invoke-WebRequest -Uri $url -Headers $headers -OutFile $zip -UseBasicParsing -ErrorAction Stop Add-Type -AssemblyName System.IO.Compression.FileSystem [System.IO.Compression.ZipFile]::ExtractToDirectory($zip, $Destination) Remove-Item -LiteralPath $zip -Force $graph = Get-ChildItem -LiteralPath $Destination -Filter 'graph.jsonl' -File -Recurse | Select-Object -First 1 if (-not $graph) { Write-ALbuildLog -Level Warning "The '$ArtifactName' artifact of build $($build.buildNumber) has no graph.jsonl."; continue } Write-ALbuildLog "Call graph baseline: build $($build.buildNumber) ($($build.sourceBranch), $("$($build.sourceVersion)".Substring(0, [Math]::Min(8, "$($build.sourceVersion)".Length))))." return [PSCustomObject]@{ Path = $graph.DirectoryName; BuildId = $build.id; BuildNumber = $build.buildNumber Branch = $build.sourceBranch; SourceVersion = $build.sourceVersion } } Write-ALbuildLog "Call graph baseline: no successful build of definition $DefinitionId on '$Branch' has a '$ArtifactName' artifact yet." return $null } |