diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca45d84..43ea686 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -136,9 +136,9 @@ jobs: # (Slint 退役树)的 bin —— 也就是说这条流水线一直在发 Slint 版二进制,而 D26 早已把 # iced 定为产品 UI。现在 GUI 来源只有 `unlucky-launcher-iced`;`crates/app` 也已在根 # Cargo.toml 被 `exclude`,不参与任何 workspace 构建(它连 `--manifest-path` 都编不了)。 - # 对外产物文件名**不变**(`unlucky-launcher--.*`):改名发生在 - # `packaging/make_artifacts.py` 内部(`LAUNCHER_BIN` 找 iced 产物、落盘用对外名), - # 下载链接 / SHA256SUMS / README 都不用跟着漂。 + # 对外产物名由 `packaging/make_artifacts.py` 的 `asset_stem()` 统一生成,格式是 + # `<名字>_<版本号>_<平台>_<架构>`(用户令 2026-11-05):下划线分段,且**不带实现细节 + # 字样**(产物名里不出现 `iced`)。`LAUNCHER_BIN` 只用来在构建产物里找文件。 - name: cargo build --release --locked shell: bash env: @@ -303,7 +303,7 @@ jobs: # GUI 与 CLI 都要进清单:漏了 ull 就是「发了但没有校验和」。 cd dist rm -f SHA256SUMS.txt - sha256sum unlucky-launcher-* ull-* > SHA256SUMS.txt + sha256sum unlucky-launcher_* ull_* > SHA256SUMS.txt sha256sum -c SHA256SUMS.txt cat SHA256SUMS.txt @@ -358,7 +358,7 @@ jobs: # Windows 单 exe 装不下 LICENSE/NOTICE/README.md,这三份与产物并列挂到 Release 上, # 保证每个下载平台的许可条款都随包分发。 # `ull` 是独立资产(不在 tar.gz 里):headless 用户下一个小文件直接跑,不用解档。 - assets=(dist/unlucky-launcher-* dist/ull-* dist/SHA256SUMS.txt LICENSE NOTICE README.md) + 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`(那会因已存在而失败,把整条流水线在最后一步染红)。 diff --git a/packaging/README.md b/packaging/README.md index 371c924..7213583 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -8,21 +8,21 @@ | 槽位 | 产物文件名 | 形态 | 归档内容 | |------|-----------|------|---------| -| `windows-x64` | `unlucky-launcher--windows-x64.exe` | **裸 exe**(双击即用) | exe 单文件;三份合规文本作为并列 Release 资产 | -| `windows-arm64` | `unlucky-launcher--windows-arm64.exe` | **裸 exe**(同 x64 策略) | 同上;在 x64 宿主上交叉编译到 `aarch64-pc-windows-msvc` | -| `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` | 单二进制归档 | 同上 | -| `macos-x64` | `unlucky-launcher--macos-x64.tar.gz` | 单二进制归档 | 同上 | -| 每槽位 | `ull--<槽位>[.exe]` | **裸 CLI 文件**(独立资产,不进 tar.gz) | 零 UI 依赖的 `ull` 本体,进同一份 `SHA256SUMS.txt` | +| `windows-x64` | `unlucky-launcher__windows_x64.exe` | **裸 exe**(双击即用) | exe 单文件;三份合规文本作为并列 Release 资产 | +| `windows-arm64` | `unlucky-launcher__windows_arm64.exe` | **裸 exe**(同 x64 策略) | 同上;在 x64 宿主上交叉编译到 `aarch64-pc-windows-msvc` | +| `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` | 单二进制归档 | 同上 | +| `macos-x64` | `unlucky-launcher__macos_x64.tar.gz` | 单二进制归档 | 同上 | +| 每槽位 | `ull__<平台>_<架构>[.exe]` | **裸 CLI 文件**(独立资产,不进 tar.gz) | 零 UI 依赖的 `ull` 本体,进同一份 `SHA256SUMS.txt` | | 全平台 | `SHA256SUMS.txt` | 校验清单 | 每行 `␠␠<文件名>`(`sha256sum -c` 可直吃) | 约定细节: -- **"单文件"指可执行文件本身零运行时依赖**(无 WebView、无 JRE、无 sidecar 资源目录、无 DLL 旁挂)。 - 归档里额外放三份合规文本是许可义务要求,不是运行时依赖;Windows 因为"一个 exe 装不下文本", +- **产物命名**:`<名字>_<版本号>_<平台>_<架构>`,下划线分段、**不带实现细节字样**(不出现 `iced`);真相源是 `make_artifacts.py::asset_stem()`。 +- **"单文件"指可执行文件本身零运行时依赖**(无 WebView、无 JRE、无 sidecar、无 DLL 旁挂);归档里的三份合规文本是许可义务、不是运行时依赖,Windows 因为"一个 exe 装不下文本", 把 `LICENSE` / `NOTICE` / `README.md` 作为 Release 的并列资产分发,条款同样随下载走。 -- 归档解压后是**一个顶层目录** `unlucky-launcher--<槽位>/`,用户不会解压出一堆散文件。 +- 归档解压后是**一个顶层目录** `unlucky-launcher__<平台>_<架构>/`,用户不会解压出一堆散文件。 - **`ull` 是并列的裸资产,不塞进 tar.gz**:headless 用户要的就是「下一个小文件直接跑」;归档里放 LICENSE/NOTICE/README.md 是许可义务,而这三份本来就作为并列 Release 资产分发,裸 `ull` 不缺条款。 - `unlucky-launcher` 本体也认全部 CLI 子命令(M82),`ull` 不是第二个界面,而是**不链接图形栈**的同一套命令(差异见 §4 与 §7.4),两者不是重复品。 diff --git a/packaging/make_artifacts.py b/packaging/make_artifacts.py index 2db5752..c20631d 100644 --- a/packaging/make_artifacts.py +++ b/packaging/make_artifacts.py @@ -3,15 +3,19 @@ Release matrix (see `packaging/README.md` for the full contract): - Windows x64 unlucky-launcher--windows-x64.exe (bare exe, no zip) - Windows arm64 unlucky-launcher--windows-arm64.exe (bare exe, cross-built) - Linux x64 unlucky-launcher--linux-x64.tar.gz - Linux arm64 unlucky-launcher--linux-arm64.tar.gz - macOS arm64 unlucky-launcher--macos-arm64.tar.gz - macOS x64 unlucky-launcher--macos-x64.tar.gz - every slot ull--[.exe] (bare CLI binary, no archive) + Windows x64 unlucky-launcher__windows_x64.exe (bare exe, no zip) + Windows arm64 unlucky-launcher__windows_arm64.exe (bare exe, cross-built) + Linux x64 unlucky-launcher__linux_x64.tar.gz + Linux arm64 unlucky-launcher__linux_arm64.tar.gz + macOS arm64 unlucky-launcher__macos_arm64.tar.gz + macOS x64 unlucky-launcher__macos_x64.tar.gz + every slot ull___[.exe] (bare CLI binary, no archive) all SHA256SUMS.txt +Naming rule (user's call, 2026-11-05): `___`, underscore +separated, and **no implementation detail in the file name** — the asset never says which UI +framework produced it. `asset_stem()` is the single source of truth for that. + `--version` is taken from the cargo workspace so a tag can never drift away from `Cargo.toml`; pass `--version` to cross-check a tag against it (mismatch is fatal). The archives carry the launcher binary plus LICENSE / NOTICE / README.md, which is @@ -205,9 +209,20 @@ def write_sums(out_dir: Path, assets: list[Path]) -> Path: return sums +def asset_stem(name: str, version: str, slot: str) -> str: + """`___` — 产物命名的唯一真相源。 + + 用户令(2026-11-05):产物名按 `名字_版本号_平台_架构` 下划线分段,且**不得出现 `iced` + 这类实现细节字样** —— 下载的人不该关心我们内部用哪个 UI 框架。槽位键(`windows-x64`) + 仍是脚本内部的写法,落到文件名时拆成 `windows_x64` 两段。 + """ + family, arch, _kind = SLOTS[slot] + return f"{name}_{version}_{family}_{arch}" + + def stage_cli(out_dir: Path, version: str, slot: str, cli: Path) -> Path: - """Copy the CLI out as its own bare asset (`ull--[.exe]`).""" - name = f"{CLI_BIN}-{version}-{slot}" + (".exe" if cli.suffix == ".exe" else "") + """Copy the CLI out as its own bare asset (`ull___[.exe]`).""" + name = asset_stem(CLI_BIN, version, slot) + (".exe" if cli.suffix == ".exe" else "") asset = out_dir / name shutil.copy2(cli, asset) # 裸文件下载即可运行:Windows 宿主机上源文件没有 x 位,这里补上(UNIX 侧才有意义)。 @@ -217,7 +232,7 @@ def stage_cli(out_dir: Path, version: str, slot: str, cli: Path) -> Path: def stage_archive(out_dir: Path, tag: str, version: str, launcher: Path) -> Path: """tar.gz with one top-level dir holding the binary + compliance docs.""" - stem = f"unlucky-launcher-{version}-{tag}" + stem = asset_stem(LAUNCHER_ASSET, version, tag) stage = out_dir / stem if stage.exists(): shutil.rmtree(stage) @@ -281,7 +296,7 @@ def main() -> int: out_dir.mkdir(parents=True, exist_ok=True) if kind == "exe": - asset = out_dir / f"unlucky-launcher-{version}-{slot}.exe" + asset = out_dir / f"{asset_stem(LAUNCHER_ASSET, version, slot)}.exe" shutil.copy2(launcher, asset) else: asset = stage_archive(out_dir, slot, version, launcher)