fix(release): M140 r1 复审三条 p2 —— vswhere -find 单值、根因措辞、SDK 双根

P2-1(真缺陷):`-find` 是单值参数,老写法 `... "${find_args[@]}"` 一次传 4 个
`-find`,官方定位器只认其中一个(复现:last-wins 下取到 Hostx64),
所以 round-1 新加的「ARM64 优先 → Hostarm64」在 vswhere 分支上从未执行。
改成每个 pattern 各起一次定位器,不赌取值规则;注释同步为真话。

P2-2(措辞):README §4 不再声称 v0.2.1 那次是「x64 镜像缺库」——windows-2025
镜像清单里 VC.Tools.ARM64 与 SDK 26100 都在,根因未定论,更像自检的路径探测;
§7.4 的 dumpbin 门禁补一句覆盖 windows-latest 与 windows-11-arm 两个镜像。

P2-3(放宽):arm64 库体检的 SDK 侧也改成两个安装根(Program Files (x86) 与
Program Files),把「路径猜错 → fail-closed 误报」这个正是本次事故的形态再降一档。

行数:release.yml 400 / README 400(均 ≤400);PUBLISH_SLOTS 未动。
This commit is contained in:
AskaEth committed 2026-11-06 11:59:31 +08:00
1 parent b27e86c65b
commit 5678683561
2 files changed
+26 -25

No files matched your search

