ci: SDK 仓发布流水线 —— release/** 推送即出 hmapdev 五平台产物

与主仓 Release 流水线同构,差异只在产物与打包命令:

  prepare  读 meta.Version;tag 已存在则整轮跳过;go test 门可显式跳过
           (改 meta 的提交里写 [skip-release-tests],查该提交而非 HEAD)
  build    go build(硬门)→ go test → build.sh all hmapdev → 验证 5 平台齐全
           → 生成 SHA256SUMS → artifact
  publish  打 tag → gh release create 传附件 → 回读校验
  sync-gitcode  有 GITCODE_TOKEN 时同步(无则跳过)

产物清单不是猜的,依 gitcode 上 v1.2.0/v1.3.0 的实际附件(各 6 个):
  hmapdev_{linux,darwin}_{amd64,arm64} + hmapdev_windows_amd64.exe + SHA256SUMS

两个易错点都有实测依据:

1. **测试要分两处跑**:tools/hmapdev 是**独立 module**,根模块的
   `go test ./...` 不会进入它(Go 的模块边界,不是配置问题)。
2. **gitcode 上传必须显式列文件名**:上传脚本的路径语义是
   `os.path.join(ASSET_DIR, name)`,所以要 cd 进目录 + `ASSET_DIR=.` + 裸名;
   而它按扩展名识别产物的默认扫描对 hmapdev **无效** —— 五个产物里只有
   windows 那个有扩展名,自动扫描会静默地一个都不传。
   故把主仓的 upload_assets.py 一并纳入本仓 scripts/(两仓各自独立可取)。

本地已验证:`VERSION=1.4.0 bash package/build.sh all hmapdev` 产出 5 个
二进制(各 27–29M),根模块 go test 通过,actionlint 全绿。
This commit is contained in:
JianFeeeee
2026-09-29 14:09:35 +08:00
parent 29c61f0e07
commit dc9e7d0495
2 changed files with 445 additions and 0 deletions

312
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,312 @@
# HomeAgent SDK 仓发布流水线:release/** 推送即发版。
#
# 与主仓的 Release 流水线同构(见主仓 .github/workflows/release.yml),
# 差异只在产物与打包命令:
#
# 主仓 → 3 个 deb + 1 个 tar.gz(含 719MB 向量模型)
# SDK → hmapdev_{linux,darwin}_{amd64,arm64} + hmapdev_windows_amd64.exe
# + SHA256SUMS(约 140MB)
#
# 产物清单依据:gitcode 上 v1.2.0/v1.3.0 的实际附件(各 6 个),
# 以及 docs/git-branching.md §七 的「发版产物清单」。
name: Release
on:
push:
branches: ['release/**']
workflow_dispatch:
inputs:
skip_tests:
description: '跳过发版前的 go test 门(仅用于已知红的历史维护线)'
type: boolean
default: false
permissions:
contents: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
CGO_ENABLED: 0
GOFLAGS: -buildvcs=false
jobs:
prepare:
name: Prepare
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
version: ${{ steps.ver.outputs.version }}
tag: ${{ steps.ver.outputs.tag }}
prerelease: ${{ steps.ver.outputs.prerelease }}
exists: ${{ steps.ver.outputs.exists }}
skip_tests: ${{ steps.ver.outputs.skip_tests }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
# 版本事实源是 meta/meta.go 的 Version,故发版动作 =
# 在 release/vX.Y.x 上把它改成目标版本后推送。
#
# 幂等闸门:tag 已存在 ⇒ 整轮跳过(改文档不会重发版)。
#
# go test 门可跳过:新旧发布线的测试健康状况不同,硬门会让历史
# 维护线完全无法发版。发版人可在**改动 meta 的那个提交**里写
# [skip-release-tests] 显式跳过 —— 决定因此可被 git 历史审计。
# (标记查在改 meta 的提交上而不是 HEAD:发版提交之后常还会跟
# 几个提交,只看 HEAD 会让标记被顶掉、静默失效。)
- id: ver
name: 读取 meta.Version 并检查 tag
run: |
set -euo pipefail
V=$(sed -n 's/^[[:space:]]*Version = "\(.*\)"/\1/p' \
meta/meta.go | head -1)
if [ -z "$V" ]; then
echo "ERROR: 无法从 meta/meta.go 读出 Version"
exit 1
fi
echo "version=$V" >> "$GITHUB_OUTPUT"
echo "tag=v$V" >> "$GITHUB_OUTPUT"
case "$V" in
*-*) echo "prerelease=true" >> "$GITHUB_OUTPUT" ;;
*) echo "prerelease=false" >> "$GITHUB_OUTPUT" ;;
esac
if git ls-remote --exit-code --tags origin "refs/tags/v$V" \
>/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo " tag v$V 已存在 —— 跳过发版"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo " 将为 v$V 发版"
fi
SKIP="${{ inputs.skip_tests }}"
REL_COMMIT=$(git log -1 --format=%H -- meta/meta.go)
REL_MSG=$(git log -1 --pretty=%B "$REL_COMMIT")
case "$REL_MSG" in
*'[skip-release-tests]'*) MARKER=1 ;;
*) MARKER=0 ;;
esac
echo " 发版提交: ${REL_COMMIT:0:12}"
if [ "$SKIP" = "true" ] || [ "$MARKER" = "1" ]; then
echo "skip_tests=true" >> "$GITHUB_OUTPUT"
echo " ⚠️ **已请求跳过发版前的 go test 门**"
else
echo "skip_tests=false" >> "$GITHUB_OUTPUT"
echo " 发版前会跑 go test 门"
fi
build:
name: Build hmapdev
needs: prepare
if: needs.prepare.outputs.exists == 'false'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
# 门:go build 是硬门;go test 可在发版 commit 里显式跳过。
#
# 测试分两处跑 —— tools/hmapdev 是**独立 module**,根模块的
# `go test ./...` 不会进入它(这是 Go 的模块边界,不是配置问题)。
- name: go build(硬门)
run: |
set -euo pipefail
go build ./...
- name: go test(根模块)
if: needs.prepare.outputs.skip_tests != 'true'
run: go test ./... -count=1 -timeout 15m
- name: go test(tools/hmapdev 独立 module)
if: needs.prepare.outputs.skip_tests != 'true'
working-directory: tools/hmapdev
run: go test ./... -count=1 -timeout 15m
- name: go test 被跳过(显式声明的后果)
if: needs.prepare.outputs.skip_tests == 'true'
run: |
echo "::warning title=go test 门已跳过::本次发版未跑 go test,产物可能建立在单元测试失败的代码上。"
- name: 打包 hmapdev(5 个平台)
env:
VERSION: ${{ needs.prepare.outputs.version }}
run: |
set -euo pipefail
rm -rf build
bash package/build.sh all hmapdev
echo
echo " 产物:"
for f in build/*; do
printf " %8.1fMB %s\n" \
"$(stat -c %s "$f" | awk '{print $1/1048576}')" "$(basename "$f")"
done
# 逐个确认 5 个平台都产出了 —— 只查目录非空会漏掉"少了一个平台"。
- name: 验证产物齐全
run: |
set -euo pipefail
missing=0
for f in hmapdev_linux_amd64 hmapdev_linux_arm64 \
hmapdev_darwin_amd64 hmapdev_darwin_arm64 \
hmapdev_windows_amd64.exe; do
if [ -s "build/$f" ]; then
echo " ✓ $f"
else
echo " ✗ 缺 $f" >&2
missing=1
fi
done
[ "$missing" = "0" ] || exit 1
# 校验和必须**全部产物齐全之后**一次算完(边打边算会漏包);
# 且只覆盖本批产物 —— build/ 可能残留上次的,故先清干净再建。
- name: 生成 SHA256SUMS
run: |
set -euo pipefail
mkdir -p /tmp/out
cp build/hmapdev_linux_amd64 build/hmapdev_linux_arm64 \
build/hmapdev_darwin_amd64 build/hmapdev_darwin_arm64 \
build/hmapdev_windows_amd64.exe /tmp/out/
cd /tmp/out
sha256sum hmapdev_* > SHA256SUMS
echo " SHA256SUMS:"
sed 's/^/ /' SHA256SUMS
sha256sum -c SHA256SUMS
- uses: actions/upload-artifact@v7
with:
name: hmapdev
path: /tmp/out/*
retention-days: 7
if-no-files-found: error
publish:
name: Publish
needs: [prepare, build]
if: needs.prepare.outputs.exists == 'false'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/download-artifact@v8
with:
name: hmapdev
path: dist
- name: 打 tag
env:
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "$TAG"
git push origin "$TAG"
- name: 建 release 并上传附件
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
VERSION: ${{ needs.prepare.outputs.version }}
PRE: ${{ needs.prepare.outputs.prerelease }}
run: |
set -euo pipefail
cd dist
FLAGS=()
[ "$PRE" = "true" ] && FLAGS+=(--prerelease)
gh release create "$TAG" \
--title "HomeAgent SDK $VERSION" \
--notes "HomeAgent 插件 SDK $VERSION
\`hmapdev\` 工具链(Linux / macOS / Windows,amd64 + arm64)。
校验见 SHA256SUMS。
内核版本需与 SDK 的中版本对齐;协议不配套时插件握手会失败
(魔数不匹配),此时升级内核或改用对应版本的 SDK。" \
"${FLAGS[@]}" \
./hmapdev_* ./SHA256SUMS
echo "=== release 内容 ==="
gh release view "$TAG" --json assets \
--jq '.assets[] | " \(.name) \(.size) 字节"'
- name: 回读校验
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
mkdir -p /tmp/back && cd /tmp/back
gh release download "$TAG"
for f in *; do
printf " %8.1fMB %s\n" \
"$(stat -c %s "$f" | awk '{print $1/1048576}')" "$f"
done
sha256sum -c SHA256SUMS
echo " ✓ 回读校验通过"
sync-gitcode:
name: Sync to gitcode
needs: [prepare, publish]
if: needs.prepare.outputs.exists == 'false'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- id: tok
name: 检查 gitcode 凭据
run: |
if [ -n "${{ secrets.GITCODE_TOKEN }}" ]; then
echo "ok=true" >> "$GITHUB_OUTPUT"
else
echo "ok=false" >> "$GITHUB_OUTPUT"
echo " 未配置 GITCODE_TOKEN —— 跳过 gitcode 同步"
fi
- uses: actions/download-artifact@v8
if: steps.tok.outputs.ok == 'true'
with:
name: hmapdev
path: dist
- name: 推 tag 与附件到 gitcode
if: steps.tok.outputs.ok == 'true'
env:
GC_TOKEN: ${{ secrets.GITCODE_TOKEN }}
TAG: ${{ needs.prepare.outputs.tag }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "$TAG" 2>/dev/null || true
GC_URL="https://JianFeeeee:${GC_TOKEN}@gitcode.com"
git push "${GC_URL}/JianFeeeee/homeagent-sdk.git" "$TAG"
curl -sS --max-time 60 -X POST \
-H "private-token: ${GC_TOKEN}" \
-H "Content-Type: application/json" \
"https://gitcode.com/api/v5/repos/JianFeeeee/homeagent-sdk/releases" \
-d "{\"tag_name\":\"$TAG\",\"body\":\"同步自 GitHub\"}" \
-o /tmp/.gcrel -w " 建 release → %{http_code}\n"
cd dist
# 脚本的路径语义是 os.path.join(ASSET_DIR, name) ⇒
# 必须 cd 进资产目录、ASSET_DIR=.、并传**裸文件名**。
# 传 "./x" 或 "dist/x" 都会拼成 dist/dist/x 而找不到文件。
#
# 为何显式列名而不是让它自动扫描:自动扫描只认 ARTIFACT_SUFFIXES
# 里的扩展名,而 hmapdev 的产物多数**没有扩展名**(只有 windows
# 那个是 .exe)⇒ 自动扫描会静默地一个都不传。
ASSET_DIR=. GITCODE_REPO=JianFeeeee/homeagent-sdk \
python3 ../scripts/upload-assets.py "$TAG" "$GC_TOKEN" \
hmapdev_linux_amd64 hmapdev_linux_arm64 \
hmapdev_darwin_amd64 hmapdev_darwin_arm64 \
hmapdev_windows_amd64.exe SHA256SUMS