Repository navigation
283 lines (248 loc) · 11.1 KB
/
Copy pathrelease.yml
File metadata and controls
283 lines (248 loc) · 11.1 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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
name: Release
on:
push:
# Release tags: release_1.3.0 (the update checker parses the three-part version).
tags: ['release_*']
workflow_dispatch:
inputs:
version:
description: Version to package (for example 1.3.0)
required: true
type: string
# The tag run creates the GitHub release with the packaged zips.
permissions:
contents: write
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
jobs:
verify:
name: verify version and tests
runs-on: windows-latest
timeout-minutes: 30
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v7
- name: Resolve and verify version
id: version
shell: pwsh
run: |
if ('${{ github.event_name }}' -eq 'workflow_dispatch') {
$version = '${{ inputs.version }}'
} else {
$version = '${{ github.ref_name }}' -replace '^release_', ''
}
if ($version -notmatch '^\d+\.\d+\.\d+$') {
throw "Invalid version '$version'. Use a three-part version such as 1.3.0."
}
[xml]$project = Get-Content -LiteralPath SimpleXisoDrive/SimpleXisoDrive.csproj
$assemblyVersion = $project.Project.PropertyGroup.AssemblyVersion | Select-Object -First 1
if ($assemblyVersion -ne $version) {
throw "Version mismatch: '$version' does not match <AssemblyVersion> '$assemblyVersion' in SimpleXisoDrive.csproj."
}
"version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
- name: Setup .NET
# 10.0.x, not `global-json-file`: the repo pins `10.0.0` with
# rollForward latestMajor, which setup-dotnet cannot resolve.
uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x
cache: true
cache-dependency-path: |
**/*.csproj
global.json
- name: Restore
run: dotnet restore CSharp_SimpleXisoDrive.sln
- name: Build
run: dotnet build CSharp_SimpleXisoDrive.sln --no-restore --configuration Release
- name: Test
run: >-
dotnet test CSharp_SimpleXisoDrive.sln
--no-build --configuration Release
--blame-hang-timeout 15m
--blame-hang-dump-type none
package:
name: package ${{ matrix.rid }}
# Each runtime is packaged on its own operating system so the native apphost
# matches the target (Dokan on Windows, FUSE 3 on Linux and macOS).
runs-on: ${{ matrix.os }}
timeout-minutes: 30
needs: verify
strategy:
# Package every runtime even when one fails; the release job waits for all.
fail-fast: false
matrix:
include:
- rid: win-x64
os: windows-latest
project: SimpleXisoDrive/SimpleXisoDrive.csproj
binary: SimpleXisoDrive.exe
- rid: win-arm64
os: windows-latest
project: SimpleXisoDrive/SimpleXisoDrive.csproj
binary: SimpleXisoDrive.exe
- rid: linux-x64
os: ubuntu-latest
project: SimpleXisoDrive.Unix/SimpleXisoDrive.Unix.csproj
binary: SimpleXisoDrive
- rid: linux-arm64
os: ubuntu-latest
project: SimpleXisoDrive.Unix/SimpleXisoDrive.Unix.csproj
binary: SimpleXisoDrive
- rid: osx-x64
os: macos-latest
project: SimpleXisoDrive.Unix/SimpleXisoDrive.Unix.csproj
binary: SimpleXisoDrive
- rid: osx-arm64
os: macos-latest
project: SimpleXisoDrive.Unix/SimpleXisoDrive.Unix.csproj
binary: SimpleXisoDrive
steps:
- uses: actions/checkout@v7
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x
cache: true
cache-dependency-path: |
**/*.csproj
global.json
- name: Publish
# Framework-dependent single-file executable, matching the local release
# bundles: one executable per zip, no runtime included.
run: >-
dotnet publish ${{ matrix.project }}
--configuration Release
--runtime ${{ matrix.rid }}
--self-contained false
-p:PublishSingleFile=true
--output publish/${{ matrix.rid }}
- name: Package release zip
# Each zip contains the single-file executable plus ReadMe.md,
# WhatsNew.md and LICENSE.txt, matching the local release bundles.
# Built with the .NET ZipArchive API (portable across the runner OSes)
# so the Unix executable mode can be stored explicitly.
shell: pwsh
run: |
$zip = "release_${{ needs.verify.outputs.version }}_${{ matrix.rid }}.zip"
$staging = "bundle/${{ matrix.rid }}"
New-Item -ItemType Directory -Path $staging -Force | Out-Null
Copy-Item -LiteralPath "publish/${{ matrix.rid }}/${{ matrix.binary }}" -Destination $staging
Copy-Item -LiteralPath ReadMe.md, WhatsNew.md, LICENSE.txt -Destination $staging
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
$archive = [System.IO.Compression.ZipFile]::Open($zip, [System.IO.Compression.ZipArchiveMode]::Create)
try {
foreach ($file in Get-ChildItem -LiteralPath $staging -File) {
$entry = $archive.CreateEntry($file.Name, [System.IO.Compression.CompressionLevel]::Optimal)
$entry.LastWriteTime = $file.LastWriteTime
if (-not $IsWindows) {
# 0o100755 for the executable, 0o100644 for the documentation.
$entry.ExternalAttributes = if ($file.Name -eq '${{ matrix.binary }}') { 0x81ED0000 } else { 0x81A40000 }
}
$source = [System.IO.File]::OpenRead($file.FullName)
try {
$target = $entry.Open()
try {
$source.CopyTo($target)
} finally {
$target.Dispose()
}
} finally {
$source.Dispose()
}
}
} finally {
$archive.Dispose()
}
Get-Item -LiteralPath $zip | Select-Object Name, Length
- name: Upload package
uses: actions/upload-artifact@v7
with:
name: release-${{ matrix.rid }}
path: release_*.zip
if-no-files-found: error
github-release:
name: create GitHub release
# Manual runs stop at packaged artifacts; only a tag creates the release.
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [verify, package]
steps:
- name: Checkout
uses: actions/checkout@v7
with:
# Full history and tags so the previous release tag can be found for
# the Full Changelog link.
fetch-depth: 0
- name: Build release notes
# Curated release body: prerequisites (the downloads are
# framework-dependent and need the .NET runtime plus a mount driver)
# followed by the version's section from WhatsNew.md.
shell: pwsh
env:
VERSION: ${{ needs.verify.outputs.version }}
run: |
$whatsNew = Get-Content -LiteralPath WhatsNew.md -Raw
$marker = "# What's New in $env:VERSION"
$start = $whatsNew.IndexOf($marker, [StringComparison]::Ordinal)
if ($start -lt 0) {
throw "WhatsNew.md has no '$marker' section."
}
$next = $whatsNew.IndexOf("# What's New in ", $start + $marker.Length, [StringComparison]::Ordinal)
$section = if ($next -lt 0) { $whatsNew.Substring($start) } else { $whatsNew.Substring($start, $next - $start) }
# Demote the section headings by one level so the release body hierarchy stays intact.
$section = ($section -replace '(?m)^(#{1,5}) ', '$1# ').Trim()
$previous = ""
try {
$previous = (git describe --tags --abbrev=0 HEAD^ 2>$null).Trim()
} catch {
$previous = ""
}
# Single-quoted here-string: the Markdown backticks stay literal.
$template = @'
Read-only mounting of original Xbox ISO/XISO/CISO images, Xbox ISO CHD images and ZArchive files on Windows, Linux and macOS. The downloads are framework-dependent single-file executables, so the .NET runtime and the platform mount driver must be installed first.
## Before you start
| Platform | What to install |
| --- | --- |
| All | [.NET 10.0 Runtime](https://dotnet.microsoft.com/download/dotnet/10.0) - the base runtime is enough (the Desktop Runtime is not required). |
| Windows | [Dokan 2.x](https://github.com/dokan-dev/dokany/releases) - restart Windows after installing so the driver loads. |
| Linux | FUSE 3: `libfuse3` plus the `fuse3` tools, for example `sudo apt install libfuse3-3 fuse3` (Fedora: `sudo dnf install fuse3 fuse3-libs`; Arch: `sudo pacman -S fuse3`). Make sure `/dev/fuse` exists (`sudo modprobe fuse`) and `fusermount3` is on `PATH`. |
| macOS | [macFUSE](https://macfuse.io) - allow the system extension when prompted. macOS 15.4 or later can use the FSKit backend, which needs no kernel extension. |
## Downloads
| Bundle | Platform |
| --- | --- |
| `release_{VERSION}_win-x64.zip`, `release_{VERSION}_win-arm64.zip` | Windows x64 / ARM64 (Dokan) |
| `release_{VERSION}_linux-x64.zip`, `release_{VERSION}_linux-arm64.zip` | Linux x64 / ARM64 (FUSE 3) |
| `release_{VERSION}_osx-x64.zip`, `release_{VERSION}_osx-arm64.zip` | macOS Intel / Apple silicon (macFUSE) |
Each zip contains the single-file executable plus `ReadMe.md`, `LICENSE.txt` and `WhatsNew.md`. On Linux and macOS the executable bit is already set, so extract and run `./SimpleXisoDrive`. On Windows run `SimpleXisoDrive.exe`.
'@
$template = $template.Replace('{VERSION}', $env:VERSION)
$compare = if ($previous) {
"`n`n---`n`n**Full Changelog**: https://github.com/$env:GITHUB_REPOSITORY/compare/$previous...release_$env:VERSION`n"
} else {
"`n"
}
$notes = $template.TrimEnd() + "`n`n" + $section + $compare
Set-Content -LiteralPath release-notes.md -Value $notes -Encoding utf8
Write-Output "release-notes.md written ($($notes.Length) chars, previous tag: '$previous')"
- name: Download packages
uses: actions/download-artifact@v8
with:
pattern: release-*
merge-multiple: true
path: assets
- name: Create release
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.verify.outputs.version }}
run: >-
gh release create "${GITHUB_REF_NAME}"
--repo "${GITHUB_REPOSITORY}"
--title "SimpleXisoDrive ${VERSION}"
--notes-file release-notes.md
assets/release_*.zip