ci(release): M140 windows-arm64 改用原生 windows-11-arm runner

交叉编译路线在 CI 上从未成功过一次:v0.2.1 首次真跑即红在
「Windows ARM64 交叉工具链体检」(windows-latest 上探不到 VC/SDK 的
ARM64 库),故按文档标准 runner 表把 arm64 槽位改成原生
windows-11-arm(公开仓免费,4C/16G),产物回到 target/release。

- 槽位表:windows-arm64 os=windows-11-arm、build_dir=target/release;
  无交叉槽位后 target 键与 rustup target add / cargo --target 一并删掉
- 体检步骤重写为「Windows ARM64 工具链体检」:失败时打印实际探测到的
  VS 根、VC arm64 lib 目录、SDK 的 um/arm64 目录;SDK 不再只认
  tail -n1 挑出的单一版本(全版本里找)
- dumpbin 定位链补 Hostarm64/arm64 分支,按 RUNNER_ARCH 选宿主工具族
  (x64 上绝不挑 arm64 二进制),vswhere/find/PATH 三级与硬门禁不变
- packaging/README.md §1/§3/§4/§7.5 同步原生化与冒烟真跑的说法
This commit is contained in:
AskaEth committed 2026-11-06 11:51:01 +08:00
1 parent 1a290a15a3
commit b27e86c65b
2 files changed
+90 -90

No files matched your search

