Repository navigation
236 lines (218 loc) · 12.6 KB
/
Copy pathsonar.yml
File metadata and controls
236 lines (218 loc) · 12.6 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
name: Sonar
# Reusable SonarQube Cloud analysis, called by ci.yml after build-test when SONAR_EXPECTED is true. It reuses the
# coverage artifacts of the same source-mode run; only the instrumented build is repeated, because the scanner must
# observe a compilation.
#
# CI-based analysis only: Automatic Analysis must stay OFF for the project in SonarQube Cloud (Administration > Analysis
# Method). SonarQube Cloud exposes no API to read that setting; with both methods on, scanner begin fails, which is the
# earliest authoritative check.
on:
workflow_call:
inputs:
project-key:
description: SonarQube Cloud project key.
type: string
default: CheatEngineNet_CheatEngine.Client
organization:
description: SonarQube Cloud organization key.
type: string
default: cheatenginenet
wait-quality-gate:
description: Fail the job when the quality gate fails. Pull requests and manual runs wait for it; main only reports it.
type: boolean
default: true
secrets:
SONAR_TOKEN:
description: SonarQube Cloud analysis token. The job fails fast when it is empty.
required: false
permissions:
contents: read
env:
SONAR_SCANNER_VERSION: 11.3.0
defaults:
run:
shell: pwsh
jobs:
analyze:
name: Analyze
runs-on: windows-2025
timeout-minutes: 30
env:
SONAR_PROJECT_KEY: ${{ inputs.project-key }}
SONAR_ORGANIZATION: ${{ inputs.organization }}
SONAR_WAIT_QUALITY_GATE: ${{ inputs.wait-quality-gate }}
CheatEngineImportSourceSettings: 'true'
steps:
# ci.yml never calls this workflow for forks or Dependabot, so an empty token is a configuration error. Fail
# before the instrumented build instead of after it.
- name: Require token
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
if ([string]::IsNullOrEmpty($env:SONAR_TOKEN)) {
Write-Host '::error title=SONAR_TOKEN missing::Add the repository secret SONAR_TOKEN (a SonarQube Cloud analysis token for this project).'
exit 1
}
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0 # SCM blame for new-code detection
submodules: recursive # analyse the same pinned SDK source graph as CI
persist-credentials: false
# SonarScanner for .NET uses Java. The same JDK 21 distribution as CheatEngine.SDK keeps scanner behaviour
# reproducible across both repositories.
- name: Set up JDK 21
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
with:
distribution: zulu
java-version: '21'
# SDK and CLI settings only: the restore below must not use the checked-out nuget.config.
- name: Setup .NET
uses: ./.github/actions/setup-dotnet
# Every event may restore the analyzer cache; only main saves it (last step), so a pull request can read a
# main-branch cache but never publish one.
- name: Restore Sonar analyzer cache
id: sonar-cache-restore
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ runner.temp }}/sonar-user-home/cache
key: sonar-analyzers-${{ runner.os }}-${{ runner.arch }}-${{ env.SONAR_SCANNER_VERSION }}-v1
- name: Download coverage
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: coverage
path: ${{ runner.temp }}/coverage
# Do not use the checked-out nuget.config to resolve tooling. The scanner package and every restored dependency
# must come only from nuget.org, not a source a pull request can redirect.
- name: Create NuGet.org-only configuration
id: nuget-config
run: |
$ErrorActionPreference = 'Stop'
$path = Join-Path $env:RUNNER_TEMP 'sonar-nuget.config'
@'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
'@ | Set-Content -LiteralPath $path -Encoding utf8NoBOM
"path=$path" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
# Restore before scanner begin, in locked mode: the analysis build is --no-restore, so no pull-request-supplied
# source or version can take part while scanner credentials are configured.
- name: Restore from NuGet.org (locked)
env:
NUGET_CONFIG: ${{ steps.nuget-config.outputs.path }}
run: |
dotnet restore CheatEngine.Client.slnx --locked-mode --configfile $env:NUGET_CONFIG
if ($LASTEXITCODE -ne 0) {
throw "Sonar restore failed with exit code $LASTEXITCODE."
}
- name: Install scanner
env:
NUGET_CONFIG: ${{ steps.nuget-config.outputs.path }}
run: |
dotnet tool install dotnet-sonarscanner --tool-path "$env:RUNNER_TEMP/sonar-scanner" --version $env:SONAR_SCANNER_VERSION --configfile $env:NUGET_CONFIG
if ($LASTEXITCODE -ne 0) {
throw "SonarScanner installation failed with exit code $LASTEXITCODE."
}
# The begin step ignores the SONAR_TOKEN variable, so the token travels in SONARQUBE_SCANNER_PARAMS, which both
# scanner steps read. It stays off the command line and out of the build step that compiles pull-request code.
- name: Begin analysis
env:
SONARQUBE_SCANNER_PARAMS: '{"sonar.token":"${{ secrets.SONAR_TOKEN }}"}'
SONAR_USER_HOME: ${{ runner.temp }}/sonar-user-home
run: |
$coverage = "$env:RUNNER_TEMP/coverage"
$reports = @(Get-ChildItem -Path $coverage -Filter *.xml -Recurse -File -ErrorAction SilentlyContinue)
if ($reports.Count -eq 0) {
Write-Host '::error::The coverage artifact holds no report.'
exit 1
}
# Run MinVer before reading its version, then retain the core version used by the previous package-based
# calculation. A different prerelease commit must not reset Sonar's version-based new-code period.
$versionOutput = @(dotnet msbuild src/CheatEngine.Client/CheatEngine.Client.csproj -target:MinVer -getProperty:MinVerVersion --nologo)
if ($LASTEXITCODE -ne 0) {
throw "Reading the source version failed with exit code $LASTEXITCODE."
}
$version = ($versionOutput -join "`n").Trim()
if ($version -notmatch '^(?<core>\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?$') {
Write-Host "::error::Could not read the MinVer source version from:`n$($versionOutput -join "`n")"
exit 1
}
$version = $Matches['core']
Write-Host "Coverage reports: $($reports.Count); project version: $version"
# These findings conflict with deliberate repository contracts. Keep them in the scanner configuration so the
# sources stay free of Sonar-only attributes and suppressions.
$ignoredIssues = @(
@{ Key = 'noLinq'; Rule = 'csharpsquid:S3267'; Resource = '**/*.cs' }
@{ Key = 'unsafeInterop'; Rule = 'csharpsquid:S6640'; Resource = '**/*.cs' }
@{ Key = 'nullableFlow'; Rule = 'csharpsquid:S8970'; Resource = '**/*.cs' }
@{ Key = 'apiOverloadLayout'; Rule = 'csharpsquid:S4136'; Resource = 'libs/**' }
@{ Key = 'generatorInstances'; Rule = 'csharpsquid:S2325'; Resource = 'source-generators/**' }
@{ Key = 'emittedFragments'; Rule = 'csharpsquid:S1192'; Resource = 'source-generators/**' }
@{ Key = 'generatorComments'; Rule = 'csharpsquid:S125'; Resource = 'source-generators/**' }
# The qualification fault switch makes an activation-scoped resource throw from Dispose on purpose (Q06, Q43).
@{ Key = 'harnessFaultInjection'; Rule = 'csharpsquid:S3877'; Resource = 'tests/CheatEngine.Client.LivePlugin.Qualification/QualificationPlugin.cs' }
)
$arguments = @(
"/k:$env:SONAR_PROJECT_KEY"
"/o:$env:SONAR_ORGANIZATION"
"/v:$version"
'/d:sonar.exclusions=artifacts/**,externals/**,tests/CheatEngine.Client.Benchmarks/**'
# CI publishes managed coverage of the shipping code only: tests and build tooling stay out of the coverage
# metric instead of presenting an incomplete report as if it covered them. Every excluded path exists
# (the repository has no documentation folder; its Markdown files are not analysed code).
# Shipping code is excluded file by file, never by a folder pattern, so every other file in libs/** keeps
# full coverage accounting. Each listed file is an SDK production adapter that no hosted test can reach
# (xUnit has no attached Cheat Engine process or Lua state): its lines are untestable, not merely
# untested, and its non-SDK branches keep the tests they allow next to it (for example
# SdkTableRecordMutationPortTests). A lot that adds an Sdk*Port.cs adds its own entry here.
'/d:sonar.coverage.exclusions=externals/**,tests/**,eng/**,libs/CheatEngine.Client.Core/Domains/SdkTableRecordMutationPort.cs,libs/CheatEngine.Client.Core/Domains/SdkAobScanPort.cs,libs/CheatEngine.Client.Core/Domains/IMemoryCodecContextPort.cs,libs/CheatEngine.Client.Core/Domains/SdkRuntimeObservationPort.cs,libs/CheatEngine.Client.Core/Domains/SdkProcessSelectionPort.cs,libs/CheatEngine.Client.Core/Domains/SdkInspectionPort.cs,libs/CheatEngine.Client.Core/Domains/SdkTableFilePort.cs,libs/CheatEngine.Client.Core/Domains/SdkTableRecordLookupPort.cs,libs/CheatEngine.Client.Core/Domains/ValueScanning/SdkValueScanPort.cs,libs/CheatEngine.Client.Core/Domains/Allocations/SdkAllocationPort.cs,libs/CheatEngine.Client.Core/Domains/Assembly/SdkAutoAssemblerPort.cs,libs/CheatEngine.Client.Core/Domains/Assembly/SdkInstructionPort.cs'
# Never set sonar.dotnet.excludeTestProjects: the scanner then strips every analyzer, including source
# generators, from test projects, and their [GeneratedRegex]/[LoggerMessage] partial methods fail with
# CS8795 (https://github.com/SonarSource/sonar-scanner-msbuild/issues/1469). Test projects are analysed as
# test code instead.
"/d:sonar.cs.vscoveragexml.reportsPaths=$coverage/**/*.xml"
"/d:sonar.issue.ignore.multicriteria=$(($ignoredIssues.Key) -join ',')"
"/d:sonar.qualitygate.wait=$env:SONAR_WAIT_QUALITY_GATE"
'/d:sonar.qualitygate.timeout=300'
)
foreach ($issue in $ignoredIssues) {
$arguments += "/d:sonar.issue.ignore.multicriteria.$($issue.Key).ruleKey=$($issue.Rule)"
$arguments += "/d:sonar.issue.ignore.multicriteria.$($issue.Key).resourceKey=$($issue.Resource)"
}
& "$env:RUNNER_TEMP/sonar-scanner/dotnet-sonarscanner.exe" begin @arguments
if ($LASTEXITCODE -ne 0) {
throw "SonarScanner begin failed with exit code $LASTEXITCODE."
}
# Debug, like the coverage leg. The scanner turns TreatWarningsAsErrors off for this build, which is why
# build-test, not this job, gates compilation.
- name: Build
env:
SONAR_USER_HOME: ${{ runner.temp }}/sonar-user-home
run: |
dotnet build CheatEngine.Client.slnx -c Debug --no-restore --no-incremental --disable-build-servers
if ($LASTEXITCODE -ne 0) {
throw "Sonar analysis build failed with exit code $LASTEXITCODE."
}
- name: End analysis
env:
SONARQUBE_SCANNER_PARAMS: '{"sonar.token":"${{ secrets.SONAR_TOKEN }}"}'
SONAR_USER_HOME: ${{ runner.temp }}/sonar-user-home
run: |
& "$env:RUNNER_TEMP/sonar-scanner/dotnet-sonarscanner.exe" end
if ($LASTEXITCODE -ne 0) {
throw "SonarScanner end failed with exit code $LASTEXITCODE."
}
# The only cache a release-reachable workflow touches: analyzer plugins, saved from main only.
- name: Save Sonar analyzer cache from main
if: >-
${{ success()
&& github.ref == 'refs/heads/main'
&& steps.sonar-cache-restore.outputs.cache-hit != 'true' }}
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ runner.temp }}/sonar-user-home/cache
key: sonar-analyzers-${{ runner.os }}-${{ runner.arch }}-${{ env.SONAR_SCANNER_VERSION }}-v1