ci(release): M141 arm64 体检改成逐行注解 + 缺 ARM64 SDK 时就地补装组件

- 诊断全部走 ::error:: 注解(job 日志要 token,注解不要),快照固定 4 行:
  探测根存在性 / 每个 SDK Lib 版本目录的 um 架构 / MSVC lib 架构 / 两个关键库路径。
- SDK 根放宽到 4 个((x86)、Program Files、Microsoft SDKs、VS 自带的 SDK in VS),
  kernel32.lib 探测深度 4→6;MSVC find 兜底深度 7 覆盖 lib 目录。
- 缺库时先就地补装(vs_installer modify --wait),组件 ID 按已装 SDK build 反推,
  优先补一个尚未安装的 SDK 版本(同版本重装是 no-op,补不出 um/arm64);补不上才 fail。
- 门禁不降级:PUBLISH_SLOTS 与槽位表原样(windows-arm64 仍在)。
- 为守住 400 行上限,压缩了其余纯注释块与两处等价写法,逻辑未变。
This commit is contained in:
tower committed 2026-11-06 12:15:10 +08:00
1 parent 241a258e45
commit 5d5011f66c
1 file changed
+91 -95
+91 -95
View File
@@ -1,18 +1,15 @@
name: Release name: Release
# 发布流水线:tag `v*` → **只构建两个 Windows 槽位**(各自原生:`windows-latest` / `windows-11-arm`)→ 单文件产物 + SHA256SUMS → artifact + GitHub Release # 发布流水线:tag `v*` → **只构建两个 Windows 槽位**(各自原生:`windows-latest` / `windows-11-arm`)→ 单文件产物 + SHA256SUMS
# (pre-release 默认开;同 tag 重跑幂等 = 覆盖资产与说明而不 create;重跑不碰该标记,只在显式 inputs.prerelease 时才断言) # → artifact + GitHub Release。pre-release 默认开;同 tag 重跑幂等 = 覆盖资产与说明而不 create,且重跑不碰该标记,
# # 只在显式 inputs.prerelease 时才断言。
# **构建 ≠ 发布**:只有 release job 的 `PUBLISH_SLOTS` 白名单里的槽位才会进 Release 与 SHA256SUMS。 # **构建 ≠ 发布**:只有 release job 的 `PUBLISH_SLOTS` 白名单里的槽位才进 Release 与 SHA256SUMS。**CI 只跑两个 Windows
# **CI 只跑两个 Windows 槽位**(M139):Linux x64/arm64、macOS arm64/x64 四档从未真构建成功过一次、 # 槽位**(M139):Linux x64/arm64、macOS arm64/x64 四档从未真构建成功过一次、也没在真机跑过,默认一个都不跑(省配额、
# 也没在真机跑过,默认**一个都不跑**(省 CI 配额、不留没意义的红叉);要整表六槽位取证走手动入口 # 不留红叉);整表六槽位取证走 `gh workflow run release.yml -f all_slots=true`(槽位表在 `plan` job 里,唯一真相源)。
# `gh workflow run release.yml -f all_slots=true`(槽位表在 `plan` job 里,唯一真相源)。 # 契约唯一真相源 packaging/README.md(命名/目录/运行时依赖/发布白名单),打包本体 packaging/make_artifacts.py(本地与 CI
# # 同一份脚本,逐字节同布局)。`inputs.prerelease` 只在 **tag ref** 上 dispatch 才可达(Actions 页面只能选分支,跑 tag 得
# 契约唯一真相源:packaging/README.md(命名/目录/运行时依赖/发布白名单);打包本体在 # `gh workflow run release.yml --ref v0.2.1`);分支 ref 的 dispatch 与 push 一样被 release job 的
# packaging/make_artifacts.py(本地与 CI 同一份脚本,逐字节同布局)。`inputs.prerelease` 只在 **tag ref** # `if: github.ref_type == 'tag'` 挡在发布之外,故「只构建不发布」与「能关 pre-release」不矛盾。
# 上 dispatch 才可达(Actions 页面只能选分支,跑 tag 得 `gh workflow run release.yml --ref v0.2.1`);
# 分支 ref 的 dispatch 与 push 一样被 release job 的 `if: github.ref_type == 'tag'` 挡在发布之外,所以
# 「dispatch 只构建不发布」与「dispatch 能关 pre-release」并不矛盾。
on: on:
push: push:
@@ -41,9 +38,8 @@ concurrency:
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
# M139:矩阵不写死在 `build` 里,改由本 job 按 `inputs.all_slots` 生成 —— 矩阵不能带 `if`,而 # M139:矩阵不写死在 `build` 里,改由本 job 按 `inputs.all_slots` 生成 —— 矩阵不能带 `if`,而 `jobs.<id>.strategy`
# `jobs.<id>.strategy` 可读 `needs`,所以「默认两个 Windows / 手动整表六槽位」用 # 可读 `needs`,故用 `fromJSON(needs.plan.outputs.matrix)`。**槽位表只在下面这一处**,改槽位只改这里。
# `fromJSON(needs.plan.outputs.matrix)` 实现。**槽位表只在下面这一处**,改槽位只改这里。
plan: plan:
name: 选槽位(默认两个 Windows) name: 选槽位(默认两个 Windows)
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -65,11 +61,10 @@ jobs:
SLOTS = [ SLOTS = [
# Windows x64:MSVC + 静态 CRT(+crt-static)→ exe 不依赖 VC++ 可再发行包,双击即用的单文件。 # Windows x64:MSVC + 静态 CRT(+crt-static)→ exe 不依赖 VC++ 可再发行包,双击即用的单文件。
dict(slot="windows-x64", os="windows-latest", build_dir="target/release", rustflags="-C target-feature=+crt-static", mode="ci"), dict(slot="windows-x64", os="windows-latest", build_dir="target/release", rustflags="-C target-feature=+crt-static", mode="ci"),
# Windows arm64:**原生** `windows-11-arm`(4C/16G,公开仓库免费的标准 arm64 runner,GitHub # Windows arm64:**原生** `windows-11-arm`(4C/16G,公开仓库免费的标准 arm64 runner;GitHub 文档的
# 文档的 Standard runners 表明列 `windows-11-arm` / `windows-11-vs2026-arm`)。M140 之前是 # Standard runners 也列 `windows-11-vs2026-arm`)。M140 之前是 windows-latest 上交叉到 aarch64-pc-windows-msvc,
# windows-latest 上交叉编译到 aarch64-pc-windows-msvc,**CI 从未成功过一次**(v0.2.1 首次 # **CI 从未成功过一次**(v0.2.1 首跑就红在工具链体检,M141 在此修)。原生还让 `ull --version` 冒烟真跑
# 真跑就红在「ARM64 工具链体检」),那条惯性理由不再成立;原生还让 `ull --version` 冒烟 # (宿主=产物架构),产物在 target/release,dumpbin 走 Hostarm64 分支。
# 真跑(宿主=产物架构,不再被跳过)。产物在 target/release,dumpbin 走 Hostarm64 分支。
dict(slot="windows-arm64", os="windows-11-arm", build_dir="target/release", dict(slot="windows-arm64", os="windows-11-arm", build_dir="target/release",
rustflags="-C target-feature=+crt-static", mode="ci"), rustflags="-C target-feature=+crt-static", mode="ci"),
# ↓ 四档**未验证,不纳入 CI**:从未真构建成功过一次、也没在真机跑过,默认一个都不编; # ↓ 四档**未验证,不纳入 CI**:从未真构建成功过一次、也没在真机跑过,默认一个都不编;
@@ -102,48 +97,66 @@ jobs:
shell: bash shell: bash
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends libxkbcommon-dev libfontconfig1-dev \
libxkbcommon-dev libfontconfig1-dev libgl1-mesa-dev libegl1-mesa-dev \ libgl1-mesa-dev libegl1-mesa-dev libwayland-dev libxcb1-dev
libwayland-dev libxcb1-dev
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
# (M140)windows-arm64 跑在**原生** `windows-11-arm` 上,本步查"这台机器上的 ARM64 库齐不齐": # (M141)windows-arm64 跑在**原生** `windows-11-arm` 上,本步查这台机器上 ARM64 库齐不齐:
# `+crt-static` 要 VC 的 `lib/arm64/libcmt.lib`,链接要 SDK 的 `um/arm64/kernel32.lib`。门禁不降级 # `+crt-static` 要 MSVC 的 `lib/arm64/libcmt.lib`,链接要 SDK 的 `um/arm64/kernel32.lib`。
# (缺了就 fail),但失败必须能自己定位:把探测到的根目录 / lib 目录全打出来——原来只报"缺失", # **诊断走注解**:job 日志下载要 token(我们没有),check-run 注解不用;每条一行(多行会被拼成一行而截断,
# 2026-11-06 首次真跑就卡在这里而看不出原因。宽容化:VS 与 SDK **各扫两个安装根**,SDK 全版本找。 # 上一轮只看到半句),error 注解上限 10 条故快照固定 4 行。门禁不降级:缺了就地补装再判,补不上才 fail。
- name: Windows ARM64 工具链体检 - name: Windows ARM64 工具链体检
if: matrix.slot == 'windows-arm64' if: matrix.slot == 'windows-arm64'
shell: bash shell: bash
run: | run: |
set -euo pipefail set -uo pipefail # 刻意不用 -e:探测/补装失败也要把注解打完,再由显式 exit 定门禁
vs_roots=("C:/Program Files/Microsoft Visual Studio" "C:/Program Files (x86)/Microsoft Visual Studio") ann() { printf '::error::[arm64sdk] %s\n' "$*"; }
# SDK 同样两个根:老安装器落在 `(x86)`,别只猜一个 —— 猜错路径就是这次事故的形态(fail-closed 误报)。 vs=("C:/Program Files/Microsoft Visual Studio" "C:/Program Files (x86)/Microsoft Visual Studio")
sdk_roots=("C:/Program Files (x86)/Windows Kits/10/Lib" "C:/Program Files/Windows Kits/10/Lib") # SDK 根要放宽:新版装 `(x86)`、也有装 `Program Files`;老安装器把 lib 留在 Microsoft SDKs;
echo "── 探测的根目录 ──" # VS 自己还带一份「SDK in VS」(`<VS>/Windows Kits/10/Lib`),用 vswhere 定位后追加。
for r in "${vs_roots[@]}" "${sdk_roots[@]}"; do sdk=("C:/Program Files (x86)/Windows Kits/10/Lib" "C:/Program Files/Windows Kits/10/Lib"
if [ -d "$r" ]; then echo " 有 $r"; else echo " 无 $r"; fi "C:/Program Files (x86)/Microsoft SDKs/Windows/v10.0A/Lib")
vswhere="C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
vspath=""; [ -x "$vswhere" ] && vspath=$("$vswhere" -latest -products '*' -property installationPath 2>/dev/null | head -n1)
[ -n "$vspath" ] && sdk+=("${vspath//\\//}/Windows Kits/10/Lib")
scan() { vc_lib=$(find "${vs[@]}" -maxdepth 11 -type f -path '*/VC/Tools/MSVC/*/lib/arm64/libcmt.lib' 2>/dev/null | head -n1)
sdk_lib=$(find "${sdk[@]}" -maxdepth 6 -type f -path '*/um/arm64/kernel32.lib' 2>/dev/null | head -n1); }
scan
if [ -n "$vc_lib" ] && [ -n "$sdk_lib" ]; then echo "ARM64 工具链齐: vc=$vc_lib sdk=$sdk_lib"; exit 0; fi
# 补装前的原样快照(固定 4 行):探测根 / SDK Lib 版本目录的 um 架构 / MSVC lib 架构 / 两个关键库。
ann "探测根: $(for d in "${vs[@]}" "${sdk[@]}"; do [ -d "$d" ] && printf '%s=有 ' "$d" || printf '%s=无 ' "$d"; done)"
v=""
for d in "${sdk[@]}"; do
[ -d "$d" ] || continue
for t in "$d"/*/; do [ -d "$t" ] && v+="$(basename "$t")→um:[$(ls "$t/um" 2>/dev/null | tr '\n' ',')] "; done
done done
vc_dirs=$(find "${vs_roots[@]}" -maxdepth 9 -type d -path '*/VC/Tools/MSVC/*/lib/arm64' 2>/dev/null || true) ann "SDK Lib 版本→um 架构: ${v:-<没有任何版本目录>}"
echo "── VC 的 arm64 lib 目录(*/VC/Tools/MSVC/*/lib/arm64)──" v=""
echo "${vc_dirs:- <无>}" while IFS= read -r t; do v+="$(basename "$(dirname "$t")")→[$(ls "$t" 2>/dev/null | tr '\n' ',')] "; done \
sdk_dirs=$(find "${sdk_roots[@]}" -maxdepth 3 -type d -path '*/um/arm64' 2>/dev/null || true) < <(find "${vs[@]}" -maxdepth 7 -type d -path '*/VC/Tools/MSVC/*/lib' 2>/dev/null | head -n4)
echo "── SDK 的 um/arm64 目录 ──" ann "MSVC lib/ 架构: ${v:-<无 MSVC lib 目录>}"
echo "${sdk_dirs:- <无>}" ann "关键库: libcmt=${vc_lib:-缺失} kernel32=${sdk_lib:-缺失}"
vc_lib=$(find "${vs_roots[@]}" -maxdepth 11 -type f -path '*/VC/Tools/MSVC/*/lib/arm64/libcmt.lib' 2>/dev/null | head -n1 || true) # 补装:VS 安装器就地 modify(门禁不降级:先补再判)。组件 ID 随 VS 大版本不同;**优先补一个尚未
sdk_lib=$(find "${sdk_roots[@]}" -maxdepth 4 -type f -path '*/um/arm64/kernel32.lib' 2>/dev/null | head -n1 || true) # 装上的 SDK 版本**——同版本重装是 no-op,补不出 arm64 子目录。补装失败不致命,重探与结论给真相。
echo "libcmt.lib: ${vc_lib:-<缺失>}" setup="C:/Program Files (x86)/Microsoft Visual Studio/Installer/setup.exe"
echo "kernel32.lib: ${sdk_lib:-<缺失>}" build=$(ls "${sdk[@]}" 2>/dev/null | grep -oE '^10\.0\.[0-9]+' | cut -d. -f3 | sort -u | tail -n1)
if [ -z "$vc_lib" ] || [ -z "$sdk_lib" ]; then id=Microsoft.VisualStudio.Component.Windows11SDK.$([ "$build" = "26100" ] && echo 22621 || echo 26100)
echo "::error::windows-11-arm 上缺 ARM64 库(vc=${vc_lib:-缺失} / sdk=${sdk_lib:-缺失}),上面就是全部探测结果;给 VS 补 Microsoft.VisualStudio.Component.VC.Tools.ARM64 与 Windows 11 SDK" [ "$build" = "19041" ] && id=Microsoft.VisualStudio.Component.Windows10SDK.20348
exit 1 adds=""; [ -n "$vc_lib" ] || adds+="Microsoft.VisualStudio.Component.VC.Tools.ARM64 "; [ -n "$sdk_lib" ] || adds+="$id"
fi if [ -x "$setup" ] && [ -n "$vspath" ]; then
ann "补装: $vspath ← ${adds}(已装 SDK build=${build:-未装})"
timeout 1200 "$setup" modify --installPath "$vspath" --quiet --norestart --nocache --wait \
--add ${adds} || ann "补装返回非 0:组件 ID 可能不被这台 VS 认(见下一条结论)"
else ann "补装跳过: 无 vs_installer 或没定位到 VS 安装路径(vspath=${vspath:-<空>})"; fi
scan
if [ -z "$vc_lib" ] || [ -z "$sdk_lib" ]; then ann "补装后结论: 仍缺(libcmt=${vc_lib:-缺失} kernel32=${sdk_lib:-缺失}),门禁不降级、不摘 arm64"; exit 1; fi
echo "补装后齐全: vc=$vc_lib sdk=$sdk_lib"
# M118(D26):**Slint 版停发** —— GUI 来源只有 `unlucky-launcher-iced`(`crates/app` 已被根 # M118(D26):**Slint 版停发** —— GUI 只有 `unlucky-launcher-iced`(`crates/app` 已被根 Cargo.toml
# Cargo.toml `exclude`,不参与任何 workspace 构建)。对外产物名由 make_artifacts.py 的 # `exclude`,不参与 workspace 构建)。产物名由 make_artifacts.py 的 `asset_stem()` 生成
# `asset_stem()` 生成 `<名字>_<版本号>_<平台>_<架构>`;`LAUNCHER_BIN` 只用来在产物里找文件。 # `<名字>_<版本号>_<平台>_<架构>`,`LAUNCHER_BIN` 只用来在产物里找文件;不传 `--target`(M140 起全原生编译)。
# 不传 `--target`:M140 起每个槽位都在自己的架构上原生编译,host triple 就是产物 triple。
- name: cargo build --release --locked - name: cargo build --release --locked
shell: bash shell: bash
env: env:
@@ -162,12 +175,10 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
# +crt-static 是否真生效只看导入表:出现 VCRUNTIME/MSVCP/msvcr/ucrtbase 就是还得装可再发行包,fail。 # +crt-static 是否真生效只看导入表:出现 VCRUNTIME/MSVCP/msvcr/ucrtbase 就是还得装可再发行包,fail。
# # dumpbin 定位链(三级,全不命中才 fail —— 门禁不降级,只是把"猜路径"换成官方定位):① vswhere(VS 官方
# dumpbin 定位链(三级,全不命中才 fail —— 门禁不降级,只是把"猜路径"换成官方定位): # 定位器,安装器路径固定)② find 兜底(两个 VS 根都扫,Build Tools 在 x86 根)③ PATH(排最后是刻意的:
# ① vswhere(VS 官方定位器,安装器路径固定)② find 兜底(两个 VS 根都扫,Build Tools 在 # PATH 最不可控)。dumpbin 在 MSVC 的**宿主**工具目录里:x64 runner 是 `Hostx64/x64`,arm64 runner(M140)
# x86 根)③ PATH(镜像预置则直接可用;排最后是刻意的:PATH 最不可控,官方定位器优先)。 # 是 `Hostarm64/arm64`(x64 那族兜底,靠模拟层能跑);反向不行——x64 上挑 Hostarm64 是跑不起来的二进制。
# dumpbin 在 MSVC 的**宿主**工具目录里:x64 runner 是 `Hostx64/x64`,arm64 runner(M140)是
# `Hostarm64/arm64`(x64 那族兜底,靠模拟层能跑);反向不行——x64 上挑 Hostarm64 是跑不起来的二进制。
pats=('VC/Tools/MSVC/*/bin/Hostx64/x64/dumpbin.exe') pats=('VC/Tools/MSVC/*/bin/Hostx64/x64/dumpbin.exe')
if [ "${RUNNER_ARCH:-X64}" = "ARM64" ]; then if [ "${RUNNER_ARCH:-X64}" = "ARM64" ]; then
pats=('VC/Tools/MSVC/*/bin/Hostarm64/arm64/dumpbin.exe' "${pats[@]}") pats=('VC/Tools/MSVC/*/bin/Hostarm64/arm64/dumpbin.exe' "${pats[@]}")
@@ -176,9 +187,8 @@ jobs:
DUMPBIN="" DUMPBIN=""
VIA="" VIA=""
if [ -x "$VSWHERE" ]; then if [ -x "$VSWHERE" ]; then
# `-find` 是**单值**参数:一次传多个 pattern 只有一个生效(3.x 取最后一个 —— 老写法里 Hostarm64 # `-find` 是**单值**参数:一次传多个 pattern 只有一个生效(3.x 取最后一个 —— 老写法里 Hostarm64 因此永远
# 因此永远轮不到)。所以每个 pattern 各起一次定位器,不赌它取哪个。 # 轮不到),故每个 pattern 各起一次定位器,不赌它取哪个。反斜杠形态优先(官方文档即此形式),再试正斜杠。
# 反斜杠形态优先(vswhere 官方文档即此形式),再试正斜杠以规避分隔符差异。
for p in "${pats[@]}"; do for p in "${pats[@]}"; do
for pat in "${p//\//\\}" "$p"; do for pat in "${p//\//\\}" "$p"; do
hit=$("$VSWHERE" -latest -products '*' -find "$pat" 2>/dev/null | head -n1 || true) hit=$("$VSWHERE" -latest -products '*' -find "$pat" 2>/dev/null | head -n1 || true)
@@ -197,22 +207,15 @@ jobs:
fi fi
if [ -z "$DUMPBIN" ]; then if [ -z "$DUMPBIN" ]; then
hit=$(command -v dumpbin || true) hit=$(command -v dumpbin || true)
if [ -n "$hit" ]; then if [ -n "$hit" ]; then DUMPBIN=$hit; VIA="PATH"; fi
DUMPBIN=$hit
VIA="PATH"
fi
fi fi
# 工具缺失同样 fail:"找不到就跳过"等于让这道门禁在最需要它的时候静默失效。 # 工具缺失同样 fail:"找不到就跳过"等于让这道门禁在最需要它的时候静默失效。
if [ -z "$DUMPBIN" ]; then if [ -z "$DUMPBIN" ]; then echo "::error::找不到 dumpbin,无法校验导入表(静态 CRT 门禁不降级)"; exit 1; fi
echo "::error::找不到 dumpbin,无法校验导入表(静态 CRT 门禁不降级)"
exit 1
fi
echo "dumpbin: $DUMPBIN(定位自 $VIA)" echo "dumpbin: $DUMPBIN(定位自 $VIA)"
ls -l "$EXE" ls -l "$EXE"
"$DUMPBIN" -nologo -dependents "$EXE" | tee dependents.txt "$DUMPBIN" -nologo -dependents "$EXE" | tee dependents.txt
if grep -Eiq 'VCRUNTIME|MSVCP|MSVCR|ucrtbase\.dll' dependents.txt; then if grep -Eiq 'VCRUNTIME|MSVCP|MSVCR|ucrtbase\.dll' dependents.txt; then
echo "::error::静态 CRT 未生效:exe 仍动态依赖 VC++ 可再发行包" echo "::error::静态 CRT 未生效:exe 仍动态依赖 VC++ 可再发行包"; exit 1
exit 1
fi fi
- name: 打包(产物 + SHA256SUMS) - name: 打包(产物 + SHA256SUMS)
@@ -265,20 +268,19 @@ jobs:
release: release:
name: GitHub Release name: GitHub Release
needs: build needs: build
# `always()` + `!cancelled()`:白名单外的槽位(只有 `all_slots` 取证跑里才存在)红了**不能连坐** # `always()` + `!cancelled()`:白名单外的槽位(只在 `all_slots` 取证跑里存在)红了**不能连坐** Windows 发布 ——
# Windows 发布。真正拦发布的是下面「发布白名单齐全性」那一步:白名单里少任何一个就 fail。 # 真正拦发布的是下面「发布白名单齐全性」那一步:白名单里少任何一个就 fail。
if: always() && !cancelled() && github.ref_type == 'tag' if: always() && !cancelled() && github.ref_type == 'tag'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
env: env:
# ── 发布白名单:唯一决定「哪几个槽位进 Release」的开关(空格分隔,键同 matrix.slot)── # ── 发布白名单:唯一决定「哪几个槽位进 Release」的开关(空格分隔,键同 matrix.slot)──
# 为什么只有两个 Windows 槽位:Linux x64/arm64、macOS arm64/x64 这四档**从未真构建成功过一次、 # 为什么只有两个 Windows 槽位:Linux x64/arm64、macOS arm64/x64 这四档**从未真构建成功过一次、更没在真机跑过**,
# 更没在真机上跑过**,未验证的产物不推给用户;它们默认连构建都不跑(M139),只有 `all_slots=true` # 未验证的产物不推给用户;它们默认连构建都不跑(M139),只有 `all_slots=true` 取证跑里才有产物,留在本次运行的
# 的取证跑里才有产物,留在本次运行的 `dist-<slot>` artifact 里可下载验证,不进 Release/SHA256SUMS。 # `dist-<slot>` artifact 里可下载验证,不进 Release/SHA256SUMS。将来某槽位真机验过后「打开」它需同时改三处:
# 将来某槽位真机验证通过后「打开」它需同时改三处(缺一即是假话):① `plan` 槽位表里把它的 mode # ① `plan` 槽位表里把它的 mode 改成 `ci`;② 把槽位键加进这一行(齐全性检查与 SHA256SUMS 自动跟着扩);
# 改成 `ci`;② 把槽位键加进这一行(齐全性检查与 SHA256SUMS 自动跟着扩);③ packaging/README.md # ③ packaging/README.md §1/§9 与 packaging/release-notes.md 的 Downloads / Support matrix。
# §1/§9 与 packaging/release-notes.md 的 Downloads / Support matrix。
PUBLISH_SLOTS: "windows-x64 windows-arm64" PUBLISH_SLOTS: "windows-x64 windows-arm64"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -292,8 +294,7 @@ jobs:
shell: bash shell: bash
run: | run: |
set -euo pipefail set -euo pipefail
# 槽位键 `windows-x64` 落到文件名是 `windows_x64`(make_artifacts.py::asset_stem),故取文件名里的 # 槽位键 `windows-x64` 落到文件名是 `windows_x64`(asset_stem),故取文件名里的「平台_架构」两段比白名单。
# 「平台_架构」两段跟白名单比:`<名字>_<版本>_<平台>_<架构>[.exe]`。
keep=" " keep=" "
for slot in $PUBLISH_SLOTS; do keep+="${slot//-/_} "; done for slot in $PUBLISH_SLOTS; do keep+="${slot//-/_} "; done
for f in dist/*; do for f in dist/*; do
@@ -316,12 +317,10 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
ls -l dist ls -l dist
# 白名单内槽位必须**全齐**才发版(宁可不出 Release,也不出少一档的 Release)。每个槽位都要 # 白名单内槽位必须**全齐**才发版(宁可不出 Release,也不出少一档的 Release)。每个槽位都要 GUI 与 CLI
# GUI 与 CLI 两个资产——只按「槽位名出现过」判会被其中一个掩盖。档位比对用下划线形态: # 两个资产——只按「槽位名出现过」判会被其中一个掩盖。档位比对用下划线形态(资产名自 D49 起是
# 资产名自 D49 起是 `unlucky-launcher_<ver>_windows_x64.exe`,旧写法 `dist/$prefix-*-$slot*` # `unlucky-launcher_<ver>_windows_x64.exe`)。矩阵裁到两个槽位(M139)后这条检查依然有效:它比的是
# (连字符)对现行命名永远匹配不上,已一并修正。 # **下载下来的产物**,白名单里的槽位没编或编挂了,就在这里因缺产物 fail,不会永远过。
# 矩阵裁到两个槽位(M139)后这条检查依然有效:它比的是**下载下来的产物**,不看矩阵大小 ——
# 白名单里的槽位没编(比如忘了改 plan 里的 mode)或编挂了,就在这里因缺产物 fail,不会永远过。
for slot in $PUBLISH_SLOTS; do for slot in $PUBLISH_SLOTS; do
stem=${slot//-/_} stem=${slot//-/_}
for prefix in unlucky-launcher ull; do for prefix in unlucky-launcher ull; do
@@ -363,11 +362,9 @@ jobs:
run: | run: |
set -euo pipefail set -euo pipefail
base_flags=(--title "Unlucky Launcher $TAG" --notes-file release-body.md) base_flags=(--title "Unlucky Launcher $TAG" --notes-file release-body.md)
# pre-release 标记只在**显式要求**时才断言: # pre-release 标记只在**显式要求**时才断言:PRERELEASE=true/false 来自 tag ref 上的 dispatch 输入;
# PRERELEASE=true / false —— 来自 tag ref 上的 workflow_dispatch 输入; # PRERELEASE 空 = push 触发的运行或它的 Re-run,此时**不碰**该标记(否则会把用户手动提升为 stable 的
# PRERELEASE 空 —— push 触发的运行或它的 Re-run,此时**不碰**该标记, # Release 静默翻回 pre-release)。首次 create 仍保持「pre-release 默认开」的老契约:空值按 true 处理。
# 否则会把用户手动提升为 stable 的 Release 静默翻回 pre-release。
# 首次创建(create)仍保持「pre-release 默认开」的老契约:空值按 true 处理。
create_mark=() create_mark=()
edit_mark=() edit_mark=()
case "${PRERELEASE:-}" in case "${PRERELEASE:-}" in
@@ -386,8 +383,7 @@ jobs:
exit 1 exit 1
;; ;;
esac esac
# Windows 单 exe 装不下 LICENSE/NOTICE/README.md,这三份与产物并列挂到 Release 上, # Windows 单 exe 装不下 LICENSE/NOTICE/README.md,这三份与产物并列挂 Release,保证许可条款随包分发。
# 保证每个下载平台的许可条款都随包分发。
# `ull` 是独立资产(不在 tar.gz 里):headless 用户下一个小文件直接跑,不用解档。 # `ull` 是独立资产(不在 tar.gz 里):headless 用户下一个小文件直接跑,不用解档。
assets=(dist/unlucky-launcher_* dist/ull_* dist/SHA256SUMS.txt LICENSE NOTICE README.md) assets=(dist/unlucky-launcher_* dist/ull_* dist/SHA256SUMS.txt LICENSE NOTICE README.md)
if gh release view "$TAG" >/dev/null 2>&1; then if gh release view "$TAG" >/dev/null 2>&1; then