diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e29dd89..3368fd1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,15 +1,13 @@ name: 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。**CI 只跑两个 Windows -# 槽位**(M139):Linux x64/arm64、macOS arm64/x64 四档从未真构建成功过一次、也没在真机跑过,默认一个都不跑(省配额、 -# 不留红叉);整表六槽位取证走 `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 得 -# `gh workflow run release.yml --ref v0.2.1`);分支 ref 的 dispatch 与 push 一样被 release job 的 -# `if: github.ref_type == 'tag'` 挡在发布之外,故「只构建不发布」与「能关 pre-release」不矛盾。 +# 发布流水线:tag `v*` → **只构建两个 Windows 槽位**(各自原生 `windows-latest` / `windows-11-arm`)→ 单文件产物 + +# SHA256SUMS → artifact + GitHub Release。pre-release 默认开;同 tag 重跑幂等(覆盖资产与说明、不 create、不碰标记)。 +# **构建 ≠ 发布**:只有 `PUBLISH_SLOTS` 里的槽位才进 Release 与 SHA256SUMS ——**M142 起只有 `windows-x64`**(arm64 连试 +# 四轮未成,仍留矩阵里取证、红了不挡 x64 发布;它修好后把键加回那一行即可)。四档 Linux/macOS 从未验证,默认连构建都 +# 不跑(M139:省配额、不留红叉);整表六槽位取证走 `gh workflow run release.yml -f all_slots=true`(槽位表在 `plan`)。 +# 契约唯一真相源 packaging/README.md,打包本体 packaging/make_artifacts.py(本地与 CI 同一份脚本)。`inputs.prerelease` +# 只在 **tag ref** 上 dispatch 才可达(跑 tag 得 `gh workflow run release.yml --ref v0.2.1`);分支 ref 的 dispatch 与 +# push 一样被 release job 的 `if: github.ref_type == 'tag'` 挡在发布之外。 on: push: @@ -61,16 +59,14 @@ jobs: 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"), - # Windows arm64:**原生** `windows-11-arm`(4C/16G,公开仓库免费的标准 arm64 runner;GitHub 文档的 - # Standard runners 也列 `windows-11-vs2026-arm`)。M140 之前是 windows-latest 上交叉到 aarch64-pc-windows-msvc, - # **CI 从未成功过一次**(v0.2.1 首跑就红在工具链体检,M141 在此修)。原生还让 `ull --version` 冒烟真跑 - # (宿主=产物架构),产物在 target/release,dumpbin 走 Hostarm64 分支。 + # Windows arm64:**原生** `windows-11-arm`(4C/16G,公开仓库免费;GitHub 文档的 Standard runners 也列 + # `windows-11-vs2026-arm`)。M140 前是在 windows-latest 上交叉,**CI 从未成功过一次**(v0.2.1 首跑红在 + # 工具链体检,M141/M142 在此修)。原生还让 `ull --version` 冒烟真跑,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 + 同步 README §1/§9 与 release-notes.md。 - # Linux 固定 22.04 而非 latest:否则 glibc 地板跟着 runner 漂移(依据见 README §4)。 + # ↓ 四档**未验证,不纳入 CI**:从未真构建成功、也没在真机跑过,默认一个都不编;`all_slots=true` 时照编 + # (将来验证 Linux/macOS 的唯一入口,别删)。真机验过后:mode 改 "ci" + 键加进 PUBLISH_SLOTS + + # 同步 README §1/§9 与 release-notes.md。Linux 固定 22.04 而非 latest:否则 glibc 地板跟着 runner 漂移。 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 已下线)。 @@ -103,60 +99,67 @@ jobs: - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - # (M141)windows-arm64 跑在**原生** `windows-11-arm` 上,本步查这台机器上 ARM64 库齐不齐: - # `+crt-static` 要 MSVC 的 `lib/arm64/libcmt.lib`,链接要 SDK 的 `um/arm64/kernel32.lib`。 - # **诊断走注解**:job 日志下载要 token(我们没有),check-run 注解不用;每条一行(多行会被拼成一行而截断, - # 上一轮只看到半句),error 注解上限 10 条故快照固定 4 行。门禁不降级:缺了就地补装再判,补不上才 fail。 - - name: Windows ARM64 工具链体检 + # (M141/M142)windows-arm64 跑在**原生** `windows-11-arm` 上,本步查这台机器上 ARM64 库齐不齐:`+crt-static` + # 要 MSVC 的 `lib/arm64/libcmt.lib`,链接要 SDK 的 `um/arm64/kernel32.lib`。诊断**只走注解**(job 日志下载要 + # token 我们没有,check-run 注解不用;每条一行,多行会被拼成一行截断,每步 error 注解上限 10 条)。 + # **M142 修本步的自毁 bug**:`shell: bash` 实为 `bash -eo pipefail`,脚本里的 `set -uo pipefail` **关不掉** -e—— + # M141 死在 `vc_lib=$(find 不存在的根 | head -n1)`(pipefail 把 find 的非零带进赋值、errexit 当场退出), + # **一条注解都没打**,上一轮只剩 `Process completed with exit code 1.`。四条规矩:① 显式 `set +e`;② 每处探测 + # 自带 `|| true`;③ 问题记进 ERRORS 而不当场退出;④ 快照先打完、再由显式 `exit` 定红绿。门禁不降级(缺了就 + # 就地补装再判),但 **arm64 红不挡发布**(PUBLISH_SLOTS 已无它),本槽位只取证。 + - name: Windows ARM64 工具链体检(红不挡发布,只取证) if: matrix.slot == 'windows-arm64' shell: bash run: | - set -uo pipefail # 刻意不用 -e:探测/补装失败也要把注解打完,再由显式 exit 定门禁 - ann() { printf '::error::[arm64sdk] %s\n' "$*"; } + set +e; set -uo pipefail; ERRORS=() # -e 由 runner 命令行注入,只能显式关 + say() { printf '::%s::[arm64sdk] %s\n' "$1" "$2"; [ "$1" = error ] && ERRORS+=("$2"); return 0; } vs=("C:/Program Files/Microsoft Visual Studio" "C:/Program Files (x86)/Microsoft Visual Studio") # SDK 根要放宽:新版装 `(x86)`、也有装 `Program Files`;老安装器把 lib 留在 Microsoft SDKs; # VS 自己还带一份「SDK in VS」(`/Windows Kits/10/Lib`),用 vswhere 定位后追加。 sdk=("C:/Program Files (x86)/Windows Kits/10/Lib" "C:/Program Files/Windows Kits/10/Lib" "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) + inst="C:/Program Files (x86)/Microsoft Visual Studio/Installer"; vspath="" + [ -x "$inst/vswhere.exe" ] && vspath=$("$inst/vswhere.exe" -latest -products '*' -property installationPath 2>/dev/null | head -n1 || true) [ -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); } + # 下面两个探测正是 M141 断气处(find 起点不存在 → 非 0 → `| head` 的 pipefail 带进赋值 → errexit),故处处 + # `|| true`:探不到就是「没找到」,由结论下判断,不许由它决定进程生死。 + scan() { vc_lib=$(find "${vs[@]}" -maxdepth 11 -type f -path '*/VC/Tools/MSVC/*/lib/arm64/libcmt.lib' 2>/dev/null | head -n1 || true) + sdk_lib=$(find "${sdk[@]}" -maxdepth 6 -type f -path '*/um/arm64/kernel32.lib' 2>/dev/null | head -n1 || true); } + build=$(ls "${sdk[@]}" 2>/dev/null | grep -oE '^10\.0\.[0-9]+' | cut -d. -f3 | sort -u | tail -n1 || true) 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)" + # 快照固定 4 行,**无论齐不齐都先打完**:齐了用 notice(绿跑也留证据 = 探测本身没坏),缺了用 error。 + sev=error; [ -n "$vc_lib" ] && [ -n "$sdk_lib" ] && sev=notice + say "$sev" "探测根: $(for d in "${vs[@]}" "${sdk[@]}"; do [ -d "$d" ] && printf '%s=有 ' "$d" || printf '%s=无 ' "$d"; done)vswhere=$([ -x "$inst/vswhere.exe" ] && echo 有 || echo 无) vspath=${vspath:-<空>}" v="" - for d in "${sdk[@]}"; do - [ -d "$d" ] || continue + 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 - ann "SDK Lib 版本→um 架构: ${v:-<没有任何版本目录>}" + say "$sev" "SDK Lib 版本→um 架构: ${v:-<没有任何版本目录>}(SDK build=${build:-未探到})" v="" while IFS= read -r t; do v+="$(basename "$(dirname "$t")")→[$(ls "$t" 2>/dev/null | tr '\n' ',')] "; done \ < <(find "${vs[@]}" -maxdepth 7 -type d -path '*/VC/Tools/MSVC/*/lib' 2>/dev/null | head -n4) - ann "MSVC lib/ 架构: ${v:-<无 MSVC lib 目录>}" - ann "关键库: libcmt=${vc_lib:-缺失} kernel32=${sdk_lib:-缺失}" - # 补装:VS 安装器就地 modify(门禁不降级:先补再判)。组件 ID 随 VS 大版本不同;**优先补一个尚未 - # 装上的 SDK 版本**——同版本重装是 no-op,补不出 arm64 子目录。补装失败不致命,重探与结论给真相。 - setup="C:/Program Files (x86)/Microsoft Visual Studio/Installer/setup.exe" - build=$(ls "${sdk[@]}" 2>/dev/null | grep -oE '^10\.0\.[0-9]+' | cut -d. -f3 | sort -u | tail -n1) + say "$sev" "MSVC lib/ 架构: ${v:-<无 MSVC lib 目录>}" + say "$sev" "关键库: libcmt=${vc_lib:-缺失} kernel32=${sdk_lib:-缺失}" + if [ "$sev" = notice ]; then echo "ARM64 工具链齐: vc=$vc_lib sdk=$sdk_lib"; exit 0; fi + # 补装:VS 安装器就地 modify(门禁不降级:先补再判)。组件 ID 随 VS 大版本不同;**优先补一个尚未装上的 + # SDK 版本**——同版本重装是 no-op,补不出 arm64 子目录。补装失败不致命,重探与结论给真相。 id=Microsoft.VisualStudio.Component.Windows11SDK.$([ "$build" = "26100" ] && echo 22621 || echo 26100) [ "$build" = "19041" ] && id=Microsoft.VisualStudio.Component.Windows10SDK.20348 adds=""; [ -n "$vc_lib" ] || adds+="Microsoft.VisualStudio.Component.VC.Tools.ARM64 "; [ -n "$sdk_lib" ] || adds+="$id" - 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 + if [ -x "$inst/setup.exe" ] && [ -n "$vspath" ]; then + say error "补装: $vspath ← ${adds}(已装 SDK build=${build:-未装})" + timeout 1200 "$inst/setup.exe" modify --installPath "$vspath" --quiet --norestart --nocache --wait \ + --add ${adds} || say error "补装返回非 0:组件 ID 可能不被这台 VS 认(见下一条结论)" + scan + else say error "补装跳过: 无 vs_installer 或没定位到 VS 安装路径(vspath=${vspath:-<空>})"; fi + if [ -z "$vc_lib" ] || [ -z "$sdk_lib" ]; then + say error "结论: 仍缺(libcmt=${vc_lib:-缺失} kernel32=${sdk_lib:-缺失}),共记 ${#ERRORS[@]} 条探测问题;门禁红、不摘 arm64——本槽位不在 PUBLISH_SLOTS 里,x64 照发" + exit 1 + fi echo "补装后齐全: vc=$vc_lib sdk=$sdk_lib" - # M118(D26):**Slint 版停发** —— GUI 只有 `unlucky-launcher-iced`(`crates/app` 已被根 Cargo.toml - # `exclude`,不参与 workspace 构建)。产物名由 make_artifacts.py 的 `asset_stem()` 生成 - # `<名字>_<版本号>_<平台>_<架构>`,`LAUNCHER_BIN` 只用来在产物里找文件;不传 `--target`(M140 起全原生编译)。 + # M118(D26):**Slint 版停发** —— GUI 只有 `unlucky-launcher-iced`(`crates/app` 已 exclude,不进 workspace)。 + # 产物名由 make_artifacts.py 的 `asset_stem()` 生成 `<名字>_<版本号>_<平台>_<架构>`;不传 `--target`(M140 起全原生)。 - name: cargo build --release --locked shell: bash env: @@ -169,16 +172,14 @@ jobs: if: runner.os == 'Windows' shell: bash env: - # 校验的是**构建产物**(iced bin 原始名);对外名 `unlucky-launcher` 由 make_artifacts.py 落盘时 - # 才换上。产物在 `matrix.build_dir`(M140 起六个槽位都是 target/release)。 + # 校验的是**构建产物**(iced bin 原始名);对外名 `unlucky-launcher` 由 make_artifacts.py 落盘时才换上。 EXE: ${{ matrix.build_dir }}/unlucky-launcher-iced.exe run: | set -euo pipefail # +crt-static 是否真生效只看导入表:出现 VCRUNTIME/MSVCP/msvcr/ucrtbase 就是还得装可再发行包,fail。 - # dumpbin 定位链(三级,全不命中才 fail —— 门禁不降级,只是把"猜路径"换成官方定位):① vswhere(VS 官方 - # 定位器,安装器路径固定)② find 兜底(两个 VS 根都扫,Build Tools 在 x86 根)③ PATH(排最后是刻意的: - # PATH 最不可控)。dumpbin 在 MSVC 的**宿主**工具目录里:x64 runner 是 `Hostx64/x64`,arm64 runner(M140) - # 是 `Hostarm64/arm64`(x64 那族兜底,靠模拟层能跑);反向不行——x64 上挑 Hostarm64 是跑不起来的二进制。 + # 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[@]}") @@ -265,23 +266,29 @@ jobs: if-no-files-found: error retention-days: 14 + # (M142)arm64 若红在别的步骤(cargo / 打包 / 导入表门禁),也要有一条注解说清「红在哪」和「不挡发布」—— + # 只剩一句 `Process completed with exit code 1.` 的教训见上一条体检步骤。 + - name: arm64 失败收尾注解(红不挡发布) + if: failure() && matrix.slot == 'windows-arm64' + shell: bash + run: echo "::error::[arm64] 本槽位已红(失败步骤见本 job 的步骤名与上面各条注解);PUBLISH_SLOTS 当前只有 windows-x64 ⇒ 本次 Release 仍照发 x64,arm64 产物不进 Release" + release: name: GitHub Release needs: build - # `always()` + `!cancelled()`:白名单外的槽位(只在 `all_slots` 取证跑里存在)红了**不能连坐** Windows 发布 —— - # 真正拦发布的是下面「发布白名单齐全性」那一步:白名单里少任何一个就 fail。 + # `always()` + `!cancelled()`:白名单外的槽位(现在的 `windows-arm64`,以及只在 `all_slots` 取证跑里存在的四档) + # 红了**不能连坐** x64 发布 —— 真正拦发布的是下面「发布白名单齐全性」那一步:白名单里少任何一个才 fail。 if: always() && !cancelled() && github.ref_type == 'tag' runs-on: ubuntu-latest permissions: contents: write env: # ── 发布白名单:唯一决定「哪几个槽位进 Release」的开关(空格分隔,键同 matrix.slot)── - # 为什么只有两个 Windows 槽位:Linux x64/arm64、macOS arm64/x64 这四档**从未真构建成功过一次、更没在真机跑过**, - # 未验证的产物不推给用户;它们默认连构建都不跑(M139),只有 `all_slots=true` 取证跑里才有产物,留在本次运行的 - # `dist-` artifact 里可下载验证,不进 Release/SHA256SUMS。将来某槽位真机验过后「打开」它需同时改三处: - # ① `plan` 槽位表里把它的 mode 改成 `ci`;② 把槽位键加进这一行(齐全性检查与 SHA256SUMS 自动跟着扩); - # ③ packaging/README.md §1/§9 与 packaging/release-notes.md 的 Downloads / Support matrix。 - PUBLISH_SLOTS: "windows-x64 windows-arm64" + # **M142 起只有 `windows-x64`**:arm64 连试四轮未走通,而下面齐全性检查是「名单内少一档就 fail」——名字挂着 + # arm64 就等于 x64 编成功也发不出去。故把它移出名单:它照留在矩阵里跑取证(诊断注解是将来修它的唯一证据), + # **红了不挡发布**。**可逆**:arm64 修好后把键加回这一行即可(齐全性检查与 SHA256SUMS 自动跟着扩),再同步 + # README §1/§9 与 release-notes.md 两张表。四档 Linux/macOS 仍未验证,默认连构建都不跑(M139)。 + PUBLISH_SLOTS: "windows-x64" steps: - uses: actions/checkout@v4 @@ -306,7 +313,7 @@ jobs: echo "publish: $base" ;; *) - echo "drop: $base($platform_arch 未在真机验证,不进 Release;仍留在 dist- artifact 里取证)" + echo "drop: $base($platform_arch 不在发布白名单:四档未验证 / arm64 待修;仍留在 dist- artifact 里取证)" rm -f "$f" ;; esac @@ -317,10 +324,9 @@ jobs: run: | set -euo pipefail ls -l dist - # 白名单内槽位必须**全齐**才发版(宁可不出 Release,也不出少一档的 Release)。每个槽位都要 GUI 与 CLI - # 两个资产——只按「槽位名出现过」判会被其中一个掩盖。档位比对用下划线形态(资产名自 D49 起是 - # `unlucky-launcher__windows_x64.exe`)。矩阵裁到两个槽位(M139)后这条检查依然有效:它比的是 - # **下载下来的产物**,白名单里的槽位没编或编挂了,就在这里因缺产物 fail,不会永远过。 + # 白名单内槽位必须**全齐**才发版(宁可不出 Release,也不出少一档的 Release)。每个槽位都要 GUI 与 CLI 两个 + # 资产——只按「槽位名出现过」判会被其中一个掩盖;比较用下划线形态(资产名自 D49 起是 `..._windows_x64.exe`)。 + # M142 后白名单只剩 x64 一档:arm64 缺产物不再拦发布,但 x64 自己缺 GUI 或 CLI 照样在这里 fail。 for slot in $PUBLISH_SLOTS; do stem=${slot//-/_} for prefix in unlucky-launcher ull; do @@ -362,9 +368,9 @@ jobs: run: | set -euo pipefail base_flags=(--title "Unlucky Launcher $TAG" --notes-file release-body.md) - # pre-release 标记只在**显式要求**时才断言:PRERELEASE=true/false 来自 tag ref 上的 dispatch 输入; - # PRERELEASE 空 = push 触发的运行或它的 Re-run,此时**不碰**该标记(否则会把用户手动提升为 stable 的 - # Release 静默翻回 pre-release)。首次 create 仍保持「pre-release 默认开」的老契约:空值按 true 处理。 + # pre-release 只在**显式要求**时断言:PRERELEASE=true/false 来自 tag ref 上的 dispatch 输入;空值 = push + # 触发(或它的 Re-run),此时**不碰**该标记(否则会把用户手动提升为 stable 的 Release 静默翻回 pre-release)。 + # 首次 create 仍守「pre-release 默认开」老契约:空值按 true 处理。 create_mark=() edit_mark=() case "${PRERELEASE:-}" in @@ -383,12 +389,10 @@ jobs: exit 1 ;; esac - # Windows 单 exe 装不下 LICENSE/NOTICE/README.md,这三份与产物并列挂 Release,保证许可条款随包分发。 - # `ull` 是独立资产(不在 tar.gz 里):headless 用户下一个小文件直接跑,不用解档。 + # Windows 单 exe 装不下 LICENSE/NOTICE/README.md,故三份与产物并列挂 Release(条款随包);`ull` 是独立资产。 assets=(dist/unlucky-launcher_* dist/ull_* dist/SHA256SUMS.txt LICENSE NOTICE README.md) if gh release view "$TAG" >/dev/null 2>&1; then - # 同 tag 重跑(补丁 / 重编):Release 已存在 → 覆盖资产 + 刷新说明, - # 不再 `create`(那会因已存在而失败,把整条流水线在最后一步染红)。 + # 同 tag 重跑(补丁/重编):Release 已存在 → 覆盖资产 + 刷新说明,再 `create` 会因已存在而失败。 gh release upload "$TAG" "${assets[@]}" --clobber gh release edit "$TAG" "${base_flags[@]}" "${edit_mark[@]}" else diff --git a/packaging/README.md b/packaging/README.md index 73d4a1d..acea8a4 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 策略) | 同上;CI 在**原生** `windows-11-arm` runner 上构建(M140 起,不再交叉编译) | +| `windows-arm64` | ❌ 不发布(构建照跑取证) | `unlucky-launcher__windows_arm64.exe` | **裸 exe**(同 x64 策略) | 同上;CI 仍在**原生** `windows-11-arm` runner 上构建(M140 起不交叉),**红了不挡 x64 发布**(M142,见 §3) | | `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` | 单二进制归档 | 同上 | @@ -20,7 +20,7 @@ 约定细节: - **「不构建」= CI 默认根本不跑**(M139):那四档未验证,产物不给用户、构建配额也不花;要整表六槽位取证走 - `gh workflow run release.yml -f all_slots=true`(§3)。发布白名单仍只有两个 Windows 槽位。 + `gh workflow run release.yml -f all_slots=true`(§3)。发布白名单**只有 `windows-x64`**(M142:arm64 照构建、不发布)。 - **产物命名**:`<名字>_<版本号>_<平台>_<架构>`,下划线分段、**不带实现细节字样**(不出现 `iced`);真相源是 `make_artifacts.py::asset_stem()`。 - **"单文件"指可执行文件本身零运行时依赖**(无 WebView、无 JRE、无 sidecar、无 DLL 旁挂);归档里的三份合规文本是许可义务、不是运行时依赖,Windows 因为"一个 exe 装不下文本", 把 `LICENSE` / `NOTICE` / `README.md` 作为 Release 的并列资产分发,条款同样随下载走。 @@ -95,13 +95,13 @@ python packaging/make_artifacts.py --slot windows-arm64 \ `macos-15-intel` 标 `mode: probe`——**未验证,默认一个都不编**,只有 `all_slots=true` 才进矩阵。 - 每个槽位 `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`、把键加进这一行(齐全性检查 - 与 `SHA256SUMS` 自动跟着扩)、再同步 §1/§9 与 `release-notes.md`——三处缺一即是假话。 + 靠它)与 SDK 的 `um/arm64/kernel32.lib`,缺了就 fail;**注解里固定打 4 行实况**(探测根 / SDK Lib 版本→um + 架构 / MSVC lib 架构 / 两个关键库)。M142 修的正是「诊断自己先死」:`set -uo pipefail` 关不掉 runner 注入的 + `-e`,`find` 扫不存在的根被 pipefail 判非零就当场退出 → 现在 `set +e` + 处处 `|| true`,清单先打完再定红绿。 +- **发布白名单(`PUBLISH_SLOTS`,唯一开关)**:只有白名单里的槽位进 Release 与 `SHA256SUMS.txt`。**M142 起只有 + `windows-x64`**:arm64 连试四轮未走通,而齐全性检查是「名单内少一档就 fail」,挂着它就等于 x64 编成功也发不出去; + 它仍留在矩阵里照跑取证、红了不挡发布。**可逆**:arm64 修好后把键加回 `release.yml` 那一行,再同步 §1/§9 与 + `release-notes.md`。四档未验证的(默认连构建都不跑)依旧不推给用户。 - `release` job:下载全部 artifact → **按白名单裁剪** → 重算 `SHA256SUMS.txt` → 用 `packaging/release-notes.md` 模板渲染支持矩阵 → `gh release create`(**pre-release 默认开**:首建时 `push` 触发按 pre-release,`workflow_dispatch` 可关)。 - **同一 tag 重跑幂等**:`gh release view` 命中已存在的 Release 时改走 `gh release upload --clobber` @@ -132,13 +132,13 @@ Windows 为什么用 MSVC 静态 CRT 而不是本机这套 GNU 工具链:两 ### Windows ARM64(Win11 on ARM) -产物形态与门禁跟 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 工具链体检」)。那次**根因未定论**:`windows-2025` 镜像清单里 `VC.Tools.ARM64` 与 SDK 26100 都在, -更像自检自己的路径探测有问题;无论哪种,交叉都多一层"在 x64 宿主上猜 ARM64 库路径"。原生的附赠好处: -宿主=产物架构,`ull --version` 冒烟**真跑**;dumpbin 链补了 `Hostarm64/arm64` 分支并按 `RUNNER_ARCH` 选族。 +**本版不发布**(CI 照构建取证,红了不挡 x64 发布;等它绿了把键加回 `PUBLISH_SLOTS` 即可)。产物形态与门禁跟 x64 +一样(裸 exe + 静态 CRT + dumpbin 导入表硬门禁),差别只在**怎么编**:**M140 起在原生 `windows-11-arm` 上编** +(GitHub 文档 2026 的 Standard runners 表明列该标签:4C/16G、公开仓库免费),产物落在 `target/release`。原路线 +是在 `windows-latest` 上交叉到 `aarch64-pc-windows-msvc`,但**CI 上从未成功过一次**(v0.2.1 首次真跑即红在 +「ARM64 工具链体检」)。M142 已确认那几轮「只剩一行 exit code 1」是**诊断脚本自己先死**(`set -uo pipefail` 关不掉 +runner 注入的 `-e`,`find` 扫不存在的根被 pipefail 判非零就退出),已修;ARM64 的静态库在这台 runner 上到底缺不缺 +要看下一次运行的新注解(固定 4 行实况清单,见 §3)。原生的附赠好处:宿主=产物架构,`ull --version` 冒烟**真跑**。 运行时依赖与 x64 同表(UCRT 是 Win11 ARM 的系统组件,`+crt-static` 静态链 CRT,不需要可再发行包)。 游戏侧如实说:**启动器原生运行**;Temurin 没有 windows-aarch64 产物,Microsoft OpenJDK 有该 @@ -314,8 +314,8 @@ libxcb 条目数 = 0,只把 fontconfig 那 7 个 `.so` 放进 `LD_LIBRARY_PATH **没在本地验证、只能由 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` 即算闭环。 +M141/M142 保留了编译前体检(查 VC 的 `lib/arm64/libcmt.lib` 与 SDK 的 `um/arm64/kernel32.lib`,无论齐不齐都先打 4 行 +实况注解)。闭环标准不是「两槽位都进 SHA256SUMS」(M142 起只有 x64 进),而是 arm64 注解转绿 + 把键加回 `PUBLISH_SLOTS`。 ## 8. 发行合规 @@ -334,7 +334,7 @@ M140 保留了一道编译前体检(查 VC 的 `lib/arm64/libcmt.lib` 与 SDK | 平台 | 本版发布 | 级别 | 运行时要求 | |------|---------|------|-----------| | Windows 10/11 x64 | ✅ | 一等 | 无(UCRT 系统自带) | -| Windows 11 ARM64 | ✅ | 一等(启动器产物) | 无额外库;启动器原生运行,游戏本体走系统 x64 模拟层 + x64 JDK | +| Windows 11 ARM64 | ❌ 不发布(构建取证中,待修) | 一等(启动器产物) | 无额外库;启动器原生运行,游戏本体走系统 x64 模拟层 + x64 JDK | | macOS 12+(arm64 / x64) | ❌ 不构建 | 一等(未真机验证) | 无第三方库;未签名需去 quarantine | | Linux x64 X11 | ❌ 不构建 | 一等(未真机验证) | glibc 2.35+、xkbcommon、fontconfig、libGL、xcb | | Linux x64 Wayland | ❌ 不构建 | 支持 | 上述 + wayland-client | @@ -346,8 +346,8 @@ M140 保留了一道编译前体检(查 VC 的 `lib/arm64/libcmt.lib` 与 SDK `packaging/release-notes.md` 的 "Support matrix" 表与上表逐行对应(英文版同样带 published / not published 一列,Termux 行标注 experimental)——改一边必须同步另一边,否则标题里的"一致"就是假话。 -两个 arm64 槽位都是**正式矩阵槽位**(不是 Termux 那种源码自建旁路),但本版只有 `windows-arm64` 发布;`linux-arm64` 连构建都不跑 -(默认矩阵只有两个 Windows 槽位),发布开关是 `release.yml` 的 `PUBLISH_SLOTS`,取证入口是 `all_slots=true`。 +两个 arm64 槽位都是**正式矩阵槽位**(不是 Termux 那种源码自建旁路),但本版两个都**不发布**:`windows-arm64` 照构建取证 +(红了不挡 x64 发布),`linux-arm64` 连构建都不跑;发布开关是 `release.yml` 的 `PUBLISH_SLOTS`(当前只有 `windows-x64`)。 ## 10. 已知边界 / 后续 diff --git a/packaging/release-notes.md b/packaging/release-notes.md index ac70b3c..b567b5c 100644 --- a/packaging/release-notes.md +++ b/packaging/release-notes.md @@ -11,8 +11,13 @@ Asset names are `___`, underscore separated — t | Platform | Asset | Notes | | --- | --- | --- | -| Windows 10/11 x64 | `unlucky-launcher_{{VERSION}}_windows_x64.exe` | single exe, double-click to run | -| Windows 11 ARM64 | `unlucky-launcher_{{VERSION}}_windows_arm64.exe` | single exe, runs natively on ARM64 Windows | +| Windows 10/11 x64 | `unlucky-launcher_{{VERSION}}_windows_x64.exe` | single exe, double-click to run; this is also what you run on ARM64 Windows (x64 emulation) | + +**Windows ARM64 is not part of this release.** CI still builds it on a native `windows-11-arm` runner for +evidence, but that slot's toolchain check has not gone green yet, so +`unlucky-launcher_{{VERSION}}_windows_arm64.exe` is deliberately kept out of the download list and out of +`SHA256SUMS.txt` instead of handing out a build nobody has verified. `windows-x64` is the only published +slot; ARM64 rejoins this table as soon as its build is green (one line in the workflow's `PUBLISH_SLOTS`). **This release ships Windows only, and CI builds Windows only.** The Linux (x64 / arm64) and macOS (Apple Silicon / Intel) slots are not built by the release workflow at all — they have not been verified @@ -21,10 +26,10 @@ stay out of the download list and out of `SHA256SUMS.txt` until that verificatio workflow run can build all six slots for that purpose, but the four extra slots are never published. The support matrix below marks those slots accordingly. -The Windows assets are bare `.exe` files, so `LICENSE`, `NOTICE` and `README.md` are attached to this -release next to them: the license terms travel with every download. Slots that ship an archive (Linux / -macOS, once published) unpack to one top-level directory of the same name holding the binary plus those -three documents. +The Windows asset is a bare `.exe`, so `LICENSE`, `NOTICE` and `README.md` are attached to this release +next to it: the license terms travel with every download. Slots that ship an archive (Linux / macOS, once +published) unpack to one top-level directory of the same name holding the binary plus those three +documents. ## Command line @@ -46,7 +51,7 @@ Published = shipped as a download of **this** release, listed in `SHA256SUMS.txt | Platform | Status in this release | Level | Runtime requirements | | --- | --- | --- | --- | | Windows 10/11 x64 | **published** | first class | nothing beyond the OS (UCRT is part of Win10+) | -| Windows 11 ARM64 | **published** | first class (launcher artifact) | nothing extra; the launcher runs natively, the game itself runs through the OS x64 emulation layer with an x64 JDK | +| Windows 11 ARM64 | not published (build chasing, unverified) | first class (launcher artifact) | nothing extra; the launcher runs natively, the game itself runs through the OS x64 emulation layer with an x64 JDK | | macOS 12+ Apple Silicon | not built (unverified) | first class (unverified) | nothing; unsigned build, needs the Gatekeeper workaround | | macOS 12+ Intel | not built (unverified) | first class (unverified) | same as above | | Linux x64, X11 | not built (unverified) | first class (unverified) | `libxkbcommon`, `fontconfig`, `libGL` (or software renderer), `libxcb` | @@ -56,17 +61,18 @@ Published = shipped as a download of **this** release, listed in `SHA256SUMS.txt | Windows 8.1/8/7 | not built | unsupported | the baseline is Windows 10 | | Android — Termux + X11 | not built (no CI slot) | experimental, not promised | CLI only and only when built from source; GUI unverified | -Both arm64 rows are formal matrix slots, not the experimental Termux path: Windows ARM64 ships here and -is cross-built with the same static-CRT gate as x64; Linux arm64 is kept as a matrix slot but is not built -by default — someone has to run it on real hardware (via a manual all-slots run) before it joins this list. -The rows above and the matrix table in `packaging/README.md` §9 are the same list — change one, change the other. +`windows-x64` is the only published slot this release; Windows ARM64 is kept out until its build is green +(see Downloads above). Both arm64 rows are formal matrix slots, not the experimental Termux path, and +Linux arm64 is not built by default — someone has to run it on real hardware (via a manual all-slots run) +before it joins this list. The rows above and the matrix table in `packaging/README.md` §9 are the same +list — change one, change the other. ## Install -**Windows** — put the `.exe` anywhere and run it (pick `_windows_arm64.exe` on ARM64 Windows, -`_windows_x64.exe` everywhere else). The first run creates `.ULL/` and `.minecraft/` next to the exe -(portable is the default data mode); the per-user `%APPDATA%\ULL` plus `%APPDATA%\.minecraft`, or a -custom root, are selectable instead. +**Windows** — put the `.exe` anywhere and run it. `_windows_x64.exe` is the only Windows asset in this +release, and it is also what you run on ARM64 Windows (the OS runs it through its x64 emulation layer). +The first run creates `.ULL/` and `.minecraft/` next to the exe (portable is the default data mode); the +per-user `%APPDATA%\ULL` plus `%APPDATA%\.minecraft`, or a custom root, are selectable instead. Linux / macOS are not part of this release; their asset names are already fixed by the same rule (`unlucky-launcher_{{VERSION}}_linux_x64.tar.gz`, `_linux_arm64`, `_macos_arm64`, `_macos_x64`) so the