diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6a4ec9..4de0582 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,3 +44,29 @@ jobs: "$PY" scripts/vendor_binaries.py --only gomc-rest-linux-amd64 && "$PY" -m pytest -v ' + + # Real execution on Apple Silicon: confirms the ad-hoc-signed arm64 binary + # actually launches (Gatekeeper/signing behavior can't be judged by + # inspecting the binary offline). The Intel (amd64) binary is only verified + # offline (SHA-256 + Mach-O inspection) because GitHub's Intel macOS runners + # are not reliably schedulable here — they sat queued for 24h and were + # auto-cancelled. + test-macos: + runs-on: macos-14 # Apple Silicon + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # hatch-vcs derives the version from git tags + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install package and test deps + run: python -m pip install -e . pytest + + - name: Vendor the macOS server binary + run: python scripts/vendor_binaries.py --only gomc-rest-darwin-arm64 + + - name: Run tests + run: python -m pytest -v diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1507cbb..a0d9857 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,10 +1,12 @@ name: release -# Build one platform-specific wheel per OS (each bundling only its matching +# Build one platform-specific wheel per target (each bundling only its matching # gomc-rest binary) plus a binary-free sdist, and publish to PyPI on a version -# tag. The sdist is the client-only fallback: on platforms without a matching -# wheel (macOS, Windows arm64, glibc < 2.34), pip builds it so connect() still -# works (launch() then raises a clear "binary not found/unsupported" error). +# tag. All wheels cross-build on a single Linux runner (a wheel just zips the +# pre-built binary). The sdist is the client-only fallback: on platforms +# without a matching wheel (Windows arm64, glibc < 2.34, macOS < 12), pip +# builds it so connect() still works (launch() then raises a clear "binary not +# found/unsupported" error). on: push: tags: @@ -14,25 +16,33 @@ on: jobs: build-wheels: name: wheel (${{ matrix.plat }}) - runs-on: ${{ matrix.os }} + # A wheel just zips a pre-built binary and gets its platform tag forced by + # `wheel tags`, so the build host's OS/arch is irrelevant — everything + # cross-builds on one Linux runner (same idea as upstream gomc-rest, which + # cross-compiles all platforms from ubuntu). This avoids depending on + # scarce Windows/arm/macOS runners for releases. + runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - - os: windows-latest - asset: gomc-rest.exe + - asset: gomc-rest.exe plat: win_amd64 # amd64 binary is dynamically linked and needs GLIBC_2.34, so it must # be tagged manylinux_2_34 (not 2014/glibc 2.17) or pip would install # it on older systems where it fails with "GLIBC_2.34 not found". - - os: ubuntu-latest - asset: gomc-rest-linux-amd64 + - asset: gomc-rest-linux-amd64 plat: manylinux_2_34_x86_64 # arm64 binary is statically linked (no glibc dependency), so the # widest valid tag is fine. - - os: ubuntu-24.04-arm - asset: gomc-rest-linux-arm64 + - asset: gomc-rest-linux-arm64 plat: manylinux2014_aarch64 + # Both darwin binaries declare a minimum OS of macOS 12 (Monterey) + # via LC_BUILD_VERSION, so the wheel must not claim an older floor. + - asset: gomc-rest-darwin-amd64 + plat: macosx_12_0_x86_64 + - asset: gomc-rest-darwin-arm64 + plat: macosx_12_0_arm64 steps: - uses: actions/checkout@v4 with: diff --git a/GOMC_REST_VERSION b/GOMC_REST_VERSION index 0d0c52f..b7c0a9b 100644 --- a/GOMC_REST_VERSION +++ b/GOMC_REST_VERSION @@ -1 +1 @@ -v1.4.0 +v1.6.0 diff --git a/README.md b/README.md index cbed687..d767cd3 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Both `launch()` and `connect()` hand you the same `PLCClient`, so one package covers "bundle and run the server" and "just be a client". `connect()` needs no bundled binary, so it works even on platforms without a -prebuilt wheel (macOS, Windows arm64, glibc < 2.34): there `pip install +prebuilt wheel (Windows arm64, glibc < 2.34, macOS < 12): there `pip install gomc-rest` installs from the sdist, and only `launch()` is unavailable (it raises a clear error). @@ -107,7 +107,7 @@ boundary is the trust boundary here. ## Versions -This package bundles a pinned `gomc-rest` binary (currently **v1.4.0**, set in +This package bundles a pinned `gomc-rest` binary (currently **v1.6.0**, set in `GOMC_REST_VERSION`) that must satisfy `gomc-rest-client`'s `MINIMUM_SUPPORTED_GOMC_REST_VERSION`; `launch()` verifies this on startup. The `gomc-rest-client` dependency is capped (`>=0.10.0,<0.11`) so a future client diff --git a/README_JP.md b/README_JP.md index 7285af5..2e1743a 100644 --- a/README_JP.md +++ b/README_JP.md @@ -71,7 +71,7 @@ with gomc_rest.connect("http://192.168.0.1:8080", token="...") as plc: 同じ読み書きAPIを利用できます。 `connect()` は同梱バイナリを必要としないため、対応するビルド済み `wheel` が -ない環境(macOS、Windows arm64、glibc 2.34未満など)でも利用できます。 +ない環境(Windows arm64、glibc 2.34未満、macOS 12未満など)でも利用できます。 その場合、`pip install gomc-rest` は `sdist` からパッケージをインストールします。 `connect()` は利用できますが、`launch()` を実行すると、対応するバイナリが ないことを示すエラーが発生します。 @@ -117,7 +117,7 @@ finally: ## バージョン 同梱するgomc-restのバージョンは `GOMC_REST_VERSION` で固定しています -(現在は **v1.4.0**)。`launch()` は起動時に、サーバーが +(現在は **v1.6.0**)。`launch()` は起動時に、サーバーが `gomc-rest-client` の `MINIMUM_SUPPORTED_GOMC_REST_VERSION` を満たしているか 確認します。 diff --git a/RELEASING.md b/RELEASING.md index 1fe7753..71acbc7 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -8,14 +8,26 @@ Pushing a `v*` tag runs `.github/workflows/release.yml`: 1. **Version** — derived from the tag itself by hatch-vcs (`dynamic = ["version"]`); there is no version string to keep in sync. -2. **Build wheels** (one per platform, each bundling only its matching - server binary, downloaded and SHA-256-verified at build time): +2. **Build wheels** — all cross-build on a single `ubuntu-latest` runner + (a wheel just zips the matching binary, SHA-256-verified at build time, + then `wheel tags` forces the platform tag; the build host's OS/arch is + irrelevant, same as upstream gomc-rest cross-compiling from Linux): - `win_amd64` — `gomc-rest.exe` - `manylinux_2_34_x86_64` — `gomc-rest-linux-amd64` (dynamically linked, needs glibc ≥ 2.34; hence the 2_34 tag) - `manylinux2014_aarch64` — `gomc-rest-linux-arm64` (statically linked) + - `macosx_12_0_x86_64` — `gomc-rest-darwin-amd64` (unsigned) + - `macosx_12_0_arm64` — `gomc-rest-darwin-arm64` (ad-hoc signed) + + Both darwin binaries declare a minimum OS of macOS 12 via + `LC_BUILD_VERSION`, hence the `macosx_12_0` tags. `ci.yml`'s + `test-macos` job actually launches the server on a real `macos-14` + (Apple Silicon) runner every PR — the authoritative check for + Gatekeeper/signing on arm64. The Intel binary is verified offline only + (SHA-256 + Mach-O inspection); GitHub's Intel macOS runners are not + reliably schedulable, so we don't gate on them. 3. **Build sdist** — no binary; the client-only fallback for platforms - without a wheel (macOS, Windows arm64, older glibc). + without a wheel (Windows arm64, older glibc, macOS < 12). 4. **Publish to PyPI** — trusted publishing (OIDC) via the `pypi` environment. `skip-existing` makes re-running a tag safe. 5. **GitHub Release** — created only after PyPI publish succeeds, with @@ -51,11 +63,15 @@ Only needed when changing the bundled server version: bump the client pin in `pyproject.toml` together if needed. 2. Add `checksums/.sha256` with the trusted SHA-256 of each release asset (`gomc-rest.exe`, `gomc-rest-linux-amd64`, - `gomc-rest-linux-arm64`). These are committed so a swapped release + `gomc-rest-linux-arm64`, `gomc-rest-darwin-amd64`, + `gomc-rest-darwin-arm64`). These are committed so a swapped release asset is detected; vendoring fails closed without them. -3. If the new binaries change their glibc requirement (check with +3. If the Linux binaries change their glibc requirement (check with `readelf -V | grep GLIBC`), update the `plat` tags in - `release.yml` and the CI floor test image in `ci.yml`. + `release.yml` and the CI floor test image in `ci.yml`. If the darwin + binaries change their minimum OS (check with `otool -l | grep + -A3 LC_BUILD_VERSION`, or parse the load command directly), update the + `macosx_*` tags in `release.yml`. ## Local builds (testing without PyPI) diff --git a/checksums/v1.6.0.sha256 b/checksums/v1.6.0.sha256 new file mode 100644 index 0000000..1cbe77b --- /dev/null +++ b/checksums/v1.6.0.sha256 @@ -0,0 +1,5 @@ +21094f62a98516f83fb1900caf84fcba3f5ba303682189f7c180d37ff3d64e30 gomc-rest.exe +314712bfddcc826fc5ff55a2087f9ae34e108adfbc88d1bbcc1d8609eb799f3d gomc-rest-linux-amd64 +f9539bdf0f5952df6b33204c0a93c9513770ad2795df468872e66a80c3963129 gomc-rest-linux-arm64 +16bffdfea51e89ee3c83c6ae23ad485d6225f464c31e07ae38aa7e8646c13b36 gomc-rest-darwin-amd64 +649c0d889e37708b1e6c0fb4c8503a1fd0c7a82fc666e0a9b5a25f84e73f839e gomc-rest-darwin-arm64 diff --git a/scripts/vendor_binaries.py b/scripts/vendor_binaries.py index 9b6a9ed..c81d629 100644 --- a/scripts/vendor_binaries.py +++ b/scripts/vendor_binaries.py @@ -24,6 +24,8 @@ "gomc-rest.exe", "gomc-rest-linux-amd64", "gomc-rest-linux-arm64", + "gomc-rest-darwin-amd64", + "gomc-rest-darwin-arm64", ] _ROOT = Path(__file__).resolve().parent.parent diff --git a/src/gomc_rest/_binaries.py b/src/gomc_rest/_binaries.py index 81450cc..76c5c0c 100644 --- a/src/gomc_rest/_binaries.py +++ b/src/gomc_rest/_binaries.py @@ -14,6 +14,8 @@ ("windows", "amd64"): "gomc-rest.exe", ("linux", "amd64"): "gomc-rest-linux-amd64", ("linux", "arm64"): "gomc-rest-linux-arm64", + ("darwin", "amd64"): "gomc-rest-darwin-amd64", + ("darwin", "arm64"): "gomc-rest-darwin-arm64", } _MACHINE_ALIASES = { diff --git a/src/gomc_rest/binaries/README.md b/src/gomc_rest/binaries/README.md index 2eb4e40..641dfbb 100644 --- a/src/gomc_rest/binaries/README.md +++ b/src/gomc_rest/binaries/README.md @@ -3,11 +3,13 @@ This directory holds the platform-specific `gomc-rest` server binaries that the Python package auto-launches (Pattern B): -| Platform | File name | -| --------------- | ------------------------ | -| Windows (amd64) | `gomc-rest.exe` | -| Linux (amd64) | `gomc-rest-linux-amd64` | -| Linux (arm64) | `gomc-rest-linux-arm64` | +| Platform | File name | +| ---------------- | -------------------------- | +| Windows (amd64) | `gomc-rest.exe` | +| Linux (amd64) | `gomc-rest-linux-amd64` | +| Linux (arm64) | `gomc-rest-linux-arm64` | +| macOS (amd64) | `gomc-rest-darwin-amd64` | +| macOS (arm64) | `gomc-rest-darwin-arm64` | The binaries are **not committed to git**. They are vendored in at release time from a pinned [gomc-rest](https://github.com/Moge800/gomc-rest) version, and each