-
-
Notifications
You must be signed in to change notification settings - Fork 6
257 lines (235 loc) · 11.8 KB
/
Build Module.yml
File metadata and controls
257 lines (235 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/virtual-environments-for-github-hosted-runners
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/software-installed-on-github-hosted-runners
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#using-a-specific-shell
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-powershell
# https://github.com/actions/upload-artifact#where-does-the-upload-go
name: Build Module
# Builds and tests the DLLPickle PowerShell module on Windows, Linux, and macOS.
# Can be run manually or called by other workflows.
on:
workflow_call:
inputs:
ref:
description: "Git reference (branch, tag, or commit SHA) to check out. Defaults to the ref that triggered the caller workflow."
type: string
required: false
workflow_dispatch:
pull_request:
# No path filter: this workflow always triggers on PRs to main so its "Build and test module"
# checks always REPORT (a required status check from a path-filtered workflow that never triggers
# stays Pending and blocks the PR forever). The expensive matrix build is gated below by the
# `changes` job — on a PR that doesn't touch build-relevant paths the build job is skipped, and a
# skipped job satisfies the required check, so docs-/CI-only PRs are never blocked.
branches: [main]
#push:
# branches:
# - main
# paths:
# - "src/**"
# - ".github/workflows/Build Module.yml"
permissions:
contents: read
security-events: write
concurrency:
group: ${{ github.workflow }}-${{ inputs.ref || github.ref }}
cancel-in-progress: true
jobs:
changes:
name: Detect build-relevant changes
# Only meaningful for a directly-triggered PR. workflow_call (release) passes inputs.ref and must
# always build; workflow_dispatch must always build. Both skip this job (handled in build's `if`).
if: github.event_name == 'pull_request' && inputs.ref == ''
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
relevant: ${{ steps.detect.outputs.relevant }}
steps:
- name: Detect whether build-relevant paths changed
id: detect
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
# Detection is intentionally INLINE (not a shared .github/ci-scripts script): keeping the
# merge-gate logic in the workflow file benefits from GitHub's stricter workflow-edit review,
# so a PR can't relocate it to a less-scrutinized script and tamper the skip decision (#228).
# Fail-safe: default to relevant=true so any error enumerating changed files runs the build
# rather than silently skipping it. Use the PAGINATED REST files endpoint -- gh pr view
# --json files caps at 100 files and could miss a later src/ change on a large PR.
$Relevant = $true
try {
$ErrorActionPreference = 'Stop'
# Project previous_filename too, so a rename moving a tracked file OUT of a matched tree
# (reported as the new path in .filename, old path in .previous_filename) is still seen.
$Files = gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" --paginate --jq '.[] | .filename, (.previous_filename // empty)'
# Native non-zero exits don't reliably throw in pwsh, so check explicitly -> fail-safe.
if ($LASTEXITCODE -ne 0) { throw "gh api exited $LASTEXITCODE while listing PR files" }
$Patterns = @('^src/', '^build/', '^tests/', '^tools/', '^\.github/ci-scripts/', '^\.github/workflows/Build Module\.yml$')
$Relevant = $false
foreach ($File in $Files) {
foreach ($Pattern in $Patterns) {
if ($File -match $Pattern) { $Relevant = $true; break }
}
if ($Relevant) { break }
}
} catch {
Write-Host "::warning::Changed-file detection failed; defaulting to relevant=true (fail-safe). $_"
$Relevant = $true
}
"relevant=$($Relevant.ToString().ToLowerInvariant())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
Write-Host "Build-relevant change detected: $Relevant"
build:
name: Build and test module (${{ matrix.os }})
needs: changes
# Always build for workflow_call (release; inputs.ref set) and workflow_dispatch. For a direct PR,
# build only when build-relevant paths changed; otherwise skip (a skipped job still satisfies the
# required "Build and test module" check). always() stops the skip of `changes` in the
# call/dispatch paths from cascading into a skip of build; the `!= 'false'` guard errs toward
# building on any ambiguity, so the release path is never accidentally skipped.
if: ${{ always() && (inputs.ref != '' || github.event_name == 'workflow_dispatch' || needs.changes.outputs.relevant != 'false') }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
include:
- os: windows-latest
module_cache_paths: |
~/Documents/PowerShell/Modules
- os: ubuntu-latest
module_cache_paths: |
~/.local/share/powershell/Modules
- os: macos-latest
module_cache_paths: |
~/.local/share/powershell/Modules
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.ref || github.ref }}
# Install the .NET 8 SDK pinned by global.json so the build does not depend on the runner image
# having an 8.0.x SDK preinstalled (Invoke-Build's RestoreDependencies task invokes dotnet).
- name: Setup .NET
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
with:
dotnet-version: "8.0.x"
cache: true
cache-dependency-path: "src/DLLPickle.Build/packages.lock.json"
- name: Cache PowerShell modules
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ matrix.module_cache_paths }}
key: ${{ runner.os }}-psmodules-${{ hashFiles('build/DLLPickle.Build.ps1', '.github/ci-scripts/Actions_Bootstrap.ps1') }}
restore-keys: |
${{ runner.os }}-psmodules-
- name: Bootstrap
shell: pwsh
run: ./.github/ci-scripts/Actions_Bootstrap.ps1
- name: Test and Build (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: Invoke-Build -File ./build/DLLPickle.Build.ps1
- name: Test and Build (Linux/macOS)
if: runner.os != 'Windows'
shell: pwsh
run: Invoke-Build -File ./build/DLLPickle.Build.ps1 -Task BuildCrossPlatform
- name: Generate PSScriptAnalyzer SARIF
if: always() && runner.os == 'Windows'
shell: pwsh
run: |
$null = New-Item -Path ./artifacts -ItemType Directory -Force
$Results = @(Invoke-ScriptAnalyzer -Path ./src/DLLPickle -Settings ./build/PSScriptAnalyzerSettings.psd1 -Recurse)
$Rules = @($Results | Group-Object RuleName | ForEach-Object {
$Item = $_.Group[0]
$Level = switch ($Item.Severity) { 'Error' { 'error' } 'Warning' { 'warning' } default { 'note' } }
[ordered]@{
id = $Item.RuleName
name = $Item.RuleName
shortDescription = @{ text = "$($Item.RuleName) violation" }
defaultConfiguration = @{ level = $Level }
}
})
$WorkDir = (Get-Location).Path
$SarifResults = @($Results | ForEach-Object {
$Level = switch ($_.Severity) { 'Error' { 'error' } 'Warning' { 'warning' } default { 'note' } }
$RelPath = $_.ScriptPath.Substring($WorkDir.Length).TrimStart('\', '/') -replace '\\', '/'
[ordered]@{
ruleId = $_.RuleName
level = $Level
message = @{ text = $_.Message }
locations = @(@{
physicalLocation = @{
artifactLocation = @{ uri = $RelPath }
region = @{ startLine = $_.Line; startColumn = $_.Column }
}
})
}
})
$Sarif = [ordered]@{
'$schema' = 'https://json.schemastore.org/sarif-2.1.0.json'
version = '2.1.0'
runs = @([ordered]@{
tool = @{ driver = @{
name = 'PSScriptAnalyzer'
version = (Get-Module PSScriptAnalyzer -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Version.ToString()
rules = $Rules
}}
results = $SarifResults
})
}
$Sarif | ConvertTo-Json -Depth 20 | Set-Content -Path ./artifacts/psscriptanalyzer.sarif -Encoding UTF8
Write-Host "SARIF written with $($Results.Count) result(s)"
- name: Upload PSScriptAnalyzer SARIF to code scanning
if: always() && runner.os == 'Windows' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
with:
sarif_file: ./artifacts/psscriptanalyzer.sarif
category: psscriptanalyzer
- name: Upload pester results
if: always() # Ensure this step runs even if prior steps fail
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pester-results-${{ runner.os }}
path: ./artifacts/testOutput
if-no-files-found: warn
- name: Upload zip module archive build
if: runner.os == 'Windows'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: zip-archive
path: ./archive
if-no-files-found: warn
- name: Upload module directory
if: runner.os == 'Windows'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: module-build
path: ./module/DLLPickle
if-no-files-found: warn
build-gate:
name: Build gate
# Aggregate required-check target. GitHub evaluates a matrix job's `if` BEFORE expanding the
# matrix, so skipping the `build` job does NOT create the per-OS "Build and test module (...)"
# check contexts -- requiring those directly would leave non-bundle PRs stuck "Expected - waiting
# for status". This single, always-running job is the stable check to require instead: it passes
# when the matrix build succeeded OR was skipped (no build-relevant changes) and fails when the
# build failed or was cancelled.
needs: build
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Aggregate build result
shell: pwsh
run: |
$Result = '${{ needs.build.result }}'
Write-Host "build job result: $Result"
if ($Result -eq 'success' -or $Result -eq 'skipped') {
Write-Host 'Build gate passed.'
exit 0
}
Write-Host "::error::Build gate failed (build result: $Result)."
exit 1