Generate Go and Python packages, OpenAPI specs, and FileDescriptorSet files from S3M protobuf source repositories.
Builds one or more proto source repositories (each containing a MODULE file and a proto/ tree), writing generated artifacts to the --out path. Use selectors to choose which packages and which output formats to produce.
Targets:
go: a Go module rooted at the source'sGO_PACKAGE, with.pb.gofiles (plus.pb.gw.gofor any package that ships aservice.yaml) and a generatedgo.mod(tidied viago mod tidy).py: agrpcio-based Python package with asetup.py, suitable forpip install.pyb: a BetterProto-based Python package with asetup.py, suitable forpip install.oas: anopenapi.yamlrendered viaprotoc-gen-oas.
--descriptor-out can be combined with any combination of targets to emit a protobuf FileDescriptorSet covering everything that was selected.
pip install git+https://github.com/olcf/s3m-protobuild.gitIf the resulting s3m-protobuild command isn't on your PATH (common when running pip outside an active venv), you can try:
uv tool install git+https://github.com/olcf/s3m-protobuild.gitmkdir /tmp/s3m-apis-pyb-build && cd /tmp/s3m-apis-pyb-build
s3m-protobuild setup python --venv .venv
s3m-protobuild build \
--source git+https://github.com/olcf/s3m-apis.git@main \
--venv .venv \
--out ./s3m-apis-pyb \
common:pyb status:pyb streaming:pybThe setup command provisions a Python environment in .venv/. The build command clones github.com/olcf/s3m-apis and generates a BetterProto Python package containing the common, status, and streaming proto packages at ./s3m-apis-pyb.
Note: pyb builds use betterproto, so client/server RPC calls are async. See Why prefer pyb to understand the tradeoffs.
mkdir /tmp/s3m-apis-go-build && cd /tmp/s3m-apis-go-build
s3m-protobuild setup python --venv .venv
s3m-protobuild setup go --go .go
s3m-protobuild build \
--source git+https://github.com/olcf/s3m-apis.git@main \
--venv .venv --go .go \
--out ./s3m-apis-go \
common:go status:go streaming:goThe two setup commands provision Python and Go environments in .venv/ and .go/. The third command clones github.com/olcf/s3m-apis and generates a Go module containing the common, status, and streaming proto packages at ./s3m-apis-go.
cd /tmp/s3m-apis-pyb-build
python -m venv venv
source venv/bin/activate
pip install ./s3m-apis-pyb
export S3M_ENDPOINT=https://s3m.olcf.ornl.gov
export S3M_TOKEN=<...>Create test.py:
import asyncio
import os
from s3m_apis_betterproto.clientfactory import S3MClientFactory
from s3m_apis_betterproto.status import v1alpha
async def main():
factory = S3MClientFactory(os.environ["S3M_ENDPOINT"], os.environ["S3M_TOKEN"])
try:
status = factory.create_client(v1alpha.StatusStub)
resource = await status.get_resource(
v1alpha.GetResourceRequest(resource_name="defiant")
)
print(resource)
finally:
factory.close()
asyncio.run(main())Then run it:
python test.pycd /tmp/s3m-apis-go-build
mkdir s3m-go-client && cd s3m-go-client
go mod init example.com/s3m-go-client
go mod edit -replace github.com/olcf/s3m-apis=../s3m-apis-go
export S3M_ENDPOINT=https://s3m.olcf.ornl.gov
export S3M_TOKEN=<...>Create main.go:
package main
import (
"context"
"log"
"os"
"time"
"github.com/olcf/s3m-apis/pkg/s3mutil"
status "github.com/olcf/s3m-apis/status/v1alpha"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := s3mutil.NewS3MConn(os.Getenv("S3M_ENDPOINT"), os.Getenv("S3M_TOKEN"))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := status.NewStatusClient(conn)
resource, err := client.GetResource(ctx, &status.GetResourceRequest{
ResourceName: "defiant",
})
if err != nil {
log.Fatal(err)
}
log.Printf("%+v", resource)
}Then resolve dependencies and run it:
go mod tidy
go run main.goprotoc must already be in your PATH. To build Go packages, go must be in your PATH. s3m-protobuild setup will not install either. Run s3m-protobuild env to see anything missing and install hints.
s3m-protobuild setup installs the various required generator plugins. It only mutates local toolchain directories (.go, .venv, if specified), and never alters system Python/Go toolchains or dotfiles.
s3m-protobuild setup python --venv PATH: creates the virtualenv atPATHif it doesn't exist, then installs the pinned Python generator dependencies into it.s3m-protobuild setup go --go PATH: installs the pinned Go protoc plugins intoPATH/bin, keepingGOPATH, the module cache, and the build cache all underPATH.
Both subcommands can be invoked together: s3m-protobuild setup python go --venv .venv --go .go
s3m-protobuild build \
--venv PATH --go PATH \
--source SOURCE [--source SOURCE ...] \
--out PATH \
SELECTOR [SELECTOR ...]--sourcemay be repeated to combine multiple proto repositories in one build (see Multi-source builds).--venvand--goare only needed to reference local toolchains (like froms3m-protobuild setup).--outsets the output root and is always required. One or more selectors must be provided.- Pass
--include-sourceto copy.protoandservice.yamlsource files to the output alongside the generated code. - Pass
--unsafe-overwriteto letbuildreplace a non-empty--outthat wasn't produced by a previouss3m-protobuildrun (as marked bybuild.info).
A selector has the shape [MODULE/]package[/version]:target. The four valid targets are go, py, pyb, and oas. A selector with no version is a family selector and expands to every v* subdirectory under the package; a selector with a version pins exactly that directory. MODULE/ is only needed when the same proto package name exists in more than one --source.
common:go # every v* directory under common, Go target
slurm/v0042:go # pin one version
slurm:py # all v* directories under slurm, grpcio Python
status:oas # OpenAPI spec from status
s3m-apis/streaming:go # disambiguate when multiple sources expose `streaming`
--source accepts either a local filesystem path or a git+<scheme>://host/path[@REF] URL (pip style). Remote sources are cloned into a temp directory that is removed when the build exits.
SSH --source example: git+ssh://git@github.com/olcf/s3m-apis.git@main
Every source root must contain a MODULE file, a simple KEY=VALUE config. Minimal example:
MODULE=s3m-apis
VERSION=0.1.0
GO_PACKAGE=github.com/olcf/s3m-apis
Required:
MODULE=: lowercase kebab-case identifierVERSION=:X.Y.ZorX.Y.ZrcNGO_PACKAGE=: Go import path forgotargets
Optional:
PY_PACKAGE=: Python import name for thepy(grpcio) target. Defaults to<MODULE>_grpcio.PYB_PACKAGE=: same, forpyb(BetterProto). Defaults to<MODULE>_betterproto.FLATTEN_NAMESPACES=: comma-separated dotted namespace paths to flatten into the package root (pybonly). Example:FLATTEN_NAMESPACES=olcf.s3mmoves everything underolcf/s3m/to the package root.
Python packages use snake_case import paths and kebab-case distribution names. s3m-protobuild derives both forms from values of MODULE=, PY_PACKAGE=, and PYB_PACKAGE=.
--out sets the output root. After a build, a typical Go output looks like:
./s3m-apis-go/
build.info
go.mod
common/v1alpha/*.pb.go
status/v1alpha/*.pb.go
streaming/v1alpha/*.pb.go
And a BetterProto Python output:
./s3m-apis-pyb/
build.info
setup.py
s3m_apis_betterproto/
__init__.py
common/...
status/...
Rules:
- You may not build
pyandpybtargets in the same call. - Every
go,py, andpybbuild selector must resolve to the same source module (otherwise, generated module/package names would compete;oasoutputs are exempt because OpenAPI specs are keyed by API paths). - The
--outroot may not equal or sit inside any--sourceroot. - Output to
.git,.venv/venv,.go/go,proto,src,internal,s3m-protobuild, ands3m_protobuilddirectories is disallowed for safety.
Every build writes a build.info manifest with build info and toolchain metadata. (s3m-protobuild also uses this as a marker to decide whether the directory is a previous output safe to overwrite.)
s3m-protobuild clean --out ./s3m-apis-goclean deletes the output root, but only if it contains a build.info marker, so it can't be pointed at an arbitrary directory. Rebuilds delete existing --out roots with build.info markers, so do not edit files in output roots or add new files to them! To allow outputting to an existing non-empty unmanaged directory (no build.info file), pass --unsafe-overwrite.
Sometimes it is necessary to reference protos from other repos. s3m-protobuild supports sourcing multiple proto repos to resolve cross-repo dependencies. This enables the OLCF internal/closed-source S3M APIs to import protos from the public/open-source S3M API repos, and allows you to write your own extension repos.
When building multiple S3M Go proto packages that rely on each other, generated go.mod files need replace directives that redirect the imported package's import path (e.g., github.com/olcf/s3m-apis) to a local path (e.g., ../s3m-apis/). The --go-mod-replace flag can add these for you.
s3m-protobuild build \
--venv .venv --go .go \
--source git+https://github.com/olcf/s3m-apis.git@main \
--out ./s3m-apis \
common:go status:go streaming:go
s3m-protobuild build \
--venv .venv --go .go \
--source ../s3m-apis-myextensions \
--source git+https://github.com/olcf/s3m-apis.git@main \
--go-mod-replace github.com/olcf/s3m-apis=../s3m-apis \
--out ./s3m-apis-myextensions \
koas:go streamingadmin:go--go-mod-replace MODULE=PATH adds a replace MODULE => PATH directive to the generated go.mod. PATH is written verbatim; Go resolves it relative to the generated go.mod (so ../s3m-apis above means "next to ./s3m-apis-myextensions"). The local replace is what makes the extension compile against the artifact you just generated rather than trying to pull the package (e.g., from github.com/olcf/s3m-apis). When go mod tidy complains about a missing import that another --source provides, s3m-protobuild will suggest a --go-mod-replace flag.
The py (grpcio) and pyb (BetterProto) targets produce installable distributions with a generated setup.py. The package import roots default to <MODULE>_grpcio (for py) and <MODULE>_betterproto (for pyb); set PY_PACKAGE= or PYB_PACKAGE= in your source's MODULE file to override.
We recommend the pyb target for most users. BetterProto generates pure-Python dataclasses with full type hints; its messages can be constructed, read, and serialized like ordinary Python objects:
from s3m_apis_betterproto.status import v1alpha
req = v1alpha.GetResourceRequest(resource_name="kestrel")
print(req.to_json())The py target generates classes from grpcio-tools. Those classes are less ergonomic and Pythonic, and require mypy-protobuf stubs to type-check cleanly.
The main potential pain point of pyb is that it uses grpclib for transport, so generated service stubs are asyncio-first. Async can look daunting at first, but only client/server RPC calls need to be async; the rest of your application can stay synchronous.
py targets (using grpcio) are synchronous by default; async callers must opt in through its grpc.aio companion API.
BetterProto builds are pure Python with no C extension requirement. py targets require grpcio's native runtime.
The oas target writes a single openapi.yaml at the --out root. --descriptor-out PATH writes a FileDescriptorSet at PATH (relative to --out; absolute paths and .. are rejected) covering every package that any selector resolved, regardless of which targets were requested.
--descriptor-imports and --descriptor-source-info pass --include_imports and --include_source_info through to protoc.
s3m-protobuild build \
--venv .venv --go .go \
--source git+https://github.com/olcf/s3m-apis.git@main \
--out ./s3m-apis-specs \
--descriptor-out s3m-apis.desc \
--descriptor-imports \
status:oas streaming:oass3m-protobuild env prints the discovered versions of every tool the builder calls, and emits a To fix: block with s3m-protobuild setup commands and OS package/toolchain hints. Pass --venv and --go to also check tools in local toolchains.
s3m-protobuild env [--venv .venv --go .go]Dual-licensed under the MIT License (LICENSE-MIT) or the Apache License 2.0 (LICENSE-APACHE), at your option.