Skip to content

Commit e36159c

Browse files
committed
Add Go binding with idiomatic generated API
1 parent a6a85bc commit e36159c

24 files changed

Lines changed: 8010 additions & 25 deletions

File tree

‎.github/workflows/go-ci.yml‎

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: Go CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "bindings/go/**"
8+
- "core"
9+
- "examples/go_*/**"
10+
- "tools/codegen/**"
11+
- "codegen"
12+
- ".github/workflows/go-ci.yml"
13+
pull_request:
14+
branches: [main]
15+
paths:
16+
- "bindings/go/**"
17+
- "core"
18+
- "examples/go_*/**"
19+
- "tools/codegen/**"
20+
- "codegen"
21+
- ".github/workflows/go-ci.yml"
22+
workflow_dispatch:
23+
24+
permissions:
25+
contents: read
26+
27+
concurrency:
28+
group: ci-${{ github.workflow }}-${{ github.ref }}
29+
cancel-in-progress: true
30+
31+
jobs:
32+
build-test:
33+
name: Build & Test (${{ matrix.os }})
34+
runs-on: ${{ matrix.os }}
35+
timeout-minutes: 30
36+
strategy:
37+
fail-fast: false
38+
matrix:
39+
os: [ubuntu-latest, macos-latest, windows-latest]
40+
steps:
41+
- uses: actions/checkout@v4
42+
- name: Checkout core
43+
run: git submodule update --init core
44+
- uses: actions/setup-go@v5
45+
with:
46+
go-version: "1.22.x"
47+
cache: false
48+
- name: Install Linux dependencies
49+
if: runner.os == 'Linux'
50+
run: sudo apt-get update && sudo apt-get install -y cmake pkg-config libgtk-3-dev libx11-dev libxi-dev xvfb
51+
- uses: msys2/setup-msys2@v2
52+
if: runner.os == 'Windows'
53+
with:
54+
msystem: UCRT64
55+
install: mingw-w64-ucrt-x86_64-gcc
56+
- name: Configure Windows cgo and DLL lookup
57+
if: runner.os == 'Windows'
58+
shell: pwsh
59+
run: |
60+
"C:\msys64\ucrt64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
61+
"$env:GITHUB_WORKSPACE\bindings\go\build" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
62+
"CC=gcc" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
63+
- name: Build the native library
64+
run: |
65+
cmake -S bindings/go -B bindings/go/build -DCMAKE_BUILD_TYPE=Release
66+
cmake --build bindings/go/build --config Release --parallel 4
67+
- name: Check Go formatting
68+
shell: bash
69+
run: |
70+
files=$(gofmt -l bindings/go/*.go examples/go_*/*.go)
71+
if [ -n "$files" ]; then echo "$files"; exit 1; fi
72+
- name: Vet
73+
working-directory: bindings/go
74+
run: go vet ./...
75+
- name: Test
76+
working-directory: bindings/go
77+
shell: bash
78+
run: |
79+
if [ "$RUNNER_OS" = "Linux" ]; then xvfb-run -a go test -v ./...; else go test -v ./...; fi
80+
- name: Build examples
81+
shell: bash
82+
run: |
83+
for example in examples/go_*_example; do
84+
(cd "$example" && go build -o "$RUNNER_TEMP/$(basename "$example").exe" .)
85+
done
86+
87+
generated:
88+
name: Check generated Go API
89+
runs-on: ubuntu-latest
90+
steps:
91+
- uses: actions/checkout@v4
92+
- run: git submodule update --init core
93+
- uses: actions/setup-go@v5
94+
with:
95+
go-version: "1.22.x"
96+
cache: false
97+
- uses: dtolnay/rust-toolchain@stable
98+
- run: sudo apt-get update && sudo apt-get install -y libclang-dev
99+
- run: cargo test --manifest-path tools/codegen/Cargo.toml -p codegen-bindings
100+
- run: ./codegen check --lang go

‎AGENTS.md‎

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# libnativeapi workspace
22

