From 5f1c2280b9f8066e75332c440ef3278054c1985c Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Tue, 28 Jul 2026 21:11:44 -0700 Subject: [PATCH 01/15] feat(go): add drop-in SDK distribution via static libmoss modules Publish per-platform Go modules from C SDK releases so consumers can go get the SDK without manual libmoss setup, build tags, or LD_LIBRARY_PATH. Co-authored-by: Cursor --- .github/workflows/go-sdk-test.yml | 55 ++++ .github/workflows/publish-go-sdk.yml | 60 ++++ examples/go/README.md | 18 +- examples/go/go.mod | 16 +- sdks/go/README.md | 33 +- sdks/go/bindings/README.md | 79 +++-- sdks/go/bindings/errors.go | 2 +- sdks/go/bindings/go.mod | 15 + sdks/go/bindings/include/libmoss.h | 287 ++++++++++++++++++ sdks/go/bindings/lib/darwin-arm64/.gitignore | 1 + sdks/go/bindings/lib/darwin-arm64/go.mod | 3 + sdks/go/bindings/lib/darwin-arm64/prebuilt.go | 9 + sdks/go/bindings/lib/linux-amd64/.gitignore | 1 + sdks/go/bindings/lib/linux-amd64/go.mod | 3 + sdks/go/bindings/lib/linux-amd64/prebuilt.go | 9 + sdks/go/bindings/lib/linux-arm64/.gitignore | 1 + sdks/go/bindings/lib/linux-arm64/go.mod | 3 + sdks/go/bindings/lib/linux-arm64/prebuilt.go | 9 + sdks/go/bindings/lib/windows-amd64/.gitignore | 3 + sdks/go/bindings/lib/windows-amd64/go.mod | 3 + .../go/bindings/lib/windows-amd64/prebuilt.go | 9 + sdks/go/bindings/libmoss.go | 6 +- sdks/go/bindings/prebuilt_darwin_arm64.go | 5 + sdks/go/bindings/prebuilt_linux_amd64.go | 5 + sdks/go/bindings/prebuilt_linux_arm64.go | 5 + sdks/go/bindings/prebuilt_windows_amd64.go | 5 + sdks/go/bindings/stub.go | 2 +- sdks/go/bindings/version.go | 7 + sdks/go/go.work | 10 + sdks/go/scripts/bump-module-versions.sh | 43 +++ sdks/go/scripts/fetch-static-libs.sh | 74 +++++ sdks/go/scripts/link_dev_lib.sh | 63 ++++ sdks/go/scripts/publish-module-tags.sh | 56 ++++ sdks/go/sdk/README.md | 31 +- 34 files changed, 873 insertions(+), 58 deletions(-) create mode 100644 .github/workflows/go-sdk-test.yml create mode 100644 .github/workflows/publish-go-sdk.yml create mode 100644 sdks/go/bindings/include/libmoss.h create mode 100644 sdks/go/bindings/lib/darwin-arm64/.gitignore create mode 100644 sdks/go/bindings/lib/darwin-arm64/go.mod create mode 100644 sdks/go/bindings/lib/darwin-arm64/prebuilt.go create mode 100644 sdks/go/bindings/lib/linux-amd64/.gitignore create mode 100644 sdks/go/bindings/lib/linux-amd64/go.mod create mode 100644 sdks/go/bindings/lib/linux-amd64/prebuilt.go create mode 100644 sdks/go/bindings/lib/linux-arm64/.gitignore create mode 100644 sdks/go/bindings/lib/linux-arm64/go.mod create mode 100644 sdks/go/bindings/lib/linux-arm64/prebuilt.go create mode 100644 sdks/go/bindings/lib/windows-amd64/.gitignore create mode 100644 sdks/go/bindings/lib/windows-amd64/go.mod create mode 100644 sdks/go/bindings/lib/windows-amd64/prebuilt.go create mode 100644 sdks/go/bindings/prebuilt_darwin_arm64.go create mode 100644 sdks/go/bindings/prebuilt_linux_amd64.go create mode 100644 sdks/go/bindings/prebuilt_linux_arm64.go create mode 100644 sdks/go/bindings/prebuilt_windows_amd64.go create mode 100644 sdks/go/bindings/version.go create mode 100644 sdks/go/go.work create mode 100755 sdks/go/scripts/bump-module-versions.sh create mode 100755 sdks/go/scripts/fetch-static-libs.sh create mode 100755 sdks/go/scripts/link_dev_lib.sh create mode 100644 sdks/go/scripts/publish-module-tags.sh diff --git a/.github/workflows/go-sdk-test.yml b/.github/workflows/go-sdk-test.yml new file mode 100644 index 00000000..ff118af4 --- /dev/null +++ b/.github/workflows/go-sdk-test.yml @@ -0,0 +1,55 @@ +name: Go SDK test + +permissions: + contents: read + +on: + push: + branches: [main] + paths: + - 'sdks/go/**' + - '.github/workflows/go-sdk-test.yml' + pull_request: + paths: + - 'sdks/go/**' + - '.github/workflows/go-sdk-test.yml' + +jobs: + unit-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.22.2' + + - name: Run SDK unit tests without native bindings + working-directory: sdks/go/sdk + env: + CGO_ENABLED: '0' + run: go test ./... + + bindings-smoke: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.22.2' + + - name: Install linux-amd64 native library + run: ./sdks/go/scripts/link_dev_lib.sh c-sdk-v0.9.0 + + - name: Verify bindings compile with CGO + working-directory: sdks/go/bindings + env: + CGO_ENABLED: '1' + run: go build -v . + + - name: Verify SDK compiles with CGO + working-directory: sdks/go/sdk + env: + CGO_ENABLED: '1' + run: go build -v . diff --git a/.github/workflows/publish-go-sdk.yml b/.github/workflows/publish-go-sdk.yml new file mode 100644 index 00000000..be57d866 --- /dev/null +++ b/.github/workflows/publish-go-sdk.yml @@ -0,0 +1,60 @@ +name: Publish Go SDK + +permissions: + contents: write + +on: + workflow_dispatch: + inputs: + c_sdk_version: + description: 'C SDK GitHub release tag' + required: true + default: 'c-sdk-v0.9.0' + go_version: + description: 'Go module version to publish' + required: true + default: 'v0.9.0' + +concurrency: + group: go-sdk-release-${{ github.event.inputs.go_version }} + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.22.2' + + - name: Fetch static native libraries + run: ./sdks/go/scripts/fetch-static-libs.sh "${{ github.event.inputs.c_sdk_version }}" + + - name: Verify linux-amd64 build + working-directory: sdks/go/sdk + env: + CGO_ENABLED: '1' + run: go build -v . + + - name: Pin module versions + run: ./sdks/go/scripts/bump-module-versions.sh "${{ github.event.inputs.go_version }}" + + - name: Update bindings native version metadata + shell: bash + run: | + C_SDK_VERSION="${{ github.event.inputs.c_sdk_version }}" + NATIVE_VERSION="${C_SDK_VERSION#c-sdk-v}" + cat > sdks/go/bindings/version.go < ../../sdks/go/sdk replace github.com/usemoss/moss/sdks/go/bindings => ../../sdks/go/bindings + +replace github.com/usemoss/moss/sdks/go/bindings/lib/darwin-arm64 => ../../sdks/go/bindings/lib/darwin-arm64 + +replace github.com/usemoss/moss/sdks/go/bindings/lib/linux-amd64 => ../../sdks/go/bindings/lib/linux-amd64 + +replace github.com/usemoss/moss/sdks/go/bindings/lib/linux-arm64 => ../../sdks/go/bindings/lib/linux-arm64 + +replace github.com/usemoss/moss/sdks/go/bindings/lib/windows-amd64 => ../../sdks/go/bindings/lib/windows-amd64 diff --git a/sdks/go/README.md b/sdks/go/README.md index 96457875..605a27a7 100644 --- a/sdks/go/README.md +++ b/sdks/go/README.md @@ -1,20 +1,33 @@ # Moss Go SDK -The Go work now has the same two-layer direction as the other Moss SDKs: +The Go SDK follows the same two-layer design as the other Moss SDKs: - `sdks/go/sdk/` contains the public Go SDK - `sdks/go/bindings/` wraps the native `libmoss` runtime via CGO -Current status: +## Install -- bindings-backed manage operations for mutations and metadata reads -- local `LoadIndex` / `UnloadIndex` / `GetIndexInfo` / local `Query` via `libmoss` -- examples under `examples/go/` and unit tests -- env-gated integration test scaffold +```bash +go get github.com/usemoss/moss/sdks/go/sdk +``` -Important note: +Published module versions bundle static `libmoss` per platform. You need CGO and a +C compiler, but not a manual C SDK download. -- all runtime operations require the `libmoss` C SDK plus `-tags libmoss` +## Local development -The public SDK module lives under [`sdks/go/sdk/`](./sdk/), and the native -bindings module lives under [`sdks/go/bindings/`](./bindings/). +```bash +./sdks/go/scripts/link_dev_lib.sh c-sdk-v0.9.0 +cd sdks/go/sdk +CGO_ENABLED=1 go test ./... +``` + +Unit tests run without native libraries when `CGO_ENABLED=0`. + +## Publishing + +See [`bindings/README.md`](./bindings/README.md) and +[`.github/workflows/publish-go-sdk.yml`](../../.github/workflows/publish-go-sdk.yml). + +The public SDK module lives under [`sdk/`](./sdk/), and the native bindings module +lives under [`bindings/`](./bindings/). diff --git a/sdks/go/bindings/README.md b/sdks/go/bindings/README.md index 18ebb016..2fac7156 100644 --- a/sdks/go/bindings/README.md +++ b/sdks/go/bindings/README.md @@ -9,33 +9,76 @@ It mirrors the role of the other language bindings packages in this repository: - local query execution - cloud-backed manage operations exposed through the native client -## Status +## Installation (consumers) -The real bindings implementation is compiled only with the `libmoss` build tag. -Without that tag, this package builds a stub that returns a clear -`ErrBindingsUnavailable` error. +```bash +go get github.com/usemoss/moss/sdks/go/sdk +``` + +Published releases bundle prebuilt static libraries per platform. No manual C SDK +download, `LD_LIBRARY_PATH`, or `-tags libmoss` is required. + +Requirements: + +- `CGO_ENABLED=1` (default on Linux and macOS) +- A C compiler (`gcc` on Linux, Xcode CLI tools on macOS, MinGW on Windows) -## Local build workflow +## Layout -Download the matching `libmoss` C SDK release archive for your platform from: +``` +bindings/ + include/libmoss.h # committed C header + libmoss.go # CGO wrapper (requires CGO) + prebuilt__.go # selects the platform linker module + lib/ + linux-amd64/ # separate Go module with libmoss.a (release tags) + linux-arm64/ + darwin-arm64/ + windows-amd64/ +``` + +On `main`, native `.a` / `.lib` files are gitignored. Release CI downloads them +from [C SDK GitHub Releases](https://github.com/usemoss/moss/releases) and +publishes versioned Go module tags. -- +## Local development -For Linux `x86_64`, extract the archive somewhere local so you have: +Install the native library for your current machine: -```text -/ -├── include/libmoss.h -└── lib/libmoss.so +```bash +./sdks/go/scripts/link_dev_lib.sh c-sdk-v0.9.0 ``` -Then build with: +Or fetch all supported platforms (release prep): ```bash -export CGO_CFLAGS="-I/include" -export CGO_LDFLAGS="-L/lib" -export LD_LIBRARY_PATH="/lib" -go test -tags libmoss ./... +./sdks/go/scripts/fetch-static-libs.sh c-sdk-v0.9.0 ``` -The Go SDK module can then be built with the same flags and tag. +Then build with CGO enabled: + +```bash +cd sdks/go/bindings +CGO_ENABLED=1 go build . +``` + +## Publishing + +Maintainers run the **Publish Go SDK** GitHub Actions workflow. It: + +1. Downloads static libraries from a C SDK release +2. Pins module versions in `go.mod` files +3. Creates a release commit and pushes module tags (without updating `main`) + +Tags follow the monorepo convention: + +- `sdks/go/sdk/v0.9.0` +- `sdks/go/bindings/v0.9.0` +- `sdks/go/bindings/lib/linux-amd64/v0.9.0` +- … + +## Build without CGO + +When `CGO_ENABLED=0`, this package builds a stub that returns +`ErrBindingsUnavailable`. The public SDK can still run unit tests and cloud query +fallback tests without native libraries. diff --git a/sdks/go/bindings/errors.go b/sdks/go/bindings/errors.go index 16ca55d4..52d22127 100644 --- a/sdks/go/bindings/errors.go +++ b/sdks/go/bindings/errors.go @@ -2,5 +2,5 @@ package mosscore import "errors" -var ErrBindingsUnavailable = errors.New("mosscore: libmoss bindings are unavailable; build with -tags libmoss and configure the libmoss C SDK") +var ErrBindingsUnavailable = errors.New("mosscore: libmoss bindings are unavailable; build with CGO_ENABLED=1 and install the platform native library (see sdks/go/bindings/README.md)") var ErrClientClosed = errors.New("mosscore: client is closed") diff --git a/sdks/go/bindings/go.mod b/sdks/go/bindings/go.mod index d34cdccc..18cd4eb4 100644 --- a/sdks/go/bindings/go.mod +++ b/sdks/go/bindings/go.mod @@ -1,3 +1,18 @@ module github.com/usemoss/moss/sdks/go/bindings go 1.22.2 + +require ( + github.com/usemoss/moss/sdks/go/bindings/lib/darwin-arm64 v0.0.0 + github.com/usemoss/moss/sdks/go/bindings/lib/linux-amd64 v0.0.0 + github.com/usemoss/moss/sdks/go/bindings/lib/linux-arm64 v0.0.0 + github.com/usemoss/moss/sdks/go/bindings/lib/windows-amd64 v0.0.0 +) + +replace github.com/usemoss/moss/sdks/go/bindings/lib/darwin-arm64 => ./lib/darwin-arm64 + +replace github.com/usemoss/moss/sdks/go/bindings/lib/linux-amd64 => ./lib/linux-amd64 + +replace github.com/usemoss/moss/sdks/go/bindings/lib/linux-arm64 => ./lib/linux-arm64 + +replace github.com/usemoss/moss/sdks/go/bindings/lib/windows-amd64 => ./lib/windows-amd64 diff --git a/sdks/go/bindings/include/libmoss.h b/sdks/go/bindings/include/libmoss.h new file mode 100644 index 00000000..969f68b8 --- /dev/null +++ b/sdks/go/bindings/include/libmoss.h @@ -0,0 +1,287 @@ +/* Generated by cbindgen — do not edit manually. */ + +#ifndef LIBMOSS_H +#define LIBMOSS_H + +/* Warning: this file is auto-generated by cbindgen. Do not modify this manually. */ + +#include +#include +#include +#include + +/** + * Result codes returned by every fallible `moss_*` function. + */ +enum MossResult +#ifdef __cplusplus + : int32_t +#endif // __cplusplus + { + OK = 0, + ERR_NULL_POINTER = -1, + ERR_INVALID_ARG = -2, + ERR_CLOUD = -3, + ERR_INDEX_NOT_FOUND = -4, + ERR_MODEL = -5, + ERR_IO = -6, + ERR_INTERNAL = -7, +}; +#ifndef __cplusplus +typedef int32_t MossResult; +#endif // __cplusplus + +typedef struct MossClient MossClient; + +typedef struct MossSession MossSession; + +typedef struct MossMetadataEntry { + char *key; + char *value; +} MossMetadataEntry; + +typedef struct MossDocumentInfo { + char *id; + char *text; + struct MossMetadataEntry *metadata; + uintptr_t metadata_count; + float *embedding; + uintptr_t embedding_dim; +} MossDocumentInfo; + +typedef struct MossMutationResult { + char *job_id; + char *index_name; + uintptr_t doc_count; +} MossMutationResult; + +typedef struct MossMutationOptions { + bool upsert; +} MossMutationOptions; + +typedef struct MossModelRef { + char *id; + char *version; +} MossModelRef; + +typedef struct MossIndexInfo { + char *id; + char *name; + char *version; + char *status; + uintptr_t doc_count; + char *created_at; + char *updated_at; + struct MossModelRef model; +} MossIndexInfo; + +typedef struct MossJobStatusResponse { + char *job_id; + char *status; + double progress; + char *current_phase; + char *error; + char *created_at; + char *updated_at; + char *completed_at; +} MossJobStatusResponse; + +typedef struct MossLoadIndexOptions { + bool auto_refresh; + uint64_t polling_interval_secs; +} MossLoadIndexOptions; + +typedef struct MossQueryOptions { + uintptr_t top_k; + float alpha; + const char *filter_json; + const float *embedding; + uintptr_t embedding_dim; +} MossQueryOptions; + +typedef struct MossQueryResultDoc { + char *id; + char *text; + struct MossMetadataEntry *metadata; + uintptr_t metadata_count; + float score; +} MossQueryResultDoc; + +typedef struct MossSearchResult { + struct MossQueryResultDoc *docs; + uintptr_t doc_count; + char *query; + char *index_name; + uint64_t time_taken_ms; +} MossSearchResult; + +typedef struct MossRefreshResult { + char *index_name; + char *previous_updated_at; + char *new_updated_at; + bool was_updated; +} MossRefreshResult; + +typedef struct MossSessionOptions { + const char *model_id; +} MossSessionOptions; + +typedef struct MossAddDocsOptions { + bool upsert; +} MossAddDocsOptions; + +typedef struct MossPushIndexResult { + char *job_id; + char *index_name; + uintptr_t doc_count; + char *status; +} MossPushIndexResult; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Returns the SDK version string (e.g. "0.8.7"). + * The returned pointer is valid for the lifetime of the library — do not free it. + */ +const char *moss_sdk_version(void); + +MossResult moss_client_new(const char *project_id, + const char *project_key, + struct MossClient **out); + +void moss_client_free(struct MossClient *client); + +MossResult moss_client_create_index(struct MossClient *client, + const char *name, + const struct MossDocumentInfo *docs, + uintptr_t doc_count, + const char *model_id, + struct MossMutationResult **out); + +MossResult moss_client_add_docs(struct MossClient *client, + const char *name, + const struct MossDocumentInfo *docs, + uintptr_t doc_count, + const struct MossMutationOptions *opts, + struct MossMutationResult **out); + +MossResult moss_client_delete_docs(struct MossClient *client, + const char *name, + const char *const *doc_ids, + uintptr_t count, + struct MossMutationResult **out); + +MossResult moss_client_delete_index(struct MossClient *client, const char *name, bool *out_deleted); + +MossResult moss_client_get_index(struct MossClient *client, + const char *name, + struct MossIndexInfo **out); + +MossResult moss_client_list_indexes(struct MossClient *client, + struct MossIndexInfo **out, + uintptr_t *out_count); + +MossResult moss_client_get_docs(struct MossClient *client, + const char *name, + const char *const *doc_ids, + uintptr_t id_count, + struct MossDocumentInfo **out_docs, + uintptr_t *out_count); + +MossResult moss_client_get_job_status(struct MossClient *client, + const char *job_id, + struct MossJobStatusResponse **out); + +MossResult moss_client_load_index(struct MossClient *client, + const char *name, + const struct MossLoadIndexOptions *opts, + struct MossIndexInfo **out); + +MossResult moss_client_unload_index(struct MossClient *client, const char *name); + +MossResult moss_client_query(struct MossClient *client, + const char *name, + const char *query, + const struct MossQueryOptions *opts, + struct MossSearchResult **out); + +MossResult moss_client_refresh_index(struct MossClient *client, + const char *name, + struct MossRefreshResult **out); + +MossResult moss_client_session(struct MossClient *client, + const char *name, + const struct MossSessionOptions *opts, + struct MossSession **out); + +/** + * Returns a pointer to a null-terminated UTF-8 error description for the most + * recent failed `moss_*` call on the current thread. The pointer is valid until + * the next `moss_*` call on the same thread. Returns NULL if no error is stored. + */ +const char *moss_last_error(void); + +void moss_session_free(struct MossSession *session); + +/** + * Returns a pointer to the session name. The pointer is valid for the lifetime + * of the session — do not free it. + */ +const char *moss_session_name(const struct MossSession *session); + +uintptr_t moss_session_doc_count(const struct MossSession *session); + +MossResult moss_session_add_docs(struct MossSession *session, + const struct MossDocumentInfo *docs, + uintptr_t doc_count, + const struct MossAddDocsOptions *opts, + uintptr_t *out_added, + uintptr_t *out_updated); + +MossResult moss_session_delete_docs(struct MossSession *session, + const char *const *doc_ids, + uintptr_t count, + uintptr_t *out_deleted); + +MossResult moss_session_get_docs(struct MossSession *session, + const char *const *doc_ids, + uintptr_t id_count, + struct MossDocumentInfo **out_docs, + uintptr_t *out_count); + +MossResult moss_session_query(struct MossSession *session, + const char *query, + const struct MossQueryOptions *opts, + struct MossSearchResult **out); + +MossResult moss_session_load_index(struct MossSession *session, + const char *index_name, + uintptr_t *out_doc_count); + +MossResult moss_session_push_index(struct MossSession *session, struct MossPushIndexResult **out); + +void moss_free_string(char *s); + +void moss_free_documents(struct MossDocumentInfo *docs, uintptr_t count); + +void moss_free_search_result(struct MossSearchResult *result); + +void moss_free_index_info(struct MossIndexInfo *info); + +void moss_free_index_info_list(struct MossIndexInfo *infos, uintptr_t count); + +void moss_free_mutation_result(struct MossMutationResult *result); + +void moss_free_push_index_result(struct MossPushIndexResult *result); + +void moss_free_job_status_response(struct MossJobStatusResponse *resp); + +void moss_free_refresh_result(struct MossRefreshResult *result); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* LIBMOSS_H */ diff --git a/sdks/go/bindings/lib/darwin-arm64/.gitignore b/sdks/go/bindings/lib/darwin-arm64/.gitignore new file mode 100644 index 00000000..fd574048 --- /dev/null +++ b/sdks/go/bindings/lib/darwin-arm64/.gitignore @@ -0,0 +1 @@ +libmoss.a diff --git a/sdks/go/bindings/lib/darwin-arm64/go.mod b/sdks/go/bindings/lib/darwin-arm64/go.mod new file mode 100644 index 00000000..614dc631 --- /dev/null +++ b/sdks/go/bindings/lib/darwin-arm64/go.mod @@ -0,0 +1,3 @@ +module github.com/usemoss/moss/sdks/go/bindings/lib/darwin-arm64 + +go 1.22.2 diff --git a/sdks/go/bindings/lib/darwin-arm64/prebuilt.go b/sdks/go/bindings/lib/darwin-arm64/prebuilt.go new file mode 100644 index 00000000..847180a7 --- /dev/null +++ b/sdks/go/bindings/lib/darwin-arm64/prebuilt.go @@ -0,0 +1,9 @@ +//go:build darwin && arm64 + +package mosscore_platform + +/* +#cgo CFLAGS: -I${SRCDIR}/../../include +#cgo LDFLAGS: -L${SRCDIR} -lmoss -lc++ -framework Security -framework SystemConfiguration +*/ +import "C" diff --git a/sdks/go/bindings/lib/linux-amd64/.gitignore b/sdks/go/bindings/lib/linux-amd64/.gitignore new file mode 100644 index 00000000..fd574048 --- /dev/null +++ b/sdks/go/bindings/lib/linux-amd64/.gitignore @@ -0,0 +1 @@ +libmoss.a diff --git a/sdks/go/bindings/lib/linux-amd64/go.mod b/sdks/go/bindings/lib/linux-amd64/go.mod new file mode 100644 index 00000000..9b261289 --- /dev/null +++ b/sdks/go/bindings/lib/linux-amd64/go.mod @@ -0,0 +1,3 @@ +module github.com/usemoss/moss/sdks/go/bindings/lib/linux-amd64 + +go 1.22.2 diff --git a/sdks/go/bindings/lib/linux-amd64/prebuilt.go b/sdks/go/bindings/lib/linux-amd64/prebuilt.go new file mode 100644 index 00000000..bdb17bc3 --- /dev/null +++ b/sdks/go/bindings/lib/linux-amd64/prebuilt.go @@ -0,0 +1,9 @@ +//go:build linux && amd64 + +package mosscore_platform + +/* +#cgo CFLAGS: -I${SRCDIR}/../../include +#cgo LDFLAGS: -L${SRCDIR} -lmoss -lstdc++ -ldl -lm -lpthread +*/ +import "C" diff --git a/sdks/go/bindings/lib/linux-arm64/.gitignore b/sdks/go/bindings/lib/linux-arm64/.gitignore new file mode 100644 index 00000000..fd574048 --- /dev/null +++ b/sdks/go/bindings/lib/linux-arm64/.gitignore @@ -0,0 +1 @@ +libmoss.a diff --git a/sdks/go/bindings/lib/linux-arm64/go.mod b/sdks/go/bindings/lib/linux-arm64/go.mod new file mode 100644 index 00000000..555aed27 --- /dev/null +++ b/sdks/go/bindings/lib/linux-arm64/go.mod @@ -0,0 +1,3 @@ +module github.com/usemoss/moss/sdks/go/bindings/lib/linux-arm64 + +go 1.22.2 diff --git a/sdks/go/bindings/lib/linux-arm64/prebuilt.go b/sdks/go/bindings/lib/linux-arm64/prebuilt.go new file mode 100644 index 00000000..3014a4bc --- /dev/null +++ b/sdks/go/bindings/lib/linux-arm64/prebuilt.go @@ -0,0 +1,9 @@ +//go:build linux && arm64 + +package mosscore_platform + +/* +#cgo CFLAGS: -I${SRCDIR}/../../include +#cgo LDFLAGS: -L${SRCDIR} -lmoss -lstdc++ -ldl -lm -lpthread +*/ +import "C" diff --git a/sdks/go/bindings/lib/windows-amd64/.gitignore b/sdks/go/bindings/lib/windows-amd64/.gitignore new file mode 100644 index 00000000..d56cec6a --- /dev/null +++ b/sdks/go/bindings/lib/windows-amd64/.gitignore @@ -0,0 +1,3 @@ +moss.lib +moss.dll +moss.dll.lib diff --git a/sdks/go/bindings/lib/windows-amd64/go.mod b/sdks/go/bindings/lib/windows-amd64/go.mod new file mode 100644 index 00000000..6f608963 --- /dev/null +++ b/sdks/go/bindings/lib/windows-amd64/go.mod @@ -0,0 +1,3 @@ +module github.com/usemoss/moss/sdks/go/bindings/lib/windows-amd64 + +go 1.22.2 diff --git a/sdks/go/bindings/lib/windows-amd64/prebuilt.go b/sdks/go/bindings/lib/windows-amd64/prebuilt.go new file mode 100644 index 00000000..76690cb9 --- /dev/null +++ b/sdks/go/bindings/lib/windows-amd64/prebuilt.go @@ -0,0 +1,9 @@ +//go:build windows && amd64 + +package mosscore_platform + +/* +#cgo CFLAGS: -I${SRCDIR}/../../include +#cgo LDFLAGS: -L${SRCDIR} moss.lib +*/ +import "C" diff --git a/sdks/go/bindings/libmoss.go b/sdks/go/bindings/libmoss.go index 4adbf8ed..474e8f11 100644 --- a/sdks/go/bindings/libmoss.go +++ b/sdks/go/bindings/libmoss.go @@ -1,11 +1,9 @@ -//go:build libmoss +//go:build cgo package mosscore /* -#cgo linux LDFLAGS: -lmoss -ldl -lm -lpthread -#cgo darwin LDFLAGS: -lmoss -lc++ -#cgo windows LDFLAGS: -lmoss +#cgo CFLAGS: -I${SRCDIR}/include #include #include */ diff --git a/sdks/go/bindings/prebuilt_darwin_arm64.go b/sdks/go/bindings/prebuilt_darwin_arm64.go new file mode 100644 index 00000000..c6c8b728 --- /dev/null +++ b/sdks/go/bindings/prebuilt_darwin_arm64.go @@ -0,0 +1,5 @@ +//go:build cgo && darwin && arm64 + +package mosscore + +import _ "github.com/usemoss/moss/sdks/go/bindings/lib/darwin-arm64" diff --git a/sdks/go/bindings/prebuilt_linux_amd64.go b/sdks/go/bindings/prebuilt_linux_amd64.go new file mode 100644 index 00000000..3148bead --- /dev/null +++ b/sdks/go/bindings/prebuilt_linux_amd64.go @@ -0,0 +1,5 @@ +//go:build cgo && linux && amd64 + +package mosscore + +import _ "github.com/usemoss/moss/sdks/go/bindings/lib/linux-amd64" diff --git a/sdks/go/bindings/prebuilt_linux_arm64.go b/sdks/go/bindings/prebuilt_linux_arm64.go new file mode 100644 index 00000000..6a78efed --- /dev/null +++ b/sdks/go/bindings/prebuilt_linux_arm64.go @@ -0,0 +1,5 @@ +//go:build cgo && linux && arm64 + +package mosscore + +import _ "github.com/usemoss/moss/sdks/go/bindings/lib/linux-arm64" diff --git a/sdks/go/bindings/prebuilt_windows_amd64.go b/sdks/go/bindings/prebuilt_windows_amd64.go new file mode 100644 index 00000000..f60bc7c4 --- /dev/null +++ b/sdks/go/bindings/prebuilt_windows_amd64.go @@ -0,0 +1,5 @@ +//go:build cgo && windows && amd64 + +package mosscore + +import _ "github.com/usemoss/moss/sdks/go/bindings/lib/windows-amd64" diff --git a/sdks/go/bindings/stub.go b/sdks/go/bindings/stub.go index dc32277e..761a1997 100644 --- a/sdks/go/bindings/stub.go +++ b/sdks/go/bindings/stub.go @@ -1,4 +1,4 @@ -//go:build !libmoss +//go:build !cgo package mosscore diff --git a/sdks/go/bindings/version.go b/sdks/go/bindings/version.go new file mode 100644 index 00000000..6fd7584e --- /dev/null +++ b/sdks/go/bindings/version.go @@ -0,0 +1,7 @@ +package mosscore + +// NativeLibVersion is the Moss C SDK version bundled with published Go bindings. +const NativeLibVersion = "0.9.0" + +// NativeLibReleaseTag is the GitHub release tag used to fetch native artifacts. +const NativeLibReleaseTag = "c-sdk-v0.9.0" diff --git a/sdks/go/go.work b/sdks/go/go.work new file mode 100644 index 00000000..63f931b9 --- /dev/null +++ b/sdks/go/go.work @@ -0,0 +1,10 @@ +go 1.22.2 + +use ( + ./bindings + ./bindings/lib/darwin-arm64 + ./bindings/lib/linux-amd64 + ./bindings/lib/linux-arm64 + ./bindings/lib/windows-amd64 + ./sdk +) diff --git a/sdks/go/scripts/bump-module-versions.sh b/sdks/go/scripts/bump-module-versions.sh new file mode 100755 index 00000000..f86b8c78 --- /dev/null +++ b/sdks/go/scripts/bump-module-versions.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Updates bindings and sdk go.mod files to pin platform lib module versions. +# +# Usage: +# ./sdks/go/scripts/bump-module-versions.sh v0.9.0 + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&2 + echo "Example: $0 v0.9.0" >&2 + exit 1 +fi + +VERSION="$1" +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BINDINGS_DIR="${ROOT_DIR}/bindings" +SDK_DIR="${ROOT_DIR}/sdk" + +PLATFORMS=( + linux-amd64 + linux-arm64 + darwin-arm64 + windows-amd64 +) + +( + cd "${BINDINGS_DIR}" + for platform in "${PLATFORMS[@]}"; do + go mod edit -require="github.com/usemoss/moss/sdks/go/bindings/lib/${platform}@${VERSION}" + done + go mod edit -dropreplace=all || true + go mod tidy +) + +( + cd "${SDK_DIR}" + go mod edit -require="github.com/usemoss/moss/sdks/go/bindings@${VERSION}" + go mod edit -dropreplace=all || true + go mod tidy +) + +echo "Pinned Go module versions to ${VERSION}" diff --git a/sdks/go/scripts/fetch-static-libs.sh b/sdks/go/scripts/fetch-static-libs.sh new file mode 100755 index 00000000..627361db --- /dev/null +++ b/sdks/go/scripts/fetch-static-libs.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Downloads static libmoss archives from GitHub Releases and installs them into +# sdks/go/bindings/lib// for local development or release publishing. +# +# Usage: +# ./sdks/go/scripts/fetch-static-libs.sh [c-sdk-tag] +# +# Example: +# ./sdks/go/scripts/fetch-static-libs.sh c-sdk-v0.9.0 + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BINDINGS_DIR="${ROOT_DIR}/bindings" +RELEASE_TAG="${1:-c-sdk-v0.9.0}" +LIBMOSS_VERSION="${RELEASE_TAG#c-sdk-}" +REPO="${MOSS_RELEASE_REPO:-usemoss/moss}" +BASE_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}" + +declare -A PLATFORM_TRIPLES=( + ["linux-amd64"]="x86_64-unknown-linux-gnu" + ["linux-arm64"]="aarch64-unknown-linux-gnu" + ["darwin-arm64"]="aarch64-apple-darwin" + ["windows-amd64"]="x86_64-pc-windows-msvc" +) + +fetch_platform() { + local platform="$1" + local triple="$2" + local archive="libmoss-${LIBMOSS_VERSION}-${triple}.tar.gz" + local url="${BASE_URL}/${archive}" + local tmpdir + tmpdir="$(mktemp -d)" + + echo "Fetching ${platform} from ${url}" + curl -fsSL "${url}" -o "${tmpdir}/${archive}" + tar -xzf "${tmpdir}/${archive}" -C "${tmpdir}" + + local extracted="${tmpdir}/libmoss-${LIBMOSS_VERSION}-${triple}" + local dest="${BINDINGS_DIR}/lib/${platform}" + mkdir -p "${dest}" + + cp "${extracted}/include/libmoss.h" "${BINDINGS_DIR}/include/libmoss.h" + + case "${platform}" in + windows-amd64) + cp "${extracted}/lib/moss.lib" "${dest}/moss.lib" + ;; + *) + cp "${extracted}/lib/libmoss.a" "${dest}/libmoss.a" + ;; + esac + + rm -rf "${tmpdir}" + + echo "Installed ${platform} native library into ${dest}" +} + +main() { + if ! command -v curl >/dev/null 2>&1; then + echo "curl is required" >&2 + exit 1 + fi + + mkdir -p "${BINDINGS_DIR}/include" + + for platform in "${!PLATFORM_TRIPLES[@]}"; do + fetch_platform "${platform}" "${PLATFORM_TRIPLES[$platform]}" + done + + echo "Done. Header: ${BINDINGS_DIR}/include/libmoss.h" +} + +main "$@" diff --git a/sdks/go/scripts/link_dev_lib.sh b/sdks/go/scripts/link_dev_lib.sh new file mode 100755 index 00000000..b802ad05 --- /dev/null +++ b/sdks/go/scripts/link_dev_lib.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Installs the native library for the current machine only (local dev helper). +# +# Usage: +# ./sdks/go/scripts/link_dev_lib.sh [c-sdk-tag] + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RELEASE_TAG="${1:-c-sdk-v0.9.0}" +LIBMOSS_VERSION="${RELEASE_TAG#c-sdk-}" +REPO="${MOSS_RELEASE_REPO:-usemoss/moss}" +BASE_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}" + +case "$(uname -s)/$(uname -m)" in + Linux/x86_64) + PLATFORM="linux-amd64" + TRIPLE="x86_64-unknown-linux-gnu" + LIB_NAME="libmoss.a" + SRC_LIB="lib/libmoss.a" + ;; + Linux/aarch64|Linux/arm64) + PLATFORM="linux-arm64" + TRIPLE="aarch64-unknown-linux-gnu" + LIB_NAME="libmoss.a" + SRC_LIB="lib/libmoss.a" + ;; + Darwin/arm64) + PLATFORM="darwin-arm64" + TRIPLE="aarch64-apple-darwin" + LIB_NAME="libmoss.a" + SRC_LIB="lib/libmoss.a" + ;; + MINGW*|MSYS*|CYGWIN*) + PLATFORM="windows-amd64" + TRIPLE="x86_64-pc-windows-msvc" + LIB_NAME="moss.lib" + SRC_LIB="lib/moss.lib" + ;; + *) + echo "Unsupported dev platform: $(uname -s)/$(uname -m)" >&2 + exit 1 + ;; +esac + +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +BINDINGS_DIR="${ROOT_DIR}/bindings" +DEST="${BINDINGS_DIR}/lib/${PLATFORM}" +ARCHIVE="libmoss-${LIBMOSS_VERSION}-${TRIPLE}.tar.gz" +TMPDIR="$(mktemp -d)" +trap 'rm -rf "${TMPDIR}"' EXIT + +mkdir -p "${DEST}" "${BINDINGS_DIR}/include" + +echo "Downloading ${ARCHIVE}" +curl -fsSL "${BASE_URL}/${ARCHIVE}" -o "${TMPDIR}/${ARCHIVE}" +tar -xzf "${TMPDIR}/${ARCHIVE}" -C "${TMPDIR}" + +EXTRACTED="${TMPDIR}/libmoss-${LIBMOSS_VERSION}-${TRIPLE}" +cp "${EXTRACTED}/include/libmoss.h" "${BINDINGS_DIR}/include/libmoss.h" +cp "${EXTRACTED}/${SRC_LIB}" "${DEST}/${LIB_NAME}" + +echo "Installed ${PLATFORM} into ${DEST}/${LIB_NAME}" diff --git a/sdks/go/scripts/publish-module-tags.sh b/sdks/go/scripts/publish-module-tags.sh new file mode 100644 index 00000000..8bfe3043 --- /dev/null +++ b/sdks/go/scripts/publish-module-tags.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Creates a release commit with native libraries and pushes module tags. +# The commit is tag-only and is not merged back into main. +# +# Usage: +# ./sdks/go/scripts/publish-module-tags.sh v0.9.0 [remote] + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 [git-remote]" >&2 + exit 1 +fi + +VERSION="$1" +REMOTE="${2:-origin}" +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REPO_ROOT="$(cd "${ROOT_DIR}/../.." && pwd)" + +TAGS=( + "sdks/go/sdk/${VERSION}" + "sdks/go/bindings/${VERSION}" + "sdks/go/bindings/lib/linux-amd64/${VERSION}" + "sdks/go/bindings/lib/linux-arm64/${VERSION}" + "sdks/go/bindings/lib/darwin-arm64/${VERSION}" + "sdks/go/bindings/lib/windows-amd64/${VERSION}" +) + +cd "${REPO_ROOT}" + +if [[ -z "$(git status --porcelain sdks/go)" ]]; then + echo "No changes under sdks/go to publish" >&2 + exit 1 +fi + +git add sdks/go/bindings/include/libmoss.h +git add sdks/go/bindings/lib/*/libmoss.a sdks/go/bindings/lib/*/moss.lib 2>/dev/null || true +git add sdks/go/bindings/go.mod sdks/go/bindings/go.sum +git add sdks/go/sdk/go.mod sdks/go/sdk/go.sum +git add sdks/go/bindings/version.go + +if git diff --cached --quiet; then + echo "Nothing staged for release commit" >&2 + exit 1 +fi + +git commit -m "chore(go): publish ${VERSION} native bindings" + +for tag in "${TAGS[@]}"; do + git tag -f "${tag}" +done + +git push "${REMOTE}" "${TAGS[@]}" + +echo "Published tags:" +printf ' %s\n' "${TAGS[@]}" diff --git a/sdks/go/sdk/README.md b/sdks/go/sdk/README.md index 05ad78f8..7220b50e 100644 --- a/sdks/go/sdk/README.md +++ b/sdks/go/sdk/README.md @@ -19,21 +19,21 @@ The Go SDK now has two layers: ## Current limitations -- the SDK requires the `libmoss` C SDK and the `libmoss` build tag for real runtime operations +- CGO and a C compiler are required for full SDK functionality (`CGO_ENABLED=1`) - cloud query fallback supports `TopK` and caller-provided embeddings; `Alpha` and `Filter` require a locally loaded index - `LoadIndexOptions.CachePath` is not exposed by the current `libmoss` C API yet ## Installation -From this repository, import the package at: - -```go -github.com/usemoss/moss/sdks/go/sdk +```bash +go get github.com/usemoss/moss/sdks/go/sdk ``` -Download the `libmoss` C SDK release and build with `-tags libmoss`. The -bindings setup is documented in -[`../bindings/README.md`](../bindings/README.md). +Published versions bundle static `libmoss` per platform. See +[`../bindings/README.md`](../bindings/README.md) for Windows toolchain notes. + +Monorepo development uses the workspace in [`../go.work`](../go.work) and +[`../scripts/link_dev_lib.sh`](../scripts/link_dev_lib.sh). ## Quick start @@ -139,14 +139,12 @@ Runnable examples live here: - [`../../../examples/go/basic/main.go`](../../../examples/go/basic/main.go) - [`../../../examples/go/custom-embeddings/main.go`](../../../examples/go/custom-embeddings/main.go) -Run them with native bindings enabled: +Run them from the monorepo: ```bash +../../sdks/go/scripts/link_dev_lib.sh c-sdk-v0.9.0 cd ../../../examples/go -export CGO_CFLAGS="-I/include" -export CGO_LDFLAGS="-L/lib" -export LD_LIBRARY_PATH="/lib" -go run -tags libmoss ./basic +go run ./basic ``` ## Integration tests @@ -163,8 +161,7 @@ Then run: ```bash cd sdks/go/sdk go test ./... -CGO_CFLAGS="-I/include" \ -CGO_LDFLAGS="-L/lib" \ -LD_LIBRARY_PATH="/lib" \ -go test -tags libmoss ./... + +# Live integration (requires credentials + native lib from link_dev_lib.sh): +CGO_ENABLED=1 go test ./... ``` From a69b1973c3511fdee767266d8fd14a6815563c72 Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Tue, 28 Jul 2026 21:15:22 -0700 Subject: [PATCH 02/15] fix(go): split publish into per-platform tags to avoid push timeouts Large monolithic native-lib commits exceed GitHub HTTP push limits. Publish each platform lib module tag separately before bindings/sdk tags. Co-authored-by: Cursor --- sdks/go/scripts/bump-module-versions.sh | 7 ++- sdks/go/scripts/publish-module-tags.sh | 6 +-- sdks/go/scripts/publish-platform-lib-tag.sh | 54 +++++++++++++++++++++ sdks/go/scripts/publish-sdk-module-tags.sh | 37 ++++++++++++++ 4 files changed, 97 insertions(+), 7 deletions(-) mode change 100644 => 100755 sdks/go/scripts/publish-module-tags.sh create mode 100755 sdks/go/scripts/publish-platform-lib-tag.sh create mode 100755 sdks/go/scripts/publish-sdk-module-tags.sh diff --git a/sdks/go/scripts/bump-module-versions.sh b/sdks/go/scripts/bump-module-versions.sh index f86b8c78..254c1b67 100755 --- a/sdks/go/scripts/bump-module-versions.sh +++ b/sdks/go/scripts/bump-module-versions.sh @@ -28,16 +28,15 @@ PLATFORMS=( cd "${BINDINGS_DIR}" for platform in "${PLATFORMS[@]}"; do go mod edit -require="github.com/usemoss/moss/sdks/go/bindings/lib/${platform}@${VERSION}" + go mod edit -dropreplace="github.com/usemoss/moss/sdks/go/bindings/lib/${platform}" 2>/dev/null || true done - go mod edit -dropreplace=all || true - go mod tidy ) ( cd "${SDK_DIR}" go mod edit -require="github.com/usemoss/moss/sdks/go/bindings@${VERSION}" - go mod edit -dropreplace=all || true - go mod tidy + go mod edit -dropreplace=github.com/usemoss/moss/sdks/go/bindings 2>/dev/null || true ) echo "Pinned Go module versions to ${VERSION}" +echo "Publish platform lib tags first, then run publish-sdk-module-tags.sh" diff --git a/sdks/go/scripts/publish-module-tags.sh b/sdks/go/scripts/publish-module-tags.sh old mode 100644 new mode 100755 index 8bfe3043..483d470c --- a/sdks/go/scripts/publish-module-tags.sh +++ b/sdks/go/scripts/publish-module-tags.sh @@ -34,9 +34,9 @@ if [[ -z "$(git status --porcelain sdks/go)" ]]; then fi git add sdks/go/bindings/include/libmoss.h -git add sdks/go/bindings/lib/*/libmoss.a sdks/go/bindings/lib/*/moss.lib 2>/dev/null || true -git add sdks/go/bindings/go.mod sdks/go/bindings/go.sum -git add sdks/go/sdk/go.mod sdks/go/sdk/go.sum +git add -f sdks/go/bindings/lib/*/libmoss.a sdks/go/bindings/lib/*/moss.lib 2>/dev/null || true +git add sdks/go/bindings/go.mod +git add sdks/go/sdk/go.mod git add sdks/go/bindings/version.go if git diff --cached --quiet; then diff --git a/sdks/go/scripts/publish-platform-lib-tag.sh b/sdks/go/scripts/publish-platform-lib-tag.sh new file mode 100755 index 00000000..90651e28 --- /dev/null +++ b/sdks/go/scripts/publish-platform-lib-tag.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Publishes one platform lib module tag at a time to avoid huge git pushes. +# +# Usage: +# ./sdks/go/scripts/publish-platform-lib-tag.sh linux-amd64 v0.1.0 + +if [[ $# -lt 2 ]]; then + echo "Usage: $0 [remote]" >&2 + echo "Platforms: linux-amd64 linux-arm64 darwin-arm64 windows-amd64" >&2 + exit 1 +fi + +PLATFORM="$1" +VERSION="$2" +REMOTE="${3:-origin}" +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REPO_ROOT="$(cd "${ROOT_DIR}/../.." && pwd)" +TAG="sdks/go/bindings/lib/${PLATFORM}/${VERSION}" +LIB_DIR="${REPO_ROOT}/sdks/go/bindings/lib/${PLATFORM}" + +cd "${REPO_ROOT}" + +if [[ ! -d "${LIB_DIR}" ]]; then + echo "Missing platform directory: ${LIB_DIR}" >&2 + exit 1 +fi + +if [[ "${PLATFORM}" == "windows-amd64" ]]; then + if [[ ! -f "${LIB_DIR}/moss.lib" ]]; then + echo "Missing ${LIB_DIR}/moss.lib — run fetch-static-libs.sh first" >&2 + exit 1 + fi +else + if [[ ! -f "${LIB_DIR}/libmoss.a" ]]; then + echo "Missing ${LIB_DIR}/libmoss.a — run fetch-static-libs.sh first" >&2 + exit 1 + fi +fi + +git add "${LIB_DIR}/go.mod" "${LIB_DIR}/prebuilt.go" "${LIB_DIR}/.gitignore" +git add -f "${LIB_DIR}/libmoss.a" "${LIB_DIR}/moss.lib" 2>/dev/null || true + +if git diff --cached --quiet; then + echo "Nothing to publish for ${PLATFORM}" >&2 + exit 1 +fi + +git commit -m "chore(go): publish ${PLATFORM} ${VERSION}" +git tag -f "${TAG}" +git push "${REMOTE}" "${TAG}" + +echo "Published ${TAG}" diff --git a/sdks/go/scripts/publish-sdk-module-tags.sh b/sdks/go/scripts/publish-sdk-module-tags.sh new file mode 100755 index 00000000..c5406f50 --- /dev/null +++ b/sdks/go/scripts/publish-sdk-module-tags.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Publishes bindings + sdk module tags (no native binaries in this commit). +# +# Usage: +# ./sdks/go/scripts/publish-sdk-module-tags.sh v0.1.0 + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 [remote]" >&2 + exit 1 +fi + +VERSION="$1" +REMOTE="${2:-origin}" +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REPO_ROOT="$(cd "${ROOT_DIR}/../.." && pwd)" + +cd "${REPO_ROOT}" + +"${ROOT_DIR}/scripts/bump-module-versions.sh" "${VERSION}" + +git add sdks/go/bindings/go.mod sdks/go/sdk/go.mod sdks/go/bindings/version.go +git add sdks/go/bindings/include/libmoss.h + +if git diff --cached --quiet; then + echo "Nothing to publish for bindings/sdk ${VERSION}" >&2 + exit 1 +fi + +git commit -m "chore(go): publish bindings and sdk ${VERSION}" + +for tag in "sdks/go/bindings/${VERSION}" "sdks/go/sdk/${VERSION}"; do + git tag -f "${tag}" + git push "${REMOTE}" "${tag}" + echo "Published ${tag}" +done From c5799058350ad4772521ac9c5c553616df052f8c Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Tue, 28 Jul 2026 23:25:17 -0700 Subject: [PATCH 03/15] feat(go): download native libs at build time via tools/install Replace per-platform Go submodules (which hit GitHub's 100MB file limit) with a source-only distribution model. Consumers run tools/install or go generate to fetch libmoss.a from C SDK GitHub Releases with checksum verification. Co-authored-by: Cursor --- .github/workflows/go-sdk-test.yml | 3 +- .github/workflows/publish-go-sdk.yml | 20 +- examples/go/README.md | 9 +- examples/go/go.mod | 16 +- sdks/go/README.md | 12 +- sdks/go/bindings/README.md | 42 ++- sdks/go/bindings/generate.go | 3 + sdks/go/bindings/go.mod | 15 - sdks/go/bindings/lib/darwin-arm64/go.mod | 3 - sdks/go/bindings/lib/darwin-arm64/prebuilt.go | 9 - sdks/go/bindings/lib/linux-amd64/go.mod | 3 - sdks/go/bindings/lib/linux-amd64/prebuilt.go | 9 - sdks/go/bindings/lib/linux-arm64/go.mod | 3 - sdks/go/bindings/lib/linux-arm64/prebuilt.go | 9 - sdks/go/bindings/lib/windows-amd64/go.mod | 3 - .../go/bindings/lib/windows-amd64/prebuilt.go | 9 - sdks/go/bindings/prebuilt_darwin_arm64.go | 6 +- sdks/go/bindings/prebuilt_linux_amd64.go | 6 +- sdks/go/bindings/prebuilt_linux_arm64.go | 6 +- sdks/go/bindings/prebuilt_windows_amd64.go | 6 +- sdks/go/go.work | 5 +- sdks/go/scripts/bump-module-versions.sh | 21 +- sdks/go/scripts/fetch-static-libs.sh | 70 +--- sdks/go/scripts/link_dev_lib.sh | 57 +-- sdks/go/scripts/publish-module-tags.sh | 56 --- sdks/go/scripts/publish-platform-lib-tag.sh | 54 --- sdks/go/scripts/publish-sdk-module-tags.sh | 10 +- sdks/go/sdk/README.md | 3 +- sdks/go/tools/install/go.mod | 3 + sdks/go/tools/install/main.go | 336 ++++++++++++++++++ 30 files changed, 428 insertions(+), 379 deletions(-) create mode 100644 sdks/go/bindings/generate.go delete mode 100644 sdks/go/bindings/lib/darwin-arm64/go.mod delete mode 100644 sdks/go/bindings/lib/darwin-arm64/prebuilt.go delete mode 100644 sdks/go/bindings/lib/linux-amd64/go.mod delete mode 100644 sdks/go/bindings/lib/linux-amd64/prebuilt.go delete mode 100644 sdks/go/bindings/lib/linux-arm64/go.mod delete mode 100644 sdks/go/bindings/lib/linux-arm64/prebuilt.go delete mode 100644 sdks/go/bindings/lib/windows-amd64/go.mod delete mode 100644 sdks/go/bindings/lib/windows-amd64/prebuilt.go delete mode 100755 sdks/go/scripts/publish-module-tags.sh delete mode 100755 sdks/go/scripts/publish-platform-lib-tag.sh create mode 100644 sdks/go/tools/install/go.mod create mode 100644 sdks/go/tools/install/main.go diff --git a/.github/workflows/go-sdk-test.yml b/.github/workflows/go-sdk-test.yml index ff118af4..a2ec8a8d 100644 --- a/.github/workflows/go-sdk-test.yml +++ b/.github/workflows/go-sdk-test.yml @@ -40,7 +40,8 @@ jobs: go-version: '1.22.2' - name: Install linux-amd64 native library - run: ./sdks/go/scripts/link_dev_lib.sh c-sdk-v0.9.0 + working-directory: sdks/go + run: go run ./tools/install --release c-sdk-v0.9.0 - name: Verify bindings compile with CGO working-directory: sdks/go/bindings diff --git a/.github/workflows/publish-go-sdk.yml b/.github/workflows/publish-go-sdk.yml index be57d866..79f0cabe 100644 --- a/.github/workflows/publish-go-sdk.yml +++ b/.github/workflows/publish-go-sdk.yml @@ -13,7 +13,7 @@ on: go_version: description: 'Go module version to publish' required: true - default: 'v0.9.0' + default: 'v0.1.0' concurrency: group: go-sdk-release-${{ github.event.inputs.go_version }} @@ -29,18 +29,6 @@ jobs: with: go-version: '1.22.2' - - name: Fetch static native libraries - run: ./sdks/go/scripts/fetch-static-libs.sh "${{ github.event.inputs.c_sdk_version }}" - - - name: Verify linux-amd64 build - working-directory: sdks/go/sdk - env: - CGO_ENABLED: '1' - run: go build -v . - - - name: Pin module versions - run: ./sdks/go/scripts/bump-module-versions.sh "${{ github.event.inputs.go_version }}" - - name: Update bindings native version metadata shell: bash run: | @@ -49,12 +37,12 @@ jobs: cat > sdks/go/bindings/version.go < ../../sdks/go/sdk replace github.com/usemoss/moss/sdks/go/bindings => ../../sdks/go/bindings - -replace github.com/usemoss/moss/sdks/go/bindings/lib/darwin-arm64 => ../../sdks/go/bindings/lib/darwin-arm64 - -replace github.com/usemoss/moss/sdks/go/bindings/lib/linux-amd64 => ../../sdks/go/bindings/lib/linux-amd64 - -replace github.com/usemoss/moss/sdks/go/bindings/lib/linux-arm64 => ../../sdks/go/bindings/lib/linux-arm64 - -replace github.com/usemoss/moss/sdks/go/bindings/lib/windows-amd64 => ../../sdks/go/bindings/lib/windows-amd64 diff --git a/sdks/go/README.md b/sdks/go/README.md index 605a27a7..5bfa9524 100644 --- a/sdks/go/README.md +++ b/sdks/go/README.md @@ -9,10 +9,18 @@ The Go SDK follows the same two-layer design as the other Moss SDKs: ```bash go get github.com/usemoss/moss/sdks/go/sdk +go run github.com/usemoss/moss/sdks/go/tools/install@latest ``` -Published module versions bundle static `libmoss` per platform. You need CGO and a -C compiler, but not a manual C SDK download. +The install tool downloads the static `libmoss` library for your platform from +[Moss C SDK GitHub Releases](https://github.com/usemoss/moss/releases). You need +CGO and a C compiler, but not a manual C SDK download or `LD_LIBRARY_PATH`. + +Alternatively, from a checkout of the bindings package: + +```bash +go generate github.com/usemoss/moss/sdks/go/bindings +``` ## Local development diff --git a/sdks/go/bindings/README.md b/sdks/go/bindings/README.md index 2fac7156..e1c99e32 100644 --- a/sdks/go/bindings/README.md +++ b/sdks/go/bindings/README.md @@ -13,10 +13,12 @@ It mirrors the role of the other language bindings packages in this repository: ```bash go get github.com/usemoss/moss/sdks/go/sdk +go run github.com/usemoss/moss/sdks/go/tools/install@latest ``` -Published releases bundle prebuilt static libraries per platform. No manual C SDK -download, `LD_LIBRARY_PATH`, or `-tags libmoss` is required. +The install tool downloads a prebuilt static library for your platform from +[C SDK GitHub Releases](https://github.com/usemoss/moss/releases). No manual C +SDK download, `LD_LIBRARY_PATH`, or `-tags libmoss` is required. Requirements: @@ -29,17 +31,18 @@ Requirements: bindings/ include/libmoss.h # committed C header libmoss.go # CGO wrapper (requires CGO) - prebuilt__.go # selects the platform linker module + prebuilt__.go # per-platform CGO linker flags + generate.go # //go:generate install hook lib/ - linux-amd64/ # separate Go module with libmoss.a (release tags) + linux-amd64/ # libmoss.a (gitignored, downloaded at build time) linux-arm64/ darwin-arm64/ windows-amd64/ ``` -On `main`, native `.a` / `.lib` files are gitignored. Release CI downloads them -from [C SDK GitHub Releases](https://github.com/usemoss/moss/releases) and -publishes versioned Go module tags. +Native `.a` / `.lib` files are gitignored. The +[`tools/install`](../tools/install) command fetches them from GitHub Releases and +verifies SHA256 checksums. ## Local development @@ -49,33 +52,34 @@ Install the native library for your current machine: ./sdks/go/scripts/link_dev_lib.sh c-sdk-v0.9.0 ``` -Or fetch all supported platforms (release prep): +Or fetch all supported platforms: ```bash ./sdks/go/scripts/fetch-static-libs.sh c-sdk-v0.9.0 ``` -Then build with CGO enabled: +Or use `go generate` from this directory: ```bash cd sdks/go/bindings +go generate . +``` + +Then build with CGO enabled: + +```bash CGO_ENABLED=1 go build . ``` ## Publishing -Maintainers run the **Publish Go SDK** GitHub Actions workflow. It: - -1. Downloads static libraries from a C SDK release -2. Pins module versions in `go.mod` files -3. Creates a release commit and pushes module tags (without updating `main`) +Maintainers run the **Publish Go SDK** GitHub Actions workflow. It creates +source-only module tags (no binaries in git): -Tags follow the monorepo convention: +- `sdks/go/sdk/v0.1.0` +- `sdks/go/bindings/v0.1.0` -- `sdks/go/sdk/v0.9.0` -- `sdks/go/bindings/v0.9.0` -- `sdks/go/bindings/lib/linux-amd64/v0.9.0` -- … +Consumers download native libraries at build time via `tools/install`. ## Build without CGO diff --git a/sdks/go/bindings/generate.go b/sdks/go/bindings/generate.go new file mode 100644 index 00000000..da5903c1 --- /dev/null +++ b/sdks/go/bindings/generate.go @@ -0,0 +1,3 @@ +package mosscore + +//go:generate go run ../tools/install diff --git a/sdks/go/bindings/go.mod b/sdks/go/bindings/go.mod index 18cd4eb4..d34cdccc 100644 --- a/sdks/go/bindings/go.mod +++ b/sdks/go/bindings/go.mod @@ -1,18 +1,3 @@ module github.com/usemoss/moss/sdks/go/bindings go 1.22.2 - -require ( - github.com/usemoss/moss/sdks/go/bindings/lib/darwin-arm64 v0.0.0 - github.com/usemoss/moss/sdks/go/bindings/lib/linux-amd64 v0.0.0 - github.com/usemoss/moss/sdks/go/bindings/lib/linux-arm64 v0.0.0 - github.com/usemoss/moss/sdks/go/bindings/lib/windows-amd64 v0.0.0 -) - -replace github.com/usemoss/moss/sdks/go/bindings/lib/darwin-arm64 => ./lib/darwin-arm64 - -replace github.com/usemoss/moss/sdks/go/bindings/lib/linux-amd64 => ./lib/linux-amd64 - -replace github.com/usemoss/moss/sdks/go/bindings/lib/linux-arm64 => ./lib/linux-arm64 - -replace github.com/usemoss/moss/sdks/go/bindings/lib/windows-amd64 => ./lib/windows-amd64 diff --git a/sdks/go/bindings/lib/darwin-arm64/go.mod b/sdks/go/bindings/lib/darwin-arm64/go.mod deleted file mode 100644 index 614dc631..00000000 --- a/sdks/go/bindings/lib/darwin-arm64/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/usemoss/moss/sdks/go/bindings/lib/darwin-arm64 - -go 1.22.2 diff --git a/sdks/go/bindings/lib/darwin-arm64/prebuilt.go b/sdks/go/bindings/lib/darwin-arm64/prebuilt.go deleted file mode 100644 index 847180a7..00000000 --- a/sdks/go/bindings/lib/darwin-arm64/prebuilt.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build darwin && arm64 - -package mosscore_platform - -/* -#cgo CFLAGS: -I${SRCDIR}/../../include -#cgo LDFLAGS: -L${SRCDIR} -lmoss -lc++ -framework Security -framework SystemConfiguration -*/ -import "C" diff --git a/sdks/go/bindings/lib/linux-amd64/go.mod b/sdks/go/bindings/lib/linux-amd64/go.mod deleted file mode 100644 index 9b261289..00000000 --- a/sdks/go/bindings/lib/linux-amd64/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/usemoss/moss/sdks/go/bindings/lib/linux-amd64 - -go 1.22.2 diff --git a/sdks/go/bindings/lib/linux-amd64/prebuilt.go b/sdks/go/bindings/lib/linux-amd64/prebuilt.go deleted file mode 100644 index bdb17bc3..00000000 --- a/sdks/go/bindings/lib/linux-amd64/prebuilt.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build linux && amd64 - -package mosscore_platform - -/* -#cgo CFLAGS: -I${SRCDIR}/../../include -#cgo LDFLAGS: -L${SRCDIR} -lmoss -lstdc++ -ldl -lm -lpthread -*/ -import "C" diff --git a/sdks/go/bindings/lib/linux-arm64/go.mod b/sdks/go/bindings/lib/linux-arm64/go.mod deleted file mode 100644 index 555aed27..00000000 --- a/sdks/go/bindings/lib/linux-arm64/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/usemoss/moss/sdks/go/bindings/lib/linux-arm64 - -go 1.22.2 diff --git a/sdks/go/bindings/lib/linux-arm64/prebuilt.go b/sdks/go/bindings/lib/linux-arm64/prebuilt.go deleted file mode 100644 index 3014a4bc..00000000 --- a/sdks/go/bindings/lib/linux-arm64/prebuilt.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build linux && arm64 - -package mosscore_platform - -/* -#cgo CFLAGS: -I${SRCDIR}/../../include -#cgo LDFLAGS: -L${SRCDIR} -lmoss -lstdc++ -ldl -lm -lpthread -*/ -import "C" diff --git a/sdks/go/bindings/lib/windows-amd64/go.mod b/sdks/go/bindings/lib/windows-amd64/go.mod deleted file mode 100644 index 6f608963..00000000 --- a/sdks/go/bindings/lib/windows-amd64/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/usemoss/moss/sdks/go/bindings/lib/windows-amd64 - -go 1.22.2 diff --git a/sdks/go/bindings/lib/windows-amd64/prebuilt.go b/sdks/go/bindings/lib/windows-amd64/prebuilt.go deleted file mode 100644 index 76690cb9..00000000 --- a/sdks/go/bindings/lib/windows-amd64/prebuilt.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build windows && amd64 - -package mosscore_platform - -/* -#cgo CFLAGS: -I${SRCDIR}/../../include -#cgo LDFLAGS: -L${SRCDIR} moss.lib -*/ -import "C" diff --git a/sdks/go/bindings/prebuilt_darwin_arm64.go b/sdks/go/bindings/prebuilt_darwin_arm64.go index c6c8b728..ffe18f89 100644 --- a/sdks/go/bindings/prebuilt_darwin_arm64.go +++ b/sdks/go/bindings/prebuilt_darwin_arm64.go @@ -2,4 +2,8 @@ package mosscore -import _ "github.com/usemoss/moss/sdks/go/bindings/lib/darwin-arm64" +/* +#cgo CFLAGS: -I${SRCDIR}/include +#cgo LDFLAGS: -L${SRCDIR}/lib/darwin-arm64 -lmoss -lc++ -framework Security -framework SystemConfiguration +*/ +import "C" diff --git a/sdks/go/bindings/prebuilt_linux_amd64.go b/sdks/go/bindings/prebuilt_linux_amd64.go index 3148bead..6f31905c 100644 --- a/sdks/go/bindings/prebuilt_linux_amd64.go +++ b/sdks/go/bindings/prebuilt_linux_amd64.go @@ -2,4 +2,8 @@ package mosscore -import _ "github.com/usemoss/moss/sdks/go/bindings/lib/linux-amd64" +/* +#cgo CFLAGS: -I${SRCDIR}/include +#cgo LDFLAGS: -L${SRCDIR}/lib/linux-amd64 -lmoss -lstdc++ -ldl -lm -lpthread +*/ +import "C" diff --git a/sdks/go/bindings/prebuilt_linux_arm64.go b/sdks/go/bindings/prebuilt_linux_arm64.go index 6a78efed..eea75d94 100644 --- a/sdks/go/bindings/prebuilt_linux_arm64.go +++ b/sdks/go/bindings/prebuilt_linux_arm64.go @@ -2,4 +2,8 @@ package mosscore -import _ "github.com/usemoss/moss/sdks/go/bindings/lib/linux-arm64" +/* +#cgo CFLAGS: -I${SRCDIR}/include +#cgo LDFLAGS: -L${SRCDIR}/lib/linux-arm64 -lmoss -lstdc++ -ldl -lm -lpthread +*/ +import "C" diff --git a/sdks/go/bindings/prebuilt_windows_amd64.go b/sdks/go/bindings/prebuilt_windows_amd64.go index f60bc7c4..17ebb18a 100644 --- a/sdks/go/bindings/prebuilt_windows_amd64.go +++ b/sdks/go/bindings/prebuilt_windows_amd64.go @@ -2,4 +2,8 @@ package mosscore -import _ "github.com/usemoss/moss/sdks/go/bindings/lib/windows-amd64" +/* +#cgo CFLAGS: -I${SRCDIR}/include +#cgo LDFLAGS: -L${SRCDIR}/lib/windows-amd64 moss.lib +*/ +import "C" diff --git a/sdks/go/go.work b/sdks/go/go.work index 63f931b9..fd6edb4a 100644 --- a/sdks/go/go.work +++ b/sdks/go/go.work @@ -2,9 +2,6 @@ go 1.22.2 use ( ./bindings - ./bindings/lib/darwin-arm64 - ./bindings/lib/linux-amd64 - ./bindings/lib/linux-arm64 - ./bindings/lib/windows-amd64 ./sdk + ./tools/install ) diff --git a/sdks/go/scripts/bump-module-versions.sh b/sdks/go/scripts/bump-module-versions.sh index 254c1b67..88fb29ef 100755 --- a/sdks/go/scripts/bump-module-versions.sh +++ b/sdks/go/scripts/bump-module-versions.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -# Updates bindings and sdk go.mod files to pin platform lib module versions. +# Pins the public sdk module to a published bindings version. # # Usage: # ./sdks/go/scripts/bump-module-versions.sh v0.9.0 @@ -14,29 +14,12 @@ fi VERSION="$1" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -BINDINGS_DIR="${ROOT_DIR}/bindings" SDK_DIR="${ROOT_DIR}/sdk" -PLATFORMS=( - linux-amd64 - linux-arm64 - darwin-arm64 - windows-amd64 -) - -( - cd "${BINDINGS_DIR}" - for platform in "${PLATFORMS[@]}"; do - go mod edit -require="github.com/usemoss/moss/sdks/go/bindings/lib/${platform}@${VERSION}" - go mod edit -dropreplace="github.com/usemoss/moss/sdks/go/bindings/lib/${platform}" 2>/dev/null || true - done -) - ( cd "${SDK_DIR}" go mod edit -require="github.com/usemoss/moss/sdks/go/bindings@${VERSION}" go mod edit -dropreplace=github.com/usemoss/moss/sdks/go/bindings 2>/dev/null || true ) -echo "Pinned Go module versions to ${VERSION}" -echo "Publish platform lib tags first, then run publish-sdk-module-tags.sh" +echo "Pinned sdk module to bindings ${VERSION}" diff --git a/sdks/go/scripts/fetch-static-libs.sh b/sdks/go/scripts/fetch-static-libs.sh index 627361db..d691d9ed 100755 --- a/sdks/go/scripts/fetch-static-libs.sh +++ b/sdks/go/scripts/fetch-static-libs.sh @@ -1,74 +1,14 @@ #!/usr/bin/env bash set -euo pipefail -# Downloads static libmoss archives from GitHub Releases and installs them into -# sdks/go/bindings/lib// for local development or release publishing. +# Downloads static libmoss archives for all supported platforms. # # Usage: # ./sdks/go/scripts/fetch-static-libs.sh [c-sdk-tag] -# -# Example: -# ./sdks/go/scripts/fetch-static-libs.sh c-sdk-v0.9.0 -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -BINDINGS_DIR="${ROOT_DIR}/bindings" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" RELEASE_TAG="${1:-c-sdk-v0.9.0}" -LIBMOSS_VERSION="${RELEASE_TAG#c-sdk-}" -REPO="${MOSS_RELEASE_REPO:-usemoss/moss}" -BASE_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}" - -declare -A PLATFORM_TRIPLES=( - ["linux-amd64"]="x86_64-unknown-linux-gnu" - ["linux-arm64"]="aarch64-unknown-linux-gnu" - ["darwin-arm64"]="aarch64-apple-darwin" - ["windows-amd64"]="x86_64-pc-windows-msvc" -) - -fetch_platform() { - local platform="$1" - local triple="$2" - local archive="libmoss-${LIBMOSS_VERSION}-${triple}.tar.gz" - local url="${BASE_URL}/${archive}" - local tmpdir - tmpdir="$(mktemp -d)" - - echo "Fetching ${platform} from ${url}" - curl -fsSL "${url}" -o "${tmpdir}/${archive}" - tar -xzf "${tmpdir}/${archive}" -C "${tmpdir}" - - local extracted="${tmpdir}/libmoss-${LIBMOSS_VERSION}-${triple}" - local dest="${BINDINGS_DIR}/lib/${platform}" - mkdir -p "${dest}" - - cp "${extracted}/include/libmoss.h" "${BINDINGS_DIR}/include/libmoss.h" - - case "${platform}" in - windows-amd64) - cp "${extracted}/lib/moss.lib" "${dest}/moss.lib" - ;; - *) - cp "${extracted}/lib/libmoss.a" "${dest}/libmoss.a" - ;; - esac - - rm -rf "${tmpdir}" - - echo "Installed ${platform} native library into ${dest}" -} - -main() { - if ! command -v curl >/dev/null 2>&1; then - echo "curl is required" >&2 - exit 1 - fi - - mkdir -p "${BINDINGS_DIR}/include" - - for platform in "${!PLATFORM_TRIPLES[@]}"; do - fetch_platform "${platform}" "${PLATFORM_TRIPLES[$platform]}" - done - - echo "Done. Header: ${BINDINGS_DIR}/include/libmoss.h" -} -main "$@" +cd "${ROOT_DIR}" +go run ./tools/install --all --release "${RELEASE_TAG}" diff --git a/sdks/go/scripts/link_dev_lib.sh b/sdks/go/scripts/link_dev_lib.sh index b802ad05..243374d6 100755 --- a/sdks/go/scripts/link_dev_lib.sh +++ b/sdks/go/scripts/link_dev_lib.sh @@ -1,63 +1,14 @@ #!/usr/bin/env bash set -euo pipefail -# Installs the native library for the current machine only (local dev helper). +# Installs the native library for the current machine (local dev helper). # # Usage: # ./sdks/go/scripts/link_dev_lib.sh [c-sdk-tag] SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -RELEASE_TAG="${1:-c-sdk-v0.9.0}" -LIBMOSS_VERSION="${RELEASE_TAG#c-sdk-}" -REPO="${MOSS_RELEASE_REPO:-usemoss/moss}" -BASE_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}" - -case "$(uname -s)/$(uname -m)" in - Linux/x86_64) - PLATFORM="linux-amd64" - TRIPLE="x86_64-unknown-linux-gnu" - LIB_NAME="libmoss.a" - SRC_LIB="lib/libmoss.a" - ;; - Linux/aarch64|Linux/arm64) - PLATFORM="linux-arm64" - TRIPLE="aarch64-unknown-linux-gnu" - LIB_NAME="libmoss.a" - SRC_LIB="lib/libmoss.a" - ;; - Darwin/arm64) - PLATFORM="darwin-arm64" - TRIPLE="aarch64-apple-darwin" - LIB_NAME="libmoss.a" - SRC_LIB="lib/libmoss.a" - ;; - MINGW*|MSYS*|CYGWIN*) - PLATFORM="windows-amd64" - TRIPLE="x86_64-pc-windows-msvc" - LIB_NAME="moss.lib" - SRC_LIB="lib/moss.lib" - ;; - *) - echo "Unsupported dev platform: $(uname -s)/$(uname -m)" >&2 - exit 1 - ;; -esac - ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" -BINDINGS_DIR="${ROOT_DIR}/bindings" -DEST="${BINDINGS_DIR}/lib/${PLATFORM}" -ARCHIVE="libmoss-${LIBMOSS_VERSION}-${TRIPLE}.tar.gz" -TMPDIR="$(mktemp -d)" -trap 'rm -rf "${TMPDIR}"' EXIT - -mkdir -p "${DEST}" "${BINDINGS_DIR}/include" - -echo "Downloading ${ARCHIVE}" -curl -fsSL "${BASE_URL}/${ARCHIVE}" -o "${TMPDIR}/${ARCHIVE}" -tar -xzf "${TMPDIR}/${ARCHIVE}" -C "${TMPDIR}" - -EXTRACTED="${TMPDIR}/libmoss-${LIBMOSS_VERSION}-${TRIPLE}" -cp "${EXTRACTED}/include/libmoss.h" "${BINDINGS_DIR}/include/libmoss.h" -cp "${EXTRACTED}/${SRC_LIB}" "${DEST}/${LIB_NAME}" +RELEASE_TAG="${1:-c-sdk-v0.9.0}" -echo "Installed ${PLATFORM} into ${DEST}/${LIB_NAME}" +cd "${ROOT_DIR}" +go run ./tools/install --release "${RELEASE_TAG}" diff --git a/sdks/go/scripts/publish-module-tags.sh b/sdks/go/scripts/publish-module-tags.sh deleted file mode 100755 index 483d470c..00000000 --- a/sdks/go/scripts/publish-module-tags.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Creates a release commit with native libraries and pushes module tags. -# The commit is tag-only and is not merged back into main. -# -# Usage: -# ./sdks/go/scripts/publish-module-tags.sh v0.9.0 [remote] - -if [[ $# -lt 1 ]]; then - echo "Usage: $0 [git-remote]" >&2 - exit 1 -fi - -VERSION="$1" -REMOTE="${2:-origin}" -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -REPO_ROOT="$(cd "${ROOT_DIR}/../.." && pwd)" - -TAGS=( - "sdks/go/sdk/${VERSION}" - "sdks/go/bindings/${VERSION}" - "sdks/go/bindings/lib/linux-amd64/${VERSION}" - "sdks/go/bindings/lib/linux-arm64/${VERSION}" - "sdks/go/bindings/lib/darwin-arm64/${VERSION}" - "sdks/go/bindings/lib/windows-amd64/${VERSION}" -) - -cd "${REPO_ROOT}" - -if [[ -z "$(git status --porcelain sdks/go)" ]]; then - echo "No changes under sdks/go to publish" >&2 - exit 1 -fi - -git add sdks/go/bindings/include/libmoss.h -git add -f sdks/go/bindings/lib/*/libmoss.a sdks/go/bindings/lib/*/moss.lib 2>/dev/null || true -git add sdks/go/bindings/go.mod -git add sdks/go/sdk/go.mod -git add sdks/go/bindings/version.go - -if git diff --cached --quiet; then - echo "Nothing staged for release commit" >&2 - exit 1 -fi - -git commit -m "chore(go): publish ${VERSION} native bindings" - -for tag in "${TAGS[@]}"; do - git tag -f "${tag}" -done - -git push "${REMOTE}" "${TAGS[@]}" - -echo "Published tags:" -printf ' %s\n' "${TAGS[@]}" diff --git a/sdks/go/scripts/publish-platform-lib-tag.sh b/sdks/go/scripts/publish-platform-lib-tag.sh deleted file mode 100755 index 90651e28..00000000 --- a/sdks/go/scripts/publish-platform-lib-tag.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Publishes one platform lib module tag at a time to avoid huge git pushes. -# -# Usage: -# ./sdks/go/scripts/publish-platform-lib-tag.sh linux-amd64 v0.1.0 - -if [[ $# -lt 2 ]]; then - echo "Usage: $0 [remote]" >&2 - echo "Platforms: linux-amd64 linux-arm64 darwin-arm64 windows-amd64" >&2 - exit 1 -fi - -PLATFORM="$1" -VERSION="$2" -REMOTE="${3:-origin}" -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -REPO_ROOT="$(cd "${ROOT_DIR}/../.." && pwd)" -TAG="sdks/go/bindings/lib/${PLATFORM}/${VERSION}" -LIB_DIR="${REPO_ROOT}/sdks/go/bindings/lib/${PLATFORM}" - -cd "${REPO_ROOT}" - -if [[ ! -d "${LIB_DIR}" ]]; then - echo "Missing platform directory: ${LIB_DIR}" >&2 - exit 1 -fi - -if [[ "${PLATFORM}" == "windows-amd64" ]]; then - if [[ ! -f "${LIB_DIR}/moss.lib" ]]; then - echo "Missing ${LIB_DIR}/moss.lib — run fetch-static-libs.sh first" >&2 - exit 1 - fi -else - if [[ ! -f "${LIB_DIR}/libmoss.a" ]]; then - echo "Missing ${LIB_DIR}/libmoss.a — run fetch-static-libs.sh first" >&2 - exit 1 - fi -fi - -git add "${LIB_DIR}/go.mod" "${LIB_DIR}/prebuilt.go" "${LIB_DIR}/.gitignore" -git add -f "${LIB_DIR}/libmoss.a" "${LIB_DIR}/moss.lib" 2>/dev/null || true - -if git diff --cached --quiet; then - echo "Nothing to publish for ${PLATFORM}" >&2 - exit 1 -fi - -git commit -m "chore(go): publish ${PLATFORM} ${VERSION}" -git tag -f "${TAG}" -git push "${REMOTE}" "${TAG}" - -echo "Published ${TAG}" diff --git a/sdks/go/scripts/publish-sdk-module-tags.sh b/sdks/go/scripts/publish-sdk-module-tags.sh index c5406f50..adf7c326 100755 --- a/sdks/go/scripts/publish-sdk-module-tags.sh +++ b/sdks/go/scripts/publish-sdk-module-tags.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -# Publishes bindings + sdk module tags (no native binaries in this commit). +# Publishes source-only bindings + sdk module tags. # # Usage: # ./sdks/go/scripts/publish-sdk-module-tags.sh v0.1.0 @@ -21,7 +21,8 @@ cd "${REPO_ROOT}" "${ROOT_DIR}/scripts/bump-module-versions.sh" "${VERSION}" git add sdks/go/bindings/go.mod sdks/go/sdk/go.mod sdks/go/bindings/version.go -git add sdks/go/bindings/include/libmoss.h +git add sdks/go/bindings/include/libmoss.h sdks/go/bindings/generate.go +git add sdks/go/tools/install if git diff --cached --quiet; then echo "Nothing to publish for bindings/sdk ${VERSION}" >&2 @@ -30,7 +31,10 @@ fi git commit -m "chore(go): publish bindings and sdk ${VERSION}" -for tag in "sdks/go/bindings/${VERSION}" "sdks/go/sdk/${VERSION}"; do +for tag in \ + "sdks/go/bindings/${VERSION}" \ + "sdks/go/sdk/${VERSION}" \ + "sdks/go/tools/install/${VERSION}"; do git tag -f "${tag}" git push "${REMOTE}" "${tag}" echo "Published ${tag}" diff --git a/sdks/go/sdk/README.md b/sdks/go/sdk/README.md index 7220b50e..5f8759c0 100644 --- a/sdks/go/sdk/README.md +++ b/sdks/go/sdk/README.md @@ -27,9 +27,10 @@ The Go SDK now has two layers: ```bash go get github.com/usemoss/moss/sdks/go/sdk +go run github.com/usemoss/moss/sdks/go/tools/install@latest ``` -Published versions bundle static `libmoss` per platform. See +The install tool downloads the static `libmoss` library for your platform. See [`../bindings/README.md`](../bindings/README.md) for Windows toolchain notes. Monorepo development uses the workspace in [`../go.work`](../go.work) and diff --git a/sdks/go/tools/install/go.mod b/sdks/go/tools/install/go.mod new file mode 100644 index 00000000..74c4e7e6 --- /dev/null +++ b/sdks/go/tools/install/go.mod @@ -0,0 +1,3 @@ +module github.com/usemoss/moss/sdks/go/tools/install + +go 1.22.2 diff --git a/sdks/go/tools/install/main.go b/sdks/go/tools/install/main.go new file mode 100644 index 00000000..e4814f48 --- /dev/null +++ b/sdks/go/tools/install/main.go @@ -0,0 +1,336 @@ +// Command install downloads Moss C SDK static libraries from GitHub Releases +// into sdks/go/bindings for local CGO linking. +package main + +import ( + "archive/tar" + "bufio" + "compress/gzip" + "crypto/sha256" + "encoding/hex" + "errors" + "flag" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "runtime" + "strings" +) + +const ( + defaultReleaseTag = "c-sdk-v0.9.0" + defaultRepo = "usemoss/moss" +) + +type platform struct { + id string + triple string + libFile string + srcLib string + ldflags string +} + +var platforms = []platform{ + { + id: "linux-amd64", triple: "x86_64-unknown-linux-gnu", + libFile: "libmoss.a", srcLib: "lib/libmoss.a", + }, + { + id: "linux-arm64", triple: "aarch64-unknown-linux-gnu", + libFile: "libmoss.a", srcLib: "lib/libmoss.a", + }, + { + id: "darwin-arm64", triple: "aarch64-apple-darwin", + libFile: "libmoss.a", srcLib: "lib/libmoss.a", + }, + { + id: "windows-amd64", triple: "x86_64-pc-windows-msvc", + libFile: "moss.lib", srcLib: "lib/moss.lib", + }, +} + +func main() { + all := flag.Bool("all", false, "install libraries for all supported platforms") + releaseTag := flag.String("release", defaultReleaseTag, "C SDK GitHub release tag") + repo := flag.String("repo", defaultRepo, "GitHub repository (owner/name)") + bindingsDir := flag.String("bindings", "", "bindings directory (default: auto-detect)") + force := flag.Bool("force", false, "re-download even if the library is already installed") + flag.Parse() + + root, err := resolveBindingsDir(*bindingsDir) + if err != nil { + fatal(err) + } + + version := strings.TrimPrefix(*releaseTag, "c-sdk-") + version = strings.TrimPrefix(version, "v") + baseURL := fmt.Sprintf("https://github.com/%s/releases/download/%s", *repo, *releaseTag) + + checksums, err := fetchChecksums(baseURL) + if err != nil { + fatal(err) + } + + targets, err := selectPlatforms(*all) + if err != nil { + fatal(err) + } + + for _, p := range targets { + if err := installPlatform(root, baseURL, version, p, checksums, *force); err != nil { + fatal(fmt.Errorf("%s: %w", p.id, err)) + } + } + + fmt.Printf("Installed Moss C SDK %s into %s\n", *releaseTag, root) +} + +func resolveBindingsDir(explicit string) (string, error) { + if explicit != "" { + return filepath.Abs(explicit) + } + if env := strings.TrimSpace(os.Getenv("MOSS_BINDINGS_DIR")); env != "" { + return filepath.Abs(env) + } + _, file, _, ok := runtime.Caller(0) + if !ok { + return "", errors.New("unable to locate install tool path") + } + return filepath.Abs(filepath.Join(filepath.Dir(file), "..", "..", "bindings")) +} + +func selectPlatforms(all bool) ([]platform, error) { + if all { + return platforms, nil + } + id, err := currentPlatformID() + if err != nil { + return nil, err + } + for _, p := range platforms { + if p.id == id { + return []platform{p}, nil + } + } + return nil, fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH) +} + +func currentPlatformID() (string, error) { + switch runtime.GOOS + "/" + runtime.GOARCH { + case "linux/amd64": + return "linux-amd64", nil + case "linux/arm64": + return "linux-arm64", nil + case "darwin/arm64": + return "darwin-arm64", nil + case "windows/amd64": + return "windows-amd64", nil + default: + return "", fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH) + } +} + +func fetchChecksums(baseURL string) (map[string]string, error) { + resp, err := http.Get(baseURL + "/checksums-sha256.txt") + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("checksums: HTTP %d", resp.StatusCode) + } + + checksums := map[string]string{} + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + parts := strings.Fields(strings.TrimSpace(scanner.Text())) + if len(parts) == 2 { + checksums[parts[1]] = parts[0] + } + } + return checksums, scanner.Err() +} + +func installPlatform(bindingsRoot, baseURL, version string, p platform, checksums map[string]string, force bool) error { + archive := fmt.Sprintf("libmoss-v%s-%s.tar.gz", version, p.triple) + wantChecksum, ok := checksums[archive] + if !ok { + return fmt.Errorf("checksum not found for %s", archive) + } + + destDir := filepath.Join(bindingsRoot, "lib", p.id) + destLib := filepath.Join(destDir, p.libFile) + headerPath := filepath.Join(bindingsRoot, "include", "libmoss.h") + + if !force { + if err := verifyFileChecksum(destLib, wantChecksum); err == nil { + if _, err := os.Stat(headerPath); err == nil { + fmt.Printf("skip %s (already installed)\n", p.id) + return nil + } + } + } + + if err := os.MkdirAll(destDir, 0o755); err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(headerPath), 0o755); err != nil { + return err + } + + tmp, err := os.MkdirTemp("", "moss-install-*") + if err != nil { + return err + } + defer os.RemoveAll(tmp) + + archivePath := filepath.Join(tmp, archive) + if err := downloadFile(baseURL+"/"+archive, archivePath, wantChecksum); err != nil { + return err + } + + extractedRoot := filepath.Join(tmp, fmt.Sprintf("libmoss-v%s-%s", version, p.triple)) + if err := extractTarGz(archivePath, extractedRoot, version, p.triple); err != nil { + return err + } + + if err := copyFile(filepath.Join(extractedRoot, "include", "libmoss.h"), headerPath); err != nil { + return err + } + if err := copyFile(filepath.Join(extractedRoot, p.srcLib), destLib); err != nil { + return err + } + + fmt.Printf("installed %s -> %s\n", p.id, destLib) + return nil +} + +func extractTarGz(archivePath, destRoot, version, triple string) error { + f, err := os.Open(archivePath) + if err != nil { + return err + } + defer f.Close() + + gz, err := gzip.NewReader(f) + if err != nil { + return err + } + defer gz.Close() + + prefix := fmt.Sprintf("libmoss-v%s-%s/", version, triple) + tr := tar.NewReader(gz) + for { + hdr, err := tr.Next() + if errors.Is(err, io.EOF) { + return nil + } + if err != nil { + return err + } + if hdr.Typeflag != tar.TypeReg || !strings.HasPrefix(hdr.Name, prefix) { + continue + } + rel := strings.TrimPrefix(hdr.Name, prefix) + target := filepath.Join(destRoot, rel) + if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { + return err + } + if err := writeFile(target, tr, hdr.Mode); err != nil { + return err + } + } +} + +func writeFile(path string, r io.Reader, mode int64) error { + out, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, fileMode(mode)) + if err != nil { + return err + } + defer out.Close() + _, err = io.Copy(out, r) + return err +} + +func fileMode(mode int64) os.FileMode { + if mode == 0 { + return 0o644 + } + return os.FileMode(mode) +} + +func downloadFile(url, dest, wantChecksum string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("download %s: HTTP %d", url, resp.StatusCode) + } + + tmp, err := os.CreateTemp(filepath.Dir(dest), ".download-*") + if err != nil { + return err + } + tmpPath := tmp.Name() + + hasher := sha256.New() + if _, err := io.Copy(io.MultiWriter(tmp, hasher), resp.Body); err != nil { + tmp.Close() + os.Remove(tmpPath) + return err + } + if err := tmp.Close(); err != nil { + os.Remove(tmpPath) + return err + } + + got := hex.EncodeToString(hasher.Sum(nil)) + if got != wantChecksum { + os.Remove(tmpPath) + return fmt.Errorf("checksum mismatch for %s: got %s want %s", filepath.Base(dest), got, wantChecksum) + } + return os.Rename(tmpPath, dest) +} + +func verifyFileChecksum(path, want string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + hasher := sha256.New() + if _, err := io.Copy(hasher, f); err != nil { + return err + } + if hex.EncodeToString(hasher.Sum(nil)) != want { + return fmt.Errorf("checksum mismatch") + } + return nil +} + +func copyFile(src, dest string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + return err +} + +func fatal(err error) { + fmt.Fprintf(os.Stderr, "moss install: %v\n", err) + os.Exit(1) +} From a811acd0d884d7880a4221bac1056f00252ae5e2 Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Tue, 28 Jul 2026 23:25:25 -0700 Subject: [PATCH 04/15] chore(go): publish bindings and sdk v0.1.0 --- sdks/go/sdk/go.mod | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sdks/go/sdk/go.mod b/sdks/go/sdk/go.mod index fe443db1..5af5cfed 100644 --- a/sdks/go/sdk/go.mod +++ b/sdks/go/sdk/go.mod @@ -2,6 +2,4 @@ module github.com/usemoss/moss/sdks/go/sdk go 1.22.2 -require github.com/usemoss/moss/sdks/go/bindings v0.0.0 - -replace github.com/usemoss/moss/sdks/go/bindings => ../bindings +require github.com/usemoss/moss/sdks/go/bindings v0.1.0 From 48aefd0c73d018face5b3d7ecd4d7226daa35506 Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Tue, 28 Jul 2026 23:29:26 -0700 Subject: [PATCH 05/15] fix(go): install natives into vendor or user cache Resolve the bindings package directory with go list so installs land in the vendored module tree. Fall back to ~/.cache/moss-go when the module cache is read-only and print CGO_LDFLAGS for that case. Co-authored-by: Cursor --- sdks/go/tools/install/main.go | 96 +++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 11 deletions(-) diff --git a/sdks/go/tools/install/main.go b/sdks/go/tools/install/main.go index e4814f48..c951e4f9 100644 --- a/sdks/go/tools/install/main.go +++ b/sdks/go/tools/install/main.go @@ -14,14 +14,16 @@ import ( "io" "net/http" "os" + "os/exec" "path/filepath" "runtime" "strings" ) const ( - defaultReleaseTag = "c-sdk-v0.9.0" - defaultRepo = "usemoss/moss" + defaultReleaseTag = "c-sdk-v0.9.0" + defaultRepo = "usemoss/moss" + bindingsModulePath = "github.com/usemoss/moss/sdks/go/bindings" ) type platform struct { @@ -79,12 +81,24 @@ func main() { } for _, p := range targets { - if err := installPlatform(root, baseURL, version, p, checksums, *force); err != nil { + libDir, installHeader, err := resolveLibDir(root, version, p.id) + if err != nil { + fatal(err) + } + if err := installPlatform(root, libDir, installHeader, baseURL, version, p, checksums, *force); err != nil { fatal(fmt.Errorf("%s: %w", p.id, err)) } + if libDir != filepath.Join(root, "lib", p.id) { + printLinkerHint(p, libDir) + } } - fmt.Printf("Installed Moss C SDK %s into %s\n", *releaseTag, root) + fmt.Printf("Installed Moss C SDK %s\n", *releaseTag) + if !isWritableDir(root) { + fmt.Println("Build with -mod=vendor after `go mod vendor`, or export the CGO_LDFLAGS shown above.") + } else { + fmt.Printf("Native libraries installed under %s\n", root) + } } func resolveBindingsDir(explicit string) (string, error) { @@ -94,13 +108,65 @@ func resolveBindingsDir(explicit string) (string, error) { if env := strings.TrimSpace(os.Getenv("MOSS_BINDINGS_DIR")); env != "" { return filepath.Abs(env) } + if dir, err := bindingsDirFromGoList(); err == nil { + return dir, nil + } _, file, _, ok := runtime.Caller(0) if !ok { - return "", errors.New("unable to locate install tool path") + return "", errors.New("unable to locate bindings directory; set MOSS_BINDINGS_DIR or run from a module that requires github.com/usemoss/moss/sdks/go/bindings") } return filepath.Abs(filepath.Join(filepath.Dir(file), "..", "..", "bindings")) } +func bindingsDirFromGoList() (string, error) { + cmd := exec.Command("go", "list", "-f", "{{.Dir}}", bindingsModulePath) + out, err := cmd.Output() + if err != nil { + return "", err + } + dir := strings.TrimSpace(string(out)) + if dir == "" { + return "", errors.New("bindings package directory not found") + } + return filepath.Abs(dir) +} + +func resolveLibDir(bindingsRoot, version, platformID string) (string, bool, error) { + inBindings := filepath.Join(bindingsRoot, "lib", platformID) + if isWritableDir(bindingsRoot) { + return inBindings, true, nil + } + + cacheRoot, err := os.UserCacheDir() + if err != nil { + return "", false, err + } + return filepath.Join(cacheRoot, "moss-go", version, platformID), false, nil +} + +func isWritableDir(dir string) bool { + if dir == "" { + return false + } + test := filepath.Join(dir, ".moss-install-write-test") + if err := os.WriteFile(test, []byte("ok"), 0o644); err != nil { + return false + } + _ = os.Remove(test) + return true +} + +func printLinkerHint(p platform, libDir string) { + switch p.id { + case "darwin-arm64": + fmt.Printf("export CGO_LDFLAGS=\"-L%s -lmoss -lc++ -framework Security -framework SystemConfiguration\"\n", libDir) + case "windows-amd64": + fmt.Printf("set CGO_LDFLAGS=-L%s %s\n", libDir, p.libFile) + default: + fmt.Printf("export CGO_LDFLAGS=\"-L%s -lmoss -lstdc++ -ldl -lm -lpthread\"\n", libDir) + } +} + func selectPlatforms(all bool) ([]platform, error) { if all { return platforms, nil @@ -153,19 +219,23 @@ func fetchChecksums(baseURL string) (map[string]string, error) { return checksums, scanner.Err() } -func installPlatform(bindingsRoot, baseURL, version string, p platform, checksums map[string]string, force bool) error { +func installPlatform(bindingsRoot, libDir string, installHeader bool, baseURL, version string, p platform, checksums map[string]string, force bool) error { archive := fmt.Sprintf("libmoss-v%s-%s.tar.gz", version, p.triple) wantChecksum, ok := checksums[archive] if !ok { return fmt.Errorf("checksum not found for %s", archive) } - destDir := filepath.Join(bindingsRoot, "lib", p.id) + destDir := libDir destLib := filepath.Join(destDir, p.libFile) headerPath := filepath.Join(bindingsRoot, "include", "libmoss.h") if !force { if err := verifyFileChecksum(destLib, wantChecksum); err == nil { + if !installHeader { + fmt.Printf("skip %s (already installed)\n", p.id) + return nil + } if _, err := os.Stat(headerPath); err == nil { fmt.Printf("skip %s (already installed)\n", p.id) return nil @@ -176,8 +246,10 @@ func installPlatform(bindingsRoot, baseURL, version string, p platform, checksum if err := os.MkdirAll(destDir, 0o755); err != nil { return err } - if err := os.MkdirAll(filepath.Dir(headerPath), 0o755); err != nil { - return err + if installHeader { + if err := os.MkdirAll(filepath.Dir(headerPath), 0o755); err != nil { + return err + } } tmp, err := os.MkdirTemp("", "moss-install-*") @@ -196,8 +268,10 @@ func installPlatform(bindingsRoot, baseURL, version string, p platform, checksum return err } - if err := copyFile(filepath.Join(extractedRoot, "include", "libmoss.h"), headerPath); err != nil { - return err + if installHeader { + if err := copyFile(filepath.Join(extractedRoot, "include", "libmoss.h"), headerPath); err != nil { + return err + } } if err := copyFile(filepath.Join(extractedRoot, p.srcLib), destLib); err != nil { return err From e6fc81fb2b8d2e6d67549a8df91f153c939a40a0 Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Tue, 28 Jul 2026 23:29:30 -0700 Subject: [PATCH 06/15] chore(go): publish bindings and sdk v0.1.1 --- sdks/go/sdk/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/go/sdk/go.mod b/sdks/go/sdk/go.mod index 5af5cfed..533300bf 100644 --- a/sdks/go/sdk/go.mod +++ b/sdks/go/sdk/go.mod @@ -2,4 +2,4 @@ module github.com/usemoss/moss/sdks/go/sdk go 1.22.2 -require github.com/usemoss/moss/sdks/go/bindings v0.1.0 +require github.com/usemoss/moss/sdks/go/bindings v0.1.1 From 9a2a1e9a85bf92fae48ba71aa6f21c07159a6b1a Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Wed, 29 Jul 2026 14:09:39 -0700 Subject: [PATCH 07/15] fix(go): vendor bindings before native install --- sdks/go/README.md | 4 +- sdks/go/bindings/README.md | 10 +++-- sdks/go/sdk/README.md | 2 + sdks/go/tools/install/main.go | 79 +++++++++++++++++++---------------- 4 files changed, 55 insertions(+), 40 deletions(-) diff --git a/sdks/go/README.md b/sdks/go/README.md index 5bfa9524..3a26d079 100644 --- a/sdks/go/README.md +++ b/sdks/go/README.md @@ -15,8 +15,10 @@ go run github.com/usemoss/moss/sdks/go/tools/install@latest The install tool downloads the static `libmoss` library for your platform from [Moss C SDK GitHub Releases](https://github.com/usemoss/moss/releases). You need CGO and a C compiler, but not a manual C SDK download or `LD_LIBRARY_PATH`. +For external projects, the installer vendors the bindings before placing the +library next to them, so the normal `go build` command finds it. -Alternatively, from a checkout of the bindings package: +From a checkout of the bindings package, you can instead use: ```bash go generate github.com/usemoss/moss/sdks/go/bindings diff --git a/sdks/go/bindings/README.md b/sdks/go/bindings/README.md index e1c99e32..5260907a 100644 --- a/sdks/go/bindings/README.md +++ b/sdks/go/bindings/README.md @@ -18,7 +18,9 @@ go run github.com/usemoss/moss/sdks/go/tools/install@latest The install tool downloads a prebuilt static library for your platform from [C SDK GitHub Releases](https://github.com/usemoss/moss/releases). No manual C -SDK download, `LD_LIBRARY_PATH`, or `-tags libmoss` is required. +SDK download, `LD_LIBRARY_PATH`, or `-tags libmoss` is required. For a +downloaded module, it runs `go mod vendor` and installs the library beside the +vendored bindings; subsequent `go build` commands use that copy automatically. Requirements: @@ -32,7 +34,7 @@ bindings/ include/libmoss.h # committed C header libmoss.go # CGO wrapper (requires CGO) prebuilt__.go # per-platform CGO linker flags - generate.go # //go:generate install hook + generate.go # //go:generate install hook for checkouts lib/ linux-amd64/ # libmoss.a (gitignored, downloaded at build time) linux-arm64/ @@ -58,7 +60,7 @@ Or fetch all supported platforms: ./sdks/go/scripts/fetch-static-libs.sh c-sdk-v0.9.0 ``` -Or use `go generate` from this directory: +Or use `go generate` from a checkout of this directory: ```bash cd sdks/go/bindings @@ -79,7 +81,7 @@ source-only module tags (no binaries in git): - `sdks/go/sdk/v0.1.0` - `sdks/go/bindings/v0.1.0` -Consumers download native libraries at build time via `tools/install`. +Consumers download native libraries during the explicit `tools/install` step. ## Build without CGO diff --git a/sdks/go/sdk/README.md b/sdks/go/sdk/README.md index 5f8759c0..f750297f 100644 --- a/sdks/go/sdk/README.md +++ b/sdks/go/sdk/README.md @@ -32,6 +32,8 @@ go run github.com/usemoss/moss/sdks/go/tools/install@latest The install tool downloads the static `libmoss` library for your platform. See [`../bindings/README.md`](../bindings/README.md) for Windows toolchain notes. +It vendors the SDK so CGO can link the downloaded library from a writable +directory; commit `vendor/` if your project commits vendored dependencies. Monorepo development uses the workspace in [`../go.work`](../go.work) and [`../scripts/link_dev_lib.sh`](../scripts/link_dev_lib.sh). diff --git a/sdks/go/tools/install/main.go b/sdks/go/tools/install/main.go index c951e4f9..738df612 100644 --- a/sdks/go/tools/install/main.go +++ b/sdks/go/tools/install/main.go @@ -61,7 +61,7 @@ func main() { force := flag.Bool("force", false, "re-download even if the library is already installed") flag.Parse() - root, err := resolveBindingsDir(*bindingsDir) + root, err := resolveWritableBindingsDir(*bindingsDir) if err != nil { fatal(err) } @@ -81,24 +81,47 @@ func main() { } for _, p := range targets { - libDir, installHeader, err := resolveLibDir(root, version, p.id) - if err != nil { - fatal(err) - } - if err := installPlatform(root, libDir, installHeader, baseURL, version, p, checksums, *force); err != nil { + libDir := filepath.Join(root, "lib", p.id) + if err := installPlatform(root, libDir, true, baseURL, version, p, checksums, *force); err != nil { fatal(fmt.Errorf("%s: %w", p.id, err)) } - if libDir != filepath.Join(root, "lib", p.id) { - printLinkerHint(p, libDir) - } } fmt.Printf("Installed Moss C SDK %s\n", *releaseTag) + fmt.Printf("Native libraries installed under %s\n", root) +} + +// resolveWritableBindingsDir returns the bindings package CGO will compile. +// Downloaded Go modules are read-only, so an external consumer is vendored +// before installing the native library beside the bindings source. +func resolveWritableBindingsDir(explicit string) (string, error) { + root, err := resolveBindingsDir(explicit) + if err != nil { + return "", err + } + if isWritableDir(root) { + return root, nil + } + if explicit != "" || strings.TrimSpace(os.Getenv("MOSS_BINDINGS_DIR")) != "" { + return "", fmt.Errorf("bindings directory %s is not writable", root) + } + + goMod, err := goEnv("GOMOD") + if err != nil || goMod == os.DevNull || goMod == "" { + return "", fmt.Errorf("the downloaded bindings module is read-only; run this command from a Go module that imports the Moss SDK") + } + if out, err := exec.Command("go", "mod", "vendor").CombinedOutput(); err != nil { + return "", fmt.Errorf("vendor Moss bindings for native installation: %w\n%s", err, strings.TrimSpace(string(out))) + } + + root, err = bindingsDirFromGoList("-mod=vendor") + if err != nil { + return "", fmt.Errorf("locate vendored Moss bindings after `go mod vendor`: %w", err) + } if !isWritableDir(root) { - fmt.Println("Build with -mod=vendor after `go mod vendor`, or export the CGO_LDFLAGS shown above.") - } else { - fmt.Printf("Native libraries installed under %s\n", root) + return "", fmt.Errorf("vendored bindings directory %s is not writable", root) } + return root, nil } func resolveBindingsDir(explicit string) (string, error) { @@ -118,8 +141,10 @@ func resolveBindingsDir(explicit string) (string, error) { return filepath.Abs(filepath.Join(filepath.Dir(file), "..", "..", "bindings")) } -func bindingsDirFromGoList() (string, error) { - cmd := exec.Command("go", "list", "-f", "{{.Dir}}", bindingsModulePath) +func bindingsDirFromGoList(args ...string) (string, error) { + args = append([]string{"list"}, args...) + args = append(args, "-f", "{{.Dir}}", bindingsModulePath) + cmd := exec.Command("go", args...) out, err := cmd.Output() if err != nil { return "", err @@ -131,19 +156,6 @@ func bindingsDirFromGoList() (string, error) { return filepath.Abs(dir) } -func resolveLibDir(bindingsRoot, version, platformID string) (string, bool, error) { - inBindings := filepath.Join(bindingsRoot, "lib", platformID) - if isWritableDir(bindingsRoot) { - return inBindings, true, nil - } - - cacheRoot, err := os.UserCacheDir() - if err != nil { - return "", false, err - } - return filepath.Join(cacheRoot, "moss-go", version, platformID), false, nil -} - func isWritableDir(dir string) bool { if dir == "" { return false @@ -156,15 +168,12 @@ func isWritableDir(dir string) bool { return true } -func printLinkerHint(p platform, libDir string) { - switch p.id { - case "darwin-arm64": - fmt.Printf("export CGO_LDFLAGS=\"-L%s -lmoss -lc++ -framework Security -framework SystemConfiguration\"\n", libDir) - case "windows-amd64": - fmt.Printf("set CGO_LDFLAGS=-L%s %s\n", libDir, p.libFile) - default: - fmt.Printf("export CGO_LDFLAGS=\"-L%s -lmoss -lstdc++ -ldl -lm -lpthread\"\n", libDir) +func goEnv(name string) (string, error) { + out, err := exec.Command("go", "env", name).Output() + if err != nil { + return "", err } + return strings.TrimSpace(string(out)), nil } func selectPlatforms(all bool) ([]platform, error) { From 9a984920802237da94dc36d03a455d70ac371ea5 Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Wed, 29 Jul 2026 14:23:36 -0700 Subject: [PATCH 08/15] chore(go): align source-only module release version --- .github/workflows/publish-go-sdk.yml | 2 +- sdks/go/bindings/README.md | 4 ++-- sdks/go/scripts/publish-sdk-module-tags.sh | 2 +- sdks/go/sdk/go.mod | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-go-sdk.yml b/.github/workflows/publish-go-sdk.yml index 79f0cabe..6f9a8bc8 100644 --- a/.github/workflows/publish-go-sdk.yml +++ b/.github/workflows/publish-go-sdk.yml @@ -13,7 +13,7 @@ on: go_version: description: 'Go module version to publish' required: true - default: 'v0.1.0' + default: 'v0.1.2' concurrency: group: go-sdk-release-${{ github.event.inputs.go_version }} diff --git a/sdks/go/bindings/README.md b/sdks/go/bindings/README.md index 5260907a..c080e1de 100644 --- a/sdks/go/bindings/README.md +++ b/sdks/go/bindings/README.md @@ -78,8 +78,8 @@ CGO_ENABLED=1 go build . Maintainers run the **Publish Go SDK** GitHub Actions workflow. It creates source-only module tags (no binaries in git): -- `sdks/go/sdk/v0.1.0` -- `sdks/go/bindings/v0.1.0` +- `sdks/go/sdk/v0.1.2` +- `sdks/go/bindings/v0.1.2` Consumers download native libraries during the explicit `tools/install` step. diff --git a/sdks/go/scripts/publish-sdk-module-tags.sh b/sdks/go/scripts/publish-sdk-module-tags.sh index adf7c326..26bd128a 100755 --- a/sdks/go/scripts/publish-sdk-module-tags.sh +++ b/sdks/go/scripts/publish-sdk-module-tags.sh @@ -4,7 +4,7 @@ set -euo pipefail # Publishes source-only bindings + sdk module tags. # # Usage: -# ./sdks/go/scripts/publish-sdk-module-tags.sh v0.1.0 +# ./sdks/go/scripts/publish-sdk-module-tags.sh v0.1.2 if [[ $# -lt 1 ]]; then echo "Usage: $0 [remote]" >&2 diff --git a/sdks/go/sdk/go.mod b/sdks/go/sdk/go.mod index 533300bf..e0f64675 100644 --- a/sdks/go/sdk/go.mod +++ b/sdks/go/sdk/go.mod @@ -2,4 +2,4 @@ module github.com/usemoss/moss/sdks/go/sdk go 1.22.2 -require github.com/usemoss/moss/sdks/go/bindings v0.1.1 +require github.com/usemoss/moss/sdks/go/bindings v0.1.2 From b56b2675152b3ecf31910057774ed27dfcd88e23 Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Wed, 29 Jul 2026 15:23:54 -0700 Subject: [PATCH 09/15] fix(go): harden native SDK publishing and install --- .github/workflows/publish-go-sdk.yml | 10 +++++ examples/go/README.md | 2 +- sdks/go/README.md | 7 ++-- sdks/go/bindings/README.md | 13 ++++-- sdks/go/bindings/prebuilt_windows_amd64.go | 2 +- sdks/go/scripts/publish-sdk-module-tags.sh | 7 +--- sdks/go/sdk/README.md | 8 ++-- sdks/go/tools/install/main.go | 47 ++++++++++++++++++---- sdks/go/tools/install/main_test.go | 25 ++++++++++++ sdks/go/tools/install/release.go | 3 ++ 10 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 sdks/go/tools/install/main_test.go create mode 100644 sdks/go/tools/install/release.go diff --git a/.github/workflows/publish-go-sdk.yml b/.github/workflows/publish-go-sdk.yml index 6f9a8bc8..0245ce6f 100644 --- a/.github/workflows/publish-go-sdk.yml +++ b/.github/workflows/publish-go-sdk.yml @@ -29,6 +29,11 @@ jobs: with: go-version: '1.22.2' + - name: Configure Git author + run: | + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + - name: Update bindings native version metadata shell: bash run: | @@ -43,6 +48,11 @@ jobs: // NativeLibReleaseTag is the GitHub release tag used to fetch native artifacts. const NativeLibReleaseTag = "${C_SDK_VERSION}" EOF + cat > sdks/go/tools/install/release.go <&2 - exit 1 +if ! git diff --cached --quiet; then + git commit -m "chore(go): publish bindings and sdk ${VERSION}" fi -git commit -m "chore(go): publish bindings and sdk ${VERSION}" - for tag in \ "sdks/go/bindings/${VERSION}" \ "sdks/go/sdk/${VERSION}" \ diff --git a/sdks/go/sdk/README.md b/sdks/go/sdk/README.md index f750297f..747b1779 100644 --- a/sdks/go/sdk/README.md +++ b/sdks/go/sdk/README.md @@ -27,13 +27,15 @@ The Go SDK now has two layers: ```bash go get github.com/usemoss/moss/sdks/go/sdk -go run github.com/usemoss/moss/sdks/go/tools/install@latest +go run github.com/usemoss/moss/sdks/go/tools/install@latest --vendor ``` The install tool downloads the static `libmoss` library for your platform. See [`../bindings/README.md`](../bindings/README.md) for Windows toolchain notes. -It vendors the SDK so CGO can link the downloaded library from a writable -directory; commit `vendor/` if your project commits vendored dependencies. +The explicit `--vendor` option vendors the SDK so CGO can link the downloaded +library from a writable directory; commit `vendor/` if your project commits +vendored dependencies. Run it after your application imports the SDK so the +bindings package is included in `vendor/`. Monorepo development uses the workspace in [`../go.work`](../go.work) and [`../scripts/link_dev_lib.sh`](../scripts/link_dev_lib.sh). diff --git a/sdks/go/tools/install/main.go b/sdks/go/tools/install/main.go index 738df612..d56a5a91 100644 --- a/sdks/go/tools/install/main.go +++ b/sdks/go/tools/install/main.go @@ -15,13 +15,13 @@ import ( "net/http" "os" "os/exec" + "path" "path/filepath" "runtime" "strings" ) const ( - defaultReleaseTag = "c-sdk-v0.9.0" defaultRepo = "usemoss/moss" bindingsModulePath = "github.com/usemoss/moss/sdks/go/bindings" ) @@ -31,7 +31,6 @@ type platform struct { triple string libFile string srcLib string - ldflags string } var platforms = []platform{ @@ -58,10 +57,11 @@ func main() { releaseTag := flag.String("release", defaultReleaseTag, "C SDK GitHub release tag") repo := flag.String("repo", defaultRepo, "GitHub repository (owner/name)") bindingsDir := flag.String("bindings", "", "bindings directory (default: auto-detect)") + vendor := flag.Bool("vendor", false, "run go mod vendor before installing into a downloaded module") force := flag.Bool("force", false, "re-download even if the library is already installed") flag.Parse() - root, err := resolveWritableBindingsDir(*bindingsDir) + root, err := resolveWritableBindingsDir(*bindingsDir, *vendor) if err != nil { fatal(err) } @@ -92,9 +92,9 @@ func main() { } // resolveWritableBindingsDir returns the bindings package CGO will compile. -// Downloaded Go modules are read-only, so an external consumer is vendored -// before installing the native library beside the bindings source. -func resolveWritableBindingsDir(explicit string) (string, error) { +// Downloaded Go modules are read-only, so consumers either need existing +// vendored bindings or must explicitly permit the installer to create them. +func resolveWritableBindingsDir(explicit string, allowVendor bool) (string, error) { root, err := resolveBindingsDir(explicit) if err != nil { return "", err @@ -105,6 +105,12 @@ func resolveWritableBindingsDir(explicit string) (string, error) { if explicit != "" || strings.TrimSpace(os.Getenv("MOSS_BINDINGS_DIR")) != "" { return "", fmt.Errorf("bindings directory %s is not writable", root) } + if vendoredRoot, err := bindingsDirFromGoList("-mod=vendor"); err == nil && isWritableDir(vendoredRoot) { + return vendoredRoot, nil + } + if !allowVendor { + return "", errors.New("the downloaded bindings module is read-only; run `go mod vendor` first, or rerun moss install with --vendor to allow it to regenerate vendor/") + } goMod, err := goEnv("GOMOD") if err != nil || goMod == os.DevNull || goMod == "" { @@ -317,7 +323,10 @@ func extractTarGz(archivePath, destRoot, version, triple string) error { continue } rel := strings.TrimPrefix(hdr.Name, prefix) - target := filepath.Join(destRoot, rel) + target, err := safeArchiveTarget(destRoot, rel) + if err != nil { + return fmt.Errorf("invalid archive path %q: %w", hdr.Name, err) + } if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { return err } @@ -327,6 +336,30 @@ func extractTarGz(archivePath, destRoot, version, triple string) error { } } +func safeArchiveTarget(destRoot, rel string) (string, error) { + clean := path.Clean(rel) + if clean == "." || clean == ".." || strings.HasPrefix(clean, "../") || path.IsAbs(clean) { + return "", errors.New("path escapes extraction root") + } + + root, err := filepath.Abs(destRoot) + if err != nil { + return "", err + } + target, err := filepath.Abs(filepath.Join(root, filepath.FromSlash(clean))) + if err != nil { + return "", err + } + relative, err := filepath.Rel(root, target) + if err != nil { + return "", err + } + if relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) { + return "", errors.New("path escapes extraction root") + } + return target, nil +} + func writeFile(path string, r io.Reader, mode int64) error { out, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, fileMode(mode)) if err != nil { diff --git a/sdks/go/tools/install/main_test.go b/sdks/go/tools/install/main_test.go new file mode 100644 index 00000000..638800b3 --- /dev/null +++ b/sdks/go/tools/install/main_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "path/filepath" + "testing" +) + +func TestSafeArchiveTarget(t *testing.T) { + root := t.TempDir() + + target, err := safeArchiveTarget(root, "include/libmoss.h") + if err != nil { + t.Fatalf("safeArchiveTarget returned an error: %v", err) + } + want := filepath.Join(root, "include", "libmoss.h") + if target != want { + t.Fatalf("safeArchiveTarget() = %q, want %q", target, want) + } + + for _, input := range []string{"..", "../outside", "/outside"} { + if _, err := safeArchiveTarget(root, input); err == nil { + t.Errorf("safeArchiveTarget(%q) succeeded, want error", input) + } + } +} diff --git a/sdks/go/tools/install/release.go b/sdks/go/tools/install/release.go new file mode 100644 index 00000000..4f7ef139 --- /dev/null +++ b/sdks/go/tools/install/release.go @@ -0,0 +1,3 @@ +package main + +const defaultReleaseTag = "c-sdk-v0.9.0" From e127a6d9cf6bdb64e2b7c9d7ec756cb5b5cafcc6 Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Wed, 29 Jul 2026 15:35:18 -0700 Subject: [PATCH 10/15] fix(go): align bindings and installer versions --- .github/workflows/publish-go-sdk.yml | 5 - sdks/go/bindings/lib/darwin-arm64/.gitignore | 1 + sdks/go/bindings/lib/linux-amd64/.gitignore | 1 + sdks/go/bindings/lib/linux-arm64/.gitignore | 1 + sdks/go/bindings/lib/windows-amd64/.gitignore | 1 + sdks/go/bindings/libmoss.go | 2 +- sdks/go/bindings/stub.go | 2 +- sdks/go/tools/install/main.go | 99 +++++++++++++------ sdks/go/tools/install/main_test.go | 29 ++++++ sdks/go/tools/install/release.go | 3 - 10 files changed, 105 insertions(+), 39 deletions(-) delete mode 100644 sdks/go/tools/install/release.go diff --git a/.github/workflows/publish-go-sdk.yml b/.github/workflows/publish-go-sdk.yml index 0245ce6f..45622882 100644 --- a/.github/workflows/publish-go-sdk.yml +++ b/.github/workflows/publish-go-sdk.yml @@ -48,11 +48,6 @@ jobs: // NativeLibReleaseTag is the GitHub release tag used to fetch native artifacts. const NativeLibReleaseTag = "${C_SDK_VERSION}" EOF - cat > sdks/go/tools/install/release.go < %s\n", p.id, destLib) return nil } +func fileExists(path string) bool { + info, err := os.Stat(path) + return err == nil && !info.IsDir() +} + +func receiptMatches(path, archive, checksum string) bool { + contents, err := os.ReadFile(path) + if err != nil { + return false + } + return strings.TrimSpace(string(contents)) == checksum+" "+archive +} + +func writeReceipt(path, archive, checksum string) error { + tmp, err := os.CreateTemp(filepath.Dir(path), ".moss-install-*") + if err != nil { + return err + } + tmpPath := tmp.Name() + defer os.Remove(tmpPath) + + if _, err := fmt.Fprintf(tmp, "%s %s\n", checksum, archive); err != nil { + tmp.Close() + return err + } + if err := tmp.Close(); err != nil { + return err + } + return os.Rename(tmpPath, path) +} + func extractTarGz(archivePath, destRoot, version, triple string) error { f, err := os.Open(archivePath) if err != nil { @@ -412,23 +470,6 @@ func downloadFile(url, dest, wantChecksum string) error { return os.Rename(tmpPath, dest) } -func verifyFileChecksum(path, want string) error { - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - - hasher := sha256.New() - if _, err := io.Copy(hasher, f); err != nil { - return err - } - if hex.EncodeToString(hasher.Sum(nil)) != want { - return fmt.Errorf("checksum mismatch") - } - return nil -} - func copyFile(src, dest string) error { in, err := os.Open(src) if err != nil { diff --git a/sdks/go/tools/install/main_test.go b/sdks/go/tools/install/main_test.go index 638800b3..cb17b013 100644 --- a/sdks/go/tools/install/main_test.go +++ b/sdks/go/tools/install/main_test.go @@ -1,6 +1,7 @@ package main import ( + "os" "path/filepath" "testing" ) @@ -23,3 +24,31 @@ func TestSafeArchiveTarget(t *testing.T) { } } } + +func TestNativeReleaseTag(t *testing.T) { + root := t.TempDir() + if err := os.WriteFile(filepath.Join(root, "version.go"), []byte("package mosscore\n\nconst NativeLibReleaseTag = \"c-sdk-v1.2.3\"\n"), 0o644); err != nil { + t.Fatal(err) + } + + tag, err := nativeReleaseTag(root) + if err != nil { + t.Fatalf("nativeReleaseTag returned an error: %v", err) + } + if tag != "c-sdk-v1.2.3" { + t.Fatalf("nativeReleaseTag() = %q, want c-sdk-v1.2.3", tag) + } +} + +func TestInstallReceipt(t *testing.T) { + path := filepath.Join(t.TempDir(), installReceiptName) + if err := writeReceipt(path, "libmoss-v0.9.0-x86_64-unknown-linux-gnu.tar.gz", "abc123"); err != nil { + t.Fatalf("writeReceipt returned an error: %v", err) + } + if !receiptMatches(path, "libmoss-v0.9.0-x86_64-unknown-linux-gnu.tar.gz", "abc123") { + t.Fatal("receiptMatches() = false, want true") + } + if receiptMatches(path, "libmoss-v0.9.0-x86_64-unknown-linux-gnu.tar.gz", "different") { + t.Fatal("receiptMatches() = true for a different checksum, want false") + } +} diff --git a/sdks/go/tools/install/release.go b/sdks/go/tools/install/release.go deleted file mode 100644 index 4f7ef139..00000000 --- a/sdks/go/tools/install/release.go +++ /dev/null @@ -1,3 +0,0 @@ -package main - -const defaultReleaseTag = "c-sdk-v0.9.0" From 6909a8c0f83788898875efaba6888049ae76549b Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Wed, 29 Jul 2026 16:08:42 -0700 Subject: [PATCH 11/15] fix(go): limit native bindings to supported targets --- sdks/go/README.md | 4 ++++ sdks/go/bindings/README.md | 8 ++++++-- sdks/go/bindings/lib/windows-amd64/.gitignore | 4 ---- sdks/go/bindings/libmoss.go | 2 +- sdks/go/bindings/prebuilt_windows_amd64.go | 9 --------- sdks/go/bindings/stub.go | 2 +- sdks/go/sdk/README.md | 5 ++++- sdks/go/tools/install/main.go | 6 ------ 8 files changed, 16 insertions(+), 24 deletions(-) delete mode 100644 sdks/go/bindings/lib/windows-amd64/.gitignore delete mode 100644 sdks/go/bindings/prebuilt_windows_amd64.go diff --git a/sdks/go/README.md b/sdks/go/README.md index 24169446..aa8396d6 100644 --- a/sdks/go/README.md +++ b/sdks/go/README.md @@ -19,6 +19,10 @@ For external projects, the explicit `--vendor` option lets the installer vendor the bindings before placing the library next to them, so the normal `go build` command finds it. +Native bindings currently support Linux (`amd64`, `arm64`) and Apple Silicon +macOS (`arm64`). Other platforms, including Windows, use the +bindings-unavailable stub until a compatible native release is available. + From a checkout of the bindings package, you can instead use: ```bash diff --git a/sdks/go/bindings/README.md b/sdks/go/bindings/README.md index 5426fc45..1111a6a6 100644 --- a/sdks/go/bindings/README.md +++ b/sdks/go/bindings/README.md @@ -30,7 +30,12 @@ vendor` includes the bindings package. Requirements: - `CGO_ENABLED=1` (default on Linux and macOS) -- A C compiler (`gcc` on Linux, Xcode CLI tools on macOS, MinGW on Windows) +- A C compiler (`gcc` on Linux, Xcode CLI tools on macOS) +- One of the supported native targets: Linux (`amd64`, `arm64`) or Apple Silicon + macOS (`arm64`) + +Windows currently uses the bindings-unavailable stub. Native Windows support +will require a MinGW-compatible C SDK release artifact. ## Layout @@ -44,7 +49,6 @@ bindings/ linux-amd64/ # libmoss.a (gitignored, downloaded at build time) linux-arm64/ darwin-arm64/ - windows-amd64/ ``` Native `.a` / `.lib` files are gitignored. The diff --git a/sdks/go/bindings/lib/windows-amd64/.gitignore b/sdks/go/bindings/lib/windows-amd64/.gitignore deleted file mode 100644 index d66e6e49..00000000 --- a/sdks/go/bindings/lib/windows-amd64/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -moss.lib -moss.dll -moss.dll.lib -.moss-install-checksum diff --git a/sdks/go/bindings/libmoss.go b/sdks/go/bindings/libmoss.go index 4f7bcfec..d25b3f0f 100644 --- a/sdks/go/bindings/libmoss.go +++ b/sdks/go/bindings/libmoss.go @@ -1,4 +1,4 @@ -//go:build cgo && ((linux && (amd64 || arm64)) || (darwin && arm64) || (windows && amd64)) +//go:build cgo && ((linux && (amd64 || arm64)) || (darwin && arm64)) package mosscore diff --git a/sdks/go/bindings/prebuilt_windows_amd64.go b/sdks/go/bindings/prebuilt_windows_amd64.go deleted file mode 100644 index 90bcf91a..00000000 --- a/sdks/go/bindings/prebuilt_windows_amd64.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build cgo && windows && amd64 - -package mosscore - -/* -#cgo CFLAGS: -I${SRCDIR}/include -#cgo LDFLAGS: ${SRCDIR}/lib/windows-amd64/moss.lib -*/ -import "C" diff --git a/sdks/go/bindings/stub.go b/sdks/go/bindings/stub.go index 495703ee..1f1d4071 100644 --- a/sdks/go/bindings/stub.go +++ b/sdks/go/bindings/stub.go @@ -1,4 +1,4 @@ -//go:build !cgo || !((linux && (amd64 || arm64)) || (darwin && arm64) || (windows && amd64)) +//go:build !cgo || !((linux && (amd64 || arm64)) || (darwin && arm64)) package mosscore diff --git a/sdks/go/sdk/README.md b/sdks/go/sdk/README.md index 747b1779..c4cfa273 100644 --- a/sdks/go/sdk/README.md +++ b/sdks/go/sdk/README.md @@ -20,6 +20,8 @@ The Go SDK now has two layers: ## Current limitations - CGO and a C compiler are required for full SDK functionality (`CGO_ENABLED=1`) +- Native bindings support Linux (`amd64`, `arm64`) and Apple Silicon macOS + (`arm64`); unsupported platforms use the bindings-unavailable stub - cloud query fallback supports `TopK` and caller-provided embeddings; `Alpha` and `Filter` require a locally loaded index - `LoadIndexOptions.CachePath` is not exposed by the current `libmoss` C API yet @@ -31,7 +33,8 @@ go run github.com/usemoss/moss/sdks/go/tools/install@latest --vendor ``` The install tool downloads the static `libmoss` library for your platform. See -[`../bindings/README.md`](../bindings/README.md) for Windows toolchain notes. +[`../bindings/README.md`](../bindings/README.md) for supported platforms and +toolchain notes. The explicit `--vendor` option vendors the SDK so CGO can link the downloaded library from a writable directory; commit `vendor/` if your project commits vendored dependencies. Run it after your application imports the SDK so the diff --git a/sdks/go/tools/install/main.go b/sdks/go/tools/install/main.go index 66536a19..4c04ad51 100644 --- a/sdks/go/tools/install/main.go +++ b/sdks/go/tools/install/main.go @@ -47,10 +47,6 @@ var platforms = []platform{ id: "darwin-arm64", triple: "aarch64-apple-darwin", libFile: "libmoss.a", srcLib: "lib/libmoss.a", }, - { - id: "windows-amd64", triple: "x86_64-pc-windows-msvc", - libFile: "moss.lib", srcLib: "lib/moss.lib", - }, } func main() { @@ -238,8 +234,6 @@ func currentPlatformID() (string, error) { return "linux-arm64", nil case "darwin/arm64": return "darwin-arm64", nil - case "windows/amd64": - return "windows-amd64", nil default: return "", fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH) } From 2389b7c84b99454589e05c9ee52efef329979303 Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Wed, 29 Jul 2026 21:40:28 -0700 Subject: [PATCH 12/15] fix(go): harden release and workspace installs --- .github/workflows/publish-go-sdk.yml | 14 +++++++-- sdks/go/bindings/README.md | 3 ++ sdks/go/sdk/README.md | 1 + sdks/go/tools/install/main.go | 43 ++++++++++++++++++++++++---- sdks/go/tools/install/main_test.go | 29 +++++++++++++++++++ 5 files changed, 82 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish-go-sdk.yml b/.github/workflows/publish-go-sdk.yml index 45622882..ac68ee1a 100644 --- a/.github/workflows/publish-go-sdk.yml +++ b/.github/workflows/publish-go-sdk.yml @@ -22,6 +22,9 @@ concurrency: jobs: publish: runs-on: ubuntu-latest + env: + C_SDK_VERSION: ${{ github.event.inputs.c_sdk_version }} + GO_VERSION: ${{ github.event.inputs.go_version }} steps: - uses: actions/checkout@v4 @@ -37,7 +40,14 @@ jobs: - name: Update bindings native version metadata shell: bash run: | - C_SDK_VERSION="${{ github.event.inputs.c_sdk_version }}" + if [[ ! "${C_SDK_VERSION}" =~ ^c-sdk-v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "c_sdk_version must be a c-sdk-vMAJOR.MINOR.PATCH tag" >&2 + exit 1 + fi + if [[ ! "${GO_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "go_version must be a vMAJOR.MINOR.PATCH tag" >&2 + exit 1 + fi NATIVE_VERSION="${C_SDK_VERSION#c-sdk-v}" cat > sdks/go/bindings/version.go < Date: Wed, 29 Jul 2026 22:21:27 -0700 Subject: [PATCH 13/15] fix(go): validate releases and atomic tags --- .github/workflows/publish-go-sdk.yml | 9 ++++++ sdks/go/scripts/publish-sdk-module-tags.sh | 34 +++++++++++++++++----- sdks/go/tools/install/main.go | 22 +++++++------- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/.github/workflows/publish-go-sdk.yml b/.github/workflows/publish-go-sdk.yml index ac68ee1a..137ca708 100644 --- a/.github/workflows/publish-go-sdk.yml +++ b/.github/workflows/publish-go-sdk.yml @@ -59,5 +59,14 @@ jobs: const NativeLibReleaseTag = "${C_SDK_VERSION}" EOF + - name: Verify selected C SDK release + working-directory: sdks/go + run: go run ./tools/install --all --release "$C_SDK_VERSION" + + - name: Verify native Go SDK build + run: | + (cd sdks/go/bindings && CGO_ENABLED=1 go build .) + (cd sdks/go/sdk && CGO_ENABLED=1 go build .) + - name: Publish source-only module tags run: ./sdks/go/scripts/publish-sdk-module-tags.sh "$GO_VERSION" diff --git a/sdks/go/scripts/publish-sdk-module-tags.sh b/sdks/go/scripts/publish-sdk-module-tags.sh index 3f6cb97f..3e5dc242 100755 --- a/sdks/go/scripts/publish-sdk-module-tags.sh +++ b/sdks/go/scripts/publish-sdk-module-tags.sh @@ -18,6 +18,23 @@ REPO_ROOT="$(cd "${ROOT_DIR}/../.." && pwd)" cd "${REPO_ROOT}" +TAGS=( + "sdks/go/bindings/${VERSION}" + "sdks/go/sdk/${VERSION}" + "sdks/go/tools/install/${VERSION}" +) + +for tag in "${TAGS[@]}"; do + if git rev-parse --verify --quiet "refs/tags/${tag}" >/dev/null; then + echo "Refusing to overwrite existing local tag ${tag}" >&2 + exit 1 + fi + if [[ -n "$(git ls-remote --tags "${REMOTE}" "refs/tags/${tag}")" ]]; then + echo "Refusing to overwrite existing remote tag ${tag}" >&2 + exit 1 + fi +done + "${ROOT_DIR}/scripts/bump-module-versions.sh" "${VERSION}" git add sdks/go/bindings/go.mod sdks/go/sdk/go.mod sdks/go/bindings/version.go @@ -28,11 +45,14 @@ if ! git diff --cached --quiet; then git commit -m "chore(go): publish bindings and sdk ${VERSION}" fi -for tag in \ - "sdks/go/bindings/${VERSION}" \ - "sdks/go/sdk/${VERSION}" \ - "sdks/go/tools/install/${VERSION}"; do - git tag -f "${tag}" - git push "${REMOTE}" "${tag}" - echo "Published ${tag}" +for tag in "${TAGS[@]}"; do + git tag "${tag}" done + +REFS=("${TAGS[@]/#/refs/tags/}") +if ! git push --atomic "${REMOTE}" "${REFS[@]}"; then + git tag -d "${TAGS[@]}" >/dev/null + exit 1 +fi + +printf 'Published %s\n' "${TAGS[@]}" diff --git a/sdks/go/tools/install/main.go b/sdks/go/tools/install/main.go index 7b5286f2..b256c258 100644 --- a/sdks/go/tools/install/main.go +++ b/sdks/go/tools/install/main.go @@ -115,17 +115,23 @@ func resolveWritableBindingsDir(explicit string, allowVendor bool) (string, erro return "", errors.New("the downloaded bindings module is read-only; run `go mod vendor` first, or rerun moss install with --vendor to allow it to regenerate vendor/") } - goMod, err := goEnv("GOMOD") - if err != nil || goMod == os.DevNull || goMod == "" { - return "", fmt.Errorf("the downloaded bindings module is read-only; run this command from a Go module that imports the Moss SDK") + goWork, err := goEnv("GOWORK") + if err != nil { + return "", fmt.Errorf("detect Go workspace: %w", err) + } + if goWork == "" || goWork == "off" { + goMod, err := goEnv("GOMOD") + if err != nil || goMod == os.DevNull || goMod == "" { + return "", fmt.Errorf("the downloaded bindings module is read-only; run this command from a Go module that imports the Moss SDK") + } } - if err := vendorDependencies(); err != nil { + if err := vendorDependencies(goWork); err != nil { return "", err } root, err = bindingsDirFromGoList("-mod=vendor") if err != nil { - return "", fmt.Errorf("locate vendored Moss bindings after `go mod vendor`: %w", err) + return "", fmt.Errorf("locate vendored Moss bindings after vendoring dependencies: %w", err) } if !isWritableDir(root) { return "", fmt.Errorf("vendored bindings directory %s is not writable", root) @@ -133,11 +139,7 @@ func resolveWritableBindingsDir(explicit string, allowVendor bool) (string, erro return root, nil } -func vendorDependencies() error { - goWork, err := goEnv("GOWORK") - if err != nil { - return fmt.Errorf("detect Go workspace: %w", err) - } +func vendorDependencies(goWork string) error { args := vendorArgs(goWork) out, err := exec.Command("go", args...).CombinedOutput() if err != nil { From af9e9615fa4b50b51f9c00009ec1729527d355de Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Wed, 29 Jul 2026 23:15:18 -0700 Subject: [PATCH 14/15] fix(go): protect module cache installs --- .github/workflows/go-sdk-test.yml | 11 +++++++++++ sdks/go/sdk/go.mod | 2 ++ sdks/go/tools/install/main.go | 19 ++++++++++++++++++- sdks/go/tools/install/main_test.go | 13 +++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go-sdk-test.yml b/.github/workflows/go-sdk-test.yml index a2ec8a8d..f81415f7 100644 --- a/.github/workflows/go-sdk-test.yml +++ b/.github/workflows/go-sdk-test.yml @@ -30,6 +30,17 @@ jobs: CGO_ENABLED: '0' run: go test ./... + - name: Verify SDK standalone module mode + working-directory: sdks/go/sdk + env: + CGO_ENABLED: '0' + GOWORK: 'off' + run: go test ./... + + - name: Run native installer tests + working-directory: sdks/go/tools/install + run: go test ./... + bindings-smoke: runs-on: ubuntu-latest steps: diff --git a/sdks/go/sdk/go.mod b/sdks/go/sdk/go.mod index e0f64675..9766bf29 100644 --- a/sdks/go/sdk/go.mod +++ b/sdks/go/sdk/go.mod @@ -3,3 +3,5 @@ module github.com/usemoss/moss/sdks/go/sdk go 1.22.2 require github.com/usemoss/moss/sdks/go/bindings v0.1.2 + +replace github.com/usemoss/moss/sdks/go/bindings => ../bindings diff --git a/sdks/go/tools/install/main.go b/sdks/go/tools/install/main.go index b256c258..8f3ee621 100644 --- a/sdks/go/tools/install/main.go +++ b/sdks/go/tools/install/main.go @@ -102,7 +102,7 @@ func resolveWritableBindingsDir(explicit string, allowVendor bool) (string, erro if err != nil { return "", err } - if isWritableDir(root) { + if isWritableDir(root) && (explicit != "" || strings.TrimSpace(os.Getenv("MOSS_BINDINGS_DIR")) != "" || !isModuleCacheDir(root)) { return root, nil } if explicit != "" || strings.TrimSpace(os.Getenv("MOSS_BINDINGS_DIR")) != "" { @@ -139,6 +139,23 @@ func resolveWritableBindingsDir(explicit string, allowVendor bool) (string, erro return root, nil } +func isModuleCacheDir(dir string) bool { + cacheDir, err := goEnv("GOMODCACHE") + if err != nil || cacheDir == "" { + return false + } + cacheDir, err = filepath.Abs(cacheDir) + if err != nil { + return false + } + dir, err = filepath.Abs(dir) + if err != nil { + return false + } + rel, err := filepath.Rel(cacheDir, dir) + return err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) +} + func vendorDependencies(goWork string) error { args := vendorArgs(goWork) out, err := exec.Command("go", args...).CombinedOutput() diff --git a/sdks/go/tools/install/main_test.go b/sdks/go/tools/install/main_test.go index aba1215a..22276ffd 100644 --- a/sdks/go/tools/install/main_test.go +++ b/sdks/go/tools/install/main_test.go @@ -81,3 +81,16 @@ func TestCopyFileDoesNotTruncateDestinationOnSourceError(t *testing.T) { t.Fatalf("destination = %q, want known-good", contents) } } + +func TestIsModuleCacheDir(t *testing.T) { + cacheDir := t.TempDir() + t.Setenv("GOMODCACHE", cacheDir) + + inside := filepath.Join(cacheDir, "github.com", "usemoss", "moss") + if !isModuleCacheDir(inside) { + t.Fatalf("isModuleCacheDir(%q) = false, want true", inside) + } + if isModuleCacheDir(filepath.Dir(cacheDir)) { + t.Fatal("isModuleCacheDir() = true outside the module cache") + } +} From d318f47e2fac3cf5dd0168b6921d543f82d46b14 Mon Sep 17 00:00:00 2001 From: anirudh-makuluri Date: Thu, 30 Jul 2026 13:21:30 -0700 Subject: [PATCH 15/15] updated README.md to reflect the correct path --- sdks/go/sdk/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/go/sdk/README.md b/sdks/go/sdk/README.md index 98ee96e0..b41804d6 100644 --- a/sdks/go/sdk/README.md +++ b/sdks/go/sdk/README.md @@ -151,7 +151,7 @@ Runnable examples live here: Run them from the monorepo: ```bash -../../sdks/go/scripts/link_dev_lib.sh c-sdk-v0.9.0 +../scripts/link_dev_lib.sh c-sdk-v0.9.0 cd ../../../examples/go go run ./basic ```