Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,10 @@
# setup does not add "ruby" and then fail finalize with "no source code seen"
# (the brew cask added in #1086 is the only Ruby in the repo).
packaging/**/*.rb linguist-detectable=false

# Go binding:生成文件由 bindings/go/generator 重写,评审时折叠
bindings/go/uc_engine_uniffi/uc_engine_uniffi.go linguist-generated=true text eol=lf
bindings/go/uc_engine_uniffi/uc_engine_uniffi.h linguist-generated=true text eol=lf
bindings/go/uc_engine_uniffi/generated_sources.go linguist-generated=true
bindings/go/**/*.go text eol=lf
tests/hosts/go/*.go text eol=lf
190 changes: 190 additions & 0 deletions .github/workflows/go-binding.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
name: Go binding

# Go module(bindings/go)的生成、原生构建与真实进程验收。
# - macOS arm64:重新生成并要求零差异;构建发布配置原生库;Go 1.24 与 1.27 编译、链接;在拒绝外网的沙箱里运行三个验收进程。
# - Linux amd64 / arm64:在对应原生 runner 上构建发布配置原生库并编译、链接 Go 门面与验收宿主。
# Linux 与 Windows 的真实进程验收、macOS amd64、Windows 均未覆盖,见 bindings/go/README.md。
on:
workflow_dispatch:
pull_request:
branches: [main]
paths:
- bindings/go/**
- bindings/uc-engine-uniffi/**
- crates/uc-engine/**
- Cargo.toml
- Cargo.lock
- rust-toolchain.toml
- scripts/build-cache/**
- tests/hosts/go/**
- scripts/architecture/check-go-binding.mjs
- .github/workflows/go-binding.yml
push:
branches: [main]
paths:
- bindings/go/**
- bindings/uc-engine-uniffi/**
- crates/uc-engine/**
- Cargo.toml
- Cargo.lock
- rust-toolchain.toml
- scripts/build-cache/**
- scripts/architecture/check-go-binding.mjs
- tests/hosts/go/**
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- .github/workflows/go-binding.yml

permissions:
contents: read

concurrency:
group: go-binding-${{ github.ref }}
cancel-in-progress: true

jobs:
macos-arm64:
name: Go binding (macOS arm64)
runs-on: macos-14
environment:
name: ${{ github.ref == 'refs/heads/main' && 'engine-build-cache-writer' || 'engine-build-cache-reader' }}
deployment: false
timeout-minutes: 90
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
fetch-depth: 0

- name: Prepare Rust build
uses: ./.github/actions/rust-ci-setup
with:
cache-key: go-binding-macos
compile-cache-access: ${{ github.ref == 'refs/heads/main' && 'write' || 'read' }}
compile-cache-r2-endpoint: ${{ secrets.BUILD_CACHE_R2_ENDPOINT }}
compile-cache-r2-access-key-id: ${{ secrets.BUILD_CACHE_R2_ACCESS_KEY_ID }}
compile-cache-r2-secret-access-key: ${{ secrets.BUILD_CACHE_R2_SECRET_ACCESS_KEY }}

- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16
with:
go-version: "1.24.0"

- name: Static checks
run: |
node scripts/architecture/check-go-binding.mjs
test -z "$(gofmt -l bindings/go/engine bindings/go/native bindings/go/uc_engine_uniffi/link.go bindings/go/uc_engine_uniffi/generated_sources.go tests/hosts/go)"

# 生成器按固定 revision 与补丁构建,输入不变时复用(无缓存的构建约 45 分钟)。
- name: Restore pinned generator
id: generator-cache
uses: actions/cache@v4
with:
path: ${{ runner.temp }}/generator
key: uniffi-bindgen-go-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('bindings/go/generator/PIN.env', 'bindings/go/generator/template-local-names.patch', 'bindings/go/generator/build-generator.sh', 'rust-toolchain.toml') }}

- name: Build pinned generator
if: steps.generator-cache.outputs.cache-hit != 'true'
run: bash bindings/go/generator/build-generator.sh "$RUNNER_TEMP/generator"

- name: Build development library for interface metadata
run: bash bindings/go/native/build-native.sh dev

- name: Regenerate and require zero drift
run: |
bash bindings/go/generator/generate.sh "$RUNNER_TEMP/generator/bin/uniffi-bindgen-go" target/debug/libuc_engine_uniffi.dylib
git diff --exit-code -- bindings/go/uc_engine_uniffi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
test -z "$(git status --porcelain --untracked-files=all -- bindings/go/uc_engine_uniffi)"

- name: Build release library and stage with provenance manifest
run: |
bash bindings/go/native/build-native.sh release
bash bindings/go/native/stage-native.sh release "$RUNNER_TEMP/native"

- name: Real-process acceptance (Go 1.24)
run: node tests/hosts/go/run-e2e.mjs --native "$RUNNER_TEMP/native" --evidence "$RUNNER_TEMP/e2e-go124" --require-network-positive-control

- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16
with:
go-version: "1.27.1"

- name: Real-process acceptance (Go 1.27)
run: node tests/hosts/go/run-e2e.mjs --native "$RUNNER_TEMP/native" --evidence "$RUNNER_TEMP/e2e-go127" --require-network-positive-control

- name: Retain acceptance evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: go-binding-macos-arm64-evidence
path: |
${{ runner.temp }}/e2e-go124
${{ runner.temp }}/e2e-go127
${{ runner.temp }}/native/native-manifest.json
if-no-files-found: warn

- name: Retain MBX compiler evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: mbx-${{ github.job }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }}/mbx-evidence
if-no-files-found: warn

linux-native-link:
name: Go binding link check (${{ matrix.name }})
strategy:
fail-fast: false
matrix:
include:
- name: linux amd64
runner: ubuntu-24.04
- name: linux arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
environment:
name: ${{ github.ref == 'refs/heads/main' && 'engine-build-cache-writer' || 'engine-build-cache-reader' }}
deployment: false
timeout-minutes: 90
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
fetch-depth: 0

- name: Install build tools
run: sudo apt-get update && sudo apt-get install -y clang libclang-dev cmake patchelf

- name: Prepare Rust build
uses: ./.github/actions/rust-ci-setup
with:
cache-key: go-binding-${{ matrix.runner }}
compile-cache-access: ${{ github.ref == 'refs/heads/main' && 'write' || 'read' }}
compile-cache-r2-endpoint: ${{ secrets.BUILD_CACHE_R2_ENDPOINT }}
compile-cache-r2-access-key-id: ${{ secrets.BUILD_CACHE_R2_ACCESS_KEY_ID }}
compile-cache-r2-secret-access-key: ${{ secrets.BUILD_CACHE_R2_SECRET_ACCESS_KEY }}

- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16
with:
go-version: "1.24.0"

- name: Build release library and stage with provenance manifest
run: |
bash bindings/go/native/build-native.sh release
bash bindings/go/native/stage-native.sh release "$RUNNER_TEMP/native"

- name: Compile and link the facade and acceptance host
env:
CGO_ENABLED: "1"
run: |
export CGO_LDFLAGS="-L$RUNNER_TEMP/native -Wl,-rpath,$RUNNER_TEMP/native"
(cd bindings/go && go vet ./... && go build ./...)
(cd tests/hosts/go && go build -o "$RUNNER_TEMP/go-engine-host" .)
ldd "$RUNNER_TEMP/go-engine-host" | grep libuc_engine_uniffi

# 只运行来源校验(不启动 Engine):证明 Linux 上 dladdr 解析到的是共享库而不是可执行文件。
- name: Verify native library identity at runtime
run: |
"$RUNNER_TEMP/go-engine-host" --phase verify --root "$RUNNER_TEMP/verify-root" --manifest "$RUNNER_TEMP/native/native-manifest.json"

- name: Retain MBX compiler evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: mbx-${{ github.job }}-${{ matrix.runner }}
path: ${{ runner.temp }}/mbx-evidence
if-no-files-found: warn
3 changes: 3 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ jobs:
- name: Check no direct tracing log macros
run: node scripts/architecture/check-direct-log-macros.mjs

- name: Check Go binding static rules
run: node scripts/architecture/check-go-binding.mjs

# 公告库在运行时拉取最新版本,工具版本固定并与本地 cargo audit 一致。漏洞与 unsound 公告都会阻断,
# 当前锁无需任何例外;新增公告必须通过升级修复,不得以忽略方式放行。
- name: Install cargo-audit
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
## 目录地图

- `crates/`:Core 规则、Application 流程、Infra 实现与 Engine 稳定入口。
- `bindings/`:iOS、Android、HarmonyOS 薄绑定。
- `bindings/`:iOS、Android、HarmonyOS 薄绑定,以及 Go module(`bindings/go`)。
- `compatibility/`:独立版本、独立发布的 LAN 兼容线。
- `tests/hosts/`:移动平台验收宿主,不承载产品功能。
- `scripts/architecture/`:所有权、依赖方向和发布来源检查。
Expand Down
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Port 归需要能力的层所有,因此 Infra 既可以实现 Core port,也
| `crates/uc-infra-profile/` | Profile 升级/备份/恢复、配置迁移与跨存储的 Space 准入激活 |
| `crates/uc-sync-protocol/` | 传输无关的同步线上格式(帧、版本、上限、编解码与 golden 向量),只依赖 `uc-core`,被 `uc-infra-p2p`/`uc-infra-profile` 使用 |
| `crates/uc-engine/` | 唯一稳定 Rust 入口、生命周期、运行期与依赖组装 |
| `bindings/` | iOS、Android、HarmonyOS 的薄语言绑定 |
| `bindings/` | iOS、Android、HarmonyOS 的薄语言绑定,以及 Go module(`bindings/go`) |
| `compatibility/` | 用户显式启用、独立版本与发布的 LAN 兼容线 |
| `tests/hosts/` | 平台验收宿主,不承载产品业务 |

Expand Down
90 changes: 90 additions & 0 deletions bindings/go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Go binding

Go module `github.com/UniClipboard/Engine/bindings/go`:让 Go 宿主(例如后续的 Go daemon)在同一进程内托管真实的 Rust Engine。
Engine、Iroh、加密与持久化仍由 Rust 负责;本模块只提供生成的 UniFFI 绑定、一个薄 Go 门面和原生库身份校验。
设计取舍、失败模型与验收契约见[执行计划](../../docs/exec-plans/active/2026-10-07-go-binding.md)与[ADR-034](../../docs/design-docs/decisions/034-go-binding-via-generated-uniffi.md)。

## 目录

| 路径 | 内容 | 维护方式 |
|---|---|---|
| `engine/` | 门面:`Open`、查询、事件、`Close`、稳定错误、输入校验 | 手写 |
| `native/` | 原生库来源清单校验(`Verify`)、构建与整理脚本 | 手写 |
| `uc_engine_uniffi/` | 生成的 UniFFI 绑定(`.go` 与 `.h`);`link.go` 为手写 cgo 指令 | 生成,禁止手改 |
| `generator/` | 固定的生成器来源、模板补丁、构建与生成脚本 | 手写 |

## 使用方式

这不是纯 Go 包:需要 `CGO_ENABLED=1`、C 编译器,以及与源码提交匹配的原生库。`go get` 不会下载或构建原生库。

```bash
# 1. 在 Engine 仓库根目录构建并整理原生库(macOS arm64 已实测)
bash bindings/go/native/build-native.sh release
bash bindings/go/native/stage-native.sh release <交付目录>

# 2. 在宿主构建环境提供库路径与 rpath,然后正常 go build
export CGO_ENABLED=1
export CGO_LDFLAGS="-L<交付目录> -Wl,-rpath,<交付目录>"
```

交付目录包含 `libuc_engine_uniffi.<dylib|so>` 与 `native-manifest.json`。宿主应用打包时必须把库放在可执行文件能解析的位置
(`@rpath`/`$ORIGIN`),不要依赖开发者 shell 环境。macOS 上 `stage-native.sh` 把 install_name 改为 `@rpath/…` 并重新 ad-hoc 签名;
面向用户分发仍须宿主自己的 Developer ID 签名与公证。

```go
import "github.com/UniClipboard/Engine/bindings/go/engine"

eng, err := engine.Open(engine.Config{
AppVersion: "1.1.0-rc.22",
ProfileID: "default",
NativeManifest: "/path/to/native-manifest.json", // 必填;校验失败即拒绝启动
}, myHost)
if err != nil { /* engine.EngineError / ErrHost* / native.ErrMismatch … */ }
defer eng.Close(10 * time.Second)

