forked from AnotherSymbiote/WitcherScriptMerger
-
Notifications
You must be signed in to change notification settings - Fork 0
239 lines (215 loc) · 11.8 KB
/
Copy pathrelease.yml
File metadata and controls
239 lines (215 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
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
# Cheap, dotnet-free sanity check, run first so a bad tag fails fast before any
# publish work starts. Also the single source of truth for "version" (a job output)
# every later job consumes - always the value read back from the repo's own
# AssemblyInfo.cs, not the raw tag text, once the two are confirmed equal below.
verify-version:
name: Verify release version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.check.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
# WitcherScriptMerger (the WinForms host) has GenerateAssemblyInfo=false and
# hand-maintains its version in Properties/AssemblyInfo.cs instead (see that
# project's CLAUDE.md's "Compatibility constraint: TFM must keep the explicit 7.0
# OS-version suffix" section) - a `-p:Version=` passed to `dotnet publish` can't
# reach it, unlike WitcherScriptMerger.Headless (see the build job below).
# WitcherScriptMerger.Headless.csproj's own <Version> property is a second,
# independently hand-maintained copy of the same value (see its own comment) with
# nothing else enforcing the two stay in sync. Nothing else enforces either against
# a pushed release tag. Check all three here and fail loudly on any mismatch,
# instead of silently shipping a release with a wrong/inconsistent --version.
- name: Check AssemblyInfo.cs / csproj / tag versions agree
id: check
shell: pwsh
run: |
$tagVersion = $env:GITHUB_REF_NAME -replace '^v', ''
# Select-String matches per-line, so the commented-out SDK boilerplate example
# two lines above the real attribute ("// [assembly: AssemblyVersion(...")
# can never match this ^-anchored pattern - only a line that actually starts
# with "[assembly:" can.
$asmMatch = Select-String -Path 'WitcherScriptMerger/Properties/AssemblyInfo.cs' `
-Pattern '^\[assembly:\s*AssemblyVersion\("([^"]+)"\)\]' | Select-Object -First 1
if (-not $asmMatch) {
throw "Could not find an uncommented [assembly: AssemblyVersion(...)] line in WitcherScriptMerger/Properties/AssemblyInfo.cs"
}
$assemblyVersion = $asmMatch.Matches[0].Groups[1].Value
$csprojMatch = Select-String -Path 'WitcherScriptMerger.Headless/WitcherScriptMerger.Headless.csproj' `
-Pattern '<Version>([^<]+)</Version>' | Select-Object -First 1
if (-not $csprojMatch) {
throw "Could not find a <Version> element in WitcherScriptMerger.Headless/WitcherScriptMerger.Headless.csproj"
}
$csprojVersion = $csprojMatch.Matches[0].Groups[1].Value
if ($csprojVersion -ne $assemblyVersion) {
throw "WitcherScriptMerger.Headless.csproj's <Version> ('$csprojVersion') does not match WitcherScriptMerger/Properties/AssemblyInfo.cs's AssemblyVersion ('$assemblyVersion') - keep them in sync before tagging a release."
}
if ($assemblyVersion -ne $tagVersion) {
throw "Tag '$env:GITHUB_REF_NAME' (version '$tagVersion') does not match WitcherScriptMerger/Properties/AssemblyInfo.cs's AssemblyVersion ('$assemblyVersion'). Bump AssemblyVersion/AssemblyFileVersion there (and WitcherScriptMerger.Headless.csproj's <Version>) before tagging a release."
}
"version=$assemblyVersion" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
# build.yml (dotnet build + dotnet format whitespace --verify-no-changes) only runs on
# pull_request - a tag pushed directly, without going through a PR, would otherwise
# reach the publish/release steps below with zero build or test verification anywhere
# in this path. Mirrors build.yml's own build step (windows-latest, Release
# configuration) and adds dotnet test, gating the publish matrix job on both passing.
test:
name: Build & test
needs: verify-version
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
cache: true
cache-dependency-path: WitcherScriptMerger/WitcherScriptMerger.csproj
- name: Restore
run: dotnet restore WitcherScriptMerger.sln
- name: Build
run: dotnet build WitcherScriptMerger.sln --no-restore --configuration Release
- name: Test
run: dotnet test WitcherScriptMerger.sln --no-build --configuration Release
# Each matrix entry publishes one host/RID combination via the matching checked-in
# Properties/PublishProfiles/<profile>.pubxml (RuntimeIdentifier, SelfContained,
# PublishSingleFile - see each host's own CLAUDE.md's publish section) and uploads the
# result as a build artifact for the packaging job below. Runs on windows-latest for
# all three (matching build.yml's own convention) even though the linux-x64 leg is a
# cross-compile that doesn't strictly require it - producing that binary doesn't need
# a Linux machine, only running it does (see WitcherScriptMerger.Headless/CLAUDE.md).
build:
name: Publish ${{ matrix.name }}
needs: [verify-version, test]
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- name: WitcherScriptMerger (win-x64)
project: WitcherScriptMerger/WitcherScriptMerger.csproj
profile: win-x64
publish-dir: WitcherScriptMerger/bin/Release/net10.0-windows7.0/win-x64/publish
artifact-name: WitcherScriptMerger-win-x64
pass-version: 'false'
- name: WitcherScriptMerger.Headless (win-x64)
project: WitcherScriptMerger.Headless/WitcherScriptMerger.Headless.csproj
profile: win-x64
publish-dir: WitcherScriptMerger.Headless/bin/Release/net10.0/win-x64/publish
artifact-name: WitcherScriptMerger.Headless-win-x64
pass-version: 'true'
- name: WitcherScriptMerger.Headless (linux-x64)
project: WitcherScriptMerger.Headless/WitcherScriptMerger.Headless.csproj
profile: linux-x64
publish-dir: WitcherScriptMerger.Headless/bin/Release/net10.0/linux-x64/publish
artifact-name: WitcherScriptMerger.Headless-linux-x64
pass-version: 'true'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
cache: true
cache-dependency-path: WitcherScriptMerger/WitcherScriptMerger.csproj
# Not passed --no-restore: a RID-specific self-contained publish needs runtime
# packages a plain solution-wide restore wouldn't fetch, so each publish does its
# own implicit restore for its own RID.
#
# -p:Version (WitcherScriptMerger.Headless legs only - matrix.pass-version) sets
# this build's version from verify-version's checked value. GenerateAssemblyInfo
# is off for the WinForms host (matrix.pass-version: 'false' there), so passing it
# there would be a silent no-op - omitted rather than included-but-ignored.
- name: Publish
shell: pwsh
env:
RELEASE_VERSION: ${{ needs.verify-version.outputs.version }}
run: |
$versionArg = @()
if ("${{ matrix.pass-version }}" -eq 'true') {
$versionArg = @("-p:Version=$env:RELEASE_VERSION")
}
dotnet publish "${{ matrix.project }}" -c Release -p:PublishProfile=${{ matrix.profile }} @versionArg
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact-name }}
path: ${{ matrix.publish-dir }}
if-no-files-found: error
# Runs on ubuntu-latest specifically for the linux-x64 asset: `tar` needs to run on a
# filesystem with real Unix permission bits to set/preserve the executable bit on the
# WitcherScriptMerger.Headless binary before archiving - building the tarball on
# windows-latest (NTFS has no such bit) was tried and confirmed to produce a
# non-executable entry, defeating the entire reason tar (not zip) was chosen for this
# asset. `chmod +x` here is unconditional and explicit rather than relying on the
# download-artifact transfer having preserved any prior mode bit.
package-release:
name: Package & create release
needs: [verify-version, build]
runs-on: ubuntu-latest
steps:
- name: Download WitcherScriptMerger (win-x64)
uses: actions/download-artifact@v4
with:
name: WitcherScriptMerger-win-x64
path: publish/WitcherScriptMerger-win-x64
- name: Download WitcherScriptMerger.Headless (win-x64)
uses: actions/download-artifact@v4
with:
name: WitcherScriptMerger.Headless-win-x64
path: publish/WitcherScriptMerger.Headless-win-x64
- name: Download WitcherScriptMerger.Headless (linux-x64)
uses: actions/download-artifact@v4
with:
name: WitcherScriptMerger.Headless-linux-x64
path: publish/WitcherScriptMerger.Headless-linux-x64
# Packages each publish output (including the <AssemblyName>.dll.config the SDK
# already copies in next to the exe - see each host's CLAUDE.md's "Publishing"
# section for why that file, not App.config itself, is what
# ConfigurationManager actually reads at runtime) as one archive per host/RID
# combination. zip for the two win-x64 outputs; tar.gz (not zip) for linux-x64
# specifically - zip doesn't preserve the Unix executable bit, so an unzipped
# Linux binary wouldn't be runnable.
- name: Package release assets
env:
RELEASE_VERSION: ${{ needs.verify-version.outputs.version }}
run: |
set -euo pipefail
mkdir -p dist
chmod +x "publish/WitcherScriptMerger.Headless-linux-x64/WitcherScriptMerger.Headless"
( cd publish/WitcherScriptMerger-win-x64 && zip -r "../../dist/WitcherScriptMerger-${RELEASE_VERSION}-win-x64.zip" . )
( cd publish/WitcherScriptMerger.Headless-win-x64 && zip -r "../../dist/WitcherScriptMerger.Headless-${RELEASE_VERSION}-win-x64.zip" . )
( cd publish/WitcherScriptMerger.Headless-linux-x64 && tar -czf "../../dist/WitcherScriptMerger.Headless-${RELEASE_VERSION}-linux-x64.tar.gz" . )
# --repo is required here: this job never checks out the repo (it only downloads
# build artifacts from earlier jobs), so gh has no local git context to
# auto-detect the target repository from - confirmed by the first real run of
# this workflow (tag v0.6.2) failing with "failed to run git: fatal: not a git
# repository (or any of the parent directories): .git" on exactly this step.
# $GITHUB_REPOSITORY (a built-in Actions env var, "owner/repo") sidesteps that
# without needing an actions/checkout step just for this. Same class of mistake
# this repo's own CLAUDE.md already warns about for `gh pr create` run without
# --repo/--base - explicit is required, not optional, for any `gh` invocation
# that can't infer its target from a real git working directory.
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_VERSION: ${{ needs.verify-version.outputs.version }}
run: |
set -euo pipefail
gh release create "$GITHUB_REF_NAME" \
"dist/WitcherScriptMerger-${RELEASE_VERSION}-win-x64.zip" \
"dist/WitcherScriptMerger.Headless-${RELEASE_VERSION}-win-x64.zip" \
"dist/WitcherScriptMerger.Headless-${RELEASE_VERSION}-linux-x64.tar.gz" \
--repo "$GITHUB_REPOSITORY" \
--title "$GITHUB_REF_NAME" \
--generate-notes