3-
This is the workspace repo (`libnativeapi/nativeapi`, formerly `nativeapi-flutter` and `nativeapi-workspace`) for the [libnativeapi](https://github.com/libnativeapi) project family. Every binding (`bindings/dart/`, `bindings/rust/`, `bindings/csharp/`, `bindings/js/`, `bindings/python/`), the code generator (`tools/codegen/`), the `./codegen` script, the specs and the shared tooling live directly in this repo (the Rust and C# histories were merged in from `nativeapi-rust` and `nativeapi-csharp`); only `core/` and `bindings/dart/tray_manager/` are git submodules of independent repositories. Work inside a submodule is committed and pushed from that subdirectory; everything else is committed here.
3+
This is the workspace repo (`libnativeapi/nativeapi`, formerly `nativeapi-flutter` and `nativeapi-workspace`) for the [libnativeapi](https://github.com/libnativeapi) project family. Every binding (`bindings/dart/`, `bindings/rust/`, `bindings/csharp/`, `bindings/js/`, `bindings/python/`, `bindings/go/`), the code generator (`tools/codegen/`), the `./codegen` script, the specs and the shared tooling live directly in this repo (the Rust and C# histories were merged in from `nativeapi-rust` and `nativeapi-csharp`); only `core/` and `bindings/dart/tray_manager/` are git submodules of independent repositories. Work inside a submodule is committed and pushed from that subdirectory; everything else is committed here.
44

55
## Layout
66

@@ -11,8 +11,9 @@ bindings/
1111
├── rust/ # the Rust binding: crates/{nativeapi,cnativeapi}
1212
├── csharp/ # the C# binding: src/, tests/, NativeAPI.slnx
1313
├── js/ # the JS/TS binding: a Node-API addon (src/) + TypeScript (lib/)
14-
└── python/ # the Python binding: ctypes package (nativeapi/) + native shim (src/)
15-
examples/ # every binding's example apps, prefixed dart_*, flutter_*, rust_*, csharp_*, js_*, python_*
14+
├── python/ # the Python binding: ctypes package (nativeapi/) + native shim (src/)
15+
└── go/ # the Go binding: generated cgo API + native shared library
16+
examples/ # every binding's example apps, prefixed dart_*, flutter_*, rust_*, csharp_*, js_*, python_*, go_*
1617
pubspec.yaml # pub workspace + melos root: Dart packages and Flutter examples
1718
Cargo.toml # cargo workspace root: Rust crates and examples
1819
tools/codegen/ # in-repo Rust workspace: the code generator
@@ -25,8 +26,8 @@ codegen # Python entry point orchestrating the generators
2526
## Architecture
2627

2728
- `core` — the C++ core library (repo: `nativeapi-core`). The source of truth for the native API surface (windows, tray icons, menus, displays, keyboard, dialogs, storage, etc.) with per-platform implementations (macOS/Windows/Linux).
28-
- `tools/codegen` — three crates: `shared` (libclang parser, IR, naming), `capi` (C ABI + umbrella header), `bindings` (Rust/Dart/C#/JS/Python generators, consuming the IR JSON emitted by `capi`). Only `capi` depends on libclang. See tools/codegen/README.md.
29-
- `bindings/*` — language bindings wrapping the core library. All live in this repo and build against the `core/` submodule directly (Rust `build.rs`, the Dart `cnativeapi` package's build hook, the C# native CMake, the JS addon's and the Python binding's `CMakeLists.txt`). Only a published package carries its own copy of core, in `cxx_impl/`, which the release workflows vendor and never commit. The Rust binding layers `nativeapi` (safe API) over `cnativeapi` (FFI). The Python binding has no compiled extension: generated `ctypes` code (`nativeapi/_capi.py` plus one module per header) calls a shared library built from core and a small event loop shim, which `Application.run_async()` pumps from asyncio.
29+
- `tools/codegen` — three crates: `shared` (libclang parser, IR, naming), `capi` (C ABI + umbrella header), `bindings` (Rust/Dart/C#/JS/Python/Go generators, consuming the IR JSON emitted by `capi`). Only `capi` depends on libclang. See tools/codegen/README.md.
30+
- `bindings/*` — language bindings wrapping the core library. All live in this repo and build against the `core/` submodule directly (Rust `build.rs`, the Dart `cnativeapi` package's build hook, the C# native CMake, the JS, Python and Go bindings' `CMakeLists.txt`). Only a published package carries its own copy of core, in `cxx_impl/`, which the release workflows vendor and never commit. The Rust binding layers `nativeapi` (safe API) over `cnativeapi` (FFI). The Python binding has no compiled extension: generated `ctypes` code (`nativeapi/_capi.py` plus one module per header) calls a shared library built from core and a small event loop shim, which `Application.run_async()` pumps from asyncio.
3031

3132
## Design specs
3233

@@ -50,13 +51,24 @@ breaks). When one is resolved, edit the spec text itself.
5051
Always drive the generators through `./codegen` at the workspace root:
5152

5253
- `./codegen` — full run: C ABI, then all bindings
53-
- `./codegen capi` / `./codegen bindings [--lang rust,dart,csharp,js,python]`
54+
- `./codegen capi` / `./codegen bindings [--lang rust,dart,csharp,js,python,go]`
5455
- `./codegen check` — read-only verification, non-zero exit when stale (CI mode)
5556
- `./codegen readme` — copy the shared README sections (`tools/readme/*.md`, e.g. Contributing) into core and every binding; `check` flags drift, `sync` runs it. Edit the snippet, never the copies.
5657
- `./codegen sync [-m "msg"] [--push]` — full downstream propagation, see below
5758

5859
Generated files start with `// AUTO-GENERATED. DO NOT EDIT.` (`#` in Python) — change the C++ headers in `core/src/` and regenerate instead of editing outputs. Files without that banner are hand-written and never overwritten. The header list (`API_HEADERS`) lives in `tools/codegen/shared/src/lib.rs`.
5960

61+
Go builds require Go 1.22+, cgo and CMake 3.24+; build the shared library with
62+
`cmake -S bindings/go -B bindings/go/build && cmake --build bindings/go/build --config Release`.
63+
Go examples are standalone modules with a local `replace` pointing to `../../bindings/go`.
64+
The Go generator uses `gofmt` before writing and checking its output. Public Go getters
65+
omit `Get`, `ToString` becomes `String`, constructor names omit shared overload
66+
parameters, and fallible operations return `error` while predicates keep `bool`.
67+
Native singletons are exported values with methods (`DisplayManager.All()`,
68+
`Application.Run()`), backed by private stateless types.
69+
Object getters retain nil-for-absence semantics; native failures without detailed
70+
causes wrap `ErrOperationFailed`. Examples intentionally use dot imports as requested.
71+
6072
## Changing the core API
6173

6274
A core change ripples to every binding. After editing headers in `core`, run:
@@ -95,8 +107,8 @@ scenarios for *this project's* examples live in [tools/gui/](tools/gui/README.md
95107
- `core` tracks `branch = main`. Use `make sync` to fast-forward it; `make status` to see dirty state everywhere; `make bump` to stage its pointer.
96108
- The leanflutter packages built on nativeapi (`tray_manager`, `window_manager`, `launch_at_startup`, …) live in their own repos under github.com/leanflutter and depend on the published `nativeapi`. Only `tray_manager` rides along, as the submodule `bindings/dart/tray_manager` (tracking `main`); it is not a member of the root pub workspace, melos ignores it, and CI does not check it out. To try one against local changes, point a `dependency_overrides` entry in that package at `bindings/dart/nativeapi_flutter` (and `nativeapi`, `cnativeapi`) and never commit the override.
97109
- Commit workspace submodule pointer updates only when the combination is compatible (a known-good snapshot).
98-
- Examples live in `examples/<binding>_<name>_example` (`dart_` for plain Dart programs, `flutter_`, `rust_`, `csharp_`, `js_`, `python_`), not inside the bindings; a new Dart, Flutter or Rust example must also be listed in the root `pubspec.yaml` / `Cargo.toml`, a C# one in `bindings/csharp/NativeAPI.slnx`, a JS one in the root `package.json`. A Python example is a standalone uv project whose `pyproject.toml` points `nativeapi` at `../../bindings/python` (`uv run main.py` builds the wheel). Only the pub.dev package examples (`bindings/dart/*/example`) stay inside their package.
99-
- CI is one workflow per binding (`dart-ci.yml`, `rust-ci.yml`, `csharp-ci.yml`, `js-ci.yml`, `python-ci.yml`), each running only for changes under its `bindings/<lang>/` and its examples (`examples/dart_*` and `examples/flutter_*` for Dart, `examples/rust_*`, `examples/csharp_*`, `examples/js_*`, `examples/python_*`). Release tags are per binding: `v*` publishes the Dart packages (`dart-release.yml`), `rust-v*` publishes the crates (`rust-release.yml`), `python-v*` publishes the Python package to PyPI (`python-release.yml`: an sdist carrying core in `cxx_impl/`, and wheels built from it with cibuildwheel; the tag must equal `python-v` + the version in `bindings/python/pyproject.toml`); never push a bare `v*` tag for anything but the Dart packages. Nothing has been published to PyPI yet: the first release needs `nativeapi` set up on PyPI with this workflow and the `pypi` environment as a trusted publisher.
110+
- Examples live in `examples/<binding>_<name>_example` (`dart_` for plain Dart programs, `flutter_`, `rust_`, `csharp_`, `js_`, `python_`, `go_`), not inside the bindings; a new Dart, Flutter or Rust example must also be listed in the root `pubspec.yaml` / `Cargo.toml`, a C# one in `bindings/csharp/NativeAPI.slnx`, a JS one in the root `package.json`. A Python example is a standalone uv project whose `pyproject.toml` points `nativeapi` at `../../bindings/python` (`uv run main.py` builds the wheel). A Go example is a standalone module whose `go.mod` replaces `github.com/libnativeapi/nativeapi/bindings/go` with `../../bindings/go`. Only the pub.dev package examples (`bindings/dart/*/example`) stay inside their package.
111+
- CI is one workflow per binding (`dart-ci.yml`, `rust-ci.yml`, `csharp-ci.yml`, `js-ci.yml`, `python-ci.yml`, `go-ci.yml`), each running only for changes under its `bindings/<lang>/` and its examples (`examples/dart_*` and `examples/flutter_*` for Dart, `examples/rust_*`, `examples/csharp_*`, `examples/js_*`, `examples/python_*`, `examples/go_*`). Release tags are per binding: `v*` publishes the Dart packages (`dart-release.yml`), `rust-v*` publishes the crates (`rust-release.yml`), `python-v*` publishes the Python package to PyPI (`python-release.yml`: an sdist carrying core in `cxx_impl/`, and wheels built from it with cibuildwheel; the tag must equal `python-v` + the version in `bindings/python/pyproject.toml`); never push a bare `v*` tag for anything but the Dart packages. Nothing has been published to PyPI yet: the first release needs `nativeapi` set up on PyPI with this workflow and the `pypi` environment as a trusted publisher.
100112
- The Dart packages `cnativeapi`, `nativeapi` and `nativeapi_flutter` share one version: a release bumps all three pubspecs and CHANGELOGs, because pub.dev's automated publishing matches the tag `v<version>` against each package's own version. `cnativeapi` and `nativeapi` are plain Dart (no Flutter dependency; a build hook compiles core); `nativeapi_flutter` is the Flutter-facing package, holding the widgets, the `dart:ui` conversions and `windowing.dart`, and re-exporting `nativeapi` minus the names that clash with Flutter's.
101113
- Never commit in a submodule while on a detached HEAD — check out `main` first (`./codegen sync` enforces this).
102114
- Do not add Co-Authored-By trailers to commits.

‎README.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# nativeapi
22

3-
Unified access to native system APIs — windows, tray icons, menus, displays, keyboard, dialogs, storage and more — from Dart/Flutter, Rust, C#, JavaScript/TypeScript and Python, all built on one C++ core.
3+
Unified access to native system APIs — windows, tray icons, menus, displays, keyboard, dialogs, storage and more — from Dart/Flutter, Rust, C#, JavaScript/TypeScript, Python and Go, all built on one C++ core.
44

55
| Android | iOS | Linux | macOS | Windows |
66
|:-------:|:---:|:-----:|:-----:|:-------:|
77
| ✅ | ✅ | ✅ | ✅ | ✅ |
88

99
🚧 **Work in Progress**: the API is under active development.
1010

11-
This repository is the home of every binding: the Dart, Rust, C#, JS and Python bindings, the code generator, the design specs and the shared tooling live here directly, and the C++ core ([nativeapi-core](https://github.com/libnativeapi/nativeapi-core)) is checked out as a git submodule. A core change and its regenerated bindings (core → codegen → bindings) are made and tracked together.
11+
This repository is the home of every binding: the Dart, Rust, C#, JS, Python and Go bindings, the code generator, the design specs and the shared tooling live here directly, and the C++ core ([nativeapi-core](https://github.com/libnativeapi/nativeapi-core)) is checked out as a git submodule. A core change and its regenerated bindings (core → codegen → bindings) are made and tracked together.
1212

1313
## Bindings
1414

@@ -19,6 +19,7 @@ This repository is the home of every binding: the Dart, Rust, C#, JS and Python
1919
| [C#](bindings/csharp) | `NativeAPI` | not yet on NuGet; build from source |
2020
| [JavaScript / TypeScript](bindings/js) | `nativeapi` (Node-API addon for Node.js, Deno and Bun) | not yet on npm |
2121
| [Python](bindings/python) | `nativeapi` (`ctypes`, Python 3.10+) | prototype, not yet on PyPI |
22+
| [Go](bindings/go) | `github.com/libnativeapi/nativeapi/bindings/go` (cgo, Go 1.22+) | build from source |
2223

2324
Each binding's README covers installation and usage.
2425

@@ -36,7 +37,8 @@ This repository was `nativeapi-flutter`, then `nativeapi-workspace`, and is now
3637
| [bindings/csharp](bindings/csharp) | C# binding |
3738
| [bindings/js](bindings/js) | JavaScript / TypeScript binding (Node-API addon) |
3839
| [bindings/python](bindings/python) | Python binding (`ctypes`) |
39-
| [examples](examples) | example apps of every binding, prefixed by binding: `flutter_*`, `rust_*`, `csharp_*`, `js_*`, `python_*`, … |
40+
| [bindings/go](bindings/go) | Go binding (cgo) |
41+
| [examples](examples) | example apps of every binding, prefixed by binding: `flutter_*`, `rust_*`, `csharp_*`, `js_*`, `python_*`, `go_*`, … |
4042
| [tools/codegen](tools/codegen) | the C ABI and binding generators |
4143
| [specs](specs) | design rules for the core's public API |
4244

@@ -86,7 +88,7 @@ Work on the bindings, the tooling and the specs is committed here directly. Work
8688

8789
## Releases
8890

89-
Each binding is released from a tag of its own: `v<version>` publishes the Dart packages to pub.dev (`dart-release.yml`), `rust-v<version>` publishes the crates to crates.io (`rust-release.yml`). The C#, JS and Python bindings are not published yet.
91+
Each binding is released from a tag of its own: `v<version>` publishes the Dart packages to pub.dev (`dart-release.yml`), `rust-v<version>` publishes the crates to crates.io (`rust-release.yml`). The C#, JS, Python and Go bindings are not published yet.
9092

9193
## License
9294

‎bindings/go/.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build/

‎bindings/go/CMakeLists.txt‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
cmake_minimum_required(VERSION 3.24)
2+
project(nativeapi_go LANGUAGES C CXX)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
7+
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../../core/src" nativeapi_core)
8+
9+
# Whole-archive keeps every C ABI symbol, including APIs Go hasn't called yet.
10+
add_library(nativeapi_go SHARED src/runtime.cpp)
11+
target_link_libraries(nativeapi_go PRIVATE $<LINK_LIBRARY:WHOLE_ARCHIVE,nativeapi>)
12+
set_target_properties(nativeapi_go PROPERTIES
13+
LINKER_LANGUAGE CXX
14+
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
15+
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
16+
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
17+
)
18+
# cgo uses one stable path for both single- and multi-configuration generators.
19+
foreach(config DEBUG RELEASE RELWITHDEBINFO MINSIZEREL)
20+
set_target_properties(nativeapi_go PROPERTIES
21+
LIBRARY_OUTPUT_DIRECTORY_${config} "${CMAKE_BINARY_DIR}"
22+
RUNTIME_OUTPUT_DIRECTORY_${config} "${CMAKE_BINARY_DIR}"
23+
ARCHIVE_OUTPUT_DIRECTORY_${config} "${CMAKE_BINARY_DIR}"
24+
)
25+
endforeach()

0 commit comments

Comments
 (0)