440e7cf03d
Code review of the CLI-39 branch caught an Important gap: Contracts.csproj was left at the already-published 0.1.2 while the .NET Client moved to 0.2.0. Invoke-PackDotnet in scripts/pack-clients.ps1 packs and publishes both ZB.MOM.WW.MxGateway.Contracts and .Client through the same -Publish loop, and the new collision guard runs every nupkg it finds through Assert-GiteaPackageNotPublished. Left as-is, the next real .NET publish would pack Contracts at 0.1.2, the guard would correctly refuse to republish it, and the loop would abort mid-way with Client (alphabetically first) possibly already pushed -- the two packages permanently out of lockstep. - src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj: <Version> 0.1.2 -> 0.2.0, matching the .NET Client (they have always released together). - src/Directory.Build.props: corrected a comment that was now stale -- it claimed the repo-wide 0.1.2 default was kept to match the Contracts package, which is no longer true now that Contracts.csproj overrides it. The <Version> value itself is unchanged; Server/Worker/Tests staying at 0.1.2 is a separate, not-yet-made decision, out of scope for CLI-39. - docs/ClientPackaging.md: Contracts.csproj added as a fifth manifest in the Versioning section, with the near-miss recorded. Also hardened scripts/pack-clients.ps1 per the same review: the Python (pyproject.toml) and Rust (Cargo.toml) version-extraction regexes now scope to the [project]/[package] section header instead of matching the first "version = ..." line anywhere in the file (Cargo.toml has an identical second one under [workspace.package] -- matching whichever came first was luck of ordering, not correctness). One-line comment added on the nuget filename-parse regex. Verified live against the real Gitea registry: Contracts and Client both still refuse at 0.1.2 and both now pass at 0.2.0, including running the actual Invoke-PackDotnet filename-parse-then-guard logic against two freshly packed real .nupkg files. dotnet build of Contracts.csproj and the client slnx both clean. No publish performed.
344 lines
14 KiB
Markdown
344 lines
14 KiB
Markdown
# Client Packaging
|
|
|
|
This document defines the clean-checkout commands for building, packaging, and
|
|
running the official MXAccess Gateway clients. Use the tool paths and versions
|
|
in [Toolchain Links](./ToolchainLinks.md) when a command is missing from
|
|
`PATH`.
|
|
|
|
## Shared Inputs
|
|
|
|
All clients generate bindings from the shared protobuf files under
|
|
`src/ZB.MOM.WW.MxGateway.Contracts/Protos`. Regenerate the published client descriptor
|
|
after changing either `.proto` file or `clients/proto/proto-inputs.json`:
|
|
|
|
```powershell
|
|
scripts/publish-client-proto-inputs.ps1
|
|
scripts/publish-client-proto-inputs.ps1 -Check
|
|
```
|
|
|
|
Generated protobuf and gRPC files are generator output. Do not edit them by
|
|
hand.
|
|
|
|
## Environment
|
|
|
|
The examples use these common variables:
|
|
|
|
```powershell
|
|
$env:MXGATEWAY_ENDPOINT = 'localhost:5000'
|
|
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
|
|
$env:MXGATEWAY_TEST_ITEM = 'TestObject.TestInt'
|
|
```
|
|
|
|
Use plaintext only for a local gateway. Use TLS when the gateway crosses a
|
|
machine boundary or uses a production certificate.
|
|
|
|
## Versioning
|
|
|
|
Every client's version lives in its own manifest: `clients/rust/Cargo.toml`
|
|
(`[package]` and `[workspace.package]`, both must match — `crates/mxgw-cli`
|
|
inherits via `version.workspace = true`), `clients/python/pyproject.toml`
|
|
(`[project].version`) and `clients/python/src/zb_mom_ww_mxgateway/version.py`
|
|
(`__version__`, must match `pyproject.toml`), `clients/go/mxgateway/version.go`
|
|
(`ClientVersion`), `clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj`
|
|
(`<Version>`), and `clients/java/build.gradle` (`subprojects { version = ... }`,
|
|
mirrored by the hand-maintained `MxGatewayClientVersion.CLIENT_VERSION`
|
|
constant — the two have drifted before and there is no build-time link
|
|
between them, so bump both together).
|
|
|
|
`src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj`
|
|
(`<Version>`) is a fifth, easy-to-miss manifest: it is not itself a
|
|
language client, but `Invoke-PackDotnet` in `scripts/pack-clients.ps1`
|
|
packs and publishes it in lockstep with the .NET Client (both
|
|
`ZB.MOM.WW.MxGateway.*` nupkgs go through the same `-Publish` loop), and
|
|
Contracts and the .NET Client have always released at the same version.
|
|
Bump Contracts' `<Version>` alongside the .NET Client's — leaving it behind
|
|
means the next `-Publish` packs a stale Contracts version, the collision
|
|
guard below correctly refuses to re-publish it, and the loop aborts
|
|
mid-way with the Client possibly already pushed (nupkgs are enumerated
|
|
alphabetically, and `Client` sorts before `Contracts`). This is distinct
|
|
from `src/Directory.Build.props`'s repo-wide `<Version>` default, which
|
|
stamps the Server/Worker/test assemblies and is not part of the published
|
|
client package set — see the comment there.
|
|
|
|
**Bump the version before every publish, never after.** A Gitea package feed
|
|
rejects re-uploading an existing name+version, and `scripts/pack-clients.ps1`
|
|
enforces this before it ever attempts a push: each per-language `-Publish`
|
|
step queries the Gitea package API
|
|
(`GET /api/v1/packages/dohertj2/{type}/{name}/{version}`) for the version
|
|
about to be published and aborts with a clear error if it already exists —
|
|
the script never force-overwrites a published artifact. `scripts/tag-go-module.ps1`
|
|
carries the equivalent guard for the Go module: it refuses to create a
|
|
`clients/go/vX.Y.Z` tag unless `clients/go/mxgateway/version.go`'s
|
|
`ClientVersion` already equals `X.Y.Z` (CLI-21/CLI-39), so a forgotten
|
|
version bump fails the tag instead of shipping a mismatched module.
|
|
|
|
As of 2026-08-07 (CLI-39) all five clients — plus `ZB.MOM.WW.MxGateway.Contracts`,
|
|
which releases in lockstep with the .NET Client — moved to **0.2.0**,
|
|
converging on one number after four of the five had drifted onto the
|
|
*already-published* 0.1.2/0.1.1 while their public APIs kept changing
|
|
underneath it (see `archreview/2026-07-12/remediation/50-clients.md` CLI-39).
|
|
A code-review follow-up on the same branch caught that the initial CLI-39
|
|
pass bumped the .NET Client but left `Contracts.csproj` at 0.1.2 — since
|
|
both publish through the same `Invoke-PackDotnet` `-Publish` loop, that
|
|
would have made the very next `.NET` publish abort on the new collision
|
|
guard partway through (Client already pushed, Contracts refused as a
|
|
re-publish of the already-published 0.1.2). Fixed in the same branch.
|
|
Verified against the live Gitea package API at that time: `nuget` had
|
|
`ZB.MOM.WW.MxGateway.Client` and `.Contracts` published through 0.1.2; `pypi` (`zb-mom-ww-mxaccess-gateway-client`)
|
|
and `cargo` (`zb-mom-ww-mxgateway-client`) had only reached 0.1.1 despite their
|
|
source pinning 0.1.2; **`maven`
|
|
(`com.zb.mom.ww.mxgateway:zb-mom-ww-mxgateway-client`) had already published
|
|
0.2.0 on 2026-06-26** — before the CLI-37/38/40/41 conformance fixes changed
|
|
the client's observable behavior (`category`-based status validation,
|
|
`hresult < 0`, exact-secret redaction, typed malformed-reply errors). Reusing
|
|
0.2.0 for the conformant Java build would have labeled two different APIs
|
|
with the same coordinate, so **Java is the one exception: it shipped as
|
|
0.2.1**, not 0.2.0. Operators publishing a future release must re-check the
|
|
target version against the live registry before assuming any of these
|
|
numbers are still unclaimed — the guards above do this automatically at
|
|
publish time, but a version bump in the source is still a manual step per
|
|
client.
|
|
|
|
## .NET
|
|
|
|
The .NET client uses .NET 10 and references
|
|
`src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj` for generated C# contract
|
|
types. `clients/dotnet/generated` remains reserved for client-local generator
|
|
output if the client later decouples from the contracts project.
|
|
|
|
Regenerate the generated C# contract types:
|
|
|
|
```powershell
|
|
dotnet build src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj
|
|
```
|
|
|
|
Build and test from the repository root:
|
|
|
|
```powershell
|
|
dotnet build clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx
|
|
dotnet test clients/dotnet/ZB.MOM.WW.MxGateway.Client.slnx --no-build
|
|
```
|
|
|
|
Create local package artifacts:
|
|
|
|
```powershell
|
|
$dotnetPackageOutput = Join-Path (Get-Location) 'artifacts/clients/dotnet'
|
|
dotnet pack clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj -c Release -p:PackageOutputPath="$dotnetPackageOutput"
|
|
dotnet publish clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/ZB.MOM.WW.MxGateway.Client.Cli.csproj -c Release -o artifacts/clients/dotnet/mxgw-dotnet
|
|
```
|
|
|
|
Run the CLI from source:
|
|
|
|
```powershell
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- version --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- smoke --endpoint "http://$env:MXGATEWAY_ENDPOINT" --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
|
|
dotnet run --project clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli -- smoke --endpoint "https://mxgateway.example.local:5001" --tls --ca-file C:\certs\mxgateway-ca.pem --server-name mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
|
|
```
|
|
|
|
## Go
|
|
|
|
The Go client is the module
|
|
`gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go`.
|
|
Generated Go files live under `clients/go/internal/generated`.
|
|
|
|
Regenerate the Go bindings:
|
|
|
|
```powershell
|
|
Push-Location clients/go
|
|
./generate-proto.ps1
|
|
Pop-Location
|
|
```
|
|
|
|
Build and test from `clients/go`:
|
|
|
|
```powershell
|
|
Push-Location clients/go
|
|
go test ./...
|
|
go build ./...
|
|
go vet ./...
|
|
Pop-Location
|
|
```
|
|
|
|
Create a local CLI executable:
|
|
|
|
```powershell
|
|
Push-Location clients/go
|
|
New-Item -ItemType Directory -Force ../../artifacts/clients/go | Out-Null
|
|
go build -o ../../artifacts/clients/go/mxgw-go.exe ./cmd/mxgw-go
|
|
Pop-Location
|
|
```
|
|
|
|
Run the CLI from source:
|
|
|
|
```powershell
|
|
Push-Location clients/go
|
|
go run ./cmd/mxgw-go version -json
|
|
go run ./cmd/mxgw-go smoke -endpoint $env:MXGATEWAY_ENDPOINT -plaintext -api-key-env MXGATEWAY_API_KEY -item $env:MXGATEWAY_TEST_ITEM -json
|
|
go run ./cmd/mxgw-go smoke -endpoint mxgateway.example.local:5001 -ca-cert C:\certs\mxgateway-ca.pem -server-name-override mxgateway.example.local -api-key-env MXGATEWAY_API_KEY -item $env:MXGATEWAY_TEST_ITEM -json
|
|
Pop-Location
|
|
```
|
|
|
|
## Rust
|
|
|
|
The Rust workspace builds the `mxgateway-client` library crate and the `mxgw`
|
|
CLI crate. `build.rs` generates `tonic` and `prost` modules into Cargo build
|
|
output on each build that needs updated protobuf output.
|
|
|
|
`build.rs` resolves its `.proto` inputs repo-path-first, then vendored: it
|
|
prefers the canonical protos under `src/ZB.MOM.WW.MxGateway.Contracts/Protos`
|
|
so an in-repo edit is live immediately, and falls back to the copies vendored
|
|
into `clients/rust/protos/` only when the canonical directory is absent — the
|
|
case for a published crate unpacked outside this repo. The vendored copies
|
|
are declared in `Cargo.toml`'s `include` list, so `cargo package`/`cargo
|
|
publish` ship them inside the `.crate`, making the crate buildable standalone
|
|
with no access to the rest of the mxaccessgw repo. Any Contracts proto change
|
|
must refresh `clients/rust/protos/` in the same commit; `scripts/check-codegen.ps1`
|
|
Check 3 byte-compares the vendored copies against the canonical protos and
|
|
fails on drift. Because the vendored protos make a standalone build possible,
|
|
`cargo package`/`cargo publish` run **with** verification (no `--no-verify`) —
|
|
a `cargo package` that cannot build from the vendored tree alone would mean
|
|
the vendored copies are stale, and verification is what catches that before
|
|
publish.
|
|
|
|
Regenerate and compile Rust bindings:
|
|
|
|
```powershell
|
|
Push-Location clients/rust
|
|
cargo check --workspace
|
|
Pop-Location
|
|
```
|
|
|
|
Build and test from `clients/rust`:
|
|
|
|
```powershell
|
|
Push-Location clients/rust
|
|
cargo fmt --all --check
|
|
cargo test --workspace
|
|
cargo check --workspace
|
|
Pop-Location
|
|
```
|
|
|
|
Create local release artifacts:
|
|
|
|
```powershell
|
|
Push-Location clients/rust
|
|
cargo build --workspace --release
|
|
cargo install --path crates/mxgw-cli --locked --force
|
|
Pop-Location
|
|
```
|
|
|
|
Run the CLI from source:
|
|
|
|
```powershell
|
|
Push-Location clients/rust
|
|
cargo run -p mxgw-cli -- version --json
|
|
cargo run -p mxgw-cli -- smoke --endpoint "http://127.0.0.1:5000" --plaintext --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
|
|
cargo run -p mxgw-cli -- smoke --endpoint "https://mxgateway.example.local:5001" --tls --ca-file C:\certs\mxgateway-ca.pem --server-name-override mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
|
|
Pop-Location
|
|
```
|
|
|
|
## Python
|
|
|
|
The Python package is `zb-mom-ww-mxaccess-gateway-client`. Generated modules
|
|
live under `clients/python/src/zb_mom_ww_mxgateway/generated`.
|
|
|
|
Regenerate the Python bindings:
|
|
|
|
```powershell
|
|
Push-Location clients/python
|
|
./generate-proto.ps1
|
|
Pop-Location
|
|
```
|
|
|
|
Install, test, and build a wheel from `clients/python`:
|
|
|
|
```powershell
|
|
Push-Location clients/python
|
|
python -m pip install -e ".[dev]"
|
|
python -m pytest
|
|
python -m pip wheel . --no-deps --wheel-dir "$env:TEMP\mxgateway-python-wheel"
|
|
Pop-Location
|
|
```
|
|
|
|
Run the CLI from the editable install or with `python -m`:
|
|
|
|
```powershell
|
|
Push-Location clients/python
|
|
mxgw-py version --json
|
|
mxgw-py smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
|
|
mxgw-py smoke --endpoint mxgateway.example.local:5001 --tls --ca-file C:\certs\mxgateway-ca.pem --server-name-override mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json
|
|
python -m zb_mom_ww_mxgateway_cli version --json
|
|
Pop-Location
|
|
```
|
|
|
|
## Java
|
|
|
|
The Java workspace uses Gradle, Java 17, `zb-mom-ww-mxgateway-client`, and
|
|
`zb-mom-ww-mxgateway-cli`. The Gradle protobuf plugin writes generated Java protobuf and
|
|
gRPC sources under `clients/java/src/main/generated`.
|
|
|
|
Regenerate Java bindings:
|
|
|
|
```powershell
|
|
Push-Location clients/java
|
|
gradle :zb-mom-ww-mxgateway-client:generateProto
|
|
Pop-Location
|
|
```
|
|
|
|
Build and test from `clients/java`:
|
|
|
|
```powershell
|
|
Push-Location clients/java
|
|
gradle test
|
|
Pop-Location
|
|
```
|
|
|
|
Create local library and CLI artifacts:
|
|
|
|
```powershell
|
|
Push-Location clients/java
|
|
gradle :zb-mom-ww-mxgateway-client:jar :zb-mom-ww-mxgateway-cli:installDist
|
|
Pop-Location
|
|
```
|
|
|
|
Run the CLI through Gradle:
|
|
|
|
```powershell
|
|
Push-Location clients/java
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="version --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json"
|
|
gradle :zb-mom-ww-mxgateway-cli:run --args="smoke --endpoint mxgateway.example.local:5001 --ca-file C:\certs\mxgateway-ca.pem --server-name-override mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json"
|
|
Pop-Location
|
|
```
|
|
|
|
## Integration Tests
|
|
|
|
Client integration checks are opt-in because they need a live gateway and a
|
|
gateway host that can create MXAccess worker sessions. Set the common
|
|
environment before running a client smoke:
|
|
|
|
```powershell
|
|
$env:MXGATEWAY_INTEGRATION = '1'
|
|
$env:MXGATEWAY_ENDPOINT = 'localhost:5000'
|
|
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
|
|
$env:MXGATEWAY_TEST_ITEM = 'TestObject.TestInt'
|
|
$env:MXGATEWAY_TEST_CONTEXT = ''
|
|
$env:MXGATEWAY_TEST_WRITE_VALUE = '123'
|
|
```
|
|
|
|
Run the bounded `smoke` command for each client against the same item. The
|
|
smoke commands open a session, register a client name, add one item, advise it,
|
|
and close the session. The .NET and Python smoke commands also read a bounded
|
|
event stream; the Go, Rust, and Java smoke commands exercise the command path
|
|
and can be paired with their `stream-events` commands after a session is open.
|
|
|
|
Client-side cancellation or timeout stops waiting for the gateway response. It
|
|
does not abort an MXAccess COM call that is already executing on the worker STA.
|
|
|
|
## Related Documentation
|
|
|
|
- [Client Proto Generation](./ClientProtoGeneration.md)
|
|
- [Client Libraries Detailed Design](./ClientLibrariesDesign.md)
|
|
- [Client Behavior Fixtures](./ClientBehaviorFixtures.md)
|
|
- [Toolchain Links](./ToolchainLinks.md)
|