Skip to content

Commit 264857e

Browse files
committed
ci(session-e2e): build the Windows arm via Linux cross-compile
Round out the Session E2E workflow: add the macOS arm, wire up the portaudio submodule + ALSA headers, and build Windows with zig to match .goreleaser.yaml's toolchain. Native Windows can't link the webrtc/portaudio cgo objects (the ~560-object link overflows the command-line limit), so cross-compile lk.exe and the e2e test binary on Linux and run them natively on Windows. buildLK honors LK_SESSION_E2E_BIN so the Windows runner drives the prebuilt binary instead of rebuilding.
1 parent 89a0844 commit 264857e

2 files changed

Lines changed: 145 additions & 7 deletions

File tree

.github/workflows/session-e2e.yaml

Lines changed: 135 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
name: Session E2E
22

33
# Drives the real `lk agent session` start/say/end lifecycle against the minimal
4-
# one-file echo agent in cmd/lk/testdata/echo-agent, on Linux and Windows. This
5-
# exercises the detached daemon, the readiness handshake, the console IPC
6-
# transport, and the model round-trip end to end -- runtime behavior that
7-
# `go test` alone never covers.
4+
# one-file echo agent in cmd/lk/testdata/echo-agent, on Linux, macOS, and
5+
# Windows. This exercises the detached daemon, the readiness handshake, the
6+
# console IPC transport, and the model round-trip end to end -- runtime behavior
7+
# that `go test` alone never covers.
88
#
99
# Runs on manual dispatch and on pushes to any repo branch (forks can't trigger
1010
# `push`, so secrets are only exposed to trusted collaborators). It needs live
@@ -19,6 +19,15 @@ name: Session E2E
1919
# console mode to the TCP console directly to bridge the daemon's
2020
# `python <entry> console --connect-addr` launch.
2121
#
22+
# Windows is split into two jobs: pkg/apm/webrtc's C++ uses MSVC SEH
23+
# (__try/__except) that the runner's mingw GCC can't compile, so we build with
24+
# zig (clang) exactly as .goreleaser.yaml does. zig-as-CC must run on Linux: on
25+
# native Windows the cgo link of ~560 webrtc/portaudio objects overflows the
26+
# ~32KB command-line limit. So `cross-build-windows` cross-compiles lk.exe AND
27+
# the e2e test binary on Linux and uploads them; `e2e-windows` downloads them
28+
# and runs the test natively (LK_SESSION_E2E_BIN points the test at the
29+
# prebuilt lk, so nothing is rebuilt on the Windows runner).
30+
#
2231
# Node is intentionally not in the matrix yet: this branch's session daemon only
2332
# supports Python agents (`detectProject` rejects non-Python), and Node console
2433
# support depends on the brian/agent-session-node-support CLI line (#868/#878)
@@ -34,21 +43,31 @@ concurrency:
3443
cancel-in-progress: true
3544

3645
jobs:
46+
# Linux and macOS build + run natively in one job -- their linkers have no
47+
# command-line-length limit, so `go test` building lk in-process is fine.
3748
e2e:
3849
strategy:
3950
fail-fast: false
4051
matrix:
41-
os: [ubuntu-latest, windows-latest]
52+
os: [ubuntu-latest, macos-latest]
4253

4354
runs-on: ${{ matrix.os }}
44-
name: python on ${{ matrix.os }}
55+
name: Agent Session with Python Agent on ${{ matrix.os }}
4556

4657
permissions:
4758
contents: read
4859

4960
steps:
5061
- name: Checkout livekit-cli
5162
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
63+
with:
64+
# pkg/portaudio/pa_src is a submodule with the vendored PortAudio C
65+
# source; needed now that console (cgo) builds unconditionally.
66+
submodules: true
67+
68+
- name: Install ALSA headers (Linux)
69+
if: matrix.os == 'ubuntu-latest'
70+
run: sudo apt-get update && sudo apt-get install -y libasound2-dev
5271

