Skip to content

Commit

Permalink
Fix partial build (microsoft#837)
Browse files Browse the repository at this point in the history
_**Issue**_:
Currently, when building partially (e.g. PR Build), the baseline
artifacts are fetched from a CICD workflow run on the fly.
This could lead to discrepancies: one build job downloading artifacts
from one CICD run, while the next one: from another CICD run (which
completed in-between the two build jobs in the PR Build).

_**Solution**_: Determine the baseline CICD workflow once per PR Build
and use artifacts from it in all build jobs.

Test run partial build:
https://github.com/mazhelez/BCApps/actions/runs/7165434696?pr=2
Test run full build:
https://github.com/mazhelez/BCApps/actions/runs/7165432643?pr=5
TODO:
- [x] Tests
- [x] Propagate workflow changes to all templates
- [x] Smarter check to determine the baseline workflow ID only when
needed (non-full build)

---------

Co-authored-by: Alexander Holstrup <[email protected]>
Co-authored-by: Freddy Kristiansen <[email protected]>
  • Loading branch information
3 people authored Dec 11, 2023
1 parent 3b3aa14 commit e9c7f5e
Show file tree
Hide file tree
Showing 18 changed files with 524 additions and 278 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/powershell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
with:
path: .\
recurse: true
excludeRule: '"PSAvoidUsingInvokeExpression", "PSUseShouldProcessForStateChangingFunctions", "PSAvoidUsingWriteHost", "PSAvoidUsingCmdletAliases"'
excludeRule: '"PSAvoidUsingInvokeExpression", "PSUseShouldProcessForStateChangingFunctions", "PSAvoidUsingWriteHost", "PSAvoidUsingCmdletAliases", "PSUseSingularNouns"'
output: results.sarif

# Upload the SARIF file generated in the previous step
Expand Down
134 changes: 134 additions & 0 deletions Actions/DetermineBaselineWorkflowRun/DetermineBaselineWorkflowRun.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
Param(
[Parameter(Mandatory = $true)]
[string] $repository,
[Parameter(Mandatory = $true)]
[string] $branch,
[Parameter(Mandatory = $true)]
[string] $token
)

Import-Module (Join-Path $PSScriptRoot '..\Github-Helper.psm1' -Resolve)

<#
Checks if all build jobs in a workflow run completed successfully.
#>
function CheckBuildJobsInWorkflowRun {
Param(
[Parameter(Mandatory = $true)]
[string] $token,
[Parameter(Mandatory = $true)]
[string] $repository,
[Parameter(Mandatory = $true)]
[string] $workflowRunId
)

$headers = GetHeader -token $token
$per_page = 100
$page = 1

$allSuccessful = $true
$anySuccessful = $false

while($true) {
$jobsURI = "https://api.github.com/repos/$repository/actions/runs/$workflowRunId/jobs?per_page=$per_page&page=$page"
Write-Host "- $jobsURI"
$workflowJobs = InvokeWebRequest -Headers $headers -Uri $jobsURI | ConvertFrom-Json

if($workflowJobs.jobs.Count -eq 0) {
# No more jobs, breaking out of the loop
break
}

$buildJobs = @($workflowJobs.jobs | Where-Object { $_.name.StartsWith('Build ') })

if($buildJobs.conclusion -eq 'success') {
$anySuccessful = $true
}

if($buildJobs.conclusion -ne 'success') {
# If there is a build job that is not successful, there is not need to check further
$allSuccessful = $false
break
}

$page += 1
}

return ($allSuccessful -and $anySuccessful)
}

<#
Gets the last successful CICD run ID for the specified repository and branch.
Successful CICD runs are those that have a workflow run named ' CI/CD' and successfully built all the projects.
If no successful CICD run is found, 0 is returned.
#>
function FindLatestSuccessfulCICDRun {
Param(
[Parameter(Mandatory = $true)]
[string] $repository,
[Parameter(Mandatory = $true)]
[string] $branch,
[Parameter(Mandatory = $true)]
[string] $token
)

$headers = GetHeader -token $token
$lastSuccessfulCICDRun = 0
$per_page = 100
$page = 1

Write-Host "Finding latest successful CICD run for branch $branch in repository $repository"

# Get the latest CICD workflow run
while($true) {
$runsURI = "https://api.github.com/repos/$repository/actions/runs?per_page=$per_page&page=$page&exclude_pull_requests=true&status=completed&branch=$branch"
Write-Host "- $runsURI"
$workflowRuns = InvokeWebRequest -Headers $headers -Uri $runsURI | ConvertFrom-Json

if($workflowRuns.workflow_runs.Count -eq 0) {
# No more workflow runs, breaking out of the loop
break
}

$CICDRuns = @($workflowRuns.workflow_runs | Where-Object { $_.name -eq ' CI/CD' })

foreach($CICDRun in $CICDRuns) {
if($CICDRun.conclusion -eq 'success') {
# CICD run is successful
$lastSuccessfulCICDRun = $CICDRun.id
break
}

# CICD run is considered successful if all build jobs were successful
$areBuildJobsSuccessful = CheckBuildJobsInWorkflowRun -workflowRunId $($CICDRun.id) -token $token -repository $repository

if($areBuildJobsSuccessful) {
$lastSuccessfulCICDRun = $CICDRun.id
Write-Host "Found last successful CICD run: $($lastSuccessfulCICDRun), from $($CICDRun.created_at)"
break
}

Write-Host "CICD run $($CICDRun.id) is not successful. Skipping."
}

if($lastSuccessfulCICDRun -ne 0) {
break
}

$page += 1
}

if($lastSuccessfulCICDRun -ne 0) {
Write-Host "Last successful CICD run for branch $branch in repository $repository is $lastSuccessfulCICDRun"
} else {
Write-Host "No successful CICD run found for branch $branch in repository $repository"
}

return $lastSuccessfulCICDRun
}

$workflowRunID = FindLatestSuccessfulCICDRun -token $token -repository $repository -branch $branch

# Set output variables
Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "WorkflowRunID=$workflowRunID"
23 changes: 23 additions & 0 deletions Actions/DetermineBaselineWorkflowRun/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Determine Baseline Workflow Run
Finds the latest CICD workflow run that completed and built all the AL-Go projects successfully.
This workflow run is to be used as a baseline for all the build jobs in the current workflow run in case incremental build is required.

## INPUT

### ENV variables
none

### Parameters
| Name | Required | Description | Default value |
| :-- | :-: | :-- | :-- |
| shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell |

## OUTPUT

### ENV variables
none

### OUTPUT variables
| Name | Description |
| :-- | :-- |
| BaselineWorkflowRunId | The workflow run ID to use as a baseline. 0, if no baseline CICD was found.
33 changes: 33 additions & 0 deletions Actions/DetermineBaselineWorkflowRun/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Determine Baseline Workflow Run
author: Microsoft Corporation
inputs:
shell:
description: Shell in which you want to run the action (powershell or pwsh)
required: false
default: powershell
outputs:
BaselineWorkflowRunId:
description: The ID of the baseline workflow run
value: ${{ steps.determineBaselineWorkflowRun.outputs.WorkflowRunID }}
runs:
using: composite
steps:
- name: Determine Projects to Build
shell: ${{ inputs.shell }}
id: determineBaselineWorkflowRun
env:
_gitHubToken: ${{ github.token }}
_branch: ${{ github.base_ref }}
_repository: ${{ github.repository }}
run: |
$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0
try {
${{ github.action_path }}/DetermineBaselineWorkflowRun.ps1 -branch $env:_branch -repository $env:_repository -token $env:_gitHubToken
}
catch {
Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))";
exit 1
}
branding:
icon: terminal
color: blue
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
Param(
[Parameter(HelpMessage = "The folder to scan for projects to build", Mandatory = $true)]
[string] $baseFolder,
[Parameter(HelpMessage = "An array of changed files paths, used to filter the projects to build", Mandatory = $false)]
[string[]] $modifiedFiles = @(),
[Parameter(HelpMessage = "The maximum depth to build the dependency tree", Mandatory = $false)]
[int] $maxBuildDepth = 0,

[Parameter(HelpMessage = "The GitHub token to use to fetch the modified files", Mandatory = $true)]
[string] $token,
[Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)]
[string] $parentTelemetryScopeJson = '7b7d'
)
Expand All @@ -15,16 +14,28 @@ $telemetryScope = $null
try {
#region Action: Setup
. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve)

