diff --git a/.azure-pipelines/PipelineSteps/BatchGeneration/FailedJobTeamsTemplate.html b/.azure-pipelines/PipelineSteps/BatchGeneration/FailedJobTeamsTemplate.html new file mode 100644 index 000000000000..52ff7a103c1e --- /dev/null +++ b/.azure-pipelines/PipelineSteps/BatchGeneration/FailedJobTeamsTemplate.html @@ -0,0 +1,26 @@ + + + + + + + + +

Pipeline Failure Alert 🚨

+

The pipeline **{{ pipelineName }}** has failed.

+

You can review the details at the following links:

+ +
+

Please check and address the issue as soon as possible.

+

Sincerely,

+

Your Azure CLI Tools Team

+ + + diff --git a/.azure-pipelines/PipelineSteps/BatchGeneration/analyse-modules.ps1 b/.azure-pipelines/PipelineSteps/BatchGeneration/analyse-modules.ps1 new file mode 100644 index 000000000000..16c4bf3b168f --- /dev/null +++ b/.azure-pipelines/PipelineSteps/BatchGeneration/analyse-modules.ps1 @@ -0,0 +1,92 @@ +param ( + [string]$MatrixKey, + [string]$RepoRoot +) + +$utilFilePath = Join-Path $RepoRoot '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'util.psm1' +Import-Module $utilFilePath -Force +$moduleGroup = Get-Targets -RepoRoot $RepoRoot -TargetsOutputFileName "analyzeTargets.json" -MatrixKey $MatrixKey +$RepoArtifacts = Join-Path $RepoRoot 'artifacts' +$StaticAnalysisOutputDirectory = Join-Path $RepoArtifacts 'StaticAnalysisResults' +if (-not (Test-Path -Path $StaticAnalysisOutputDirectory)) { + New-Item -ItemType Directory -Path $StaticAnalysisOutputDirectory +} +$toolsDirectory = Join-Path $RepoRoot 'tools' + +$results = @() +foreach ($moduleName in $moduleGroup) { + Write-Host "==============================================================" + Write-Host "Analysing Module: $moduleName" + + $startTime = Get-Date + $result = @{ + MatrixKey = $MatrixKey + Module = $moduleName + Status = "Success" + DurationSeconds = 0 + Error = "" + FailedTasks = @() + } + $Parameters = @{ + RepoArtifacts = $RepoArtifacts + StaticAnalysisOutputDirectory = $StaticAnalysisOutputDirectory + Configuration = "Debug" + TargetModule = @($moduleName) + } + $FailedTasks = @() + $ErrorLogPath = "$StaticAnalysisOutputDirectory/error.log" + + try { + .("$toolsDirectory/ExecuteCIStep.ps1") -StaticAnalysisBreakingChange @Parameters 2>$ErrorLogPath + If (($LASTEXITCODE -ne 0) -and ($LASTEXITCODE -ne $null)) + { + $FailedTasks += "BreakingChange" + } + .("$toolsDirectory/ExecuteCIStep.ps1") -StaticAnalysisDependency @Parameters 2>>$ErrorLogPath + If (($LASTEXITCODE -ne 0) -and ($LASTEXITCODE -ne $null)) + { + $FailedTasks += "Dependency" + } + .("$toolsDirectory/ExecuteCIStep.ps1") -StaticAnalysisSignature @Parameters 2>>$ErrorLogPath + If (($LASTEXITCODE -ne 0) -and ($LASTEXITCODE -ne $null)) + { + $FailedTasks += "Signature" + } + .("$toolsDirectory/ExecuteCIStep.ps1") -StaticAnalysisHelp @Parameters 2>>$ErrorLogPath + If (($LASTEXITCODE -ne 0) -and ($LASTEXITCODE -ne $null)) + { + $FailedTasks += "Help" + } + .("$toolsDirectory/ExecuteCIStep.ps1") -StaticAnalysisUX @Parameters 2>>$ErrorLogPath + If (($LASTEXITCODE -ne 0) -and ($LASTEXITCODE -ne $null)) + { + $FailedTasks += "UXMetadata" + } + .("$toolsDirectory/ExecuteCIStep.ps1") -StaticAnalysisCmdletDiff @Parameters 2>>$ErrorLogPath + If (($LASTEXITCODE -ne 0) -and ($LASTEXITCODE -ne $null)) + { + $FailedTasks += "CmdletDiff" + } + If ($FailedTasks.Length -ne 0) + { + Write-Host "There are failed tasks: $FailedTasks" + $ErrorLog = Get-Content -Path $ErrorLogPath | Join-String -Separator "`n" + Write-Error $ErrorLog + $result.Status = "Failed" + $result.Error = "Failed tasks: $($FailedTasks -join ', ')" + $result.FailedTasks = $FailedTasks + } + } catch { + Write-Warning "Failed to analyse module: $moduleName" + Write-Warning "Error message: $($_.Exception.Message)" + $result.Status = "Failed" + $result.Error = $_.Exception.Message + } finally { + $endTine = Get-Date + $result.DurationSeconds = ($endTine - $startTime).TotalSeconds + $results += $result + } +} + +$reportPath = Join-Path $RepoRoot "artifacts" "AnalyseReport-$MatrixKey.json" +$results | ConvertTo-Json -Depth 5 | Out-File -FilePath $reportPath -Encoding utf8 diff --git a/.azure-pipelines/PipelineSteps/BatchGeneration/batch-generate-modules.ps1 b/.azure-pipelines/PipelineSteps/BatchGeneration/batch-generate-modules.ps1 new file mode 100644 index 000000000000..96d51bef1aba --- /dev/null +++ b/.azure-pipelines/PipelineSteps/BatchGeneration/batch-generate-modules.ps1 @@ -0,0 +1,119 @@ +param ( + [string]$MatrixKey, + [string]$RepoRoot, + [string]$AutorestVersion +) + +$generationTargetsOutputFile = Join-Path $RepoRoot "artifacts" "generationTargets.json" +$generationTargets = Get-Content -Path $generationTargetsOutPutFile -Raw | ConvertFrom-Json +$moduleGroup = $generationTargets.$MatrixKey +Write-Host "##[group]Generating module group $MatrixKey" +foreach ($key in $moduleGroup.PSObject.Properties.Name | Sort-Object) { + $values = $moduleGroup.$key -join ', ' + Write-Output "$key : $values" +} +Write-Host "##[endgroup]" +Write-Host +$sortedModuleNames = $moduleGroup.PSObject.Properties.Name | Sort-Object + +$AutorestOutputDir = Join-Path $RepoRoot "artifacts" "autorest" +New-Item -ItemType Directory -Force -Path $AutorestOutputDir + +$sourceDirectory = Join-Path $RepoRoot "src" +$generatedDirectory = Join-Path $RepoRoot "generated" +$buildScriptsModulePath = Join-Path $RepoRoot 'tools' 'BuildScripts' 'BuildScripts.psm1' +Import-Module $buildScriptsModulePath -Force + +$results = @() + +foreach ($moduleName in $sortedModuleNames) { + Write-Host "==============================================================" + Write-Host "Regenerating Module: $moduleName" + $moduleStartTime = Get-Date + $moduleResult = @{ + Module = $moduleName + DurationSeconds = 0 + Status = "Success" + Changed = "No" + SubModules = @() + } + + $subModuleNames = $moduleGroup.$moduleName + foreach ($subModuleName in $subModuleNames) { + Write-Host "Regenerating SubModule: $subModuleName" + $subModuleStartTime = Get-Date + $subModuleResult = @{ + MatrixKey = $MatrixKey + SubModule = $subModuleName + Status = "Success" + DurationSeconds = 0 + Error = "" + } + + try { + $generateLog = Join-Path $AutorestOutputDir $moduleName "$subModuleName.log" + if (Test-Path $generateLog) { + Remove-Item -Path $generateLog -Recurse -Force + } + New-Item -ItemType File -Force -Path $generateLog + if (-not (Update-GeneratedSubModule -ModuleRootName $moduleName -SubModuleName $subModuleName -SourceDirectory $sourceDirectory -GeneratedDirectory $generatedDirectory -GenerateLog $generateLog -IsInvokedByPipeline $true)) { + Write-Warning "Failed to regenerate module: $moduleName, sub module: $subModuleName" + Write-Warning "log can be found at $generateLog" + $moduleResult.Status = "Failed" + $subModuleResult.Status = "Failed" + $subModuleResult.Error = "Update-GeneratedSubModule function returned false." + } + + } catch { + Write-Warning "Failed to regenerate module: $moduleName, sub module: $subModuleName" + Write-Warning "Error message: $($_.Exception.Message)" + $moduleResult.Status = "Failed" + $subModuleResult.Status = "Failed" + $subModuleResult.Error = $_.Exception.Message + } finally { + $subModuleEndTime = Get-Date + $subModuleResult.DurationSeconds = ($subModuleEndTime - $subModuleStartTime).TotalSeconds + $moduleResult.SubModules += $subModuleResult + } + } + + # If the module is changed in either src or generated folder, add a change log entry + Set-Location $RepoRoot + $srcFolderModuleRelativePath = ".\src\$moduleName" + $generatedFolderModuleRelativePath = ".\generated\$moduleName" + $diffSrc = git diff --name-only HEAD -- $srcFolderModuleRelativePath + $diffGenerated = git diff --name-only HEAD -- $generatedFolderModuleRelativePath + $diff = $diffSrc -or $diffGenerated + if ($diff) { + Write-Host "Changes detected in $moduleName, adding change log" + $moduleResult.Changed = "Yes" + $changeLogPath = Join-Path $RepoRoot "src" $moduleName $moduleName "AutorestUpgradeLog.md" + if (-not (Test-Path $changeLogPath)) { + New-Item -Path $changeLogPath -ItemType File -Force | Out-Null + Add-Content -Path $changeLogPath -Value "## Autorest upgrade log" + } + $changeLogContent = Get-Content -Path $changeLogPath + $date = Get-Date -Format "dd/MM/yy" + $newChangeLogEntry = "* Autorest version: $AutorestVersion - $date" + $changeLogContent.Insert(1, $newChangeLogEntry) + Set-Content $changeLogPath -Value $changeLogContent + $moduleResult.Changed = "Yes, Autorest Change Log Updated" + Write-Host "New change log entry added to $changeLogPath" + } + + $moduleEndTime = Get-Date + $moduleResult.DurationSeconds = ($moduleEndTime - $moduleStartTime).TotalSeconds + $results += $moduleResult +} + +$ArtifactOutputDir = Join-Path $RepoRoot "artifacts" +Set-Location $RepoRoot + +git add . +$patchPath = Join-Path $ArtifactOutputDir "changed-$MatrixKey.patch" +git diff --cached > $patchPath + +$reportPath = Join-Path $ArtifactOutputDir "GenerationReport-$MatrixKey.json" +$results | ConvertTo-Json -Depth 5 | Out-File -FilePath $reportPath -Encoding utf8 + +Write-Host "Build report written to $reportPath" diff --git a/.azure-pipelines/PipelineSteps/BatchGeneration/build-modules.ps1 b/.azure-pipelines/PipelineSteps/BatchGeneration/build-modules.ps1 new file mode 100644 index 000000000000..ca212b900b2e --- /dev/null +++ b/.azure-pipelines/PipelineSteps/BatchGeneration/build-modules.ps1 @@ -0,0 +1,40 @@ +param ( + [string]$MatrixKey, + [string]$RepoRoot +) + +$utilFilePath = Join-Path $RepoRoot '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'util.psm1' +Import-Module $utilFilePath -Force +$moduleGroup = Get-Targets -RepoRoot $RepoRoot -TargetsOutputFileName "buildTargets.json" -MatrixKey $MatrixKey +$buildModulesPath = Join-Path $RepoRoot 'tools' 'BuildScripts' 'BuildModules.ps1' + +$results = @() +foreach ($moduleName in $moduleGroup) { + Write-Host "==============================================================" + Write-Host "Building Module: $moduleName" + + $startTime = Get-Date + $result = @{ + MatrixKey = $MatrixKey + Module = $moduleName + Status = "Success" + DurationSeconds = 0 + Error = "" + } + + try { + & $buildModulesPath -TargetModule $moduleName -InvokedByPipeline + } catch { + Write-Warning "Failed to build module: $moduleName" + Write-Warning "Error message: $($_.Exception.Message)" + $result.Status = "Failed" + $result.Error = $_.Exception.Message + } finally { + $endTine = Get-Date + $result.DurationSeconds = ($endTine - $startTime).TotalSeconds + $results += $result + } +} + +$reportPath = Join-Path $RepoRoot "artifacts" "BuildReport-$MatrixKey.json" +$results | ConvertTo-Json -Depth 5 | Out-File -FilePath $reportPath -Encoding utf8 diff --git a/.azure-pipelines/PipelineSteps/BatchGeneration/create-branch.ps1 b/.azure-pipelines/PipelineSteps/BatchGeneration/create-branch.ps1 new file mode 100644 index 000000000000..6e08cd07ab2b --- /dev/null +++ b/.azure-pipelines/PipelineSteps/BatchGeneration/create-branch.ps1 @@ -0,0 +1,22 @@ +[CmdletBinding(DefaultParameterSetName="AllSet")] +param ( + [string]$Owner, + [string]$Repo, + [string]$BaseBranch, + [string]$NewBranch, + [string]$Token +) + +$headers = @{ Authorization = "Bearer $Token"; "User-Agent" = "ADO-Pipeline" } +$branchInfo = Invoke-RestMethod -Uri "https://api.github.com/repos/$Owner/$Repo/git/ref/heads/$BaseBranch" -Headers $headers +$sha = $branchInfo.object.sha + +$body = @{ + ref = "refs/heads/$NewBranch" + sha = $sha +} | ConvertTo-Json + +Invoke-RestMethod -Uri "https://api.github.com/repos/$Owner/$Repo/git/refs" ` + -Method Post -Headers $headers -Body $body -ContentType "application/json" + +Write-Host "Created branch '$NewBranch' from '$BaseBranch'" diff --git a/.azure-pipelines/PipelineSteps/BatchGeneration/filter.ps1 b/.azure-pipelines/PipelineSteps/BatchGeneration/filter.ps1 new file mode 100644 index 000000000000..ec3d85208ecf --- /dev/null +++ b/.azure-pipelines/PipelineSteps/BatchGeneration/filter.ps1 @@ -0,0 +1,79 @@ +[CmdletBinding(DefaultParameterSetName="AllSet")] +param ( + [int]$MaxParallelBuildJobs = 3, + [int]$MaxParallelAnalyzeJobs = 3, + [int]$MaxParallelTestWindowsJobs = 3, + [int]$MaxParallelTestLinuxJobs = 3, + [int]$MaxParallelTestMacJobs = 3, + [string[]]$ChangedFiles, + [string]$RepoRoot +) + +$utilFilePath = Join-Path $RepoRoot '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'util.psm1' +Import-Module $utilFilePath -Force +$artifactsDir = Join-Path $RepoRoot 'artifacts' + +$changedModulesDict = @{} +$changedSubModulesDict = @{} +if ($env:RUN_TEST_ON_ALL_MODULES -eq "True") { + Write-Host "Run test on all modules" + $V4ModulesFile = Join-Path $artifactsDir "generationTargets.json" + $V4ModuleMaps = Get-Content -Raw -Path $V4ModulesFile | ConvertFrom-Json + + foreach ($matrixKey in $V4ModuleMaps.PSObject.Properties.Name) { + $moduleMap = $V4ModuleMaps.$matrixKey + foreach ($moduleName in $moduleMap.PSObject.Properties.Name) { + foreach ($subModuleName in $moduleMap.$moduleName) { + $subModule = "$moduleName/$subModuleName" + $changedModulesDict[$moduleName] = $true + $changedSubModulesDict[$subModule] = $true + } + } + } +} +else { + Write-Host "Run test on generated folder changed modules" + # Only generated filder change should trigger the test + for ($i = 0; $i -lt $ChangedFiles.Count; $i++) { + if ($ChangedFiles[$i] -match '^generated/([^/]+)/([^/]+\.autorest)/') { + $moduleName = $Matches[2] + $subModuleName = $Matches[3] + $subModule = "$moduleName/$subModuleName" + + $changedModulesDict[$moduleName] = $true + $changedSubModulesDict[$subModule] = $true + } + } +} + +$changedModules = $changedModulesDict.Keys | Sort-Object +$changedSubModules = $changedSubModulesDict.Keys | Sort-Object + +Write-Host "##[group]Changed modules: $($changedModules.Count)" +foreach ($module in $changedModules) { + Write-Host $module +} +Write-Host "##[endgroup]" + Write-Host + +Write-Host "##[group]Changed sub modules: $($changedSubModules.Count)" +foreach ($subModule in $changedSubModules) { + Write-Host $subModule +} +Write-Host "##[endgroup]" +Write-Host + +$groupedBuildModules = Group-Modules -Modules $changedModules -MaxParallelJobs $MaxParallelBuildJobs +Write-Matrix -GroupedModules $groupedBuildModules -VariableName 'buildTargets' -RepoRoot $RepoRoot + +$groupedAnalyzeModules = Group-Modules -Modules $changedModules -MaxParallelJobs $MaxParallelAnalyzeJobs +Write-Matrix -GroupedModules $groupedAnalyzeModules -VariableName 'analyzeTargets' -RepoRoot $RepoRoot + +$groupedTestWindowsModules = Group-Modules -Modules $changedSubModules -MaxParallelJobs $MaxParallelTestWindowsJobs +Write-Matrix -GroupedModules $groupedTestWindowsModules -VariableName 'testWindowsTargets' -RepoRoot $RepoRoot + +$groupedTestLinuxModules = Group-Modules -Modules $changedSubModules -MaxParallelJobs $MaxParallelTestLinuxJobs +Write-Matrix -GroupedModules $groupedTestLinuxModules -VariableName 'testLinuxTargets' -RepoRoot $RepoRoot + +$groupedTestMacModules = Group-Modules -Modules $changedSubModules -MaxParallelJobs $MaxParallelTestMacJobs +Write-Matrix -GroupedModules $groupedTestMacModules -VariableName 'testMacOSTargets' -RepoRoot $RepoRoot diff --git a/.azure-pipelines/PipelineSteps/BatchGeneration/notify-failed-job.ps1 b/.azure-pipelines/PipelineSteps/BatchGeneration/notify-failed-job.ps1 new file mode 100644 index 000000000000..ff1ccc6f68ab --- /dev/null +++ b/.azure-pipelines/PipelineSteps/BatchGeneration/notify-failed-job.ps1 @@ -0,0 +1,28 @@ +param ( + [string]$RepoRoot +) + +$utilFilePath = Join-Path $RepoRoot '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'util.psm1' +Import-Module $utilFilePath -Force + +$batchGenSubscribers = @( + "bernardpan@microsoft.com", + "xidi@microsoft.com", + "yabhu@microsoft.com" +) +$receivers = $batchGenSubscribers -join "," + +$pipelineName = $env:BUILD_DEFINITIONNAME +$pipelineUrl = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$($env:SYSTEM_TEAMPROJECT)/_build?definitionId=$($env:SYSTEM_DEFINITIONID)" +$runUrl = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$($env:SYSTEM_TEAMPROJECT)/_build/results?buildId=$($env:BUILD_BUILDID)" + +$templateFilePath = Join-Path $RepoRoot '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'FailedJobTeamsTemplate.html' +$notificationTemplate = Get-Content -Path $templateFilePath -Raw +$notificationContent = $notificationTemplate -replace "{{ pipelineName }}", $pipelineName ` + -replace "{{ pipelineUrl }}", $pipelineUrl ` + -replace "{{ runUrl }}", $runUrl + +Send-Teams ` + -to $receivers ` + -title "Batch Generation Job Failed" ` + -content $notificationContent diff --git a/.azure-pipelines/PipelineSteps/BatchGeneration/prepare.ps1 b/.azure-pipelines/PipelineSteps/BatchGeneration/prepare.ps1 new file mode 100644 index 000000000000..a8520e0e9463 --- /dev/null +++ b/.azure-pipelines/PipelineSteps/BatchGeneration/prepare.ps1 @@ -0,0 +1,47 @@ +[CmdletBinding(DefaultParameterSetName="AllSet")] +param ( + [string]$RepoRoot, + [int]$MaxParallelJobs = 3 +) + +$utilFilePath = Join-Path $RepoRoot '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'util.psm1' +Import-Module $utilFilePath -Force + +$srcPath = Join-Path $RepoRoot 'src' +$moduleMap = Get-AutorestV4ModuleMap -srcPath $srcPath +Write-Host "Total matched modules: $($moduleMap.Count)" + +$modules = @($moduleMap.Keys | Sort-Object) +$groupedModules = Group-Modules -modules $modules -maxParallelJobs $MaxParallelJobs +Write-Host "Total module groups: $($groupedModules.Count)" + +$index = 0 +$generationTargets = @{} +foreach ($moduleGroup in $groupedModules) { + Write-Host "##[group]Prepareing module group $($index + 1) with $($moduleGroup.Count) modules" + $mergedModules = @{} + foreach ($moduleName in $moduleGroup) { + Write-Host "Module $($moduleName): $($moduleMap[$moduleName] -join ',')" + $mergedModules[$moduleName] = @($moduleMap[$moduleName]) + $subIndex++ + } + + $key = ($index + 1).ToString() + "-" + $moduleGroup.Count + $generationTargets[$key] = $mergedModules + $MatrixStr = "$MatrixStr,'$key':{'MatrixKey':'$key'}" + Write-Host "##[endgroup]" + Write-Host + $index++ +} + +$generationTargetsOutputDir = Join-Path $RepoRoot "artifacts" +if (-not (Test-Path -Path $generationTargetsOutputDir)) { + New-Item -ItemType Directory -Path $generationTargetsOutputDir +} +$generationTargetsOutputFile = Join-Path $generationTargetsOutputDir "generationTargets.json" +$generationTargets | ConvertTo-Json -Depth 5 | Out-File -FilePath $generationTargetsOutputFile -Encoding utf8 + +if ($MatrixStr -and $MatrixStr.Length -gt 1) { + $MatrixStr = $MatrixStr.Substring(1) +} +Write-Host "##vso[task.setVariable variable=generationTargets;isOutput=true]{$MatrixStr}" diff --git a/.azure-pipelines/PipelineSteps/BatchGeneration/report.ps1 b/.azure-pipelines/PipelineSteps/BatchGeneration/report.ps1 new file mode 100644 index 000000000000..007ce3aa6a71 --- /dev/null +++ b/.azure-pipelines/PipelineSteps/BatchGeneration/report.ps1 @@ -0,0 +1,89 @@ +param ( + [string]$RepoRoot, + [string]$PipelineWorkspace +) + +# Check number of generation targets +$generationTargetsFile = Join-Path $RepoRoot "artifacts" "generationTargets.json" +$generationTargets = Get-Content -Raw -Path $generationTargetsFile | ConvertFrom-Json +$totalGenerationModules = 0 +foreach ($outerKey in $generationTargets.PSObject.Properties.Name) { + $innerKeys = $generationTargets.$outerKey.PSObject.Properties.Name.Count + $totalGenerationModules += $innerKeys +} +Write-Output "Total regeneration target modules: $totalGenerationModules" + +# Check number of other targets +$targetPatterns = @{ + Analyse = "analyzeTargets.json" + Build = "buildTargets.json" + Test = "testWindowsTargets.json" +} +foreach ($pattern in $targetPatterns.GetEnumerator()) { + $targetFilePath = Join-Path $RepoRoot "artifacts" $pattern.Value + $targetJson = Get-Content -Raw -Path $targetFilePath | ConvertFrom-Json + $total = ($targetJson.PSObject.Properties.Value | ForEach-Object { + $_.Count + }) | Measure-Object -Sum | Select-Object -ExpandProperty Sum + + Write-Output "Total $($pattern.Key) targets : $total" +} + +# Check all steps reports +$reportFilePattern = @{ + Generation = "GenerationReport-*.json" + Build = "BuildReport-*.json" + Analyze = "AnalyseReport-*.json" + TestWindows = "TestWindowsReport-*.json" + TestLinux = "TestLinuxReport-*.json" + TestMacOS = "TestMacOSReport-*.json" +} + +$artifactRoot = Join-Path $RepoRoot 'artifacts' +if (-not (Test-Path -Path $artifactRoot)) { + New-Item -Path $artifactRoot -ItemType Directory +} +$failedReports = @{} + +foreach ($pattern in $reportFilePattern.GetEnumerator()) { + Write-Host "Filtering $($pattern.Key) Report with pattern: $($pattern.Value)" + $allResults = @() + + foreach ($dir in Get-ChildItem -Path $PipelineWorkspace -Directory) { + $file = Get-ChildItem -Path $dir.FullName -Filter $pattern.Value -File | Select-Object -First 1 + if ($file) { + Write-Host "Found file: $($file.FullName)" + $jsonContent = Get-Content $file.FullName -Raw | ConvertFrom-Json + + if ($jsonContent -isnot [System.Collections.IEnumerable] -or $jsonContent -is [string]) { + $jsonContent = @($jsonContent) + } + + $allResults += $jsonContent + } + } + + Write-Host "$($pattern.Key): $($allResults.Count) result(s) found." + $reportPath = Join-Path $artifactRoot "$($pattern.Key)Report.json" + $allResults | ConvertTo-Json -Depth 10 | Set-Content -Path $reportPath -Encoding UTF8 + Write-Host "Written report to $reportPath" + + $failed = $allResults | Where-Object { $_.Status -ne "Success" } + if ($failed.Count -gt 0) { + $failedReports[$pattern.Key] = $failed + } +} + +if ($failedReports.Count -eq 0) { + Write-Host "`nāœ… Exist Successfully: All reports passed." + exit 0 +} else { + Write-Host "`nāŒ Exist with Errors: Some reports failed." + foreach ($key in $failedReports.Keys) { + Write-Host "##[group]Failed entries in $key" + $failedReports[$key] | ConvertTo-Json -Depth 10 | Write-Host + Write-Host "##[endgroup]" + Write-Host + } + exit 1 +} diff --git a/.azure-pipelines/PipelineSteps/BatchGeneration/test-modules.ps1 b/.azure-pipelines/PipelineSteps/BatchGeneration/test-modules.ps1 new file mode 100644 index 000000000000..d90f1bc8b9b6 --- /dev/null +++ b/.azure-pipelines/PipelineSteps/BatchGeneration/test-modules.ps1 @@ -0,0 +1,64 @@ +param ( + [string]$MatrixKey, + [string]$TestEnvName, + [string]$RepoRoot +) + +# Install Az.Accounts module and Pester module +Install-Module -Name Pester -Repository PSGallery -RequiredVersion 4.10.1 -Force -SkipPublisherCheck +Install-Module -Name Az.Accounts -AllowClobber -Force -Repository PSGallery + +$utilFilePath = Join-Path $RepoRoot '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'util.psm1' +Import-Module $utilFilePath -Force +$subModuleGroup = Get-Targets -RepoRoot $RepoRoot -TargetsOutputFileName "test$($TestEnvName)Targets.json" -MatrixKey $MatrixKey + +$results = @() + +foreach ($subModule in $subModuleGroup) { + $startTime = Get-Date + $moduleName, $subModuleName = $subModule -split '/' + $result = @{ + OSName = $TestEnvName + MatrixKey = $MatrixKey + Module = $moduleName + SubModule = $subModuleName + Status = "Success" + DurationSeconds = 0 + Error = "" + } + + try { + Write-Host "Testing sub module: $subModule" + $subModulePath = Join-Path $RepoRoot 'artifacts' 'Debug' "Az.$ModuleName" $subModuleName + Set-Location $subModulePath + # remove the integrated Az Accounts so that the installed latest one could be used for test + $integratedAzAccounts = Join-Path $subModulePath 'generated' 'modules' 'Az.Accounts' + If (Test-Path $integratedAzAccounts){ + Write-Host "Removing integrated Az.Accounts module from $integratedAzAccounts" + Remove-Item -Path $integratedAzAccounts -Recurse -Force + } + + & ".\test-module.ps1" + + if ($LASTEXITCODE -ne 0) { + Write-Warning "āŒ Test failed: $subModule (exit code $LASTEXITCODE)" + $result.Status = "Failed" + $result.Error = "Test failed with exit code $LASTEXITCODE" + } + } + catch { + Write-Warning "Failed to test module: $module" + Write-Warning "Error message: $($_.Exception.Message)" + $result.Status = "Failed" + $result.Error = $_.Exception.Message + } + finally { + $endTime = Get-Date + $result.DurationSeconds = ($endTime - $startTime).TotalSeconds + $results += $result + } +} + +$artifactRoot = Join-Path $RepoRoot 'artifacts' +$reportPath = Join-Path $artifactRoot "Test$($TestEnvName)Report-$MatrixKey.json" +$results | ConvertTo-Json -Depth 3 | Out-File -FilePath $reportPath -Encoding utf8 diff --git a/.azure-pipelines/PipelineSteps/BatchGeneration/util.psm1 b/.azure-pipelines/PipelineSteps/BatchGeneration/util.psm1 new file mode 100644 index 000000000000..cc39536925ba --- /dev/null +++ b/.azure-pipelines/PipelineSteps/BatchGeneration/util.psm1 @@ -0,0 +1,150 @@ +function Get-AutorestV4ModuleMap { + param ( + [string]$srcPath + ) + + $result = @{} + + Get-ChildItem -Path $srcPath -Directory | ForEach-Object { + $module = $_ + + Get-ChildItem -Path $module.FullName -Directory | Where-Object { + $_.Name -like '*.autorest' + } | ForEach-Object { + $subModule = $_ + + $readmePath = Join-Path $subModule.FullName 'README.md' + + if (Test-Path $readmePath) { + $readmeContent = Get-Content -Path $readmePath -Raw + + if ($readmeContent -notmatch 'use-extension:\s+"@autorest/powershell":\s+"3.x"') { + if ($result.ContainsKey($module.Name)) { + $result[$module.Name] += $subModule.Name + } else { + $result[$module.Name] = @($subModule.Name) + } + } + } + } + } + + return $result +} + +function Group-Modules { + param ( + [array]$Modules, + [int]$MaxParallelJobs + ) + + $count = $Modules.Count + $n = [Math]::Min($count, $MaxParallelJobs) + + if ($n -eq 0) { + return @() + } + + $result = @() + + for ($i = 0; $i -lt $n; $i++) { + $result += ,@() + } + + for ($i = 0; $i -lt $count; $i++) { + $groupIndex = $i % $n + $result[$groupIndex] += $Modules[$i] + } + + return ,$result +} + +function Write-Matrix { + param ( + [array]$GroupedModules, + [string]$VariableName, + [string]$RepoRoot + ) + + Write-Host "$VariableName module groups: $($GroupedModules.Count)" + $GroupedModules | ForEach-Object { $_ -join ', ' } | ForEach-Object { Write-Host $_ } + + $targets = @{} + $MatrixStr = "" + $index = 0 + foreach ($modules in $GroupedModules) { + $key = ($index + 1).ToString() + "-" + $modules.Count + $MatrixStr = "$MatrixStr,'$key':{'MatrixKey':'$key'}" + $targets[$key] = $modules + $index++ + } + + if ($MatrixStr -and $MatrixStr.Length -gt 1) { + $MatrixStr = $MatrixStr.Substring(1) + } + Write-Host "##vso[task.setVariable variable=$VariableName;isOutput=true]{$MatrixStr}" + Write-Host "variable=$VariableName; value=$MatrixStr" + + $targetsOutputDir = Join-Path $RepoRoot "artifacts" + if (-not (Test-Path -Path $targetsOutputDir)) { + New-Item -ItemType Directory -Path $targetsOutputDir -Force | Out-Null + } + $targetsOutputFile = Join-Path $targetsOutputDir "$VariableName.json" + $targets | ConvertTo-Json -Depth 5 | Out-File -FilePath $targetsOutputFile -Encoding utf8 +} + +function Get-Targets { + param ( + [string]$RepoRoot, + [string]$TargetsOutputFileName, + [string]$MatrixKey + ) + + $targetsOutputFile = Join-Path $RepoRoot "artifacts" $TargetsOutputFileName + $targetGroups = Get-Content -Path $targetsOutputFile -Raw | ConvertFrom-Json + $targetGroup = $targetGroups.$MatrixKey + Write-Host "##[group]Target group: $MatrixKey" + $targetGroup | ForEach-Object { Write-Host $_ } + Write-Host "##[endgroup]" + Write-Host + return $targetGroup +} + +function Send-Teams { + param ( + [string]$to, + [string]$title, + [string]$content + ) + + $teamsUrl = $env:TEAMS_URL + if ([string]::IsNullOrEmpty($teamsUrl)) { + Write-Host "TEAMS_URL environment variable is not set." -ForegroundColor Red + exit 1 + } + + if ([string]::IsNullOrEmpty($to)) { + Write-Host "'to' parameter is empty, nothing to send." -ForegroundColor Yellow + return 0 + } + + $body = @{ + to = $to + title = $title + content = $content + } | ConvertTo-Json -Depth 3 + + try { + Invoke-RestMethod -Uri $teamsUrl -Method Post -Headers @{ + 'Accept' = 'application/json' + 'Content-Type' = 'application/json' + } -Body $body + + Write-Host "Message sent successfully." + return 0 + } + catch { + Write-Host "Failed to send message: $_" -ForegroundColor Red + exit 1 + } +} diff --git a/.azure-pipelines/batch-generation.yml b/.azure-pipelines/batch-generation.yml new file mode 100644 index 000000000000..e9a335f55848 --- /dev/null +++ b/.azure-pipelines/batch-generation.yml @@ -0,0 +1,492 @@ +parameters: +- name: AutorestVersion + displayName: 'Version of Autorest that trigger batch generation' + type: string + default: 'Default version' + +variables: + IntermediateStepTimeoutInMinutes: 30 + GenerateTimeoutInMinutes: 120 + BuildTimeoutInMinutes: 120 + AnalysisTimeoutInMinutes: 120 + TestTimeoutInMinutes: 180 + MaxParallelGenerateJobs: 15 + MaxParallelBuildJobs: 15 + MaxParallelAnalyzeJobs: 5 + MaxParallelTestWindowsJobs: 5 + MaxParallelTestLinuxJobs: 5 + MaxParallelTestMacJobs: 5 + WindowsAgentPoolName: pool-windows-2019 + LinuxAgentPoolName: pool-ubuntu-2004 + MacOSAgentPoolName: 'Azure Pipelines' + MacOSAgentPoolVMImage: macOS-latest + +trigger: none + +stages: + - stage: Generate + jobs: + - job: prepare + displayName: Generate Build Targets Matrix + timeoutInMinutes: ${{ variables.IntermediateStepTimeoutInMinutes }} + pool: ${{ variables.WindowsAgentPoolName }} + steps: + + # TODO: (Bernard) Uncomment the no checkout step after automatically install repo into agnets + # - checkout: none + - checkout: self + fetchDepth: 1 + fetchTags: false + + - template: util/get-github-pat-steps.yml + + - task: PowerShell@2 + name: checkout + displayName: 'Create Batch Generation Branch' + inputs: + targetType: inline + pwsh: true + script: | + $date = Get-Date -Format "dd/mm/yy" + $generationBranch = "batch-generation/branch-$date-$(Build.BuildId)" + $token = "$(GitHubToken)" + + $createBranchPath = Join-Path "$(Build.SourcesDirectory)" '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'create-branch.ps1' + & $createBranchPath -Owner 'Azure' -Repo 'azure-powershell' -BaseBranch 'main' -NewBranch $generationBranch -Token $token + Write-Host "##vso[task.setvariable variable=GenerationBranch;isOutput=true]$generationBranch" + + - task: PowerShell@2 + name: mtrx + displayName: 'Generate Build Targets Matrix' + inputs: + targetType: inline + pwsh: true + script: | + $prepareModulesPath = Join-Path "$(Build.SourcesDirectory)" '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'prepare.ps1' + & $prepareModulesPath -RepoRoot "$(Build.SourcesDirectory)" -MaxParallelJobs "${{ variables.MaxParallelGenerateJobs }}" + + - task: PublishPipelineArtifact@1 + displayName: 'Upload generated targets' + inputs: + targetPath: artifacts + artifact: 'prepare' + + - job: generate + displayName: "Batch Generate: " + dependsOn: prepare + condition: and( + ne(dependencies.prepare.outputs['mtrx.generationTargets'], ''), + ne(dependencies.prepare.outputs['mtrx.generationTargets'], '{}') + ) + variables: + GenerationBranch: $[dependencies.prepare.outputs['checkout.GenerationBranch']] + timeoutInMinutes: ${{ variables.GenerateTimeoutInMinutes }} + pool: ${{ variables.WindowsAgentPoolName }} + strategy: + matrix: $[ dependencies.prepare.outputs['mtrx.generationTargets'] ] + maxParallel: ${{ variables.MaxParallelGenerateJobs }} + + steps: + - checkout: self + persistCredentials: true + fetchTags: false + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: 'prepare' + targetPath: artifacts + + - task: PowerShell@2 + name: generate + displayName: 'Regenerate Modules' + inputs: + targetType: inline + pwsh: true + script: | + git fetch origin "$(GenerationBranch)" + git checkout "$(GenerationBranch)" + + $batchGenerateModulesPath = Join-Path "$(Build.SourcesDirectory)" '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'batch-generate-modules.ps1' + & $batchGenerateModulesPath -MatrixKey "$(MatrixKey)" -RepoRoot "$(Build.SourcesDirectory)" -AutorestVersion "${{ parameters.AutorestVersion }}" + workingDirectory: $(Build.SourcesDirectory) + + - task: PublishPipelineArtifact@1 + displayName: 'Save patch and generation report' + inputs: + targetPath: artifacts + artifact: 'generate-$(MatrixKey)' + condition: always() + + - stage: Build + dependsOn: Generate + condition: always() + variables: + GenerationBranch: $[stageDependencies.Generate.prepare.outputs['checkout.GenerationBranch']] + jobs: + - job: filter + displayName: 'Filter Changed Modules' + timeoutInMinutes: ${{ variables.IntermediateStepTimeoutInMinutes }} + pool: ${{ variables.WindowsAgentPoolName }} + steps: + - checkout: self + persistCredentials: true + fetchTags: false + + - task: PowerShell@2 + name: checkout + displayName: 'Checkout Generation Branch' + inputs: + targetType: inline + pwsh: true + script: | + git fetch origin "$(GenerationBranch)" + git checkout "$(GenerationBranch)" + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: 'prepare' + targetPath: artifacts + + - download: current + patterns: '**/changed-*.patch' + displayName: 'Download all .patch artifacts' + + - task: PowerShell@2 + name: apply + displayName: 'Apply all patches' + inputs: + targetType: inline + pwsh: true + script: | + git config user.email "65331932+azure-powershell-bot@users.noreply.github.com" + git config user.name "azure-powershell-bot" + + Write-Host "Applying all .patch files to "$(GenerationBranch)"..." + $patchFiles = Get-ChildItem -Path "$env:PIPELINE_WORKSPACE" -Recurse -Filter *.patch + + foreach ($patch in $patchFiles) { + Write-Host "Applying patch: $($patch.FullName)" + git apply --whitespace=fix "$($patch.FullName)" + } + + git add . + git commit -m "Autorest Upgrade - ${{ parameters.AutorestVersion }}" + git push origin "$(GenerationBranch)" + + - task: PowerShell@2 + name: mtrx + displayName: 'Generate Test Targets Matrix' + inputs: + targetType: inline + pwsh: true + script: | + $base = git merge-base HEAD origin/main + $changedFiles = git diff --name-only $base HEAD + + $sourceBranchName = "$(Build.SourceBranch)".Replace('refs/heads/', '') + git checkout $sourceBranchName + + $filterModulesPath = Join-Path "$(Build.SourcesDirectory)" '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'filter.ps1' + & $filterModulesPath ` + -MaxParallelBuildJobs "${{ variables.MaxParallelBuildJobs }}" ` + -MaxParallelAnalyzeJobs "${{ variables.MaxParallelAnalyzeJobs }}" ` + -MaxParallelTestWindowsJobs "${{ variables.MaxParallelTestWindowsJobs }}" ` + -MaxParallelTestLinuxJobs "${{ variables.MaxParallelTestLinuxJobs }}" ` + -MaxParallelTestMacJobs "${{ variables.MaxParallelTestMacJobs }}" ` + -ChangedFiles $changedFiles ` + -RepoRoot "$(Build.SourcesDirectory)" + env: + RUN_TEST_ON_ALL_MODULES: $(RUN_TEST_ON_ALL_MODULES) + + - task: PublishPipelineArtifact@1 + displayName: 'Upload filtered targets' + inputs: + targetPath: artifacts + artifact: 'filter' + + - job: build + displayName: "Build:" + dependsOn: filter + condition: and( + ne(dependencies.filter.outputs['mtrx.buildTargets'], ''), + ne(dependencies.filter.outputs['mtrx.buildTargets'], '{}') + ) + timeoutInMinutes: ${{ variables.BuildTimeoutInMinutes }} + pool: ${{ variables.WindowsAgentPoolName }} + strategy: + matrix: $[ dependencies.filter.outputs['mtrx.buildTargets'] ] + maxParallel: ${{ variables.MaxParallelBuildJobs }} + + steps: + - checkout: self + fetchDepth: 1 + fetchTags: false + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: 'filter' + targetPath: artifacts + + - task: PowerShell@2 + name: build + displayName: 'Build Targets' + inputs: + targetType: inline + pwsh: true + script: | + git fetch origin "$(GenerationBranch)" + git checkout "$(GenerationBranch)" + + $buildModulesPath = Join-Path "$(Build.SourcesDirectory)" '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'build-modules.ps1' + & $buildModulesPath -MatrixKey "$(MatrixKey)" -RepoRoot "$(Build.SourcesDirectory)" + + workingDirectory: $(Build.SourcesDirectory) + + - task: PublishPipelineArtifact@1 + displayName: 'Save build artifacts' + inputs: + targetPath: artifacts + artifact: 'build-$(MatrixKey)' + condition: always() + + - stage: Test + dependsOn: Build + condition: always() + jobs: + - job: collect + displayName: "Collect Artifacts" + timeoutInMinutes: ${{ variables.IntermediateStepTimeoutInMinutes }} + pool: ${{ variables.WindowsAgentPoolName }} + + steps: + - checkout: none + + - download: current + patterns: | + **/Debug/** + **/StaticAnalysis/** + displayName: 'Download build artifacts' + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: 'filter' + targetPath: artifacts + + - task: PowerShell@2 + name: collect + displayName: 'Collect modules artifacts' + inputs: + targetType: inline + pwsh: true + script: | + + $workspace = "$env:PIPELINE_WORKSPACE" + $repoRoot = "$(Build.SourcesDirectory)" + $artifactsRoot = Join-Path $repoRoot "artifacts" + $debugArtifactDestPath = Join-Path $artifactsRoot "Debug" + New-Item -ItemType Directory -Force -Path $debugArtifactDestPath | Out-Null + + $copiedModules = @{} + $StaticAnalysisCopied = $false + + Get-ChildItem -Path $workspace -Directory | ForEach-Object { + $debugArtifactSrcPath = Join-Path $_.FullName "Debug" + $StaticAnalysisSrcDirectory = Join-Path $_.FullName 'StaticAnalysis' + + if (Test-Path $debugArtifactSrcPath) { + Get-ChildItem -Path $debugArtifactSrcPath -Directory | ForEach-Object { + $moduleName = $_.Name + + if (-not $copiedModules.ContainsKey($moduleName)) { + $destPath = Join-Path $debugArtifactDestPath $moduleName + Copy-Item -Path $_.FullName -Destination $destPath -Recurse + $copiedModules[$moduleName] = $true + Write-Host "Copied $moduleName from $debugArtifactSrcPath" + } else { + Write-Host "Skipped $moduleName from $debugArtifactSrcPath (already copied)" + } + } + } + + if ((Test-Path $StaticAnalysisSrcDirectory) -and (-not $StaticAnalysisCopied)) { + $destPath = Join-Path $artifactsRoot 'StaticAnalysis' + Copy-Item -Path $StaticAnalysisSrcDirectory -Destination $destPath -Recurse + $StaticAnalysisCopied = $true + Write-Host "Copied StaticAnalysis from $StaticAnalysisSrcDirectory" + } + + } + + Get-ChildItem -Path $artifactsRoot -Directory | ForEach-Object { + Write-Host "Artifact Directory - $($_.Name)" + } + + Get-ChildItem -Path $debugArtifactDestPath -Directory | ForEach-Object { + Write-Host "Debug Directory - $($_.Name)" + } + + + - task: PublishPipelineArtifact@1 + displayName: 'Upload collected artifacts' + inputs: + targetPath: artifacts + artifact: 'collect' + + - job: analyze + displayName: "Analyze:" + dependsOn: + - collect + condition: and( + ne(stageDependencies.Build.filter.outputs['mtrx.analyzeTargets'], ''), + ne(stageDependencies.Build.filter.outputs['mtrx.analyzeTargets'], '{}') + ) + timeoutInMinutes: ${{ variables.AnalysisTimeoutInMinutes }} + pool: ${{ variables.WindowsAgentPoolName }} + strategy: + matrix: $[ stageDependencies.Build.filter.outputs['mtrx.analyzeTargets'] ] + maxParallel: ${{ variables.MaxParallelAnalyzeJobs }} + + steps: + - checkout: self + fetchDepth: 1 + fetchTags: false + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: collect + path: $(Build.SourcesDirectory)/artifacts + displayName: 'Download collected artifact to artifacts folder' + + - task: NodeTool@0 + displayName: Install autorest + inputs: + versionSpec: '14.17.1' + command: custom + verbose: false + customCommand: install autorest@latest + + - task: PowerShell@2 + displayName: Setup environment for autorest + inputs: + targetType: inline + script: "$env:NODE_OPTIONS=\"--max-old-space-size=65536\"" + pwsh: true + + - task: UseDotNet@2 + displayName: 'Use .NET SDK for Static Analysis' + inputs: + packageType: sdk + version: 8.x + + - task: PowerShell@2 + name: analyze + displayName: 'Analyze modules' + inputs: + targetType: inline + pwsh: true + script: | + Write-Host "Matrix Key: $(MatrixKey)" + + $analyseModulesPath = Join-Path "$(Build.SourcesDirectory)" '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'analyse-modules.ps1' + & $analyseModulesPath -MatrixKey "$(MatrixKey)" -RepoRoot "$(Build.SourcesDirectory)" + + - task: PublishPipelineArtifact@1 + displayName: 'Save Analyse Report' + inputs: + targetPath: artifacts + artifact: 'analyse-$(MatrixKey)' + condition: always() + + - template: util/batch-generation-test-job.yml + parameters: + jobName: 'test_windows' + OSName: 'Windows' + timeoutInMinutes: ${{ variables.TestTimeoutInMinutes }} + agentPoolName: ${{ variables.WindowsAgentPoolName }} + maxParallel: ${{ variables.MaxParallelTestWindowsJobs }} + + - template: util/batch-generation-test-job.yml + parameters: + jobName: 'test_linux' + OSName: 'Linux' + timeoutInMinutes: ${{ variables.TestTimeoutInMinutes }} + agentPoolName: ${{ variables.LinuxAgentPoolName }} + maxParallel: ${{ variables.MaxParallelTestLinuxJobs }} + + - template: util/batch-generation-test-job.yml + parameters: + jobName: 'test_mac' + OSName: 'MacOS' + timeoutInMinutes: ${{ variables.TestTimeoutInMinutes }} + agentPoolName: ${{ variables.MacOSAgentPoolName }} + agentPoolVMImage: ${{ variables.MacOSAgentPoolVMImage }} + maxParallel: ${{ variables.MaxParallelTestMacJobs }} + + - stage: Report + condition: always() + jobs: + - job: report + displayName: 'Report' + timeoutInMinutes: ${{ variables.IntermediateStepTimeoutInMinutes }} + pool: ${{ variables.WindowsAgentPoolName }} + steps: + - checkout: self + fetchDepth: 1 + fetchTags: false + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: 'prepare' + targetPath: artifacts + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: 'filter' + targetPath: artifacts + + - download: current + patterns: | + **/AnalyseReport-*.json + **/GenerationReport-*.json + **/BuildReport-*.json + **/Test*Report-*.json + displayName: 'Download build artifacts' + + - task: PowerShell@2 + name: report + displayName: 'Report Results' + inputs: + targetType: inline + pwsh: true + script: | + $reportScriptPath = Join-Path "$(Build.SourcesDirectory)" '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'report.ps1' + & $reportScriptPath -RepoRoot "$(Build.SourcesDirectory)" -PipelineWorkspace "$env:PIPELINE_WORKSPACE" + + - task: PublishPipelineArtifact@1 + displayName: 'Save Reports' + inputs: + targetPath: artifacts + artifact: 'reports' + condition: always() + + - stage: FailedJobNotification + dependsOn: + - Generate + - Build + - Test + - Report + condition: failed() + jobs: + - job: FailJobNotification + steps: + - task: PowerShell@2 + name: notification + displayName: "teams notification on failed job" + inputs: + targetType: inline + pwsh: true + script: | + $notificationScriptPath = Join-Path "$(Build.SourcesDirectory)" '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'notify-failed-job.ps1' + & $notificationScriptPath -RepoRoot "$(Build.SourcesDirectory)" + env: + TEAMS_URL: $(TEAMS_URL) diff --git a/.azure-pipelines/util/batch-generation-test-job.yml b/.azure-pipelines/util/batch-generation-test-job.yml new file mode 100644 index 000000000000..42d9e23c95cb --- /dev/null +++ b/.azure-pipelines/util/batch-generation-test-job.yml @@ -0,0 +1,60 @@ +parameters: + jobName: 'test_windows' + OSName: 'Windows' + timeoutInMinutes: 60 + agentPoolName: 'pool-windows-2019' + agentPoolVMImage: '' + maxParallel: 3 + +jobs: +- job: ${{ parameters.jobName }} + displayName: "Test ${{ parameters.OSName }}:" + dependsOn: + - collect + condition: and( + ne(stageDependencies.Build.filter.outputs['mtrx.test${{ parameters.OSName }}Targets'], ''), + ne(stageDependencies.Build.filter.outputs['mtrx.test${{ parameters.OSName }}Targets'], '{}') + ) + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + pool: + name: ${{ parameters.agentPoolName }} + ${{ if ne(parameters.agentPoolVMImage, '') }}: + vmImage: ${{ parameters.agentPoolVMImage }} + strategy: + matrix: $[ stageDependencies.Build.filter.outputs['mtrx.test${{ parameters.OSName }}Targets'] ] + maxParallel: ${{ parameters.maxParallel }} + + steps: + - checkout: self + fetchDepth: 1 + fetchTags: false + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: collect + path: $(Build.SourcesDirectory)/artifacts + displayName: 'Download collected artifact to artifacts folder' + + - task: UseDotNet@2 + displayName: 'Use .NET SDK for Test' + inputs: + packageType: sdk + version: 8.x + + - task: PowerShell@2 + name: test + displayName: 'Test ${{ parameters.OSName }}' + inputs: + targetType: inline + pwsh: true + script: | + $testModulesPath = Join-Path "$(Build.SourcesDirectory)" '.azure-pipelines' 'PipelineSteps' 'BatchGeneration' 'test-modules.ps1' + & $testModulesPath -MatrixKey "$(MatrixKey)" -TestEnvName ${{ parameters.OSName }} -RepoRoot "$(Build.SourcesDirectory)" + + - task: PublishPipelineArtifact@1 + displayName: 'Save Test ${{ parameters.OSName }} Report' + inputs: + targetPath: artifacts + artifact: 'test${{ parameters.OSName }}-$(MatrixKey)' + condition: always() + \ No newline at end of file