diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 879f19c..bf4995d 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -34,16 +34,41 @@ jobs: echo "version=${version}" >> "$GITHUB_OUTPUT" owner="${GITEA_OWNER_OVERRIDE:-${GITHUB_REPOSITORY_OWNER}}" echo "owner=${owner}" >> "$GITHUB_OUTPUT" + # 版本双真源校验:tag 必须与 gpui-app/Cargo.toml 一致。否则新包装完仍自报旧 + # 版本号,客户端表现为"每次都提示同一个更新",装多少次都消不掉。 + - name: Verify tag matches Cargo.toml version + run: | + tag="${GITHUB_REF_NAME}" + cargo_ver="$(grep -m1 '^version' gpui-app/Cargo.toml | cut -d'"' -f2)" + if [ -z "$cargo_ver" ]; then + echo "::error::无法从 gpui-app/Cargo.toml 解析版本号" + exit 1 + fi + if [ "v${cargo_ver}" != "$tag" ]; then + echo "::error::tag(${tag}) 与 gpui-app/Cargo.toml(v${cargo_ver}) 不一致:请先 bump 版本再打 tag" + exit 1 + fi + echo "版本一致:${tag}" - # ─── 测试(不依赖系统图形库) ───────────────────────────────── + # ─── 测试 ───────────────────────────────────────────────────── + # 与 build 一样要完整编译 lib(GPUI 会链接 Linux 系统库),缺库会在链接期失败。 + # 此前该 job 未安装任何依赖,叠加 sysmon 的跨平台断言缺陷,v* tag 必停在测试阶段。 test: name: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Install Linux system deps (GPUI) + run: | + sudo apt-get update + sudo apt-get install -y \ + libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev \ + libwayland-dev libasound2-dev libfontconfig-dev \ + libx11-dev libxcursor-dev libxrandr-dev libxi-dev + # --tests:让 tests/engine_live.rs 参与编译(#[ignore] 只跳过执行,不跳过编译) - name: Unit tests - run: cargo test -p mqttx-desktop --lib + run: cargo test -p mqttx-desktop --lib --tests # ─── 构建矩阵 ───────────────────────────────────────────────── # 目前 Gitea 实例只注册了 ubuntu-latest 标签的 Linux runner, diff --git a/README.md b/README.md index ae20b13..6a2bc8a 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,9 @@ export GITEA_TOKEN=gta_xxx ``` - **owner** 自动从 `git remote origin` 推断(可用 `GITEA_OWNER` / `-Owner` 覆盖) -- **版本** 依次取参数 → `git describe --tags` → `Cargo.toml` +- **版本** 依次取参数 → 当前提交的精确 tag(`git describe --tags --exact-match`)→ `Cargo.toml`; + 必须是合法 semver(形如 `v1.0.1` 或 `1.0.1-rc.1`),否则脚本直接拒绝发布 + —— 非 semver 版本(如 `v1.0.0-4-gabc1234`)会被客户端截断比对,更新永远发不出去 - 每个文件附带 `.sha256` 校验侧车;同名版本重复上传按 409 跳过(Gitea 不允许覆盖) - 软件包页面:`{GITEA_URL}/{owner}?tab=packages` @@ -156,11 +158,18 @@ export GITEA_TOKEN=gta_xxx 推送 `v*` tag 即触发 [.gitea/workflows/release.yml](.gitea/workflows/release.yml): -1. 单元测试 -2. Windows / Linux 双平台 release 构建(产物重命名为 `mqttx---`) -3. 调用 `scripts/publish-gitea.sh` 上传到包注册表 +1. 版本一致性校验:tag 必须等于 `v` + `gpui-app/Cargo.toml` 的版本,否则整条流水线失败 +2. 单元测试(`--lib --tests`,集成测试只编译不执行) +3. Linux release 构建(产物重命名为 `mqttx-v--mqttx`) +4. 调用 `scripts/publish-gitea.sh`:上传包注册表,并创建同名 Release、 + 上传 7z / 裸二进制 / `.sha256` 附件 -需要在仓库 **Settings → Actions → Secrets** 配置 `GITEA_TOKEN`; +> **应用内 OTA 只认 Release 附件**,附件名必须匹配 `mqttx-v--mqttx[.exe]`。 +> Windows 产物目前由本地 `scripts/publish-gitea.ps1` 上传补充(CI 的 Windows 矩阵项 +> 在 runner 就绪前保持注释,见工作流内说明)。 + +需要在仓库 **Settings → Actions → Secrets** 配置 `GITEA_TOKEN`(需 **repo 写权限**: +脚本除包注册表外还要创建 Release 并上传附件); 若 runner 使用自定义 label,请相应修改工作流的 `runs-on`。 ## 数据目录 diff --git a/gpui-app/src/sysmon.rs b/gpui-app/src/sysmon.rs index f42c7e0..2edc9f1 100644 --- a/gpui-app/src/sysmon.rs +++ b/gpui-app/src/sysmon.rs @@ -14,7 +14,8 @@ pub struct Sample { pub peak_working_set: u64, /// 提交内存(私有字节) pub private_bytes: u64, - /// 自上次采样以来的 CPU 占用百分比(按可用核数归一,0~100×核数内) + /// 自上次采样以来的 CPU 占用(占整机容量的百分比 0~100,已按核数归一, + /// 与任务管理器的"总利用率"同口径) pub cpu_percent: f32, /// 线程数 pub threads: u32, @@ -26,6 +27,8 @@ pub struct Sample { pub struct Sampler { pid: u32, last_cpu: Option<(u64, u64, Instant)>, + /// 上一次成功算出的占用率:采样间隔过近时沿用,避免读数跳回 0 + last_percent: f32, } impl Sampler { @@ -33,6 +36,7 @@ impl Sampler { Self { pid: std::process::id(), last_cpu: None, + last_percent: 0.0, } } @@ -120,9 +124,16 @@ impl Sampler { if dt > 0.05 { let cpu_secs = ((k - pk) + (u - pu)) as f32 / 1e7; cpu_percent = cpu_secs / dt / cores * 100.; + self.last_percent = cpu_percent; + // 只有真正算出结果才前移差分基准:否则连续两次快速采样 + // 会既报 0 又把基准推到"刚刚",用户手点"立即刷新"永远读到 0% + self.last_cpu = Some((k, u, now)); + } else { + cpu_percent = self.last_percent; } + } else { + self.last_cpu = Some((k, u, now)); } - self.last_cpu = Some((k, u, now)); } // ── 线程数 / 句柄数 ── @@ -191,7 +202,15 @@ mod tests { let first = s.sample().expect("采样应成功"); std::thread::sleep(Duration::from_millis(30)); let second = s.sample().expect("第二次采样应成功"); - assert!(second.working_set > 0, "工作集应大于 0"); + // 采样能力分平台:Windows 采集真实数据,其余平台按契约返回全零占位样本。 + // 这里用 if cfg! 而不是 #[cfg]:两个分支都参与编译,避免只在某一个平台上 + // 才发现语法/类型错误。此前无条件断言 > 0,导致 Linux 上 cargo test 必失败, + // 而 CI 的 test job 正是跑在 ubuntu-latest 上,v* tag 会直接停在测试阶段。 + if cfg!(target_os = "windows") { + assert!(second.working_set > 0, "工作集应大于 0"); + } else { + assert_eq!(second.working_set, 0, "非 Windows 平台应返回全零占位样本"); + } let _ = (first, second); } diff --git a/scripts/publish-gitea.ps1 b/scripts/publish-gitea.ps1 index ac56ee3..d33d2ef 100644 --- a/scripts/publish-gitea.ps1 +++ b/scripts/publish-gitea.ps1 @@ -21,7 +21,8 @@ .PARAMETER Files 待上传文件列表。缺省时自动探测 target/release/mqttx(.exe)。 .PARAMETER Version - 软件包版本。缺省时依次尝试 git describe --tags、gpui-app/Cargo.toml。 + 软件包版本。缺省时依次尝试当前提交的精确 tag(git describe --tags --exact-match)、 + gpui-app/Cargo.toml。必须是合法 semver,否则脚本拒绝发布。 .PARAMETER Owner 覆盖 GITEA_OWNER(缺省时从 git remote origin 推断)。 .PARAMETER Repo @@ -111,16 +112,31 @@ if (-not $Owner -or -not $Repo) { if (-not $Owner) { Die "无法推断 owner,请用 -Owner 或 GITEA_OWNER 指定" } if (-not $Repo) { $Repo = $Pkg } -# ── 版本:参数 > git describe > Cargo.toml ────────────────────────────────── +# ── 版本:参数 > 当前提交的精确 tag > Cargo.toml ──────────────────────────── +# 严禁用 'git describe --tags --always' 兜底:它会产出 v1.0.0-4-gabc1234 这类非 +# semver 描述串,据此创建的 Release 会被客户端 release_part() 截成 1.0.0 并判定 +# "不比当前版本新" —— 更新永远发不出去,而且该 Release 会占据 /releases/latest, +# 把真正的更新挡在后面。 +$CargoToml = Join-Path $Root "gpui-app\Cargo.toml" +$CargoVer = "" +$m = Select-String -Path $CargoToml -Pattern '^version = "(.+)"' | Select-Object -First 1 +if ($m) { $CargoVer = $m.Matches[0].Groups[1].Value } if (-not $Version) { - $Version = (git -C $Root describe --tags --always 2>$null) + $Version = (git -C $Root describe --tags --exact-match 2>$null) + if ($Version) { Write-Host "→ 版本取自当前提交的 tag: $Version" } } if (-not $Version) { - $m = Select-String -Path (Join-Path $Root "gpui-app\Cargo.toml") -Pattern '^version = "(.+)"' | Select-Object -First 1 - if ($m) { $Version = $m.Matches[0].Groups[1].Value } + $Version = $CargoVer + if ($Version) { Write-Host "→ HEAD 无 tag,版本回退为 gpui-app/Cargo.toml: $Version" -ForegroundColor Yellow } } -if (-not $Version) { Die "无法确定版本号" } +if (-not $Version) { Die "无法确定版本号:请用 -Version 指定(如 v1.0.1)或先给当前提交打 tag" } $Version = $Version -replace "/", "-" # Gitea 版本段不允许 "/" +if ($Version -notmatch '^[vV]?\d+\.\d+\.\d+(-[0-9A-Za-z.]+)?$') { + Die "版本号 '$Version' 不是合法 semver(应形如 v1.0.1 或 1.0.1-rc.1)" +} +if ($CargoVer -and (($Version -replace '^[vV]', '') -ne $CargoVer)) { + Write-Host " ! 警告:版本 '$Version' 与 gpui-app/Cargo.toml ($CargoVer) 不一致" -ForegroundColor Yellow +} # ── 默认产物 ──────────────────────────────────────────────────────────────── if (-not $Files -or $Files.Count -eq 0) { @@ -202,8 +218,8 @@ if (-not $rel) { Write-Host "→ 已创建 Release v$VerNum" } catch { - Write-Host " ! Release 创建/查询失败:$($_.Exception.Message);跳过附件上传" -ForegroundColor Yellow - return + # OTA 只认 Release 附件:这里失败等于本次发布无效,必须让调用方看到失败 + Die "Release 创建/查询失败:$($_.Exception.Message)" } } $releaseId = $rel.id diff --git a/scripts/publish-gitea.sh b/scripts/publish-gitea.sh index bee3530..ca2958e 100644 --- a/scripts/publish-gitea.sh +++ b/scripts/publish-gitea.sh @@ -62,20 +62,35 @@ REPO="${GITEA_REPO:-$(remote_owner_repo | tail -1 || true)}" [ -n "${OWNER:-}" ] || die "无法推断 GITEA_OWNER,请显式设置" [ -n "${REPO:-}" ] || REPO="$PKG" -# ── 版本:参数 > git describe > Cargo.toml ─────────────────────────────────── +# ── 版本:参数 > 当前提交的精确 tag > Cargo.toml ───────────────────────────── +# 严禁用 'git describe --tags --always' 兜底:它会产出 v1.0.0-4-gabc1234 这类非 +# semver 描述串,据此创建的 Release 会被客户端 release_part() 截成 1.0.0 并判定 +# "不比当前版本新" —— 更新永远发不出去,而且该 Release 会占据 /releases/latest, +# 把真正的更新挡在后面。 FILES=() for arg in "$@"; do if [ -f "$arg" ]; then FILES+=("$arg"); else VERSION="$arg"; fi done +CARGO_VER="$(grep -m1 '^version' "$ROOT/gpui-app/Cargo.toml" | cut -d'"' -f2)" if [ -z "${VERSION:-}" ]; then - VERSION="$(git -C "$ROOT" describe --tags --always 2>/dev/null || true)" + VERSION="$(git -C "$ROOT" describe --tags --exact-match 2>/dev/null || true)" + [ -n "$VERSION" ] && echo "→ 版本取自当前提交的 tag: $VERSION" fi if [ -z "${VERSION:-}" ]; then - VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' "$ROOT/gpui-app/Cargo.toml" | head -1)" + VERSION="$CARGO_VER" + [ -n "$VERSION" ] && echo "→ HEAD 无 tag,版本回退为 gpui-app/Cargo.toml: $VERSION" >&2 fi -[ -n "${VERSION:-}" ] || die "无法确定版本号" -# Gitea 版本段不允许 "/",tag 形如 v1.0.0-3-gabc123 时规范化 +[ -n "${VERSION:-}" ] || die "无法确定版本号:请显式传入(如 v1.0.1)或先给当前提交打 tag" +# Gitea 版本段不允许 "/" VERSION="${VERSION//\//-}" +# semver 校验:非 semver 版本会让 OTA 静默失效(见上方注释),宁可直接拒绝发布 +if ! printf '%s' "$VERSION" | grep -Eq '^[vV]?[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$'; then + die "版本号 '$VERSION' 不是合法 semver(应形如 v1.0.1 或 1.0.1-rc.1)" +fi +# 与编译期版本比对:不一致时客户端装完新版本仍自报旧版本号,会反复提示同一更新 +if [ -n "$CARGO_VER" ] && [ "${VERSION#v}" != "$CARGO_VER" ] && [ "${VERSION#V}" != "$CARGO_VER" ]; then + echo " ! 警告:版本 '$VERSION' 与 gpui-app/Cargo.toml ($CARGO_VER) 不一致" >&2 +fi # ── 默认产物 ──────────────────────────────────────────────────────────────── if [ "${#FILES[@]}" -eq 0 ]; then @@ -174,15 +189,17 @@ if [ "$code" = "404" ]; then if [ "$code" != "201" ]; then echo " ! Release 创建失败 (HTTP $code),跳过附件上传" >&2 cat "$STAGE/rel.json" >&2 || true - exit 0 + # OTA 只认 Release 附件:这里失败等于本次发布无效,必须让调用方看到失败, + # 而不是把 job 变绿、让"发布成功"与"用户能收到更新"脱钩。 + exit 1 fi elif [ "$code" != "200" ]; then echo " ! 查询 Release 失败 (HTTP $code),跳过附件上传" >&2 - exit 0 + exit 1 fi # 顶层 "id" 是首个出现的 id 字段 RELEASE_ID="$(sed -n 's/.*"id":[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$STAGE/rel.json" | head -1)" -[ -n "$RELEASE_ID" ] || { echo " ! 无法解析 Release id,跳过附件上传" >&2; exit 0; } +[ -n "$RELEASE_ID" ] || { echo " ! 无法解析 Release id,跳过附件上传" >&2; exit 1; } echo "→ Release v${VER_NUM} (id=$RELEASE_ID),上传附件…" for f in "${FILES[@]}"; do