+66 -66
View File
@@ -1,6 +1,6 @@
name: Release
# 发布流水线:tag `v*` → **只构建两个 Windows 槽位** → 单文件产物 + SHA256SUMS → artifact + GitHub Release
# 发布流水线:tag `v*` → **只构建两个 Windows 槽位**(各自原生:`windows-latest` / `windows-11-arm`)→ 单文件产物 + SHA256SUMS → artifact + GitHub Release
# (pre-release 默认开;同 tag 重跑幂等 = 覆盖资产与说明而不 create;重跑不碰该标记,只在显式 inputs.prerelease 时才断言)
#
# **构建 ≠ 发布**:只有 release job 的 `PUBLISH_SLOTS` 白名单里的槽位才会进 Release 与 SHA256SUMS。
@@ -60,24 +60,28 @@ 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`」两处一起加回来(现在它们已随最后一个交叉槽位删掉)。
SLOTS = [
# Windows x64:MSVC + 静态 CRT(+crt-static)→ exe 不依赖 VC++ 可再发行包,双击即用的单文件。
dict(slot="windows-x64", os="windows-latest", target="", build_dir="target/release", rustflags="-C target-feature=+crt-static", mode="ci"),
# Windows arm64:在 windows-latest(x64 宿主)**交叉编译**到 aarch64-pc-windows-msvc,产物在
# target/<triple>/release(故 build_dir 参数化);不用原生 windows-11-arm 的理由见 README §4。
dict(slot="windows-arm64", os="windows-latest", target="aarch64-pc-windows-msvc",
build_dir="target/aarch64-pc-windows-msvc/release",
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
# 文档的 Standard runners 表明列 `windows-11-arm` / `windows-11-vs2026-arm`)。M140 之前是
# windows-latest 上交叉编译到 aarch64-pc-windows-msvc,**CI 从未成功过一次**(v0.2.1 首次
# 真跑就红在「ARM64 工具链体检」),那条惯性理由不再成立;原生还让 `ull --version` 冒烟
# 真跑(宿主=产物架构,不再被跳过)。产物在 target/release,dumpbin 走 Hostarm64 分支。
dict(slot="windows-arm64", os="windows-11-arm", build_dir="target/release",
rustflags="-C target-feature=+crt-static", mode="ci"),
# ↓ 四档**未验证,不纳入 CI**:从未真构建成功过一次、也没在真机跑过,默认一个都不编;
# `all_slots=true` 时照编(那是将来验证 Linux/macOS 的唯一入口,别删)。真机验证通过后:
# 把该行 mode 改成 "ci"(一行一改,不用重写矩阵)+ 槽位键加进 PUBLISH_SLOTS + 同步
# packaging/README.md §1/§9 与 release-notes.md 的 published 列 —— 三处不一致即是假话。
# `all_slots=true` 时照编(将来验证 Linux/macOS 的唯一入口,别删)。真机验证通过后:把该行
# mode 改成 "ci" + 槽位键加进 PUBLISH_SLOTS + 同步 README §1/§9 与 release-notes.md。
# Linux 固定 22.04 而非 latest:否则 glibc 地板跟着 runner 漂移(依据见 README §4)。
dict(slot="linux-x64", os="ubuntu-22.04", target="", build_dir="target/release", rustflags="", mode="probe"),
dict(slot="linux-arm64", os="ubuntu-22.04-arm", target="", build_dir="target/release", rustflags="", mode="probe"),
dict(slot="linux-x64", os="ubuntu-22.04", build_dir="target/release", rustflags="", mode="probe"),
dict(slot="linux-arm64", os="ubuntu-22.04-arm", build_dir="target/release", rustflags="", mode="probe"),
# macOS:latest = arm64;Intel x64 用 macos-15-intel(macos-13 已下线)。
dict(slot="macos-arm64", os="macos-latest", target="", build_dir="target/release", rustflags="", mode="probe"),
dict(slot="macos-x64", os="macos-15-intel", target="", build_dir="target/release", rustflags="", mode="probe"),
dict(slot="macos-arm64", os="macos-latest", build_dir="target/release", rustflags="", mode="probe"),
dict(slot="macos-x64", os="macos-15-intel", build_dir="target/release", rustflags="", mode="probe"),
]
keep = [s for s in SLOTS if s["mode"] == "ci" or sys.argv[1] == "true"]
print("matrix=" + json.dumps({"include": keep}, separators=(",", ":")))
@@ -106,57 +110,56 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# 只有交叉槽位需要额外 target(原生槽位留空 → 本步跳过);`${{ }}` 一律经 env 传入再展开,不拼进 bash。
- name: 追加交叉编译目标 ${{ matrix.target }}
if: matrix.target != ''
shell: bash
env:
TARGET: ${{ matrix.target }}
run: rustup target add "$TARGET"
# 交叉链接的前提是镜像里真有 ARM64 的 MSVC 库(VC `/lib/arm64/libcmt.lib`,+crt-static 就靠它)
# 与 SDK 的 ARM64 导入库(`um/arm64/kernel32.lib`):缺了照样失败,只是先给一句能照做的话。
- name: Windows ARM64 交叉工具链体检
if: matrix.target == 'aarch64-pc-windows-msvc'
# (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` 挑出的那一个版本,全部版本里找。
- name: Windows ARM64 工具链体检
if: matrix.slot == 'windows-arm64'
shell: bash
run: |
set -euo pipefail
roots=("C:/Program Files/Microsoft Visual Studio" "C:/Program Files (x86)/Microsoft Visual Studio")
vc_lib=$(find "${roots[@]}" -maxdepth 11 -type f -path '*/VC/Tools/MSVC/*/lib/arm64/libcmt.lib' 2>/dev/null | head -n1 || true)
sdk_version=$(ls -d "C:/Program Files (x86)/Windows Kits/10/Lib"/* 2>/dev/null | tail -n1 || true)
sdk_lib=""
if [ -n "$sdk_version" ]; then
sdk_lib=$(find "$sdk_version/um/arm64" -maxdepth 1 -name 'kernel32.lib' 2>/dev/null | head -n1 || true)
fi
echo "MSVC ARM64 静态 CRT: ${vc_lib:-<缺失>}"
echo "SDK ARM64 导入库: ${sdk_lib:-<缺失>}"
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"
echo "── 探测的根目录 ──"
for r in "${vs_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)
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)
echo "libcmt.lib: ${vc_lib:-<缺失>}"
echo "kernel32.lib: ${sdk_lib:-<缺失>}"
if [ -z "$vc_lib" ] || [ -z "$sdk_lib" ]; then
echo "::error::交叉链接缺 ARM64 库:给 VS 加上 Microsoft.VisualStudio.Component.VC.Tools.ARM64(含 ARM64 Windows SDK),或把该槽位改用原生 windows-11-arm runner"
echo "::error::windows-11-arm 上缺 ARM64 库(vc=${vc_lib:-缺失} / sdk=${sdk_lib:-缺失}),上面就是全部探测结果;给 VS 补 Microsoft.VisualStudio.Component.VC.Tools.ARM64 与 Windows 11 SDK"
exit 1
fi
# M118(D26):**Slint 版停发** —— GUI 来源只有 `unlucky-launcher-iced`(`crates/app` 已被根
# Cargo.toml `exclude`,不参与任何 workspace 构建)。对外产物名由 make_artifacts.py 的
# `asset_stem()` 生成 `<名字>_<版本号>_<平台>_<架构>`;`LAUNCHER_BIN` 只用来在产物里找文件。
# 不传 `--target`:M140 起每个槽位都在自己的架构上原生编译,host triple 就是产物 triple。
- name: cargo build --release --locked
shell: bash
env:
RUSTFLAGS: ${{ matrix.rustflags }}
TARGET: ${{ matrix.target }}
run: |
set -euo pipefail
target_args=()
if [ -n "$TARGET" ]; then
target_args+=(--target "$TARGET")
fi
cargo build --release --locked "${target_args[@]}" --bin unlucky-launcher-iced --bin ull
cargo build --release --locked --bin unlucky-launcher-iced --bin ull
- name: Windows 单文件自检(导入表不得出现 VC++ 可再发行 DLL)
if: runner.os == 'Windows'
shell: bash
env:
# 跨槽位的关键点:产物在 target/<triple>/release;校验的是**构建产物**(iced bin 原始名),
# 对外名 `unlucky-launcher` 由 make_artifacts.py 落盘时才换上,那一步之后才存在。
# 校验的是**构建产物**(iced bin 原始名);对外名 `unlucky-launcher` 由 make_artifacts.py 落盘时
# 才换上。产物在 `matrix.build_dir`(M140 起六个槽位都是 target/release)。
EXE: ${{ matrix.build_dir }}/unlucky-launcher-iced.exe
run: |
set -euo pipefail
@@ -165,34 +168,31 @@ jobs:
# dumpbin 定位链(三级,全不命中才 fail —— 门禁不降级,只是把"猜路径"换成官方定位):
# ① 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 选。
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[@]}")
fi
VSWHERE="C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
DUMPBIN=""
VIA=""
if [ -x "$VSWHERE" ]; then
# -find 的模式优先用反斜杠(vswhere 官方文档即此形式),再试正斜杠以规避分隔符差异。
for pattern in 'VC\Tools\MSVC\*\bin\Hostx64\x64\dumpbin.exe' \
'VC/Tools/MSVC/*/bin/Hostx64/x64/dumpbin.exe'; do
hit=$("$VSWHERE" -latest -products '*' -find "$pattern" 2>/dev/null | head -n1 || true)
if [ -n "$hit" ]; then
DUMPBIN=${hit//\\//}
VIA="vswhere"
break
fi
done
# -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
fi
if [ -z "$DUMPBIN" ]; then
# VS 根形如 <年>/<版次>(2022/Enterprise、2022/BuildTools……),固定层数的 glob 会踩空,按模式扫。
for root in "C:/Program Files/Microsoft Visual Studio" \
"C:/Program Files (x86)/Microsoft Visual Studio"; do
[ -d "$root" ] || continue
hit=$(find "$root" -maxdepth 11 -type f \
-path '*/VC/Tools/MSVC/*/bin/Hostx64/x64/dumpbin.exe' 2>/dev/null | head -n1 || true)
if [ -n "$hit" ]; then
DUMPBIN=$hit
VIA="find: $root"
break
fi
done
find_pat=()
for p in "${pats[@]}"; do find_pat+=(-o -path "*/$p"); done
DUMPBIN=$(find "C:/Program Files/Microsoft Visual Studio" \
"C:/Program Files (x86)/Microsoft Visual Studio" \
-maxdepth 11 -type f \( "${find_pat[@]:1}" \) 2>/dev/null | head -n1 || true)
if [ -n "$DUMPBIN" ]; then VIA="find"; fi
fi
if [ -z "$DUMPBIN" ]; then
hit=$(command -v dumpbin || true)
@@ -226,8 +226,8 @@ jobs:
set -euo pipefail
# git-bash 与 Linux/macOS runner 的 python 入口名不一致,先探一遍。
PY=$(command -v python3 || command -v python)
# 槽位与产物根都从矩阵来:交叉槽位的二进制不在 target/release,脚本据此定位
# 并回读可执行文件头校验架构(宿主 x64 ≠ 产物 arm64 时冒烟自动跳过,只等 CI 真机)。
# 槽位与产物根都从矩阵来(M140 起六个槽位都是 target/release),脚本据此定位产物并回读
# 可执行文件头校验架构:arm64 槽位现在是**真跑** `ull --version`,「宿主≠产物就跳过」只留给本地交叉。
args=(--slot "$SLOT" --build-dir "$BUILD_DIR")
if [ "$REF_TYPE" = "tag" ]; then
args+=(--version "$TAG") # 与 Cargo.toml 版本交叉校验