Files
hermes-agent/.github/workflows/pm-bundle.yml
ethernet c98bdb77f5 fix(pm): prepare the Windows ARM64 compiler environment for every source dependency build
cryptography ships no win_arm64 wheel, so every Windows ARM64 venv sync
compiles it from the sdist and needs MSVC, Clang, Rust and static OpenSSL.
Only setup-hermes.ps1 (and so activate.ps1) prepared that environment,
between a `pm install --tools-only` and the real sync. install.ps1,
`hermes update` and repair ran the same sync without it and failed in
openssl-sys.

PM owns the sync, so PM prepares it. pm/native_build.py holds the adapter
(moved from scripts/build/windows_deps.py) plus source_build_environment(),
which prepares only on win32-arm64 when the synced project carries the
provider script. A payload has prebuilt dependencies and needs no compiler.
VenvPackage.apply and build_environment pass the result to uv children
only. It carries the bridged pip index settings, which managed_environment
applies only to the ambient environment. The state root stays the store
parent, so existing vcpkg/OpenSSL builds are reused.

setup-hermes.ps1 collapses to one `pm install`: the tools-only split existed
only for this preparation, and pm install already puts its tools on PATH
before the venv sync (pm/cli.py activate check).

Not yet verified live on Windows ARM64.
2026-09-23 16:07:47 -04:00

138 lines
5.2 KiB
YAML

name: PM Bundle
# Stages the self-contained agent payload for each (os, arch) target with
# `hermes pm bundle`: repo snapshot + tool store + facts + a relocatable
# venv built on the staged python-build-standalone interpreter. Each
# payload is smoke-tested on its native runner — the staged interpreter
# boots hermes_cli out of the payload with no network and no PYTHONPATH —
# as a pre-merge check for the pm tooling itself.
#
# Staging is native per target (no cross-target wheel tables anywhere):
# the runner IS the target. pm's arch guard fails the job if any staged
# mismatches, before anything ships.
on:
workflow_dispatch:
inputs:
ref:
description: 'Git ref to bundle (default: the triggering commit, github.sha)'
required: false
type: string
workflow_call:
inputs:
release:
description: 'Stable-release candidate run (ignored by the jobs; uniform callable surface).'
required: false
type: boolean
default: false
ref:
description: 'Git ref to bundle (default: the triggering commit, github.sha)'
required: false
type: string
default: ''
pull_request:
paths:
- 'pm/**'
- 'scripts/bundles/**'
- 'scripts/ci/desktop_build_cache.py'
- '.github/actions/desktop-build-cache/**'
- 'scripts/windows-build-deps.ps1'
- 'scripts/build/windows-deps.ps1'
- '.github/actions/setup-windows-build-deps/**'
- 'uv.lock'
- 'pyproject.toml'
- '.github/workflows/pm-bundle.yml'
permissions:
contents: read
# A workflow_call run (stable release) is never cancelled: its group uses
# github.run_id so a parent rerun cannot kill this child mid-flight.
concurrency:
group: pm-bundle-${{ inputs.release == true && github.run_id || github.ref }}
cancel-in-progress: ${{ inputs.release != true }}
jobs:
bundle:
name: bundle ${{ matrix.target.label }}
runs-on: ${{ matrix.target.runner }}
# Leave time for setup, cache saves and smoke tests around the wheel build.
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
target:
- label: linux-x64
runner: ubuntu-24.04
- label: linux-arm64
runner: ubuntu-24.04-arm
- label: darwin-arm64
runner: macos-15
- label: darwin-x64
runner: macos-15-intel
- label: win32-x64
runner: windows-2025
- label: win32-arm64
runner: windows-11-arm
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# Default to the exact candidate commit (github.sha); inputs.ref
# remains an escape hatch for a focused manual reproduction.
ref: ${{ inputs.ref || github.sha }}
fetch-tags: true
- name: Restore native dependency candidates
id: native-cache
uses: ./.github/actions/desktop-build-cache
with:
phase: restore
source: ${{ github.workspace }}
work: ${{ runner.temp }}/payload-job
cache: ${{ runner.temp }}/payload-inputs
producer: payload-test
- name: Prepare native payload dependencies
id: prepare
shell: bash
env:
PAYLOAD_WORK: ${{ runner.temp }}/payload-job
PAYLOAD_CACHE: ${{ runner.temp }}/payload-inputs
PYTHONUTF8: '1'
run: |
if [ "$RUNNER_OS" = Windows ]; then bootstrap=python; else bootstrap=python3; fi
"$bootstrap" -S -B scripts/bundles/native_build.py --prepare-only \
--source "$GITHUB_WORKSPACE" --work "$PAYLOAD_WORK" --cache "$PAYLOAD_CACHE" \
--out "$GITHUB_WORKSPACE/build/agent-payload" --ref HEAD
# PRs can restore candidates but never gain a cache-writing lane here.
- name: Save native dependency candidates before assembly
if: ${{ !cancelled() && steps.prepare.outcome == 'success' && github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && (inputs.ref == '' || inputs.ref == github.sha) }}
uses: ./.github/actions/desktop-build-cache
with:
phase: save
source: ${{ github.workspace }}
work: ${{ runner.temp }}/payload-job
cache: ${{ runner.temp }}/payload-inputs
producer: payload-test
key: ${{ steps.native-cache.outputs.cache-key }}
- name: Assemble the prepared payload without dependency acquisition
shell: bash
run: |
if [ "$RUNNER_OS" = Windows ]; then bootstrap=python; else bootstrap=python3; fi
"$bootstrap" -S -B scripts/bundles/native_build.py \
--prepared "$GITHUB_WORKSPACE/build/agent-payload.prepared.json"
# The generated command must survive relocation without checkout env/cwd.
- name: Smoke test the relocated payload (network disabled)
if: startsWith(matrix.target.label, 'linux-')
shell: bash
run: sudo --preserve-env=PATH unshare --net bash scripts/smoke-payload.sh build/agent-payload
- name: Smoke test the relocated payload (native launchers)
if: ${{ !startsWith(matrix.target.label, 'linux-') }}
shell: bash
run: bash scripts/smoke-payload.sh build/agent-payload