device, err := eng.LocalDevice(ctx)
for { event, err := eng.NextEvent(ctx); … }
```

## 契约摘要

- **来源校验**:`Open` 读取清单,要求关键字段齐全,并核对目标与当前 `GOOS/GOARCH`、清单中的生成源摘要与已编译绑定一致、`core_version`、动态链接器报告的库文件名与该文件的 sha256/size。
清单缺失、字段不符、版本错配或库与清单不一致返回 `native.ErrMismatch`;没有跳过校验的模式。这是完整性与一致性自检,用于发现错配和陈旧库,
**不是认证**:清单与库通常在同一目录,它不防御有本地写权限的攻击者。静态链接(库不是独立文件)时库名比较失败,按错配拒绝。Windows 暂无加载路径解析,返回 `native.ErrUnsupported`。
- **关闭**:`Close(deadline)` 先拒绝新调用,再请求 Rust 在期限内关闭并 join,排空在途调用后释放对象。期限内未完成返回 `ErrCloseIncomplete`(Rust 的可重试等待超时,实测 `1108`,被包装为它并保留原始 `EngineError`),可用新期限重试直至成功。
`Close` 后调用返回 `ErrClosed`(`NextEvent` 也是,关闭期间已入队的事件不再可见);并发的 `Close` 被串行化;与 `Close` 竞争的在途调用可能得到 Engine 的 `InvalidState` 稳定错误。
负载下 `Close` 要排空积压的在途调用(实测 32 个并发调用方时约 5 秒,空闲时约 1 秒),期限应按负载放宽。
- **取消**:`context` 取消只让调用方停止等待,Rust 调用继续,`Close` 仍等待其结束。
- **事件**:Rust 队列容量 256,溢出时丢最旧事件并给出 `RefreshRequired(ConsumerLagged)`,收到后重新查询。门面不再缓冲。未映射的事件只报告种类,不携带载荷。
- **输入**:零值或越界枚举、空配置在进入 Rust 之前返回 `ErrInvalidInput`。
- **宿主回调**:只能返回能力结果,不得在回调内调用同一个 Engine(同步请求会等待正在执行回调的线程;本片未提供能触发该情形的真实进程用例,仅作文档约束)。回调 panic 被门面转为 `ErrHostIO`,不终止进程。目录回调失败原样映射为 `ErrHost*`;启动期间安全存储失败由 Engine 归类为稳定的启动错误 `1101`。
- **崩溃隔离**:发布构建 `panic=abort`,Rust 崩溃会终止整个宿主进程,Go 的 `recover` 无效。嵌入后宿主与 Engine 同生共死,Engine 不再有进程隔离;
GUI/CLI 若仍是独立进程经 HTTP 连接 daemon,则它们不受 daemon 崩溃影响。这是 daemon 重写评审时需要明确讨论的取舍。
- **敏感信息**:错误与事件不含内容、路径或设备信息;`Invitation` 的 `String` 固定脱敏。

## 重新生成

```bash
bash bindings/go/generator/build-generator.sh <外置空目录> # 固定 revision + 模板补丁
bash bindings/go/generator/generate.sh <生成器>/bin/uniffi-bindgen-go target/debug/libuc_engine_uniffi.dylib
git diff --exit-code bindings/go/uc_engine_uniffi
```

生成物提交入库,CI 重新生成并要求零差异。模板补丁把模板引入的局部变量统一加 `_uniffi` 前缀,避免与用户方法参数同名(上游原样生成器因 `handle` 撞名无法编译)。
生成器与模板为 MPL-2.0,许可证文本随生成包保留为 `uc_engine_uniffi/LICENSE-uniffi-bindgen-go`;本仓其余部分为 Apache-2.0。

## 平台状态

| 目标 | 状态 |
|---|---|
| macOS arm64(本机) | 构建、链接、真实进程验收已实测(Go 1.24.0 与 1.27.1),证据见本任务报告与 CI 工件 `go-binding-macos-arm64-evidence` |
| Linux amd64/arm64 | CI 做原生构建与 Go 编译/链接检查;真实进程验收未覆盖 |
| macOS amd64、Windows amd64/arm64 | 未验证;Windows 需要与 Go C 链接器匹配的导入库,且尚无库身份校验 |

交叉编译不记为原生验证。

## 能力范围

门面当前覆盖:启动/关闭、本机设备、空间状态、设备列表、邀请签发、退出空间、网络时机通知、生命周期(暂停/恢复/状态)与事件。
现有 `MobileEngine` 是 `uc-engine` Operation 的子集;历史、设置、搜索、诊断、解锁与恢复、升级、LAN 兼容等 Desktop 需要的能力
尚未暴露,也未声称覆盖全部 62 个 Desktop 宿主命令。剪贴板与文件句柄宿主能力固定返回“不可用”。后续新增必须经 `uc-engine` Operation 加性补入统一绑定,并核对 Swift/Kotlin。
Loading
Loading