From b27e86c65b61305861ba801f7d94f8ef66f4399c Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 6 Nov 2026 11:51:01 +0800 Subject: [PATCH 1/2] =?UTF-8?q?ci(release):=20M140=20windows-arm64=20?= =?UTF-8?q?=E6=94=B9=E7=94=A8=E5=8E=9F=E7=94=9F=20windows-11-arm=20runner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 交叉编译路线在 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 同步原生化与冒烟真跑的说法 --- .github/workflows/release.yml | 132 +++++++++++++++++----------------- packaging/README.md | 48 ++++++------- 2 files changed, 90 insertions(+), 90 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c0a6807..169070e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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//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//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//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 版本交叉校验 diff --git a/packaging/README.md b/packaging/README.md index da27e62..b1cf7e9 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -9,7 +9,7 @@ | 槽位 | 本版发布 | 产物文件名 | 形态 | 归档内容 | |------|---------|-----------|------|---------| | `windows-x64` | ✅ 发布 | `unlucky-launcher__windows_x64.exe` | **裸 exe**(双击即用) | exe 单文件;三份合规文本作为并列 Release 资产 | -| `windows-arm64` | ✅ 发布 | `unlucky-launcher__windows_arm64.exe` | **裸 exe**(同 x64 策略) | 同上;在 x64 宿主上交叉编译到 `aarch64-pc-windows-msvc` | +| `windows-arm64` | ✅ 发布 | `unlucky-launcher__windows_arm64.exe` | **裸 exe**(同 x64 策略) | 同上;CI 在**原生** `windows-11-arm` runner 上构建(M140 起,不再交叉编译) | | `linux-x64` | ❌ 不构建(未验证) | `unlucky-launcher__linux_x64.tar.gz` | 单二进制归档 | `unlucky-launcher` + `LICENSE` + `NOTICE` + `README.md` | | `linux-arm64` | ❌ 不构建(未验证) | `unlucky-launcher__linux_arm64.tar.gz` | 单二进制归档(原生 ARM64 runner 构建) | 同上 | | `macos-arm64` | ❌ 不构建(未验证) | `unlucky-launcher__macos_arm64.tar.gz` | 单二进制归档 | 同上 | @@ -32,9 +32,8 @@ - `` 不带前导 `v`(tag 是 `v0.2.1`,文件名是 `0.2.1`)——由脚本统一裁剪。 - UNIX 归档内 `mtime`/`uid`/`gid` 归零、权限固定(二进制 0755、文本 0644), 同源构建的 sha256 不受打包时刻影响。 -- **架构不靠猜**:脚本打包前回读二进制的可执行文件头(PE 机器码 / ELF `e_machine` / - Mach-O `cputype`),与槽位声明的架构不符即失败——交叉槽位(x64 宿主产 arm64 exe)不会 - 出现"名字是 arm64、内容还是 x64"的假产物。 +- **架构不靠猜**:脚本打包前回读二进制的可执行文件头(PE 机器码 / ELF `e_machine` / Mach-O + `cputype`),与槽位声明的架构不符即失败——不会出现"名字是 arm64、内容还是 x64"的假产物。 ## 2. 版本来源(唯一真相源) @@ -64,7 +63,7 @@ python packaging/make_artifacts.py --no-smoke # 跳过 ull --version 冒 macOS x64),其它组合直接报错退出——不在矩阵里的平台不出"凑合能跑"的产物。 交叉构建(例如在 x64 宿主上产 Windows arm64 产物)靠两个参数把"产物根"和"槽位"显式喂进去, -脚本不去猜宿主与产物是不是同一架构: +脚本不去猜宿主与产物是不是同一架构(**CI 不用这条路径**,见下): ```bash rustup target add aarch64-pc-windows-msvc @@ -75,8 +74,8 @@ python packaging/make_artifacts.py --slot windows-arm64 \ ``` `--slot` 与宿主槽位不一致时,`ull --version` 冒烟会**自动跳过并打印原因**(arm64 产物在 x64 -宿主机上根本执行不了;拿"跳过"当通过是假证据,所以跳过永远带原因)——那条路径的验证只能落在 -CI 真机上:`windows-arm64` 由 cross 构建产物 + M50 的 dumpbin 导入表门禁覆盖;`linux-arm64` 的同类 +宿主机上根本执行不了;拿"跳过"当通过是假证据,所以跳过永远带原因)。**CI 不走交叉**(M140): +`windows-arm64` 在原生 `windows-11-arm` 上编,宿主=产物架构 → 冒烟**真跑**;`linux-arm64` 的同类 冒烟只在 `all_slots=true` 的取证跑里出现(`ubuntu-22.04-arm` 原生 runner 上真跑 `ull --version`)。 ### CI(默认只跑两个 Windows 槽位;`all_slots=true` 才整表六槽位) @@ -89,15 +88,16 @@ CI 真机上:`windows-arm64` 由 cross 构建产物 + M50 的 dumpbin 导入 分支 ref 的 dispatch 与 push 一样被 `release` job 的 `if: github.ref_type == 'tag'` 挡在发布之外,所以 「dispatch 只构建不发布」与「dispatch 能关 pre-release」是两条互不冲突的规则。 - 矩阵**由 `plan` job 按 `all_slots` 生成**(`matrix: ${{ fromJSON(needs.plan.outputs.matrix) }}`:矩阵本身 - 不能带 `if`,而 `strategy` 可读 `needs`),槽位表只在该 job 里有一份:`windows-latest` x64(MSVC + - `-C target-feature=+crt-static`,产物在 `target/release`)与 `windows-latest` **交叉编译**到 - `aarch64-pc-windows-msvc`(产物在 `target/aarch64-pc-windows-msvc/release`)标 `mode: ci`(默认编); + 不能带 `if`,而 `strategy` 可读 `needs`),槽位表只在该 job 里有一份:`windows-latest` x64 与 + **原生** `windows-11-arm` arm64(M140;后者是公开仓免费的标准 arm64 runner,4C/16G)——两者都是 MSVC + + `-C target-feature=+crt-static`、产物都在 `target/release`,标 `mode: ci`(默认编); `ubuntu-22.04`(glibc 2.35 地板)/ `ubuntu-22.04-arm`(原生 ARM64,同地板)/ `macos-latest` / `macos-15-intel` 标 `mode: probe`——**未验证,默认一个都不编**,只有 `all_slots=true` 才进矩阵。 -- 每个槽位 `cargo build --release --locked`(交叉槽位多两步:`rustup target add` + ARM64 库体检 - ——查 VC 的 `lib/arm64/libcmt.lib`(`+crt-static` 靠它)与 SDK 的 `um/arm64/kernel32.lib`, - 缺了直接给出"给 VS 加 `VC.Tools.ARM64` 或改用原生 `windows-11-arm`"的可执行提示)→ - `make_artifacts.py --slot … --build-dir …` → 归档内容自检 → 上传 artifact。 +- 每个槽位 `cargo build --release --locked`(**不传 `--target`**:每槽原生编译,交叉槽位与 `rustup + target add` 一并没了)。arm64 槽位多一步编译前体检:查 VC 的 `lib/arm64/libcmt.lib`(`+crt-static` + 靠它)与 SDK 的 `um/arm64/kernel32.lib`,缺了就 fail 并把**实际探测到的目录全打出来**(2026-11-06 + 那次只报"缺失",看不出是镜像没装还是探测写错)→ `make_artifacts.py --slot … --build-dir …` → + 归档内容自检 → 上传 artifact。 - **发布白名单(`PUBLISH_SLOTS`,唯一开关)**:默认只构建两个 Windows 槽位,只有白名单里的槽位进 Release 与 `SHA256SUMS.txt`——当前 = `windows-x64 windows-arm64`;四档未验证的(默认连构建都不跑)不推给用户。 将来某槽位真机验证通过:`plan` 槽位表里把它的 `mode` 从 `probe` 改成 `ci`、把键加进这一行(齐全性检查 @@ -133,12 +133,12 @@ Windows 为什么用 MSVC 静态 CRT 而不是本机这套 GNU 工具链:两 ### Windows ARM64(Win11 on ARM) 产物形态与门禁跟 x64 一模一样(裸 exe + 静态 CRT + dumpbin 导入表硬门禁),差别只在**怎么编**: -CI 在 `windows-latest`(x64 宿主)上交叉编译到 `aarch64-pc-windows-msvc`,产物落在 -`target/aarch64-pc-windows-msvc/release`(由 `matrix.build_dir` 注入,脚本不硬编码路径)。 -GitHub 文档(2026)已把 `windows-11-arm` / `windows-11-vs2026-arm` 列为公开仓库**免费**的标准 -arm64 runner,原生路线可行;本流水线仍选交叉编译,因为①双 Windows 槽位共用同一套已验证镜像与 -工具链、②M50 的 dumpbin 定位链取的是 `Hostx64/x64/dumpbin.exe`(读 ARM64 PE 的导入表没问题), -原生 ARM 宿主上那条链得再补 `Hostarm64` 分支——不为一个槽位放宽刚立的门禁。 +**M140 起在原生 `windows-11-arm` 上编**(GitHub 文档 2026 的 Standard runners 表明列该标签:4C/16G、 +公开仓库免费),产物落在 `target/release`(`matrix.build_dir` 注入,脚本不硬编码路径)。原路线是在 +`windows-latest` 上交叉编译到 `aarch64-pc-windows-msvc`,但**CI 上从未成功过一次**(v0.2.1 首次真跑即 +红在「ARM64 工具链体检」:x64 镜像上探不到 VC/SDK 的 ARM64 库),"双 Windows 共用一套镜像"的惯性理由 +因此不再成立。原生的附赠好处:宿主=产物架构,`ull --version` 冒烟**真跑**;M50 的 dumpbin 定位链补了 +`Hostarm64/arm64` 分支,并按 `RUNNER_ARCH` 只挑本机能执行的那族(x64 上不会挑到跑不起来的 arm64 二进制)。 运行时依赖与 x64 同表(UCRT 是 Win11 ARM 的系统组件,`+crt-static` 静态链 CRT,不需要可再发行包)。 游戏侧如实说:**启动器原生运行**;Temurin 没有 windows-aarch64 产物,Microsoft OpenJDK 有该 @@ -312,10 +312,10 @@ libxcb 条目数 = 0,只把 fontconfig 那 7 个 `.so` 放进 `LD_LIBRARY_PATH | `linux-arm64` stub 归档 | 成员 `unlucky-launcher-0.1.0-linux-arm64/{LICENSE,NOTICE,README.md,unlucky-launcher}`(旧连字符名,当次实测),权限 0644/0755、`uid=gid=0`、mtime 0、gzip 头 mtime 字段 `00 00 00 00`;连调两次 sha256 完全一致(`f949d0f5…`)→ 可复现规则与 x64 同源 | | `windows-x64` 原生回归 | `arch: … -> x64`、`smoke: ull.exe --version -> 0.1.0 (ok)`、产物 14.01 MiB、`sha256sum -c SHA256SUMS.txt` OK(这一次用的是当前工作树的二进制,与 §7.3 那次不是同一次构建,sha256 自然不同) | -**没在本地验证、只能由 CI 补的**:`windows-arm64` 的交叉链接与真实 ARM64 导入表、`linux-arm64` 在原生 arm64 -runner 上的真跑(本机连 `raw.githubusercontent.com` 都解析不了,查不到 runner 镜像里装没装 ARM64 的 MSVC 库)。 -`release.yml` 为此加了一道链接前体检(查 `libcmt.lib` 与 `um/arm64/kernel32.lib`,缺了直接给"加 `VC.Tools.ARM64` -或改原生 runner"的提示);merge 后重跑流水线,两槽位进 `SHA256SUMS.txt` 即算闭环。 +**没在本地验证、只能由 CI 补的**:`windows-arm64` 在 `windows-11-arm` 上的真实链接与 ARM64 导入表、 +`linux-arm64` 在原生 arm64 runner 上的真跑(本机既没有 ARM 机器,也连 `raw.githubusercontent.com` 都解析不了)。 +M140 保留了一道编译前体检(查 VC 的 `lib/arm64/libcmt.lib` 与 SDK 的 `um/arm64/kernel32.lib`,失败时把 +**实际探测到的目录**打印出来);下次 tag 跑绿、两槽位进 `SHA256SUMS.txt` 即算闭环。 ## 8. 发行合规 From 5678683561e1a3c0668405b73d81c12820c92f91 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Fri, 6 Nov 2026 11:59:31 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(release):=20M140=20r1=20=E5=A4=8D?= =?UTF-8?q?=E5=AE=A1=E4=B8=89=E6=9D=A1=20p2=20=E2=80=94=E2=80=94=20vswhere?= =?UTF-8?q?=20-find=20=E5=8D=95=E5=80=BC=E3=80=81=E6=A0=B9=E5=9B=A0?= =?UTF-8?q?=E6=8E=AA=E8=BE=9E=E3=80=81SDK=20=E5=8F=8C=E6=A0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 未动。 --- .github/workflows/release.yml | 41 ++++++++++++++++++----------------- packaging/README.md | 10 ++++----- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 169070e..16d09a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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//release` 与 - # 「`rustup target add` + cargo 的 `--target`」两处一起加回来(现在它们已随最后一个交叉槽位删掉)。 + # **没有交叉槽位**(M140):每槽在自己的架构上原生构建,故无 `target` 键、产物一律在 target/release。 + # 要加回交叉槽位:`target` 键 + `build_dir=target//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 会踩空,按模式扫。 diff --git a/packaging/README.md b/packaging/README.md index b1cf7e9..73d4a1d 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -135,10 +135,10 @@ Windows 为什么用 MSVC 静态 CRT 而不是本机这套 GNU 工具链:两 产物形态与门禁跟 x64 一模一样(裸 exe + 静态 CRT + dumpbin 导入表硬门禁),差别只在**怎么编**: **M140 起在原生 `windows-11-arm` 上编**(GitHub 文档 2026 的 Standard runners 表明列该标签:4C/16G、 公开仓库免费),产物落在 `target/release`(`matrix.build_dir` 注入,脚本不硬编码路径)。原路线是在 -`windows-latest` 上交叉编译到 `aarch64-pc-windows-msvc`,但**CI 上从未成功过一次**(v0.2.1 首次真跑即 -红在「ARM64 工具链体检」:x64 镜像上探不到 VC/SDK 的 ARM64 库),"双 Windows 共用一套镜像"的惯性理由 -因此不再成立。原生的附赠好处:宿主=产物架构,`ull --version` 冒烟**真跑**;M50 的 dumpbin 定位链补了 -`Hostarm64/arm64` 分支,并按 `RUNNER_ARCH` 只挑本机能执行的那族(x64 上不会挑到跑不起来的 arm64 二进制)。 +`windows-latest` 上交叉编译到 `aarch64-pc-windows-msvc`,但**CI 上从未成功过一次**(v0.2.1 首次真跑即红在 +「ARM64 工具链体检」)。那次**根因未定论**:`windows-2025` 镜像清单里 `VC.Tools.ARM64` 与 SDK 26100 都在, +更像自检自己的路径探测有问题;无论哪种,交叉都多一层"在 x64 宿主上猜 ARM64 库路径"。原生的附赠好处: +宿主=产物架构,`ull --version` 冒烟**真跑**;dumpbin 链补了 `Hostarm64/arm64` 分支并按 `RUNNER_ARCH` 选族。 运行时依赖与 x64 同表(UCRT 是 Win11 ARM 的系统组件,`+crt-static` 静态链 CRT,不需要可再发行包)。 游戏侧如实说:**启动器原生运行**;Temurin 没有 windows-aarch64 产物,Microsoft OpenJDK 有该 @@ -282,7 +282,7 @@ uxtheme/uiautomationcore/opengl32/ws2_32/bcrypt/bcryptprimitives/ntdll` + 一组 `libgcc_s_*.dll` 之类 MinGW 运行时 —— 即"拷走一个 exe 就能跑"(Win10+ 自带 UCRT)。 CI 的 MSVC `+crt-static` 产物同理(静态 CRT);`release.yml` 里用 `dumpbin -dependents` 把这条变成硬门禁, 两侧都 fail、没有降级分支:**出现 VCRUNTIME/MSVCP/ucrtbase 就 fail**,**找不到 dumpbin 同样 fail** -(`windows-latest` 自带 VS 工具链,基本找得到;"找不到就跳过"等于让门禁在最需要它的时候失效)。 +(`windows-latest` 与 `windows-11-arm` 两个镜像都自带 VS 工具链,基本找得到;跳过等于让门禁在最需要它时失效)。 总之不允许"悄悄要用户装可再发行包"。 注意:本机只装了 GNU 工具链,MSVC 那个槽位的产物本身**没有在本机验证过**,由 CI 首跑验证。