mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-29 22:12:52 +00:00
ci: 把 CI 与 Release 流水线带到本条发布线
GitHub 用**被推送 commit 里的** .github/workflows/*.yml 决定是否触发, 所以 workflow 文件必须存在于发布分支本身,否则推 release/** 不会发版。 只带 workflow 定义,不带 main 上的其它未发布改动。
This commit is contained in:
199
.github/workflows/ci.yml
vendored
Normal file
199
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,199 @@
|
||||
# HomeAgent 主仓 CI。
|
||||
#
|
||||
# 设计原则:**CI 里跑的每一条命令,都是本地已实测通过的命令**。
|
||||
# 不写「应该有用来试试」的步骤 —— 未验证的 CI 步骤会把假红灯变成常态,
|
||||
# 最后所有人学会忽略它。
|
||||
#
|
||||
# 覆盖范围与本地 `make test` 对齐(build / vet / test / client-versions /
|
||||
# gui / csrc),并按依赖拆成独立 job,便于失败定位。
|
||||
#
|
||||
# 明确**不在** CI 里跑的东西(依赖真机/密钥/内网,跑了只会变 flaky 噪音):
|
||||
# - deploy-*.sh / homed 生产部署
|
||||
# - waiter 真机验证(192.168.2.x)
|
||||
# - cmd/gui 的 `npm run test-live`(需真 Electron + Xvfb + 真后端)
|
||||
# - scripts/kernel-stress/*(需 llmsproxy 与压测端点)
|
||||
# - 需要 DEEPSEEK_API_KEY / MEDIALIVE_* 的真实 LLM 测试(已自带 t.Skip)
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, 'release/**']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
# 只读权限:CI 不需要写仓库。
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# 同一分支连续推送时取消旧跑,省额度也避免过期结果误导。
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# gojieba / onnx 相关包需要 cgo ⇒ 不能用 CGO_ENABLED=0。
|
||||
CGO_ENABLED: 1
|
||||
# 减少 go test 输出噪音。
|
||||
GOFLAGS: -buildvcs=false
|
||||
|
||||
jobs:
|
||||
# ── Go 后端:构建 + 静态检查 + 全量测试 + 跨平台客户端版本一致性 ──
|
||||
go:
|
||||
name: Go build / vet / test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
# cgo 需要 gcc/g++(gojieba 会编译自带 C++ 源码)。
|
||||
- name: 确认 cgo 工具链
|
||||
run: |
|
||||
gcc --version | head -1
|
||||
g++ --version | head -1
|
||||
|
||||
- name: go build ./...
|
||||
run: go build ./...
|
||||
|
||||
- name: go vet ./...
|
||||
run: go vet ./...
|
||||
|
||||
# ./... 不点名 cmd/gui(该目录是纯 Electron,无 .go 文件):
|
||||
# 显式 `go test ./cmd/gui` 会报 "no Go files",那是误报,不是缺陷。
|
||||
- name: go test ./...
|
||||
run: go test ./... -count=1 -timeout 20m
|
||||
|
||||
# 跨平台客户端版本一致性:内核 internal/meta 是唯一事实源,
|
||||
# GUI(package.json) / 鸿蒙(AppScope/app.json5) / waiter 都必须跟它一致。
|
||||
- name: 客户端版本一致性
|
||||
run: make check-client-versions
|
||||
|
||||
# ── 竞态检测(并发改动的主要防线)──
|
||||
race:
|
||||
name: Race detector
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
- name: go test -race(并发核心)
|
||||
run: |
|
||||
go test -race \
|
||||
./internal/agent/core/ ./cmd/waiter/ \
|
||||
-count=1 -timeout 15m
|
||||
|
||||
# ── 交叉编译:可在无 cgo 下构建的客户端/工具 ──
|
||||
#
|
||||
# 只有这三个 cmd 支持纯交叉编译。另外三个依赖 cgo(gojieba / onnx):
|
||||
# homed / memgc / homed-kb-migrate → internal/memory(gojieba)
|
||||
# homed → internal/agent/api(onnx)
|
||||
# 它们必须在原生平台构建(见 Makefile 的 build target)。
|
||||
cross:
|
||||
name: Cross-compile
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- goos: linux
|
||||
goarch: amd64
|
||||
ext: ""
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
ext: ""
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
ext: ""
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
ext: ""
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
ext: ".exe"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
- name: 构建 ${{ matrix.goos }}/${{ matrix.goarch }}
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CGO_ENABLED: 0
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p dist
|
||||
for c in waiter initconfig mock-server; do
|
||||
out="dist/${c}_${{ matrix.goos }}_${{ matrix.goarch }}${{ matrix.ext }}"
|
||||
go build -trimpath -o "$out" "./cmd/${c}"
|
||||
echo " ✓ ${c} ${{ matrix.goos }}/${{ matrix.goarch }}"
|
||||
done
|
||||
|
||||
# ── Electron GUI(纯 Node 测试,零依赖)──
|
||||
#
|
||||
# `npm test` 只跑三个 .mjs,全部只 import node: 内置模块(fs/url/path/vm),
|
||||
# 所以**不需要 npm ci、不需要 electron**,秒级完成。
|
||||
# `npm run test-live` 需真 Electron + 真后端 ⇒ 不进 CI。
|
||||
gui:
|
||||
name: GUI (node)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
- name: npm test
|
||||
working-directory: cmd/gui
|
||||
run: npm test
|
||||
|
||||
# ── C 基础设施门禁(ABI / 告警 / ASan+UBSan / 跨架构)──
|
||||
csrc:
|
||||
name: C infrastructure gates
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
# clang 供双编译器告警对照;gcc-aarch64 供跨架构编译门禁。
|
||||
# 门禁在缺工具时是显式 SKIP 而不是假通过,这里装齐以免静默降级。
|
||||
- name: 安装 C 工具链
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq cmake clang gcc-aarch64-linux-gnu
|
||||
|
||||
- name: make check-csrc
|
||||
run: make check-csrc
|
||||
|
||||
# ── 文档站构建(mkdocs,纯 Python,无外部依赖)──
|
||||
docs:
|
||||
name: Docs build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: 校验站点配置可解析
|
||||
# 这里只做「配置与文档源没坏」的轻量校验,不做完整 mkdocs build
|
||||
# (站点发布有独立流水线,见 deploy-sdk-site.sh)。
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -f mkdocs.yml ]; then
|
||||
python -c \
|
||||
"import yaml; yaml.safe_load(open('mkdocs.yml'))" \
|
||||
&& echo "mkdocs.yml OK"
|
||||
else
|
||||
echo "无 mkdocs.yml,跳过"
|
||||
fi
|
||||
test -d docs || echo "无 docs/,跳过"
|
||||
294
.github/workflows/release.yml
vendored
Normal file
294
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,294 @@
|
||||
# 发布流水线:release/** 分支推送即发版。
|
||||
#
|
||||
# 设计依据 docs/git-branching.md §七(发版产物清单)与 git-release-discipline
|
||||
# skill。核心事实:**推 tag ≠ 完成发版** —— 完整发版是四件事:
|
||||
# bump meta.Version → 打 tag → 打包产物 → 建 release 条目并上传附件。
|
||||
# (v1.3.1–v1.3.6 曾只推了 tag,产物与 release 条目全缺,事后补做。)
|
||||
#
|
||||
# 版本号来源:internal/meta/meta.go 的 Version(唯一事实源)。
|
||||
# 所以发版动作 = 在 release/vX.Y.x 上把 meta.Version 改成目标版本后推送。
|
||||
# 版本未变的推送(如改文档)会因 tag 已存在而**整轮跳过**,不会重复发版。
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['release/**']
|
||||
workflow_dispatch:
|
||||
|
||||
# 发布必须能写仓库(打 tag、建 release、传附件)。
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
# 发布不允许并发/取消:半途中断会留下 tag 存在但附件不全的状态。
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
# gojieba 需要 cgo;onnxruntime 版本经 dlopen 加载,编译期无需装 ORT。
|
||||
CGO_ENABLED: 1
|
||||
GOFLAGS: -buildvcs=false
|
||||
# CI 用的大资产(模型/运行库)存于这个 release。
|
||||
ASSETS_TAG: ci-assets-v1
|
||||
|
||||
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 }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: ver
|
||||
name: 读取 meta.Version 并检查 tag
|
||||
run: |
|
||||
set -euo pipefail
|
||||
V=$(sed -n 's/^[[:space:]]*Version = "\(.*\)"/\1/p' \
|
||||
internal/meta/meta.go | head -1)
|
||||
if [ -z "$V" ]; then
|
||||
echo "ERROR: 无法从 internal/meta/meta.go 读出 Version"
|
||||
exit 1
|
||||
fi
|
||||
echo "version=$V" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=v$V" >> "$GITHUB_OUTPUT"
|
||||
# SemVer 预发布(1.3.13-beta.1)⇒ release 标记为预发布
|
||||
case "$V" in
|
||||
*-*) echo "prerelease=true" >> "$GITHUB_OUTPUT" ;;
|
||||
*) echo "prerelease=false" >> "$GITHUB_OUTPUT" ;;
|
||||
esac
|
||||
# 幂等闸门:tag 已存在说明该版本发过了,整轮跳过。
|
||||
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
|
||||
|
||||
# ── 构建 Linux 产物(amd64)──
|
||||
#
|
||||
# 三个 deb + 一个 tar.gz,总约 2.4GB(server/full/tar 含 719MB 模型)。
|
||||
# 编译不需要 ONNX Runtime —— onnxruntime_go 是 dlopen 方式,运行期才加载
|
||||
# libonnxruntime.so;但**打包**需要它(要打进 deb),故从 ASSETS_TAG 下载。
|
||||
build-linux:
|
||||
name: Build linux/amd64
|
||||
needs: prepare
|
||||
if: needs.prepare.outputs.exists == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: 确认 cgo 工具链
|
||||
run: |
|
||||
gcc --version | head -1
|
||||
g++ --version | head -1
|
||||
|
||||
# 发版前的最后一道门:产物若建立在编译失败的代码上,发布了也没用。
|
||||
- name: go build + go test(发版前验证)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
go build ./...
|
||||
go test ./... -count=1 -timeout 20m
|
||||
|
||||
- name: 下载构建资产(模型 + ONNX Runtime)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BASE="https://github.com/${GITHUB_REPOSITORY}/releases/download/${ASSETS_TAG}"
|
||||
mkdir -p /tmp/assets/model /tmp/assets/ort
|
||||
for f in chinese-clip-vit-b16-onnx.tar \
|
||||
onnxruntime-linux-amd64-1.28.0.tar SHA256SUMS; do
|
||||
echo " 下载 $f"
|
||||
curl -sSL --retry 3 -o "/tmp/assets/$f" "$BASE/$f"
|
||||
done
|
||||
# 校验(资产是构建输入,损坏会打出坏包)
|
||||
(cd /tmp/assets && sha256sum -c SHA256SUMS)
|
||||
tar -xf /tmp/assets/chinese-clip-vit-b16-onnx.tar \
|
||||
-C /tmp/assets/model
|
||||
ORT_TAR=/tmp/assets/onnxruntime-linux-amd64-1.28.0.tar
|
||||
tar -xf "$ORT_TAR" -C /tmp/assets/ort
|
||||
echo " 模型文件:"
|
||||
ls /tmp/assets/model/chinese-clip-vit-b16-onnx
|
||||
echo " ORT 文件:"
|
||||
ls /tmp/assets/ort
|
||||
|
||||
- name: 打包(tar.gz + full/server/client deb)
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
CHINESECLIP_BUNDLE_DIR: /tmp/assets/model/chinese-clip-vit-b16-onnx
|
||||
ONNXRUNTIME_ASSET_DIR: /tmp/assets/ort
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash deploy/packaging/package-linux.sh amd64 all
|
||||
|
||||
- name: 平铺产物(附件必须同目录,SHA256SUMS 用平铺名)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p /tmp/out
|
||||
cp dist/linux/deb/*.deb /tmp/out/
|
||||
cp dist/linux/tar/*.tar.gz /tmp/out/
|
||||
cp dist/linux/SHA256SUMS /tmp/out/
|
||||
echo " 产物:"
|
||||
for f in /tmp/out/*; do
|
||||
printf " %8.1fMB %s\n" \
|
||||
"$(stat -c %s "$f" | awk '{print $1/1048576}')" "$(basename "$f")"
|
||||
done
|
||||
|
||||
- name: 验证产物(deb 元数据 + 校验和自验)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd /tmp/out
|
||||
for f in *.deb; do
|
||||
echo " $f"
|
||||
dpkg-deb -f "$f" Package Version Architecture | sed 's/^/ /'
|
||||
done
|
||||
# full/server 必须真的带模型,否则是"默认启用但装完不能用"的假包
|
||||
dpkg-deb -c homeagent-full_*_amd64.deb \
|
||||
| grep -q "chinese-clip-vit-b16-onnx/TextEncoder.onnx"
|
||||
echo " ✓ full 包含模型"
|
||||
dpkg-deb -c homeagent-full_*_amd64.deb \
|
||||
| grep -q "libonnxruntime.so"
|
||||
echo " ✓ full 包含 ONNX Runtime"
|
||||
sha256sum -c SHA256SUMS
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: linux-amd64
|
||||
path: /tmp/out/*
|
||||
retention-days: 7
|
||||
if-no-files-found: error
|
||||
|
||||
# ── 建 tag、建 release、上传附件 ──
|
||||
publish:
|
||||
name: Publish
|
||||
needs: [prepare, build-linux]
|
||||
if: needs.prepare.outputs.exists == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: linux-amd64
|
||||
path: dist
|
||||
|
||||
- name: 打 tag(打在触发本次发版的 commit 上)
|
||||
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 "$TAG" \
|
||||
--notes "HomeAgent $VERSION
|
||||
|
||||
产物清单与校验见 SHA256SUMS。
|
||||
- \`homeagent_${VERSION}_linux_amd64.tar.gz\` — 内核 + CLI + GUI 打包
|
||||
- \`homeagent-client_${VERSION}_amd64.deb\` — 客户端
|
||||
- \`homeagent-server_${VERSION}_amd64.deb\` — 服务端(含向量模型)
|
||||
- \`homeagent-full_${VERSION}_amd64.deb\` — 全量" \
|
||||
"${FLAGS[@]}" \
|
||||
./*.deb ./*.tar.gz ./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 " ✓ 回读校验通过"
|
||||
|
||||
# ── 同步到 gitcode(国内镜像)──
|
||||
#
|
||||
# 需要仓库 secret GITCODE_TOKEN;未配置则跳过(不阻断 GitHub 侧发布)。
|
||||
# gitcode 的 release 附件是"同名只写一次",故只在此处上传一次。
|
||||
sync-gitcode:
|
||||
name: Sync to gitcode
|
||||
needs: [prepare, publish]
|
||||
if: needs.prepare.outputs.exists == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
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: linux-amd64
|
||||
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
|
||||
# 1) 推 tag(附件上传前 release 条目必须先存在)
|
||||
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.git" "$TAG"
|
||||
# 2) 建 release 条目
|
||||
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/releases" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"body\":\"同步自 GitHub\"}" \
|
||||
-o /tmp/.gcrel -w " 建 release → %{http_code}\n"
|
||||
# 3) 上传附件(用仓库既有脚本,它处理 OBS 预签名两步流程)
|
||||
cd dist
|
||||
python3 ../deploy/scripts/upload_assets.py "$TAG" "$GC_TOKEN" \
|
||||
./*.deb ./*.tar.gz ./SHA256SUMS
|
||||
Reference in New Issue
Block a user