P2-1(核心:上一轮的 ② 在 git-bash 下是死代码):`cmd //c "\"${vcvars//\//\}\" arm64 …"`
不行——MSYS 把参数里的 `"` 改写成 `\"` 交给 cmd.exe,cmd 的 `/c` 引号剥离接不住,LIB/INCLUDE
全空,只剩 `set` 的空输出;于是 ③「显式 link.exe」在**没有 LIB** 的状态下生效,正是本步注释里
写的那种会造出 `LNK1181: cannot open input file 'kernel32.lib'` 的组合。改法(复审验过的路线):
往 `$RUNNER_TEMP` 写一个 **CRLF** 的 wrapper `.bat`(`call "<vcvarsall>" arm64 >/dev/null 2>&1` + `set`),
`cmd //c` 只收**不含引号**的相对文件名(先 cd 进去)——引号留在 .bat 内部由 cmd 解析,路径带空格
也不受影响。同源的 ③ 也补齐:`vswhere -find` 的 `vcvarsall.bat` 与两个 `link.exe` 候选都改成
「反斜杠形态 + 正斜杠形态各试一次」(和同文件 dumpbin 步同规矩),不再赌一种形态。
P2-2:cargo build 步末尾那条 `ls -l` 移到 `if [ "$SLOT" = "windows-arm64" ]` 门内(并加 `|| true`):
x64 是当前唯一在发的槽位,不给它加一个失败面。
P2-3:① `release-publish.yml` 步数改对——主线 7 步、本分支 8 步(`release.yml` 与
`packaging/docs/build-and-ci.md` 两处都改,并列出 8 步是哪几步);② `check_publish_slots.py`
的 report() 全部改走 **stdout**:注解(`::error::`/`::warning::`)在 stdout 上同样被 Actions 识别,
而分两个流写会让「表格 → 注解」的顺序不可控(原注释承诺的顺序当时并不成立)。顺带在
`build-and-ci.md` 的 arm64 工具链段补一句「`cmd //c` 别收带引号的路径、要 wrapper .bat」,
免得下一个 agent 再把 ② 写回死代码。
本地证据(同一 worktree 复跑):
- P2-1 新证据:把本步 `run:` 块原样抽出,用 `bash --noprofile --norc -eo pipefail`(与 runner 同
语义)、假 vswhere + **路径带空格**的假 `vcvarsall.bat`(真 cmd)跑:新写法 LIB/INCLUDE/linker
三行全部写进 `$GITHUB_ENV`、rc=0;同一套假件跑 e858664 的老写法 ⇒ LIB/INCLUDE 全空、只有 linker
(与复审结果一致)。降级路径也复测:vswhere 不存在 ⇒ 只 1 条 warning、rc=0;vswhere 不给
vcvarsall ⇒ ② warning、③ 仍拿到 Hostx64 兜底、rc=0。
- 体检步 5 场景(无 VS / vswhere 全成功 / 报错退出 / 垃圾+非零 / 路径不存在)全部 rc=0、0 条
`::error::`。
- `check_publish_slots.py --self-test` 8 条 + 警告断言全绿;`--dist` 六场景复测(x64-only 绿 /
多一份 arm64 绿+warning / 缺 CLI rc=1 / 空 dist rc=1 / `--slots` 漂移 rc=1 / `--dist` 非目录
rc=1),并且缺产物时输出顺序是「表格 → warning → error → 汇总」,stderr 为空。
- 7 个 workflow `yaml.safe_load` OK;全部 `run:` 块 `bash -n` OK;全仓 400 行扫描 0 个超限
(release-build.yml 316、check_publish_slots.py 345)。
- `check_release_semantics.py`(+`--self-mutation`)、`release_marker.py --self-test`、
`make_manifest.py --self-test`、`cargo metadata --no-deps` 全绿。
边界不变:arm64 产物仍**从未被构建过、也从未在真机跑过**;`PUBLISH_SLOTS` 仍是 `"windows-x64"`,
发布语义与 x64 静态 CRT 门禁未动。下一次 tag 的 arm64 仍可能红,红点大概率换到 C 依赖编译
(aws-lc-sys 的 CMake + aarch64 cl.exe)或链接器(LNK1181)——**不是「arm64 修好了」**。
317 lines
20 KiB
YAML
317 lines
20 KiB
YAML
name: Release / build(可复用)
|
||
|
||
# Release 流水线的**构建侧**(M145 拆分:`release.yml` 与 `packaging/README.md` 都顶到 400 行红线,下一处改动必越线)。
|
||
# M145 时本文件 = 拆分前 `.github/workflows/release.yml` 的 `build` job **逐字搬家**,改动只有四个矩阵键换成同名入参:
|
||
# `matrix.slot` → `inputs.slot`、`matrix.os` → `inputs.os`、`matrix.build_dir` → `inputs.build_dir`、
|
||
# `matrix.rustflags` → `inputs.rustflags`。
|
||
# **M164 改了两步**(只这两步):① `Windows ARM64 工具链体检` → `工具链取证`(不再 exit 1,判定权交回编译器;
|
||
# 理由见该步上方的长注释);② `cargo build` 步给 arm64 槽位显式备齐 target / `LIB`+`INCLUDE` / arm64 `link.exe`。
|
||
# 矩阵**仍由调用方** `release.yml` 的 `plan` job 生成并在外层展开(矩阵不能带 `if`,`strategy` 可读 `needs`),
|
||
# 本文件只跑**单个槽位**,不持有槽位表 —— 改槽位只改 `release.yml` 里那一处。
|
||
# `env` 必须在被调方重复声明:**被调工作流不继承调用方的 workflow 级 `env`**(CARGO_TERM_COLOR / RUST_BACKTRACE
|
||
# 都是构建产物的实参:前者只影响日志着色,后者决定 panic 是否打回溯)。
|
||
|
||
on:
|
||
workflow_call:
|
||
inputs:
|
||
slot:
|
||
description: "槽位键,同 plan matrix 的 slot(如 windows-x64 / linux-arm64)"
|
||
type: string
|
||
required: true
|
||
os:
|
||
description: "runner 标签,同 plan matrix 的 os(如 windows-latest / windows-11-arm)"
|
||
type: string
|
||
required: true
|
||
build_dir:
|
||
description: "产物根,同 plan matrix 的 build_dir(六槽位一律 target/release)"
|
||
type: string
|
||
required: true
|
||
rustflags:
|
||
description: "RUSTFLAGS,同 plan matrix 的 rustflags(两个 Windows 槽位为 -C target-feature=+crt-static,其余空)"
|
||
type: string
|
||
required: false
|
||
default: ""
|
||
# (M149)微软登录用的 Azure 公共客户端 id(公共客户端 id 随二进制一起分发,不按高密级处理,但**不进 git
|
||
# 文件**:真值放仓库 Secret,用户在仓库设置里录入)。`required: false` 是刻意的——调用方没传(本地、
|
||
# 没有 runner 的镜像站、以及 Secret 尚未录入时)求值为空串,`client_id.rs` 把空白当「没配」,
|
||
# 于是非官方构建不烘 id,官方构建才烘。**调用方必须显式传**(`secrets: inherit` 或按名传);
|
||
# 没传时这里是空,不会报错。
|
||
secrets:
|
||
ULL_MSA_CLIENT_ID:
|
||
description: "Azure 公共客户端 id,烘进发布二进制(构建期 option_env!);留空 = 不烘"
|
||
required: false
|
||
|
||
env:
|
||
CARGO_TERM_COLOR: always
|
||
RUST_BACKTRACE: 1
|
||
|
||
# 与拆分前 build job 拿到的凭据范围一致(可复用工作流的权限只取调用方与本文件的**交集**,故两边都声明 read)。
|
||
permissions:
|
||
contents: read
|
||
|
||
jobs:
|
||
build:
|
||
name: build (${{ inputs.slot }})
|
||
runs-on: ${{ inputs.os }}
|
||
permissions:
|
||
contents: read
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: Linux 图形栈构建依赖(winit / wgpu 编译期需要)
|
||
if: runner.os == 'Linux'
|
||
shell: bash
|
||
run: |
|
||
sudo apt-get update
|
||
sudo apt-get install -y --no-install-recommends libxkbcommon-dev libfontconfig1-dev \
|
||
libgl1-mesa-dev libegl1-mesa-dev libwayland-dev libxcb1-dev
|
||
|
||
- uses: dtolnay/rust-toolchain@stable
|
||
- uses: Swatinem/rust-cache@v2
|
||
|
||
# (M141/M142 → **M164 判定权移交**)windows-arm64 跑在**原生** `windows-11-arm` 上。本步**只取证,不判定**:
|
||
# M141/M142 的版本用 `find` 猜 SDK 的 `um/arm64/kernel32.lib` 在不在,猜缺就 `exit 1` —— 结果 v0.2.1~v0.2.3
|
||
# 五轮全红在**这一步**、下面 `cargo build` 一次都没跑过(第 7 步永远 skipped),"arm64 修不好"始终是探针给的
|
||
# 结论、不是编译器给的。而它跟自己打的快照自相矛盾:v0.2.3 的注解原文是
|
||
# `[arm64sdk] SDK Lib 版本→um 架构: 10.0.22621.0→um:[arm,arm64,x64,x86,] 10.0.26100.0→um:[arm64,x64,x86,]`
|
||
# 同一份注解里却报 `kernel32=缺失`(同一个 `find` 模式在本地合成目录树上命中,故这条路本身不可靠)。
|
||
# M164 把红绿交回编译器与链接器:本步只打印实况(vswhere 组件查询 / 目录里 .lib **计数** / arm64 link.exe
|
||
# 路径),一条 `::error::` 都不写、永不 `exit 1`;红不红由 `cargo build` 与静态 CRT 导入表门禁决定。
|
||
# 也不再自动补装:runner 镜像是 Windows 11 Arm64 + Visual Studio 2026(`windows-11-arm` 自 2026-09 切到 VS2026,
|
||
# VS 在 `C:\Program Files\Microsoft Visual Studio\18\Enterprise`),其镜像清单
|
||
# (actions/runner-images 的 `images/windows/Windows11-VS2026-Arm64-Readme.md`)里 `VC.Tools.ARM64`、
|
||
# `Windows11SDK.26100`、`Windows11SDK.22621` 三件都已装好;M142 那次 `setup.exe modify` 反倒返回非 0。
|
||
# 注解仍每条一行(job 日志下载要 token,check-run 注解不用;多行会被拼成一行截断)。
|
||
- name: Windows ARM64 工具链取证(只打印实况,红绿交给编译器)
|
||
if: inputs.slot == 'windows-arm64'
|
||
shell: bash
|
||
run: |
|
||
set +e; set -uo pipefail # -e 由 runner 命令行注入,只能显式关
|
||
say() { printf '::%s::[arm64sdk] %s\n' "$1" "$2"; return 0; }
|
||
shopt -s nullglob # 计数走 glob 不走 find:find 的管道 + errexit 正是 M141 的死因
|
||
vswhere="C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
|
||
[ -x "$vswhere" ] || say warning "找不到 vswhere($vswhere):下面会是 0 命中,那不代表工具链缺东西"
|
||
# ① 组件:用 vswhere 自己的 `-requires`(权威查询),不拿「某个文件名存在」当替身。
|
||
for comp in Microsoft.VisualStudio.Component.VC.Tools.ARM64 \
|
||
Microsoft.VisualStudio.Component.Windows11SDK.26100 \
|
||
Microsoft.VisualStudio.Component.Windows11SDK.22621; do
|
||
hit=""
|
||
[ -x "$vswhere" ] && hit=$("$vswhere" -latest -products '*' -requires "$comp" -property installationPath 2>/dev/null | head -n1 || true)
|
||
if [ -n "$hit" ]; then say notice "组件 $comp: 已装($hit)"
|
||
else say warning "组件 $comp: 未装 / 这台 VS 不认这个 id(继续看下面的实际 .lib 计数)"; fi
|
||
done
|
||
# ② 实况计数:`+crt-static` 要 VC 的 `lib/arm64/libcmt.lib`,链接要 SDK 的 `um/arm64/*.lib`。
|
||
for root in "C:/Program Files/Microsoft Visual Studio" "C:/Program Files (x86)/Microsoft Visual Studio"; do
|
||
hits=( "$root"/*/*/VC/Tools/MSVC/*/lib/arm64/libcmt.lib )
|
||
say notice "VC $root → lib/arm64/libcmt.lib ${#hits[@]} 个${hits[0]:+(首个 ${hits[0]})}"
|
||
done
|
||
for root in "C:/Program Files (x86)/Windows Kits/10/Lib" "C:/Program Files/Windows Kits/10/Lib"; do
|
||
[ -d "$root" ] || { say notice "SDK $root:不存在"; continue; }
|
||
for ver in "$root"/*/; do
|
||
libs=( "$ver"um/arm64/*.lib )
|
||
say notice "SDK $(basename "$ver") → um/arm64/*.lib ${#libs[@]} 个"
|
||
done
|
||
done
|
||
# ③ arm64 链接器候选:宿主 arm64 的优先(原生),x64 托管的次之。下面 cargo 步会显式导出它。
|
||
for p in 'VC/Tools/MSVC/*/bin/Hostarm64/arm64/link.exe' 'VC/Tools/MSVC/*/bin/Hostx64/arm64/link.exe'; do
|
||
hit=""
|
||
[ -x "$vswhere" ] && hit=$("$vswhere" -latest -products '*' -find "$p" 2>/dev/null | head -n1 || true)
|
||
say notice "link.exe 候选 $p → ${hit:-未命中}"
|
||
done
|
||
echo "取证完毕:本步不判红;arm64 的结论只看下面的 cargo build / 链接 / 导入表门禁"
|
||
|
||
# M118(D26):**Slint 版停发** —— GUI 只有 `unlucky-launcher-iced`(`crates/app` 已 exclude,不进 workspace)。
|
||
# 产物名由 make_artifacts.py 的 `asset_stem()` 生成 `<名字>_<版本号>_<平台>_<架构>`;不传 `--target`(M140 起全原生)。
|
||
# (M149)`ULL_MSA_CLIENT_ID` 经 `option_env!` 烘进二进制(`crates/core/src/auth/microsoft/client_id.rs`):
|
||
# 官方产物自带微软登录所需的配置,装完即可登录;运行期同名环境变量仍优先于它,可覆盖。
|
||
# 值来自仓库 Secret(不进 git 文件);Secret 没配时这里是空串,client_id.rs 把空白当「没配」,
|
||
# 于是非官方构建不烘 id。想自己烘一份就从源码构建:`ULL_MSA_CLIENT_ID=<id> cargo build`。
|
||
# (M164)arm64 槽位在编译前把工具链**显式**备齐,不再依赖「runner 反正自带」这种默契:
|
||
# ① `rustup target add aarch64-pc-windows-msvc`:宿主机本来就是 aarch64-pc-windows-msvc,这句在原生
|
||
# runner 上是幂等 no-op;它把「缺 std」这类失败挡在编译前(缺 target 时 rustc 只会说找不到 crate std);
|
||
# ② MSVC 的 `LIB` / `INCLUDE` 用微软自己的 `vcvarsall.bat arm64` 取(**不手搓路径**:少一个目录就是
|
||
# LNK1181),本步 `export` 一次、同时写进 `$GITHUB_ENV` 供下游 dumpbin / 打包步;
|
||
# ③ arm64 的 `link.exe` 用 vswhere 定位后显式导出 `CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER`
|
||
# (Hostarm64 原生优先、Hostx64 次之)。**②必须先于③**:只覆盖链接器而不给 LIB,rustc 会在
|
||
# `link.exe` 里撞 `LNK1181: cannot open input file 'kernel32.lib'`——那正是我们要定位的错误,不是要制造的错误。
|
||
# ①②③ 全是「定位不到就打印原因继续」,不用启发式决定红绿:真缺工具链时让链接器自己报出来。
|
||
- name: cargo build --release --locked
|
||
shell: bash
|
||
env:
|
||
RUSTFLAGS: ${{ inputs.rustflags }}
|
||
ULL_MSA_CLIENT_ID: ${{ secrets.ULL_MSA_CLIENT_ID }}
|
||
SLOT: ${{ inputs.slot }}
|
||
BUILD_DIR: ${{ inputs.build_dir }}
|
||
run: |
|
||
set -euo pipefail
|
||
echo "host=$(rustc -vV | sed -n 's/^host: //p') slot=$SLOT build_dir=$BUILD_DIR"
|
||
if [ "$SLOT" = "windows-arm64" ]; then
|
||
rustup target add aarch64-pc-windows-msvc
|
||
rustup target list --installed
|
||
vswhere="C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
|
||
if [ -x "$vswhere" ]; then
|
||
# ② MSVC 的 LIB/INCLUDE。**别把 vcvarsall 的路径直接塞给 `cmd //c "..."`**:MSYS 会把参数里的
|
||
# `"` 改写成 `\"` 再交给 cmd.exe,而 cmd 的 /c 引号剥离接不住这一层,只会报
|
||
# `'\"C:\...\vcvarsall.bat\"' 不是内部或外部命令` —— 结果 LIB/INCLUDE 全空、只剩 `set` 的空输出
|
||
# (M164 复审实测)。故本步改为往 `$RUNNER_TEMP` 写一个 **CRLF** 的 wrapper .bat,引号留在
|
||
# .bat 自己的 `call "..."` 里(由 cmd 解析,路径带空格也没事),`cmd //c` 只收**不含引号**的
|
||
# 相对文件名(先 cd 进去),彻底绕开 MSYS 的引号改写。
|
||
tmp=${RUNNER_TEMP:-${TEMP:-/tmp}}
|
||
mkdir -p "$tmp"
|
||
vcvars=""
|
||
# `-find` 是**单值**参数且官方文档给的是反斜杠形态:两种形态各试一次(与下面 dumpbin 步同规矩),
|
||
# 只赌一种就等于再写一个「静默退化成 warning」。
|
||
for pat in 'VC\Auxiliary\Build\vcvarsall.bat' 'VC/Auxiliary/Build/vcvarsall.bat'; do
|
||
vcvars=$("$vswhere" -latest -products '*' -find "$pat" 2>/dev/null | head -n1 || true)
|
||
[ -n "$vcvars" ] && break
|
||
done
|
||
dump=""
|
||
if [ -n "$vcvars" ]; then
|
||
# CRLF 是必须的:cmd 解析只有 LF 的 .bat 会漏行/报错。
|
||
{ printf '@echo off\r\n'
|
||
printf 'call "%s" arm64 >nul 2>&1\r\n' "${vcvars//\//\\}"
|
||
printf 'set\r\n'; } > "$tmp/ull-msvc-arm64.bat"
|
||
dump=$(cd "$tmp" && cmd //c ull-msvc-arm64.bat 2>/dev/null | tr -d '\r') || dump=""
|
||
echo "wrapper: $tmp/ull-msvc-arm64.bat ← $vcvars(set 输出 ${#dump} 字符)"
|
||
fi
|
||
for key in LIB INCLUDE; do
|
||
value=$(printf '%s\n' "$dump" | sed -n "s/^${key}=//p" | head -n1)
|
||
if [ -n "$value" ]; then
|
||
export "$key=$value"
|
||
printf '%s=%s\n' "$key" "$value" >> "$GITHUB_ENV"
|
||
echo "env $key=${value:0:90}…(${#value} 字符)"
|
||
else
|
||
echo "::warning::[arm64] 没拿到 $key(vcvarsall=${vcvars:-未定位}):留空交给 rustc 自己探测 VS"
|
||
fi
|
||
done
|
||
found=""
|
||
for p in 'VC\Tools\MSVC\*\bin\Hostarm64\arm64\link.exe' 'VC/Tools/MSVC/*/bin/Hostarm64/arm64/link.exe' \
|
||
'VC\Tools\MSVC\*\bin\Hostx64\arm64\link.exe' 'VC/Tools/MSVC/*/bin/Hostx64/arm64/link.exe'; do
|
||
[ -n "$found" ] && break
|
||
hit=$("$vswhere" -latest -products '*' -find "$p" 2>/dev/null | head -n1 || true)
|
||
[ -n "$hit" ] && found=${hit//\\//}
|
||
done
|
||
if [ -n "$found" ]; then
|
||
export CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER="$found"
|
||
printf 'CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER=%s\n' "$found" >> "$GITHUB_ENV"
|
||
echo "linker: $found"
|
||
else
|
||
echo "::warning::[arm64] 没定位到 arm64 link.exe:让 rustc 自己找(真的失败时错误里会带路径)"
|
||
fi
|
||
else
|
||
echo "::warning::[arm64] 没有 vswhere:target 已加,链接器交给 rustc 自己探测"
|
||
fi
|
||
fi
|
||
cargo build --release --locked --bin unlucky-launcher-iced --bin ull
|
||
# 产物清单只给 arm64 打:x64(当前唯一在发的槽位)这一步零变化,不额外给它加失败面。
|
||
if [ "$SLOT" = "windows-arm64" ]; then
|
||
ls -l "$BUILD_DIR"/unlucky-launcher-iced* "$BUILD_DIR"/ull* || true
|
||
fi
|
||
|
||
- name: Windows 单文件自检(导入表不得出现 VC++ 可再发行 DLL)
|
||
if: runner.os == 'Windows'
|
||
shell: bash
|
||
env:
|
||
# 校验的是**构建产物**(iced bin 原始名);对外名 `unlucky-launcher` 由 make_artifacts.py 落盘时才换上。
|
||
EXE: ${{ inputs.build_dir }}/unlucky-launcher-iced.exe
|
||
run: |
|
||
set -euo pipefail
|
||
# +crt-static 是否真生效只看导入表:出现 VCRUNTIME/MSVCP/msvcr/ucrtbase 就是还得装可再发行包,fail。
|
||
# dumpbin 定位链(三级,全不命中才 fail —— 门禁不降级):① vswhere ② find 兜底(两个 VS 根都扫)③ PATH
|
||
# (排最后是刻意的:最不可控)。dumpbin 在 MSVC 的**宿主**工具目录里:x64 runner 是 `Hostx64/x64`,arm64
|
||
# runner(M140)是 `Hostarm64/arm64`(反向不行——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[@]}")
|
||
fi
|
||
VSWHERE="C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
|
||
DUMPBIN=""
|
||
VIA=""
|
||
if [ -x "$VSWHERE" ]; then
|
||
# `-find` 是**单值**参数:一次传多个 pattern 只有一个生效(3.x 取最后一个 —— 老写法里 Hostarm64 因此永远
|
||
# 轮不到),故每个 pattern 各起一次定位器,不赌它取哪个。反斜杠形态优先(官方文档即此形式),再试正斜杠。
|
||
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 会踩空,按模式扫。
|
||
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)
|
||
if [ -n "$hit" ]; then DUMPBIN=$hit; VIA="PATH"; fi
|
||
fi
|
||
# 工具缺失同样 fail:"找不到就跳过"等于让这道门禁在最需要它的时候静默失效。
|
||
if [ -z "$DUMPBIN" ]; then echo "::error::找不到 dumpbin,无法校验导入表(静态 CRT 门禁不降级)"; exit 1; fi
|
||
echo "dumpbin: $DUMPBIN(定位自 $VIA)"
|
||
ls -l "$EXE"
|
||
"$DUMPBIN" -nologo -dependents "$EXE" | tee dependents.txt
|
||
if grep -Eiq 'VCRUNTIME|MSVCP|MSVCR|ucrtbase\.dll' dependents.txt; then
|
||
echo "::error::静态 CRT 未生效:exe 仍动态依赖 VC++ 可再发行包"; exit 1
|
||
fi
|
||
|
||
- name: 打包(产物 + SHA256SUMS)
|
||
shell: bash
|
||
env:
|
||
# 不把 `${{ }}` 直接拼进 bash 脚本:git ref 名允许 `$`/反引号/`"`,一律经 env 传入再展开。
|
||
REF_TYPE: ${{ github.ref_type }}
|
||
TAG: ${{ github.ref_name }}
|
||
SLOT: ${{ inputs.slot }}
|
||
BUILD_DIR: ${{ inputs.build_dir }}
|
||
run: |
|
||
set -euo pipefail
|
||
# git-bash 与 Linux/macOS runner 的 python 入口名不一致,先探一遍。
|
||
PY=$(command -v python3 || command -v python)
|
||
# 槽位与产物根都从矩阵来(M140 起六个槽位都是 target/release),脚本据此定位产物并回读
|
||
# 可执行文件头校验架构:arm64 槽位现在是**真跑** `ull --version`,「宿主≠产物就跳过」只留给本地交叉。
|
||
args=(--slot "$SLOT" --build-dir "$BUILD_DIR")
|
||
if [ "$REF_TYPE" = "tag" ]; then
|
||
args+=(--version "$TAG") # 与 Cargo.toml 版本交叉校验
|
||
fi
|
||
"$PY" packaging/make_artifacts.py "${args[@]}" --out dist
|
||
ls -l dist
|
||
|
||
- name: 归档内容自检(二进制定位 + 合规文本)
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
for archive in dist/*.tar.gz; do
|
||
[ -e "$archive" ] || continue
|
||
listing=$(tar -tzf "$archive")
|
||
for required in unlucky-launcher LICENSE NOTICE README.md; do
|
||
if ! grep -q "/$required$" <<<"$listing"; then
|
||
echo "::error file=$archive::$archive 缺少 $required"
|
||
exit 1
|
||
fi
|
||
done
|
||
echo "OK: $archive 含二进制与 LICENSE/NOTICE/README.md"
|
||
done
|
||
if ls dist/*.exe >/dev/null 2>&1; then
|
||
echo "Windows 单 exe 形态:LICENSE/NOTICE/README.md 作为 Release 资产并列分发"
|
||
fi
|
||
|
||
- uses: actions/upload-artifact@v4
|
||
with:
|
||
name: dist-${{ inputs.slot }}
|
||
path: dist
|
||
if-no-files-found: error
|
||
retention-days: 14
|
||
|
||
# (M142/M164)arm64 若红在别的步骤(cargo / 链接 / 打包 / 导入表门禁),也要有一条注解说清「红在哪」和
|
||
# 「不挡发布」——只剩一句 `Process completed with exit code 1.` 的教训见上面那条体检步骤。
|
||
# M164 起本槽位的红**只可能来自真实构建链路**(体检已降级为取证、永不 exit 1),所以这条注解同时也是
|
||
# 「arm64 到底能不能编出来」的答案所在:红了就把上面的编译器/链接器原文当根因,别再回去改体检。
|
||
- name: arm64 失败收尾注解(红不挡发布)
|
||
if: failure() && inputs.slot == 'windows-arm64'
|
||
shell: bash
|
||
run: echo "::error::[arm64] 本槽位已红,失败步骤见本 job 的步骤名(M164 起只会是 cargo build / 链接 / 导入表门禁 / 打包,体检步不再判红);PUBLISH_SLOTS 当前只有 windows-x64 ⇒ 本次 Release 仍照发 x64,arm64 产物不进 Release"
|