5372
- name: Set up Go
5473
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
@@ -76,3 +95,113 @@ jobs:
7695
LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
7796
LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}
7897
run: go test ./cmd/lk -run TestSessionE2E -count=1 -v -timeout 600s
98+
99+
# Cross-compile the Windows artifacts on Linux with zig, mirroring
100+
# .goreleaser.yaml's lk-windows-amd64 build. Produces lk.exe (the binary under
101+
# test) and the compiled e2e test binary, both shipped to e2e-windows.
102+
cross-build-windows:
103+
runs-on: ubuntu-latest
104+
name: Cross-build Windows artifacts (zig)
105+
106+
permissions:
107+
contents: read
108+
109+
steps:
110+
- name: Checkout livekit-cli
111+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
112+
with:
113+
submodules: true
114+
115+
- name: Set up Zig
116+
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
117+
with:
118+
version: 0.14.1
119+
120+
- name: Set up Go
121+
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
122+
with:
123+
go-version-file: go.mod
124+
cache: true
125+
126+
# Generate the MinGW import libs lld needs for Go's /DEFAULTLIB references
127+
# (dbghelp, bcrypt, ...), exactly as goreleaser's before-hook does.
128+
- name: Generate MinGW import libs
129+
run: scripts/setup-cross.sh windows/amd64
130+
131+
- name: Cross-compile lk.exe and the e2e test binary
132+
env:
133+
GOOS: windows
134+
GOARCH: amd64
135+
CGO_ENABLED: "1"
136+
# zig cc/c++ as the cgo toolchain, matching .goreleaser.yaml's Windows
137+
# build. -fms-extensions (SEH) isn't on cgo's allowlist; -fno-sanitize
138+
# is a zig UBSan-under-lld workaround -- both copied from goreleaser.
139+
CC: zig cc -target x86_64-windows-gnu
140+
CXX: zig c++ -target x86_64-windows-gnu
141+
CGO_CXXFLAGS_ALLOW: -fms-extensions
142+
CGO_CXXFLAGS: -fno-sanitize=all
143+
run: |
144+
ldflags="-extldflags=-L$PWD/.cross/windows_amd64/mingw_lib"
145+
mkdir -p dist
146+
go build -ldflags="$ldflags" -o dist/lk.exe ./cmd/lk
147+
go test -c -ldflags="$ldflags" -o dist/session-e2e.test.exe ./cmd/lk
148+
149+
- name: Upload Windows artifacts
150+
uses: actions/upload-artifact@v4
151+
with:
152+
name: windows-e2e
153+
path: dist/*.exe
154+
retention-days: 1
155+
if-no-files-found: error
156+
157+
# Run the prebuilt Windows artifacts natively. No Go/zig/cgo here: the test
158+
# binary just drives lk.exe (via LK_SESSION_E2E_BIN) against the echo agent.
159+
e2e-windows:
160+
needs: cross-build-windows
161+
runs-on: windows-latest
162+
name: Agent Session with Python Agent on windows-latest
163+
164+
permissions:
165+
contents: read
166+
167+
steps:
168+
- name: Checkout livekit-cli
169+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
170+
# No submodules: the prebuilt binaries already contain the cgo code;
171+
# only the echo-agent fixture (plain repo files) is needed at runtime.
172+
173+
- name: Download Windows artifacts
174+
uses: actions/download-artifact@v4
175+
with:
176+
name: windows-e2e
177+
path: dist
178+
179+
- name: Set up Python
180+
uses: actions/setup-python@v5
181+
with:
182+
python-version: "3.12"
183+
184+
- name: Set up uv
185+
uses: astral-sh/setup-uv@v5
186+
with:
187+
enable-cache: true
188+
189+
- name: Sync echo agent deps
190+
working-directory: cmd/lk/testdata/echo-agent
191+
run: uv sync
192+
193+
- name: Run session e2e
194+
# Run from cmd/lk so the test's default testdata/echo-agent/agent.py
195+
# path resolves; point it at the cross-built lk.exe so nothing rebuilds.
196+
shell: bash
197+
working-directory: cmd/lk
198+
env:
199+
# Relative to cmd/lk; Go's filepath.Abs resolves it. Forward slashes
200+
# keep bash happy (GITHUB_WORKSPACE would carry Windows backslashes).
201+
LK_SESSION_E2E_BIN: ../../dist/lk.exe
202+
LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
203+
LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
204+
LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}
205+
run: |
206+
../../dist/session-e2e.test.exe \
207+
-test.run TestSessionE2E -test.count=1 -test.v -test.timeout 600s

cmd/lk/session_e2e_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,18 @@ func TestSessionE2E(t *testing.T) {
9898
}, 10*time.Second, 200*time.Millisecond, "session daemon still listening on port %s after end", port)
9999
}
100100

101-
// buildLK compiles the lk binary into a temp dir and returns its path.
101+
// buildLK returns the path to the lk binary under test. If LK_SESSION_E2E_BIN
102+
// points at a prebuilt binary it's used as-is (the Windows CI arm cross-builds
103+
// lk on Linux and ships it here, so the heavy cgo build never runs on the
104+
// Windows runner); otherwise lk is compiled into a temp dir.
102105
func buildLK(t *testing.T) string {
103106
t.Helper()
107+
if prebuilt := os.Getenv("LK_SESSION_E2E_BIN"); prebuilt != "" {
108+
abs, err := filepath.Abs(prebuilt)
109+
require.NoError(t, err)
110+
require.FileExists(t, abs, "LK_SESSION_E2E_BIN does not point at a binary")
111+
return abs
112+
}
104113
bin := filepath.Join(t.TempDir(), "lk")
105114
if runtime.GOOS == "windows" {
106115
bin += ".exe"

0 commit comments

Comments
 (0)