Repository navigation
feat(go-binding): versioned Go module over generated UniFFI bindings #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mkdir700
wants to merge
13
commits into
main
Choose a base branch
from
hp/uni/t-0198-engine-go-binding-go-daemon
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
25882e0
docs(go-binding): define failure model and acceptance contract
mkdir700 f57511a
feat(go-binding): add generated Go package, facade and acceptance host
mkdir700 a0bae5a
test(go-binding): isolate network by loopback sandbox, fix host inputs
mkdir700 d96b3e5
test(go-binding): meaningful concurrent close and host error checks
mkdir700 d93d0ba
build(go-binding): add native build script
mkdir700 76addcc
docs(go-binding): add README, ADR-034, static checks and CI workflow
mkdir700 06001a0
test(go-binding): assert embedded source identity matches the manifest
mkdir700 59b666d
test(go-binding): cover short-deadline close retry; mark generated files
mkdir700 18fce3a
fix(go-binding): address independent review findings
mkdir700 3f9a2a2
fix(go-binding): deny non-loopback egress in acceptance sandbox
mkdir700 de0416f
fix(go-binding): resolve loaded library via dlsym, harden close and C…
mkdir700 807f9ec
ci(go-binding): report leaked build paths and cache the pinned generator
mkdir700 2bf8ed2
fix(go-binding): check build paths on the staged library after rewrit…
mkdir700 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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/** | ||
| - .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 | ||
|
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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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。 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.