-
Notifications
You must be signed in to change notification settings - Fork 13
429 lines (395 loc) · 26.4 KB
/
Copy pathattach-arm64-experimental.yml
File metadata and controls
429 lines (395 loc) · 26.4 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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# 260612Cl 追加: win-arm64 portable ZIP を既存 release へ後付け添付する (配布の段階導入の第 2 段階、方針 §6.1-6.2)。
# 260613Cl Phase C: arm64 MSI (WiX、ReciProSetup-arm64.msi) も同時に添付する (WiX計画 §5.2)。
# 段階 1 = build-arm64-portable.yml (artifact のみ。本ワークフローが reusable workflow として再利用する)
# 段階 3 = release.yml の後続 job へ統合 (数リリース安定後)
# 設計の要点 (方針 .project-guidance/ReciPro_ARM64化方針.md):
# - tag は作らない: 既存 release tag へ gh release upload するだけなので、v* tag 削除禁止 ruleset と
# 原子的 release 設計 (260530Cl) に一切干渉しない。x64 配布物にも触れない
# - smoke は upload の前 (windows-11-arm = arm64 ネイティブランナー): 想定故障モードが全て
# 「ビルド緑・実行時死亡」型 (BadImageFormat / DLL 黙殺フォールバック / 起動即死) のため、
# PE 検査 + LoadLibrary + ReciPro.exe --smoke (NativeWrapper/Xraylib 明示アサート) + GUI 起動を実機で行う。
# GL のフル検証は含めない (GLOn12 の flakiness、方針 §6.3。実機検証 = Phase 2 で完了済み)
# - 260613Cl checksum (SHA256SUMS-arm64.txt) の生成・添付を廃止 (checksum 公開は同一チャネルでは
# 改竄対策にならず、リリースページで初心者を惑わすため。作者決定。真正性保証は SignPath 署名で行う)
# - 添付失敗・やり直しは asset の手動削除 (gh release delete-asset <tag> <name>) → 再実行で復旧する
name: Attach ARM64 assets (ZIP + MSI) to release (experimental)
on:
workflow_dispatch:
inputs:
tag:
description: "添付先 release tag (例: v.4.926)。空なら最新 release"
type: string
required: false
default: ""
dry_run:
description: "smoke と ZIP 生成まで実行し、release への upload はしない (artifact で検分可能)"
type: boolean
default: false
permissions:
contents: read
# 同時二重実行による部分アップロード競合を防ぐ
concurrency:
group: attach-arm64-experimental
cancel-in-progress: false
jobs:
# ---- 1. 事前検査: 8 分のビルドを走らせる前に「添付できない理由」を全て洗い出して即失敗させる ----
preflight:
runs-on: ubuntu-latest # 解析と gh API だけなので最速のランナーで (pwsh は ubuntu にもプリインストール)
timeout-minutes: 5
outputs:
tag: ${{ steps.check.outputs.tag }}
version: ${{ steps.check.outputs.version }}
zip_name: ${{ steps.check.outputs.zip_name }}
msi_name: ${{ steps.check.outputs.msi_name }} # 260613Cl Phase C: arm64 MSI
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0 # tag commit の解決と ancestry 検査に全履歴 + tags が必要
- name: Resolve target release and verify preconditions
id: check
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DRY_RUN: ${{ inputs.dry_run }}
TAG_INPUT: ${{ inputs.tag }} # 式の直接展開ではなく env 経由で渡す (クォート事故防止の定石)
run: |
# dry_run はワークフロー自体の実走テスト用に検査を警告へ緩和する (release 無し・版数不一致でも続行)。
# 本添付 (dry_run=false) は全検査を強制
$dryRun = $env:DRY_RUN -eq 'true'
# 本添付は master からのみ許可 (release 資産は master の歴史と対応するため)
if ($env:GITHUB_REF_NAME -ne 'master' -and -not $dryRun) {
throw "Real attach must run from master (current: $env:GITHUB_REF_NAME). Use dry_run for branch testing."
}
# release.yml と同じ規則で Version.cs からバージョンを得る
$content = Get-Content "ReciPro/Version.cs" -Raw
if ($content -notmatch 'ver(\d+\.\d+)\(') {
throw "Could not parse version from Version.cs History."
}
$ver = $Matches[1]
# 添付先 tag: 入力が空なら最新 release
$tag = $env:TAG_INPUT
if (-not $tag) {
$tag = (gh release view --json tagName -q '.tagName')
if ($LASTEXITCODE -ne 0 -or -not $tag) {
$global:LASTEXITCODE = 0
if ($dryRun) { $tag = "v.$ver"; Write-Warning "No release found; dry_run continues with synthesized tag '$tag' (used for naming only)." }
else { throw "No existing release found (and no tag input given)." }
}
}
# release の存在確認 (入力 tag の typo もここで止まる)
gh release view $tag --json name | Out-Null
$releaseExists = ($LASTEXITCODE -eq 0)
$global:LASTEXITCODE = 0
if (-not $releaseExists -and -not $dryRun) { throw "Release '$tag' does not exist." }
# ビルドされる exe / ZIP 名のバージョンと添付先 release の版数一致を強制
# (古い release に新しいバイナリを付ける事故、およびその逆を防ぐ)
if ("v.$ver" -ne $tag) {
if ($dryRun) { Write-Warning "Version mismatch: Version.cs=v.$ver, target=$tag (dry_run continues)." }
else { throw "Version mismatch: Version.cs says v.$ver but target release is $tag. Attach only to the matching release." }
}
if ($releaseExists) {
# ancestry 検査 (codex レビュー指摘): 添付する arm64 ZIP は「release 時点の commit」ではなく
# 「今の master (GITHUB_SHA)」からビルドされる。arm64 ビルド基盤が release 後に入ったため
# tag からのビルドは不可能で、これは backfill の宿命として受容する。その代わり
# ① tag の commit がビルド commit の祖先であること (別系列の歴史への添付を拒否)
# ② 出所 (両 SHA) を step summary に明記すること
# で「同名バージョンの無関係バイナリ」を防ぐ。段階 3 (release.yml 統合) では同一 run ビルドになり自然解消
$tagSha = (git rev-list -n 1 $tag 2>$null)
if ($LASTEXITCODE -ne 0 -or -not $tagSha) { throw "Tag '$tag' was not found in the git history." }
git merge-base --is-ancestor $tagSha $env:GITHUB_SHA
if ($LASTEXITCODE -ne 0) { throw "Release tag commit $tagSha is not an ancestor of build commit $($env:GITHUB_SHA)." }
@(
"## ARM64 attach provenance"
"- Target release: $tag (commit $tagSha)"
"- ARM64 ZIP built from: $($env:GITHUB_SHA) ($($env:GITHUB_REF_NAME))"
if ($tagSha -ne $env:GITHUB_SHA) { "- Note: the ARM64 binary includes commits made after the release (experimental backfill)." }
) | Add-Content $env:GITHUB_STEP_SUMMARY
}
# asset 衝突検査: --clobber による差し替えはしない方針のため、既存なら手動削除を促して失敗
# 260613Cl 作者指示で命名簡素化 (旧: ReciPro-v.X-win-arm64-experimental-portable.zip)。checksum は公開廃止
# 260613Cl Phase C: arm64 MSI (WiX) も添付対象に追加
$zipName = "ReciPro-v.$ver-arm64.zip"
$msiName = "ReciProSetup-arm64.msi"
if ($releaseExists) {
$assets = @(gh release view $tag --json assets -q '.assets[].name')
foreach ($name in @($zipName, $msiName)) {
if ($assets -contains $name) {
if ($dryRun) { Write-Warning "Asset '$name' already exists on $tag (dry_run continues)." }
else { throw "Asset '$name' already exists on $tag. Delete it manually first (gh release delete-asset $tag $name) and re-run." }
}
}
}
Write-Host "Target: $tag / version $ver / zip $zipName / msi $msiName"
"tag=$tag" >> $env:GITHUB_OUTPUT
"version=$ver" >> $env:GITHUB_OUTPUT
"zip_name=$zipName" >> $env:GITHUB_OUTPUT
"msi_name=$msiName" >> $env:GITHUB_OUTPUT
# ---- 2. ビルド: 段階 1 のワークフローをそのまま再利用 (検査込み。二重保守を作らない) ----
build:
needs: preflight
uses: ./.github/workflows/build-arm64-portable.yml
permissions:
contents: read
# ---- 3. arm64 実機 smoke → 合格時のみ release へ添付 ----
smoke-and-attach:
needs: [preflight, build]
runs-on: windows-11-arm # arm64 ネイティブランナー (public repo 無料、方針 §1.3)
timeout-minutes: 30
permissions:
contents: write # gh release upload に必要 (他 job は read のまま)
steps:
- name: Checkout
uses: actions/checkout@v5 # native\win-arm64\ の LoadLibrary 検査用 (バンドル内 DLL と同一ソース)
- name: Verify version consistency between preflight and build
shell: pwsh
run: |
# 同一 SHA から解析しているので一致するはずだが、出力配線のミスをここで検出する
if ('${{ needs.preflight.outputs.version }}' -ne '${{ needs.build.outputs.version }}') {
throw "Version mismatch: preflight=${{ needs.preflight.outputs.version }} build=${{ needs.build.outputs.version }}"
}
- name: Download build artifact
# v7 = upload-artifact@v6 と同日リリースの Node 24 対 (download 側は 2025-08 の v5 breaking の分 major が 1 進んでいる)。
# 同一 run 内 (reusable workflow がアップロードした artifact を含む) は name 指定だけで取得できる
uses: actions/download-artifact@v7
with:
name: ${{ needs.build.outputs.artifact_name }}
path: ${{ runner.temp }}/arm64-artifact
- name: Download arm64 MSI artifact # 260613Cl Phase C
uses: actions/download-artifact@v7
with:
name: ${{ needs.build.outputs.msi_artifact_name }}
path: ${{ runner.temp }}/arm64-msi
- name: "Smoke 1: PE machine + native LoadLibrary (arm64-native pwsh)"
shell: pwsh
run: |
$dir = Join-Path $env:RUNNER_TEMP "arm64-artifact/ReciPro"
if (-not (Test-Path (Join-Path $dir "ReciPro.exe"))) {
throw "Artifact layout unexpected: ReciPro.exe not found in $dir"
}
# PE machine 再検査 (ビルド側でも検査済みだが、artifact 往復後の実物を添付直前に確認する)
function Get-PEMachine([string]$path) {
$b = [IO.File]::ReadAllBytes($path)
$pe = [BitConverter]::ToInt32($b, 0x3C)
return [BitConverter]::ToUInt16($b, $pe + 4)
}
foreach ($f in @("ReciPro.exe", "Crystallography.Native.dll")) {
$m = Get-PEMachine (Join-Path $dir $f)
"{0}: machine=0x{1:X4}" -f $f, $m
if ($m -ne 0xAA64) { throw "$f is not ARM64 (machine=0x$($m.ToString('X4')))" }
}
# arm64 ネイティブの pwsh で実 LoadLibrary (= AA64 PE + /MT 静的 CRT がクリーン環境で本当にロードできるか)。
# libxrl/glfw のリポ側コピーはここで検査し、単一ファイル exe バンドル内の実物は後続の --smoke が検査する
# (xraylib self-test + glfwInit。リポ側だけでは配布物の検査にならない、codex レビュー指摘)
foreach ($dll in @(
(Join-Path $dir "Crystallography.Native.dll"),
"ReciPro\native\win-arm64\libxrl-11.dll",
"ReciPro\native\win-arm64\glfw3.dll")) {
$abs = (Resolve-Path $dll).Path
$h = [System.Runtime.InteropServices.NativeLibrary]::Load($abs)
[System.Runtime.InteropServices.NativeLibrary]::Free($h)
Write-Host "LoadLibrary OK: $abs"
}
- name: "Smoke 2: ReciPro.exe --smoke (NativeWrapper / Xraylib explicit assertion)"
shell: pwsh
run: |
# 黙殺フォールバック型 (動くが native 無効で異常に遅い) を CI で確実に検出する (方針 §7)。
# 初回起動は単一ファイルバンドルの抽出 + Defender スキャンで時間がかかるためタイムアウトは長めに
$dir = Join-Path $env:RUNNER_TEMP "arm64-artifact/ReciPro"
$out = Join-Path $env:RUNNER_TEMP "smoke-result.txt"
# 引数はパスに空白が入っても壊れないよう明示的にクォートする (Start-Process は自動クォートしない)
$p = Start-Process -FilePath (Join-Path $dir "ReciPro.exe") -ArgumentList @("--smoke", "`"$out`"") -WorkingDirectory $dir -PassThru
if (-not $p.WaitForExit(600000)) {
Stop-Process -Id $p.Id -Force
throw "--smoke did not finish within 10 minutes."
}
if (-not (Test-Path $out)) { throw "--smoke produced no output file (exit $($p.ExitCode))." }
$result = Get-Content $out -Raw
Write-Host $result
if ($p.ExitCode -ne 0) { throw "--smoke reported failure (exit $($p.ExitCode)). See output above." }
# glfwEnabled は単一ファイルバンドル内の glfw3.dll を実際に抽出・glfwInit して検査した結果
foreach ($expect in @("arch=Arm64", "nativeEnabled=True", "xraylibEnabled=True", "glfwEnabled=True")) {
if ($result -notmatch [regex]::Escape($expect)) { throw "--smoke output missing expected '$expect'." }
}
- name: "Smoke 3: GUI launch (main window appears and survives)"
shell: pwsh
run: |
# 起動即死 (例外ダイアログ前のクラッシュ等) の検出。ウィンドウ生成までを確認して終了する。
# GL 検証はしない (方針 §6.3): GPU 無しの hosted runner は GDI Generic GL 1.1 のみで、
# GLControlAlpha の静的初期化 (checkSupportedVersion = GL コンテキスト実生成) が WndProc 内例外
# → ThreadExceptionDialog ("Microsoft .NET") のモーダル表示で永久ブロックする (run 27383602242/27384362544 で実証)。
# CRYSTALLOGRAPHY_DISABLE_OPENGL=1 (260612Cl GLControlAlpha + FormMain) で GL 判定を丸ごとスキップし
# 3D 無効モードの起動経路を検査する。GL の実機検証は Phase 2 (Surface) で完了済み、
# バンドル内 glfw3.dll のロードは Smoke 2 (glfwInit) が検査済み
# 260612Cl 初回 dry_run (run 27382975111) の知見で改訂:
# - FormMain_Load は進捗ダイアログ (Owner 付き = Process.MainWindowHandle に数えられない) を出した後、
# 全子フォームを構築してから FormMain を表示する。Process.MainWindowHandle 頼みでは進捗が見えない
# - → EnumWindows でプロセスの全トップレベルウィンドウ (hidden/owned 含む) と CPU 時間を定期ログし、
# 「unowned + visible + タイトル 'ReciPro*'」(= Load 完了後の FormMain) を合格条件にする。
# タイトル検査はエラーダイアログの誤合格防止 (codex レビュー指摘) を兼ねる
# - タイムアウト時はデスクトップをスクリーンショットして artifact 化 (次ステップ)
Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
public static class WinEnum {
delegate bool EnumProc(IntPtr h, IntPtr lp);
[DllImport("user32.dll")] static extern bool EnumWindows(EnumProc cb, IntPtr lp);
[DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
[DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder sb, int n);
[DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr h, uint cmd);
public static List<string> List(int pid) {
var r = new List<string>();
EnumWindows((h, lp) => {
uint wpid; GetWindowThreadProcessId(h, out wpid);
if (wpid == pid) {
var sb = new StringBuilder(512); GetWindowText(h, sb, 512);
r.Add((IsWindowVisible(h) ? "visible" : "hidden") + "|" + (GetWindow(h, 4) != IntPtr.Zero ? "owned" : "unowned") + "|" + sb);
}
return true;
}, IntPtr.Zero);
return r;
}
}
'@
$dir = Join-Path $env:RUNNER_TEMP "arm64-artifact/ReciPro"
$env:CRYSTALLOGRAPHY_DISABLE_OPENGL = "1" # 上記コメント参照 (タイトルは "ReciPro ... (3D rendering disable mode)" になる)
$p = Start-Process -FilePath (Join-Path $dir "ReciPro.exe") -WorkingDirectory $dir -PassThru
$deadline = (Get-Date).AddSeconds(480) # 初回起動は全子フォーム構築 + JIT で CI の非力な VM では数分かかり得る
$shown = $false
$lastLog = [DateTime]::MinValue
while ((Get-Date) -lt $deadline) {
if ($p.HasExited) { throw "ReciPro.exe exited prematurely (exit $($p.ExitCode))." }
$windows = [WinEnum]::List($p.Id)
if ($windows | Where-Object { $_ -like 'visible|unowned|ReciPro*' }) { $shown = $true; break }
if (((Get-Date) - $lastLog).TotalSeconds -ge 30) {
$p.Refresh()
Write-Host ("t={0:f0}s cpu={1:f1}s windows: {2}" -f ((Get-Date) - $p.StartTime).TotalSeconds, $p.TotalProcessorTime.TotalSeconds, ($windows -join ' / '))
$lastLog = Get-Date
}
Start-Sleep -Seconds 3
}
if (-not $shown) {
# 失敗診断: デスクトップのスクリーンショット (hosted runner は interactive desktop session) と最終ウィンドウ一覧
Write-Host "Final window list: $(([WinEnum]::List($p.Id)) -join ' / ')"
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
$bounds = [System.Windows.Forms.SystemInformation]::VirtualScreen
$bmp = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height
[System.Drawing.Graphics]::FromImage($bmp).CopyFromScreen($bounds.X, $bounds.Y, 0, 0, $bmp.Size)
$bmp.Save((Join-Path $env:RUNNER_TEMP "smoke3-desktop.png"))
Stop-Process -Id $p.Id -Force
throw "ReciPro main window did not appear within 480 s (see logged window lists and smoke3-desktop.png artifact)."
}
Write-Host "Main window appeared: $(([WinEnum]::List($p.Id) | Where-Object { $_ -like 'visible|*' }) -join ' / ')"
Start-Sleep -Seconds 10 # 表示直後のクラッシュ検出
if ($p.HasExited) { throw "ReciPro.exe crashed shortly after showing the main window (exit $($p.ExitCode))." }
Stop-Process -Id $p.Id -Force
Write-Host "GUI launch smoke OK."
- name: "Smoke 4: arm64 MSI (Template check, per-user install, --smoke, uninstall)" # 260613Cl Phase C
shell: pwsh
run: |
# MSI の Template/プロパティ検査 (x64 機での誤実行は Windows Installer が拒否するが、配布前に自前でも確認)
$msi = Join-Path $env:RUNNER_TEMP "arm64-msi/${{ needs.preflight.outputs.msi_name }}"
if (-not (Test-Path $msi)) { throw "MSI artifact not found: $msi" }
$wi = New-Object -ComObject WindowsInstaller.Installer
$db = $wi.GetType().InvokeMember('OpenDatabase','InvokeMethod',$null,$wi,@($msi,0))
$si = $db.GetType().InvokeMember('SummaryInformation','GetProperty',$null,$db,@(0))
$template = $si.GetType().InvokeMember('Property','GetProperty',$null,$si,@(7))
Write-Host "MSI Template = $template"
if ($template -notlike 'Arm64*') { throw "MSI Template is not Arm64: $template" }
function Get-MsiProp($name) {
$view = $db.GetType().InvokeMember('OpenView','InvokeMethod',$null,$db,@("SELECT Value FROM Property WHERE Property='$name'"))
$view.GetType().InvokeMember('Execute','InvokeMethod',$null,$view,$null) | Out-Null
$rec = $view.GetType().InvokeMember('Fetch','InvokeMethod',$null,$view,$null)
if ($rec) { $rec.GetType().InvokeMember('StringData','GetProperty',$null,$rec,@(1)) } else { $null }
}
$uc = Get-MsiProp 'UpgradeCode'
if ($uc -ne '{A81303DC-E1CA-44F3-AE3A-6C7CAC08E8D8}') { throw "UpgradeCode mismatch: $uc" }
$pv = Get-MsiProp 'ProductVersion'
if ($pv -ne '0.${{ needs.preflight.outputs.version }}') { throw "ProductVersion mismatch: $pv" }
# MSI 版は framework-dependent → smoke 実行に .NET 10 Desktop Runtime (arm64) が必要。
# ランナーに無ければ dotnet-install で導入 (DOTNET_ROOT は同一ステップ内の子プロセスに引き継がれる)
$hasDesktop = $false
try { $hasDesktop = ((dotnet --list-runtimes) | Select-String "Microsoft.WindowsDesktop.App 10\.") -ne $null } catch {}
if (-not $hasDesktop) {
Write-Host "Installing .NET 10 Desktop Runtime (arm64) for the smoke test..."
Invoke-WebRequest https://dot.net/v1/dotnet-install.ps1 -OutFile "$env:RUNNER_TEMP\dotnet-install.ps1"
& "$env:RUNNER_TEMP\dotnet-install.ps1" -Runtime windowsdesktop -Channel 10.0 -Architecture arm64 -InstallDir "$env:RUNNER_TEMP\dotnet"
$env:DOTNET_ROOT = "$env:RUNNER_TEMP\dotnet"
}
# per-user silent install (ALLUSERS/MSIINSTALLPERUSER は渡さない = per-user 固定パッケージの既定)
$p = Start-Process msiexec -ArgumentList '/i', "`"$msi`"", '/qn', '/l*v', "$env:RUNNER_TEMP\msi-install.log" -Wait -PassThru
if ($p.ExitCode -ne 0) { throw "MSI install failed (exit $($p.ExitCode)). See msi-install.log artifact." }
$appDir = "$env:LOCALAPPDATA\Crystallography Software\ReciPro"
if (-not (Test-Path "$appDir\ReciPro.exe")) { throw "installed ReciPro.exe missing in $appDir" }
# インストール実体で --smoke (黙殺フォールバック型故障の検出。Smoke 2 と同一アサート)
$out = Join-Path $env:RUNNER_TEMP "msi-smoke-result.txt"
$p = Start-Process -FilePath "$appDir\ReciPro.exe" -ArgumentList @("--smoke", "`"$out`"") -WorkingDirectory $appDir -PassThru
if (-not $p.WaitForExit(600000)) { Stop-Process -Id $p.Id -Force; throw "--smoke (MSI install) did not finish within 10 minutes." }
if (-not (Test-Path $out)) { throw "--smoke produced no output file (exit $($p.ExitCode))." }
$result = Get-Content $out -Raw
Write-Host $result
if ($p.ExitCode -ne 0) { throw "--smoke reported failure (exit $($p.ExitCode))." }
foreach ($expect in @("arch=Arm64", "nativeEnabled=True", "xraylibEnabled=True", "glfwEnabled=True")) {
if ($result -notmatch [regex]::Escape($expect)) { throw "--smoke output missing expected '$expect'." }
}
# アンインストールしてランナーを汚さない (残骸検査込み)
$code = Get-MsiProp 'ProductCode'
$p = Start-Process msiexec -ArgumentList '/x', $code, '/qn', '/l*v', "$env:RUNNER_TEMP\msi-uninstall.log" -Wait -PassThru
if ($p.ExitCode -ne 0) { throw "MSI uninstall failed (exit $($p.ExitCode))." }
if (Test-Path "$appDir\ReciPro.exe") { throw "uninstall left ReciPro.exe behind" }
Write-Host "MSI smoke OK (install -> --smoke -> uninstall)."
- name: Upload smoke diagnostics on failure
if: failure()
uses: actions/upload-artifact@v6
with:
name: smoke3-diagnostics
path: |
${{ runner.temp }}/smoke3-desktop.png
${{ runner.temp }}/smoke-result.txt
${{ runner.temp }}/msi-smoke-result.txt
${{ runner.temp }}/msi-install.log
${{ runner.temp }}/msi-uninstall.log
if-no-files-found: ignore
- name: Create release ZIP
id: assets
shell: pwsh
run: |
# x64 portable ZIP (release.yml) と同じ構造 (ZIP 直下に ReciPro\ フォルダ) に合わせる
# 260613Cl checksum (SHA256SUMS-arm64.txt) の生成を廃止
$zip = Join-Path $env:RUNNER_TEMP "${{ needs.preflight.outputs.zip_name }}"
Compress-Archive -Path (Join-Path $env:RUNNER_TEMP "arm64-artifact/ReciPro") -DestinationPath $zip -Force
"zip=$zip" >> $env:GITHUB_OUTPUT
# 260613Cl Phase C: smoke 済み MSI も添付対象に
$msi = Join-Path $env:RUNNER_TEMP "arm64-msi/${{ needs.preflight.outputs.msi_name }}"
if (-not (Test-Path $msi)) { throw "MSI not found: $msi" }
"msi=$msi" >> $env:GITHUB_OUTPUT
- name: Upload release assets as workflow artifact (inspection record)
# dry_run の検分用 + 実添付時の記録。release に上がる実物と bit 同一
uses: actions/upload-artifact@v6
with:
name: release-assets-${{ needs.preflight.outputs.tag }}-win-arm64-experimental
path: |
${{ steps.assets.outputs.zip }}
${{ steps.assets.outputs.msi }}
- name: Attach to release
if: ${{ !inputs.dry_run }}
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
$tag = '${{ needs.preflight.outputs.tag }}'
# preflight から時間が経っているため、直前にもう一度 asset 衝突を検査する
$assets = @(gh release view $tag --json assets -q '.assets[].name')
foreach ($name in @('${{ needs.preflight.outputs.zip_name }}', '${{ needs.preflight.outputs.msi_name }}')) {
if ($assets -contains $name) {
throw "Asset '$name' appeared on $tag after preflight. Delete it manually (gh release delete-asset $tag $name) and re-run."
}
}
# --clobber は使わない (方針 §6.2)。部分失敗時は手動で delete-asset → 再実行
# 260613Cl checksum の添付は廃止。Phase C: ZIP + MSI を添付
gh release upload $tag "${{ steps.assets.outputs.zip }}" "${{ steps.assets.outputs.msi }}"
if ($LASTEXITCODE -ne 0) { throw "gh release upload failed (exit $LASTEXITCODE)." }
Write-Host "Attached to https://github.com/${{ github.repository }}/releases/tag/$tag"