@@ -50,13 +50,13 @@ jobs:
5050
5151 steps :
5252 - name : Set up JDK 17
53- uses : actions/setup-java@v4
53+ uses : actions/setup-java@v5
5454 with :
5555 java-version : 17
5656 distribution : " zulu" # Alternative distribution options are available.
5757
5858 - name : Checkout Repository
59- uses : actions/checkout@v4
59+ uses : actions/checkout@v7
6060 with :
6161 fetch-depth : 0 # Full history for versioning
6262 fetch-tags : true
@@ -65,11 +65,20 @@ jobs:
6565 persist-credentials : true
6666
6767 - name : Setup .NET SDK ${{ env.DOTNET_VERSION }}
68- uses : actions/setup-dotnet@v4
68+ uses : actions/setup-dotnet@v6
6969 with :
7070 dotnet-version : ${{ env.DOTNET_VERSION }}.x
71+ # setup-dotnet caches the NuGet global packages folder and keys it on a hash of these
72+ # files. The csproj files alone are not enough: this repository uses central package
73+ # management, so Directory.Packages.props is where versions actually live, and global.json
74+ # pins the ktsu SDK versions, which are NuGet packages too. Without them a version bump in
75+ # either file reuses the previous key, and because a cache hit never re-saves, the newly
76+ # downloaded packages would never make it into the cache.
7177 cache : true
72- cache-dependency-path : " **/*.csproj"
78+ cache-dependency-path : |
79+ **/*.csproj
80+ **/Directory.Packages.props
81+ **/global.json
7382
7483 # Ensure NuGet packages directory exists for caching (prevents error when pipeline exits early)
7584 - name : Ensure NuGet cache directory exists
7887
7988 - name : Cache SonarQube Cloud packages
8089 if : ${{ env.SONAR_TOKEN != '' }}
81- uses : actions/cache@v4
90+ uses : actions/cache@v6
8291 env :
8392 SONAR_TOKEN : ${{ secrets.SONAR_TOKEN }}
8493 with :
8998 - name : Cache SonarQube Cloud scanner
9099 if : ${{ env.SONAR_TOKEN != '' }}
91100 id : cache-sonar-scanner
92- uses : actions/cache@v4
101+ uses : actions/cache@v6
93102 env :
94103 SONAR_TOKEN : ${{ secrets.SONAR_TOKEN }}
95104 with :
@@ -101,41 +110,52 @@ jobs:
101110 if : ${{ env.SONAR_TOKEN != '' && steps.cache-sonar-scanner.outputs.cache-hit != 'true' }}
102111 env :
103112 SONAR_TOKEN : ${{ secrets.SONAR_TOKEN }}
104- shell : powershell
113+ shell : pwsh
105114 run : |
106115 New-Item -Path .\.sonar\scanner -ItemType Directory
107116 dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner
108117
109- - name : Configure SonarQube exclusions
110- shell : bash
118+ # KtsuBuild is cloned and compiled before the SonarQube window opens. The scanner injects its
119+ # Roslyn analyzers into every project compiled between begin and end, so building KtsuBuild
120+ # inside the window made it report KtsuBuild's own code smells against this repository. That is
121+ # a compiler diagnostic rather than a reported issue, so sonar.exclusions cannot suppress it.
122+ - name : Clone KtsuBuild (Latest Tag)
123+ shell : pwsh
111124 run : |
112- EXCLUSIONS="_temp/**,_actions/**"
113- if [ "${{ github.event.repository.name }}" != "KtsuBuild" ]; then
114- EXCLUSIONS="$EXCLUSIONS,**/KtsuBuild/**"
115- fi
116- # NativeExports.cs is intentionally unsafe C ABI boundary code; exclude from all analysis.
117- EXCLUSIONS="$EXCLUSIONS,**/NativeExports.cs"
118- echo "SONAR_EXCLUSIONS=$EXCLUSIONS" >> $GITHUB_ENV
125+ # Sorting by [version] rather than as text, so v1.8.15 wins over v1.8.9.
126+ $tags = git ls-remote --tags https://github.com/ktsu-dev/KtsuBuild.git |
127+ Select-String -Pattern 'refs/tags/(v\d+\.\d+\.\d+)$' |
128+ ForEach-Object { $_.Matches[0].Groups[1].Value }
129+ $latestTag = $tags | Sort-Object { [version]$_.Substring(1) } | Select-Object -Last 1
130+
131+ if ([string]::IsNullOrEmpty($latestTag)) {
132+ Write-Host "No version tags found, falling back to HEAD"
133+ git clone --depth 1 https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild"
134+ }
135+ else {
136+ Write-Host "Cloning KtsuBuild at tag: $latestTag"
137+ git clone --depth 1 --branch $latestTag https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild"
138+ }
139+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
140+
141+ - name : Build KtsuBuild CLI
142+ shell : pwsh
143+ run : |
144+ dotnet build "${{ runner.temp }}/KtsuBuild/KtsuBuild.CLI"
145+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
119146
120147 - name : Begin SonarQube
121148 if : ${{ env.SONAR_TOKEN != '' }}
122149 env :
123150 SONAR_TOKEN : ${{ secrets.SONAR_TOKEN }}
124- shell : powershell
125- run : |
126- .\.sonar\scanner\dotnet-sonarscanner begin /k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" /o:"${{ github.repository_owner }}" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths="coverage/coverage.xml" /d:sonar.coverage.exclusions="**/*Test*.cs,**/*.Tests.cs,**/*.Tests/**/*,**/obj/**/*,**/*.dll,**/NativeExports.cs" /d:sonar.cs.vstest.reportsPaths="coverage/TestResults/**/*.trx" /d:sonar.exclusions="${{ env.SONAR_EXCLUSIONS }}"
127-
128- - name : Clone KtsuBuild (Latest Tag)
151+ shell : pwsh
129152 run : |
130- LATEST_TAG=$(git ls-remote --tags https://github.com/ktsu-dev/KtsuBuild.git | grep -o 'refs/tags/v[0-9]*\.[0-9]*\.[0-9]*$' | sed 's/refs\/tags\///' | sort -V | tail -1 || true)
131- if [ -z "$LATEST_TAG" ]; then
132- echo "No version tags found, falling back to HEAD"
133- git clone --depth 1 https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild"
134- else
135- echo "Cloning KtsuBuild at tag: $LATEST_TAG"
136- git clone --depth 1 --branch "$LATEST_TAG" https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild"
137- fi
138- shell : bash
153+ # sonar.projectBaseDir is pinned to the workspace. Scanner for .NET v8 otherwise derives it
154+ # from the projects it sees, and anything compiled outside the workspace widens it to a
155+ # common ancestor, which pulls the runner's own checkouts under _actions and _temp into the
156+ # file scan. Pinning it means only this repository is ever in scope, so no exclusions are
157+ # needed for those directories.
158+ .\.sonar\scanner\dotnet-sonarscanner begin /k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" /o:"${{ github.repository_owner }}" /d:sonar.token="$env:SONAR_TOKEN" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.projectBaseDir="${{ github.workspace }}" /d:sonar.cs.vscoveragexml.reportsPaths="coverage/coverage.xml" /d:sonar.coverage.exclusions="**/*Test*.cs,**/*.Tests.cs,**/*.Tests/**/*,**/obj/**/*,**/*.dll,**/NativeExports.cs" /d:sonar.cs.vstest.reportsPaths="coverage/TestResults/**/*.trx" /d:sonar.exclusions="**/NativeExports.cs"
139159
140160 - name : Run KtsuBuild CI Pipeline
141161 id : pipeline
@@ -155,7 +175,9 @@ jobs:
155175 $args += @("--version-bump", $versionBump)
156176 }
157177
158- & dotnet run --project "${{ runner.temp }}/KtsuBuild/KtsuBuild.CLI" -- @args
178+ # --no-build because the CLI was compiled before the SonarQube window opened. Recompiling
179+ # it here would pull the scanner's injected analyzers into KtsuBuild's own sources.
180+ & dotnet run --no-build --project "${{ runner.temp }}/KtsuBuild/KtsuBuild.CLI" -- @args
159181 if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
160182
161183 # Set outputs for downstream jobs
@@ -178,12 +200,12 @@ jobs:
178200 if : env.SONAR_TOKEN != ''
179201 env :
180202 SONAR_TOKEN : ${{ secrets.SONAR_TOKEN }}
181- shell : powershell
203+ shell : pwsh
182204 run : |
183- .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets. SONAR_TOKEN }} "
205+ .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="$env: SONAR_TOKEN"
184206
185207 - name : Upload Coverage Report
186- uses : actions/upload-artifact@v4
208+ uses : actions/upload-artifact@v7
187209 if : always()
188210 with :
189211 name : coverage-report
@@ -203,27 +225,34 @@ jobs:
203225
204226 steps :
205227 - name : Checkout Release Commit
206- uses : actions/checkout@v4
228+ uses : actions/checkout@v7
207229 with :
208230 ref : ${{ needs.build.outputs.release_hash }}
209231 fetch-depth : 0 # Full history for better auto-detection
210232
211233 - name : Setup .NET SDK ${{ env.DOTNET_VERSION }}
212- uses : actions/setup-dotnet@v4
234+ uses : actions/setup-dotnet@v6
213235 with :
214236 dotnet-version : ${{ env.DOTNET_VERSION }}.x
215237
216238 - name : Clone KtsuBuild (Latest Tag)
239+ shell : pwsh
217240 run : |
218- LATEST_TAG=$(git ls-remote --tags https://github.com/ktsu-dev/KtsuBuild.git | grep -o 'refs/tags/v[0-9]*\.[0-9]*\.[0-9]*$' | sed 's/refs\/tags\///' | sort -V | tail -1 || true)
219- if [ -z "$LATEST_TAG" ]; then
220- echo "No version tags found, falling back to HEAD"
241+ # Sorting by [version] rather than as text, so v1.8.15 wins over v1.8.9.
242+ $tags = git ls-remote --tags https://github.com/ktsu-dev/KtsuBuild.git |
243+ Select-String -Pattern 'refs/tags/(v\d+\.\d+\.\d+)$' |
244+ ForEach-Object { $_.Matches[0].Groups[1].Value }
245+ $latestTag = $tags | Sort-Object { [version]$_.Substring(1) } | Select-Object -Last 1
246+
247+ if ([string]::IsNullOrEmpty($latestTag)) {
248+ Write-Host "No version tags found, falling back to HEAD"
221249 git clone --depth 1 https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild"
222- else
223- echo "Cloning KtsuBuild at tag: $LATEST_TAG"
224- git clone --depth 1 --branch "$LATEST_TAG" https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild"
225- fi
226- shell : bash
250+ }
251+ else {
252+ Write-Host "Cloning KtsuBuild at tag: $latestTag"
253+ git clone --depth 1 --branch $latestTag https://github.com/ktsu-dev/KtsuBuild.git "${{ runner.temp }}/KtsuBuild"
254+ }
255+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
227256
228257 - name : Update Winget Manifests
229258 shell : pwsh
@@ -233,7 +262,7 @@ jobs:
233262 dotnet run --project "${{ runner.temp }}/KtsuBuild/KtsuBuild.CLI" -- winget generate --version "${{ needs.build.outputs.version }}" --workspace "${{ github.workspace }}" --verbose
234263
235264 - name : Upload Updated Manifests
236- uses : actions/upload-artifact@v4
265+ uses : actions/upload-artifact@v7
237266 with :
238267 name : winget-manifests-${{ needs.build.outputs.version }}
239268 path : winget/*.yaml
@@ -251,9 +280,12 @@ jobs:
251280
252281 steps :
253282 - name : Checkout Release Commit
254- uses : actions/checkout@v4
283+ uses : actions/checkout@v7
255284 with :
256285 ref : ${{ needs.build.outputs.release_hash }}
257286
287+ # Pinned to a full commit SHA rather than a tag. Tags are mutable, so a third party action
288+ # referenced by tag can be repointed at different code after review. The trailing comment is
289+ # the convention dependabot reads, so it still offers upgrades and rewrites both parts.
258290 - name : Detect Dependencies
259- uses : advanced-security/component-detection-dependency-submission-action@v0.0.2
291+ uses : advanced-security/component-detection-dependency-submission-action@31f25a8de68ae5ce2ca274bc28546a78683c15ce # v0.1.4
0 commit comments