Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26326)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: TravisEz13 <10873629+TravisEz13@users.noreply.github.com>
Co-authored-by: Travis Plunk <travis.plunk@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Copilot
2025-10-29 15:52:59 -07:00
committed by GitHub
co-authored by TravisEz13 Travis Plunk Copilot
parent 0d7c24fb7e
commit cdeda2a501
12 changed files with 253 additions and 33 deletions
+3 -1
View File
@@ -5,7 +5,9 @@ runs:
steps:
- name: Capture Environment
if: success() || failure()
run: 'Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose'
run: |-
Import-Module .\tools\ci.psm1
Show-Environment
shell: pwsh
- name: Set Build Name for Non-PR
if: github.event_name != 'PullRequest'
@@ -6,7 +6,9 @@ runs:
steps:
- name: Capture Environment
if: success() || failure()
run: 'Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose'
run: |-
Import-Module ./tools/ci.psm1
Show-Environment
shell: pwsh
- uses: actions/setup-dotnet@v5
+2 -4
View File
@@ -21,10 +21,8 @@ runs:
- name: Capture Environment
if: success() || failure()
run: |-
Import-Module ./build.psm1
Write-LogGroupStart -Title 'Environment'
Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose
Write-LogGroupEnd -Title 'Environment'
Import-Module ./tools/ci.psm1
Show-Environment
shell: pwsh
- name: Download Build Artifacts
+2 -4
View File
@@ -21,10 +21,8 @@ runs:
- name: Capture Environment
if: success() || failure()
run: |-
Import-Module ./build.psm1
Write-LogGroupStart -Title 'Environment'
Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose
Write-LogGroupEnd -Title 'Environment'
Import-Module ./tools/ci.psm1
Show-Environment
shell: pwsh
- name: Download Build Artifacts
@@ -0,0 +1,181 @@
---
applyTo:
- "build.psm1"
- "tools/ci.psm1"
- ".github/**/*.yml"
- ".github/**/*.yaml"
---
# Log Grouping Guidelines for GitHub Actions
## Purpose
Guidelines for using `Write-LogGroupStart` and `Write-LogGroupEnd` to create collapsible log sections in GitHub Actions CI/CD runs.
## Key Principles
### 1. Groups Cannot Be Nested
GitHub Actions does not support nested groups. Only use one level of grouping.
**❌ Don't:**
```powershell
Write-LogGroupStart -Title "Outer Group"
Write-LogGroupStart -Title "Inner Group"
# ... operations ...
Write-LogGroupEnd -Title "Inner Group"
Write-LogGroupEnd -Title "Outer Group"
```
**✅ Do:**
```powershell
Write-LogGroupStart -Title "Operation A"
# ... operations ...
Write-LogGroupEnd -Title "Operation A"
Write-LogGroupStart -Title "Operation B"
# ... operations ...
Write-LogGroupEnd -Title "Operation B"
```
### 2. Groups Should Be Substantial
Only create groups for operations that generate substantial output (5+ lines). Small groups add clutter without benefit.
**❌ Don't:**
```powershell
Write-LogGroupStart -Title "Generate Resource Files"
Write-Log -message "Run ResGen"
Start-ResGen
Write-LogGroupEnd -Title "Generate Resource Files"
```
**✅ Do:**
```powershell
Write-Log -message "Run ResGen (generating C# bindings for resx files)"
Start-ResGen
```
### 3. Groups Should Represent Independent Operations
Each group should be a logically independent operation that users might want to expand/collapse separately.
**✅ Good examples:**
- Install Native Dependencies
- Install .NET SDK
- Build PowerShell
- Restore NuGet Packages
**❌ Bad examples:**
- Individual project restores (too granular)
- Small code generation steps (too small)
- Sub-steps of a larger operation (would require nesting)
### 4. One Group Per Iteration Is Excessive
Avoid putting log groups inside loops where each iteration creates a separate group. This would probably cause nesting.
**❌ Don't:**
```powershell
$projects | ForEach-Object {
Write-LogGroupStart -Title "Restore Project: $_"
dotnet restore $_
Write-LogGroupEnd -Title "Restore Project: $_"
}
```
**✅ Do:**
```powershell
Write-LogGroupStart -Title "Restore All Projects"
$projects | ForEach-Object {
Write-Log -message "Restoring $_"
dotnet restore $_
}
Write-LogGroupEnd -Title "Restore All Projects"
```
## Usage Pattern
```powershell
Write-LogGroupStart -Title "Descriptive Operation Name"
try {
# ... operation code ...
Write-Log -message "Status updates"
}
finally {
# Ensure group is always closed
}
Write-LogGroupEnd -Title "Descriptive Operation Name"
```
## When to Use Log Groups
Use log groups for:
- Major build phases (bootstrap, restore, build, test, package)
- Installation operations (dependencies, SDKs, tools)
- Operations that produce 5+ lines of output
- Operations where users might want to collapse verbose output
Don't use log groups for:
- Single-line operations
- Code that's already inside another group
- Loop iterations with minimal output per iteration
- Diagnostic or debug output that should always be visible
## Examples from build.psm1
### Good Usage
```powershell
function Start-PSBootstrap {
# Multiple independent operations, each with substantial output
Write-LogGroupStart -Title "Install Native Dependencies"
# ... apt-get/yum/brew install commands ...
Write-LogGroupEnd -Title "Install Native Dependencies"
Write-LogGroupStart -Title "Install .NET SDK"
# ... dotnet installation ...
Write-LogGroupEnd -Title "Install .NET SDK"
}
```
### Avoid
```powershell
# Too small - just 2-3 lines
Write-LogGroupStart -Title "Generate Resource Files (ResGen)"
Write-Log -message "Run ResGen"
Start-ResGen
Write-LogGroupEnd -Title "Generate Resource Files (ResGen)"
```
## GitHub Actions Syntax
These functions emit GitHub Actions workflow commands:
- `Write-LogGroupStart` → `::group::Title`
- `Write-LogGroupEnd` → `::endgroup::`
In the GitHub Actions UI, this renders as collapsible sections with the specified title.
## Testing
Test log grouping locally:
```powershell
$env:GITHUB_ACTIONS = 'true'
Import-Module ./build.psm1
Write-LogGroupStart -Title "Test"
Write-Log -Message "Content"
Write-LogGroupEnd -Title "Test"
```
Output should show:
```
::group::Test
Content
::endgroup::
```
## References
- [GitHub Actions: Grouping log lines](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines)
- `build.psm1`: `Write-LogGroupStart` and `Write-LogGroupEnd` function definitions
+2 -1
View File
@@ -56,7 +56,8 @@ jobs:
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- run: |
Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose
Import-Module .\tools\ci.psm1
Show-Environment
name: Capture Environment
shell: pwsh
+7 -6
View File
@@ -53,6 +53,7 @@ jobs:
# Set job outputs to values from filter step
outputs:
source: ${{ steps.filter.outputs.source }}
buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }}
packagingChanged: ${{ steps.filter.outputs.packagingChanged }}
steps:
- name: checkout
@@ -70,7 +71,7 @@ jobs:
name: Build PowerShell
runs-on: ubuntu-latest
needs: changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
steps:
- name: checkout
uses: actions/checkout@v5
@@ -84,7 +85,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: ubuntu-latest
steps:
- name: checkout
@@ -101,7 +102,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: ubuntu-latest
steps:
- name: checkout
@@ -118,7 +119,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: ubuntu-latest
steps:
- name: checkout
@@ -135,7 +136,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: ubuntu-latest
steps:
- name: checkout
@@ -151,7 +152,7 @@ jobs:
name: xUnit Tests
needs:
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
uses: ./.github/workflows/xunit-tests.yml
with:
runner_os: ubuntu-latest
+8 -7
View File
@@ -53,6 +53,7 @@ jobs:
# Set job outputs to values from filter step
outputs:
source: ${{ steps.filter.outputs.source }}
buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }}
steps:
- name: checkout
uses: actions/checkout@v5
@@ -67,7 +68,7 @@ jobs:
name: Build PowerShell
runs-on: macos-15-large
needs: changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
steps:
- name: checkout
uses: actions/checkout@v5
@@ -80,7 +81,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: macos-15-large
steps:
- name: checkout
@@ -97,7 +98,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: macos-15-large
steps:
- name: checkout
@@ -114,7 +115,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: macos-15-large
steps:
- name: checkout
@@ -131,7 +132,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: macos-15-large
steps:
- name: checkout
@@ -147,7 +148,7 @@ jobs:
name: xUnit Tests
needs:
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
uses: ./.github/workflows/xunit-tests.yml
with:
runner_os: macos-15-large
@@ -156,7 +157,7 @@ jobs:
name: macOS packaging and testing
needs:
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on:
- macos-15-large
steps:
+8 -7
View File
@@ -56,6 +56,7 @@ jobs:
# Set job outputs to values from filter step
outputs:
source: ${{ steps.filter.outputs.source }}
buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }}
packagingChanged: ${{ steps.filter.outputs.packagingChanged }}
steps:
- name: checkout
@@ -70,7 +71,7 @@ jobs:
ci_build:
name: Build PowerShell
needs: changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: windows-latest
steps:
- name: checkout
@@ -84,7 +85,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: windows-latest
steps:
- name: checkout
@@ -101,7 +102,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: windows-latest
steps:
- name: checkout
@@ -118,7 +119,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: windows-latest
steps:
- name: checkout
@@ -135,7 +136,7 @@ jobs:
needs:
- ci_build
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
runs-on: windows-latest
steps:
- name: checkout
@@ -151,7 +152,7 @@ jobs:
name: xUnit Tests
needs:
- changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
uses: ./.github/workflows/xunit-tests.yml
with:
runner_os: windows-latest
@@ -159,7 +160,7 @@ jobs:
analyze:
name: CodeQL Analysis
needs: changes
if: ${{ needs.changes.outputs.source == 'true' }}
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
uses: ./.github/workflows/analyze-reusable.yml
permissions:
actions: read
@@ -43,7 +43,8 @@ jobs:
- name: Capture Environment
if: success() || failure()
run: |
Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose
Import-Module .\tools\ci.psm1
Show-Environment
shell: pwsh
- name: Capture PowerShell Version Table
+27 -1
View File
@@ -385,7 +385,7 @@ function Start-PSBuild {
}
if ($Clean) {
Write-Log -message "Cleaning your working directory. You can also do it with 'git clean -fdX --exclude .vs/PowerShell/v16/Server/sqlite3'"
Write-LogGroupStart -Title "Cleaning your working directory"
Push-Location $PSScriptRoot
try {
# Excluded sqlite3 folder is due to this Roslyn issue: https://github.com/dotnet/roslyn/issues/23060
@@ -393,6 +393,7 @@ function Start-PSBuild {
# Excluded nuget.config as this is required for release build.
git clean -fdX --exclude .vs/PowerShell/v16/Server/sqlite3 --exclude src/Modules/nuget.config --exclude nuget.config
} finally {
Write-LogGroupEnd -Title "Cleaning your working directory"
Pop-Location
}
}
@@ -536,7 +537,9 @@ Fix steps:
}
# handle Restore
Write-LogGroupStart -Title "Restore NuGet Packages"
Restore-PSPackage -Options $Options -Force:$Restore -InteractiveAuth:$InteractiveAuth
Write-LogGroupEnd -Title "Restore NuGet Packages"
# handle ResGen
# Heuristic to run ResGen on the fresh machine
@@ -566,6 +569,7 @@ Fix steps:
$publishPath = $Options.Output
}
Write-LogGroupStart -Title "Build PowerShell"
try {
# Relative paths do not work well if cwd is not changed to project
Push-Location $Options.Top
@@ -620,6 +624,7 @@ Fix steps:
} finally {
Pop-Location
}
Write-LogGroupEnd -Title "Build PowerShell"
# No extra post-building task will run if '-SMAOnly' is specified, because its purpose is for a quick update of S.M.A.dll after full build.
if ($SMAOnly) {
@@ -627,6 +632,7 @@ Fix steps:
}
# publish reference assemblies
Write-LogGroupStart -Title "Publish Reference Assemblies"
try {
Push-Location "$PSScriptRoot/src/TypeCatalogGen"
$refAssemblies = Get-Content -Path $incFileName | Where-Object { $_ -like "*microsoft.netcore.app*" } | ForEach-Object { $_.TrimEnd(';') }
@@ -640,6 +646,7 @@ Fix steps:
} finally {
Pop-Location
}
Write-LogGroupEnd -Title "Publish Reference Assemblies"
if ($ReleaseTag) {
$psVersion = $ReleaseTag
@@ -682,10 +689,13 @@ Fix steps:
# download modules from powershell gallery.
# - PowerShellGet, PackageManagement, Microsoft.PowerShell.Archive
if ($PSModuleRestore) {
Write-LogGroupStart -Title "Restore PowerShell Modules"
Restore-PSModuleToBuild -PublishPath $publishPath
Write-LogGroupEnd -Title "Restore PowerShell Modules"
}
# publish powershell.config.json
Write-LogGroupStart -Title "Generate PowerShell Configuration"
$config = [ordered]@{}
if ($Options.Runtime -like "*win*") {
@@ -731,10 +741,13 @@ Fix steps:
} else {
Write-Warning "No powershell.config.json generated for $publishPath"
}
Write-LogGroupEnd -Title "Generate PowerShell Configuration"
# Restore the Pester module
if ($CI) {
Write-LogGroupStart -Title "Restore Pester Module"
Restore-PSPester -Destination (Join-Path $publishPath "Modules")
Write-LogGroupEnd -Title "Restore Pester Module"
}
Clear-NativeDependencies -PublishFolder $publishPath
@@ -2086,6 +2099,7 @@ function Install-Dotnet {
[string]$FeedCredential
)
Write-LogGroupStart -Title "Install .NET SDK $Version"
Write-Verbose -Verbose "In install-dotnet"
# This allows sudo install to be optional; needed when running in containers / as root
@@ -2220,6 +2234,7 @@ function Install-Dotnet {
}
}
}
Write-LogGroupEnd -Title "Install .NET SDK $Version"
}
function Get-RedHatPackageManager {
@@ -2264,12 +2279,14 @@ function Start-PSBootstrap {
try {
if ($environment.IsLinux -or $environment.IsMacOS) {
Write-LogGroupStart -Title "Install Native Dependencies"
# This allows sudo install to be optional; needed when running in containers / as root
# Note that when it is null, Invoke-Expression (but not &) must be used to interpolate properly
$sudo = if (!$NoSudo) { "sudo" }
if ($BuildLinuxArm -and $environment.IsLinux -and -not $environment.IsUbuntu -and -not $environment.IsMariner) {
Write-Error "Cross compiling for linux-arm is only supported on AzureLinux/Ubuntu environment"
Write-LogGroupEnd -Title "Install Native Dependencies"
return
}
@@ -2399,9 +2416,11 @@ function Start-PSBootstrap {
}
}
}
Write-LogGroupEnd -Title "Install Native Dependencies"
}
if ($Scenario -in 'All', 'Both', 'DotNet') {
Write-LogGroupStart -Title "Install .NET SDK"
Write-Verbose -Verbose "Calling Find-Dotnet from Start-PSBootstrap"
@@ -2440,10 +2459,12 @@ function Start-PSBootstrap {
else {
Write-Log -message "dotnet is already installed. Skipping installation."
}
Write-LogGroupEnd -Title "Install .NET SDK"
}
# Install Windows dependencies if `-Package` or `-BuildWindowsNative` is specified
if ($environment.IsWindows) {
Write-LogGroupStart -Title "Install Windows Dependencies"
## The VSCode build task requires 'pwsh.exe' to be found in Path
if (-not (Get-Command -Name pwsh.exe -CommandType Application -ErrorAction Ignore))
{
@@ -2456,9 +2477,11 @@ function Start-PSBootstrap {
$isArm64 = "$env:RUNTIME" -eq 'arm64'
Install-Wix -arm64:$isArm64
}
Write-LogGroupEnd -Title "Install Windows Dependencies"
}
if ($Scenario -in 'All', 'Tools') {
Write-LogGroupStart -Title "Install .NET Global Tools"
Write-Log -message "Installing .NET global tools"
# Ensure dotnet is available
@@ -2469,12 +2492,15 @@ function Start-PSBootstrap {
Start-NativeExecution {
dotnet tool install --global dotnet-format
}
Write-LogGroupEnd -Title "Install .NET Global Tools"
}
if ($env:TF_BUILD) {
Write-LogGroupStart -Title "Capture NuGet Sources"
Write-Verbose -Verbose "--- Start - Capturing nuget sources"
dotnet nuget list source --format detailed
Write-Verbose -Verbose "--- End - Capturing nuget sources"
Write-LogGroupEnd -Title "Capture NuGet Sources"
}
} finally {
Pop-Location
+8
View File
@@ -688,6 +688,14 @@ function Set-Path
}
}
# Display environment variables in a log group for GitHub Actions
function Show-Environment
{
Write-LogGroupStart -Title 'Environment'
Get-ChildItem -Path env: | Out-String -width 9999 -Stream | Write-Verbose -Verbose
Write-LogGroupEnd -Title 'Environment'
}
# Bootstrap script for Linux and macOS
function Invoke-BootstrapStage
{