DownloadAndImportBcContainerHelper -baseFolder $baseFolder
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) -DisableNameChecking
Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "DetermineProjectsToBuild.psm1" -Resolve) -DisableNameChecking
#endregion

$telemetryScope = CreateScope -eventId 'DO0085' -parentTelemetryScopeJson $parentTelemetryScopeJson

#region Action: Determine projects to build
. (Join-Path -Path $PSScriptRoot -ChildPath "DetermineProjectsToBuild.ps1" -Resolve)
$allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder -modifiedFiles $modifiedFiles -maxBuildDepth $maxBuildDepth
Write-Host "::group::Get Modified Files"
$modifiedFiles = @(Get-ModifiedFiles -token $token)
Write-Host "$($modifiedFiles.Count) modified file(s): $($modifiedFiles -join ', ')"
Write-Host "::endgroup::"

Write-Host "::group::Determine Partial Build"
$buildAllProjects = Get-BuildAllProjects -modifiedFiles $modifiedFiles -baseFolder $baseFolder
Write-Host "::endgroup::"

Write-Host "::group::Get Projects To Build"
$allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder -buildAllProjects $buildAllProjects -modifiedFiles $modifiedFiles -maxBuildDepth $maxBuildDepth
AddTelemetryProperty -telemetryScope $telemetryScope -key "projects" -value "$($allProjects -join ', ')"
Write-Host "::endgroup::"
#endregion

#region Action: Output
Expand All @@ -36,10 +47,12 @@ try {
Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "ProjectsJson=$projectsJson"
Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "ProjectDependenciesJson=$projectDependenciesJson"
Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "BuildOrderJson=$buildOrderJson"
Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "BuildAllProjects=$([int] $buildAllProjects)"

Write-Host "ProjectsJson=$projectsJson"
Write-Host "ProjectDependenciesJson=$projectDependenciesJson"
Write-Host "BuildOrderJson=$buildOrderJson"
Write-Host "BuildAllProjects=$buildAllProjects"
#endregion

TrackTrace -telemetryScope $telemetryScope
Expand Down
Loading

0 comments on commit e9c7f5e

Please sign in to comment.