+21 -20
View File
@@ -60,9 +60,8 @@ jobs:
python3 - "$ALL_SLOTS" >> "$GITHUB_OUTPUT" <<'PY'
import json, sys
# 槽位表(唯一真相源)。mode:"ci" = 默认在 CI 里构建;"probe" = 未验证,仅 all_slots=true 时构建。
# **没有一个槽位交叉编译**(M140):每槽都在**自己的架构**上原生构建,故不设 `target` 键、产物一律
# 在 target/release。哪天再加交叉槽位,把 `target` 键、`build_dir=target/<triple>/release` 与
# 「`rustup target add` + cargo 的 `--target`」两处一起加回来(现在它们已随最后一个交叉槽位删掉)。
# **没有交叉槽位**(M140):每槽在自己的架构上原生构建,故无 `target` 键、产物一律在 target/release。
# 要加回交叉槽位:`target` 键 + `build_dir=target/<triple>/release` + `rustup target add` 与 cargo 的 `--target`。
SLOTS = [
# 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"),
@@ -110,31 +109,30 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# (M140)windows-arm64 跑在**原生** `windows-11-arm` 上,所以本步查的是"这台机器上的 ARM64 库齐不齐":
# `+crt-static` 要 VC 的 `lib/arm64/libcmt.lib`,链接要 SDK 的 `um/arm64/kernel32.lib`。
# 门禁不降级(缺了就 fail),但**失败必须能自己定位**:把实际探测到的根目录 / lib 目录全打出来
# ——原来只报"缺失",2026-11-06 第一次真跑就卡在这里,而单看日志无法判断是镜像没装还是探测写错了。
# 两个探测还都改宽容了:VS 两个根都扫;SDK 不再只认 `tail -n1` 挑出的那一个版本,全部版本里找。
# (M140)windows-arm64 跑在**原生** `windows-11-arm` 上,本步查"这台机器上的 ARM64 库齐不齐":
# `+crt-static` 要 VC 的 `lib/arm64/libcmt.lib`,链接要 SDK 的 `um/arm64/kernel32.lib`。门禁不降级
# (缺了就 fail),但失败必须能自己定位:把探测到的根目录 / lib 目录全打出来——原来只报"缺失",
# 2026-11-06 首次真跑就卡在这里而看不出原因。宽容化:VS 与 SDK **各扫两个安装根**,SDK 全版本找。
- name: Windows ARM64 工具链体检
if: matrix.slot == 'windows-arm64'
shell: bash
run: |
set -euo pipefail
vs_roots=("C:/Program Files/Microsoft Visual Studio" "C:/Program Files (x86)/Microsoft Visual Studio")
sdk_root="C:/Program Files (x86)/Windows Kits/10/Lib"
# SDK 同样两个根:老安装器落在 `(x86)`,别只猜一个 —— 猜错路径就是这次事故的形态(fail-closed 误报)。
sdk_roots=("C:/Program Files (x86)/Windows Kits/10/Lib" "C:/Program Files/Windows Kits/10/Lib")
echo "── 探测的根目录 ──"
for r in "${vs_roots[@]}"; do
for r in "${vs_roots[@]}" "${sdk_roots[@]}"; do
if [ -d "$r" ]; then echo " 有 $r"; else echo " 无 $r"; fi
done
if [ -d "$sdk_root" ]; then echo " 有 $sdk_root"; else echo " 无 $sdk_root"; fi
vc_dirs=$(find "${vs_roots[@]}" -maxdepth 9 -type d -path '*/VC/Tools/MSVC/*/lib/arm64' 2>/dev/null || true)
echo "── VC 的 arm64 lib 目录(*/VC/Tools/MSVC/*/lib/arm64)──"
echo "${vc_dirs:- <无>}"
sdk_dirs=$(find "$sdk_root" -maxdepth 3 -type d -path '*/um/arm64' 2>/dev/null || true)
sdk_dirs=$(find "${sdk_roots[@]}" -maxdepth 3 -type d -path '*/um/arm64' 2>/dev/null || true)
echo "── SDK 的 um/arm64 目录 ──"
echo "${sdk_dirs:- <无>}"
vc_lib=$(find "${vs_roots[@]}" -maxdepth 11 -type f -path '*/VC/Tools/MSVC/*/lib/arm64/libcmt.lib' 2>/dev/null | head -n1 || true)
sdk_lib=$(find "$sdk_root" -maxdepth 4 -type f -path '*/um/arm64/kernel32.lib' 2>/dev/null | head -n1 || true)
sdk_lib=$(find "${sdk_roots[@]}" -maxdepth 4 -type f -path '*/um/arm64/kernel32.lib' 2>/dev/null | head -n1 || true)
echo "libcmt.lib: ${vc_lib:-<缺失>}"
echo "kernel32.lib: ${sdk_lib:-<缺失>}"
if [ -z "$vc_lib" ] || [ -z "$sdk_lib" ]; then
@@ -169,8 +167,7 @@ jobs:
# ① vswhere(VS 官方定位器,安装器路径固定)② find 兜底(两个 VS 根都扫,Build Tools 在
# x86 根)③ PATH(镜像预置则直接可用;排最后是刻意的:PATH 最不可控,官方定位器优先)。
# dumpbin 在 MSVC 的**宿主**工具目录里:x64 runner 是 `Hostx64/x64`,arm64 runner(M140)是
# `Hostarm64/arm64`(x64 那族作为兜底,ARM64 Windows 靠模拟层跑得起来)。反向不成立 ——
# 在 x64 runner 上挑 `Hostarm64` 只会拿到跑不起来的 arm64 二进制,所以按 RUNNER_ARCH 选。
# `Hostarm64/arm64`(x64 那族兜底,靠模拟层能跑);反向不行——x64 上挑 Hostarm64 是跑不起来的二进制。
pats=('VC/Tools/MSVC/*/bin/Hostx64/x64/dumpbin.exe')
if [ "${RUNNER_ARCH:-X64}" = "ARM64" ]; then
pats=('VC/Tools/MSVC/*/bin/Hostarm64/arm64/dumpbin.exe' "${pats[@]}")
@@ -179,11 +176,15 @@ jobs:
DUMPBIN=""
VIA=""
if [ -x "$VSWHERE" ]; then
# -find 的模式先用反斜杠(vswhere 官方文档即此形式),再试正斜杠以规避分隔符差异。
find_args=()
for p in "${pats[@]}"; do find_args+=(-find "${p//\//\\}" -find "$p"); done
hit=$("$VSWHERE" -latest -products '*' "${find_args[@]}" 2>/dev/null | head -n1 || true)
if [ -n "$hit" ]; then DUMPBIN=${hit//\\//}; VIA="vswhere"; fi
# `-find` 是**单值**参数:一次传多个 pattern 只有一个生效(3.x 取最后一个 —— 老写法里 Hostarm64
# 因此永远轮不到)。所以每个 pattern 各起一次定位器,不赌它取哪个。
# 反斜杠形态优先(vswhere 官方文档即此形式),再试正斜杠以规避分隔符差异。
for p in "${pats[@]}"; do
for pat in "${p//\//\\}" "$p"; do
hit=$("$VSWHERE" -latest -products '*' -find "$pat" 2>/dev/null | head -n1 || true)
if [ -n "$hit" ]; then DUMPBIN=${hit//\\//}; VIA="vswhere"; break 2; fi
done
done
fi
if [ -z "$DUMPBIN" ]; then
# VS 根形如 <年>/<版次>(2022/Enterprise、2022/BuildTools……),固定层数的 glob 会踩空,按模式扫。