Skip to content

Commit 8b7a229

Browse files
use wheels in e2e test (#557)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 76248dd commit 8b7a229

17 files changed

+235
-102
lines changed

.github/workflows/build-rtc.yml

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,18 +79,13 @@ jobs:
7979
with:
8080
submodules: true
8181

82-
- uses: actions/setup-python@v4
83-
84-
- name: Install cibuildwheel
85-
if: runner.os != 'macOS'
86-
run: python3 -m pip install cibuildwheel==2.17.0
87-
88-
- name: Install cibuildwheel on macOS
89-
if: runner.os == 'macOS'
90-
run: python3 -m pip install --break-system-packages cibuildwheel==2.17.0
82+
- uses: actions/setup-python@v5
83+
id: setup-python
84+
with:
85+
python-version: "3.11"
9186

9287
- name: Build wheels
93-
run: python3 -m cibuildwheel --output-dir dist
88+
run: pipx run --python '${{ steps.setup-python.outputs.python-path }}' cibuildwheel==3.3.1 --output-dir dist
9489
env:
9590
CIBW_ARCHS: ${{ matrix.archs }}
9691

@@ -120,9 +115,57 @@ jobs:
120115
name: rtc-release-sdist
121116
path: livekit-rtc/dist/*.tar.gz
122117

118+
119+
test:
120+
name: Test (${{ matrix.os }}, Python ${{ matrix.python-version }})
121+
needs: [build_wheels]
122+
strategy:
123+
fail-fast: false
124+
matrix:
125+
include:
126+
# Linux x86_64 tests
127+
- os: ubuntu-latest
128+
python-version: "3.9"
129+
artifact: rtc-release-ubuntu-latest
130+
- os: ubuntu-latest
131+
python-version: "3.10"
132+
artifact: rtc-release-ubuntu-latest
133+
- os: ubuntu-latest
134+
python-version: "3.11"
135+
artifact: rtc-release-ubuntu-latest
136+
- os: ubuntu-latest
137+
python-version: "3.12"
138+
artifact: rtc-release-ubuntu-latest
139+
- os: ubuntu-latest
140+
python-version: "3.13"
141+
artifact: rtc-release-ubuntu-latest
142+
# macOS tests (arm64 runner)
143+
- os: macos-latest
144+
python-version: "3.9"
145+
artifact: rtc-release-macos-latest
146+
- os: macos-latest
147+
python-version: "3.12"
148+
artifact: rtc-release-macos-latest
149+
# Windows tests
150+
- os: windows-latest
151+
python-version: "3.9"
152+
artifact: rtc-release-windows-latest
153+
- os: windows-latest
154+
python-version: "3.12"
155+
artifact: rtc-release-windows-latest
156+
uses: ./.github/workflows/tests.yml
157+
with:
158+
os: ${{ matrix.os }}
159+
python-version: ${{ matrix.python-version }}
160+
artifact-name: ${{ matrix.artifact }}
161+
secrets:
162+
LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}
163+
LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
164+
LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
165+
123166
publish:
124167
name: Publish RTC release
125-
needs: [build_wheels, make_sdist]
168+
needs: [build_wheels, make_sdist, test]
126169
runs-on: ubuntu-latest
127170
permissions:
128171
id-token: write

.github/workflows/tests.yml

Lines changed: 116 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,140 @@
11
name: Tests
22

33
on:
4-
push:
5-
branches:
6-
- main
7-
pull_request:
8-
branches:
9-
- main
10-
workflow_dispatch:
4+
workflow_call:
5+
inputs:
6+
os:
7+
description: "Runner OS (e.g., ubuntu-latest, macos-latest, windows-latest)"
8+
required: true
9+
type: string
10+
python-version:
11+
description: "Python version to test"
12+
required: true
13+
type: string
14+
artifact-name:
15+
description: "Name of the wheel artifact to download"
16+
required: true
17+
type: string
18+
run-id:
19+
description: "Workflow run ID to download artifacts from (optional, uses current run if not specified)"
20+
required: false
21+
type: string
22+
secrets:
23+
LIVEKIT_URL:
24+
required: true
25+
LIVEKIT_API_KEY:
26+
required: true
27+
LIVEKIT_API_SECRET:
28+
required: true
1129

1230
jobs:
13-
tests:
14-
name: Run tests
15-
runs-on: ubuntu-latest
31+
test:
32+
name: Test (${{ inputs.os }}, Python ${{ inputs.python-version }})
33+
runs-on: ${{ inputs.os }}
1634
steps:
17-
- uses: actions/checkout@v6
35+
- uses: actions/checkout@v4
1836
with:
1937
submodules: true
2038
lfs: true
21-
- name: Install uv
2239

40+
- uses: actions/setup-python@v4
41+
with:
42+
python-version: ${{ inputs.python-version }}
43+
44+
- name: Install uv
2345
uses: astral-sh/setup-uv@v5
2446
with:
2547
enable-cache: true
2648
cache-dependency-glob: "uv.lock"
2749

28-
- name: Install the project
29-
run: uv sync --all-extras --dev
50+
- name: Download livekit-rtc wheel (current run)
51+
if: ${{ inputs.run-id == '' }}
52+
uses: actions/download-artifact@v4
53+
with:
54+
name: ${{ inputs.artifact-name }}
55+
path: rtc-wheel
3056

31-
- uses: actions/setup-python@v6
57+
- name: Download livekit-rtc wheel (from specific run)
58+
if: ${{ inputs.run-id != '' }}
59+
uses: actions/download-artifact@v4
3260
with:
33-
python-version: '3.13'
61+
name: ${{ inputs.artifact-name }}
62+
path: rtc-wheel
63+
run-id: ${{ inputs.run-id }}
64+
github-token: ${{ github.token }}
65+
66+
- name: Select compatible wheel (macOS)
67+
if: runner.os == 'macOS'
68+
id: select-wheel-macos
69+
run: |
70+
# macOS artifacts contain both x86_64 and arm64 wheels, select the right one
71+
WHEEL=$(python3 -c "
72+
import glob
73+
import platform
74+
import sys
75+
76+
wheels = glob.glob('rtc-wheel/*.whl')
77+
machine = platform.machine().lower()
78+
79+
arch_map = {
80+
'x86_64': ['x86_64'],
81+
'arm64': ['arm64'],
82+
}
83+
patterns = arch_map.get(machine, [machine])
84+
85+
for wheel in wheels:
86+
wheel_lower = wheel.lower()
87+
if any(p in wheel_lower for p in patterns):
88+
print(wheel)
89+
sys.exit(0)
90+
91+
print(f'No matching wheel found for {machine}', file=sys.stderr)
92+
sys.exit(1)
93+
")
94+
echo "wheel=$WHEEL" >> $GITHUB_OUTPUT
3495
35-
- name: Run tests
96+
- name: Create venv and install dependencies (Unix)
97+
if: runner.os == 'Linux'
98+
run: |
99+
uv venv .test-venv
100+
source .test-venv/bin/activate
101+
uv pip install rtc-wheel/*.whl ./livekit-api ./livekit-protocol
102+
uv pip install pytest pytest-asyncio numpy matplotlib
103+
104+
- name: Create venv and install dependencies (macOS)
105+
if: runner.os == 'macOS'
106+
run: |
107+
uv venv .test-venv
108+
source .test-venv/bin/activate
109+
uv pip install "${{ steps.select-wheel-macos.outputs.wheel }}"
110+
uv pip install ./livekit-api ./livekit-protocol
111+
uv pip install pytest pytest-asyncio numpy matplotlib
112+
113+
- name: Create venv and install dependencies (Windows)
114+
if: runner.os == 'Windows'
115+
run: |
116+
uv venv .test-venv
117+
$wheel = (Get-ChildItem rtc-wheel\*.whl)[0].FullName
118+
uv pip install --python .test-venv $wheel .\livekit-api .\livekit-protocol
119+
uv pip install --python .test-venv pytest pytest-asyncio numpy matplotlib
120+
shell: pwsh
121+
122+
- name: Run tests (Unix)
123+
if: runner.os != 'Windows'
36124
env:
37125
LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}
38126
LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
39127
LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
40128
run: |
41-
42-
uv run python ./livekit-rtc/rust-sdks/download_ffi.py --output livekit-rtc/livekit/rtc/resources
43-
uv add ./livekit-rtc ./livekit-api ./livekit-protocol
44-
uv run pytest . --ignore=livekit-rtc/rust-sdks
129+
source .test-venv/bin/activate
130+
pytest tests/
131+
132+
- name: Run tests (Windows)
133+
if: runner.os == 'Windows'
134+
env:
135+
LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}
136+
LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
137+
LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
138+
run: .test-venv\Scripts\python.exe -m pytest tests/
139+
shell: pwsh
45140

livekit-rtc/hatch_build.py

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
"""Custom build hook for platform-specific wheel tagging.
16+
17+
This hook generates py3-none-{platform} wheels because the native FFI libraries
18+
(.so/.dylib/.dll) don't use the Python C API - they're loaded via ctypes at
19+
runtime. This makes them compatible with any Python 3.x version.
20+
21+
Why not use sysconfig.get_platform()?
22+
- On macOS, it returns the Python interpreter's compile-time deployment target,
23+
not the MACOSX_DEPLOYMENT_TARGET from the environment that cibuildwheel sets.
24+
25+
Why not let hatchling infer the tag?
26+
- hatchling doesn't recognize bundled .so/.dylib/.dll as platform-specific
27+
unless we explicitly set pure_python=False and provide the tag.
28+
"""
29+
1530
import os
1631
import platform
1732
import sys
@@ -21,65 +36,57 @@
2136

2237
class CustomBuildHook(BuildHookInterface):
2338
def initialize(self, version, build_data):
24-
"""Force platform-specific wheel with py3-none tag.
25-
26-
The native libraries (.so, .dylib, .dll) are not Python C extensions -
27-
they're standalone FFI libraries loaded at runtime. This means they
28-
don't depend on a specific CPython ABI, so we use py3-none to indicate
29-
compatibility with any Python 3.x version while keeping the platform tag.
30-
"""
3139
build_data["pure_python"] = False
3240
build_data["infer_tag"] = False
41+
build_data["tag"] = f"py3-none-{self._get_platform_tag()}"
3342

43+
def _get_platform_tag(self):
44+
"""Get the wheel platform tag for the current/target platform."""
3445
if sys.platform == "darwin":
35-
plat_tag = self._get_macos_platform_tag()
46+
return self._get_macos_tag()
47+
elif sys.platform == "linux":
48+
# Return linux tag; cibuildwheel's auditwheel converts to manylinux
49+
return f"linux_{platform.machine()}"
50+
elif sys.platform == "win32":
51+
return f"win_{self._normalize_arch(platform.machine())}"
3652
else:
37-
from packaging.tags import sys_tags
53+
return f"{platform.system().lower()}_{platform.machine()}"
3854

39-
tag = next(
40-
t
41-
for t in sys_tags()
42-
if "manylinux" not in t.platform and "musllinux" not in t.platform
43-
)
44-
plat_tag = tag.platform
55+
def _get_macos_tag(self):
56+
"""Build macOS platform tag respecting cross-compilation settings.
4557
46-
build_data["tag"] = f"py3-none-{plat_tag}"
47-
48-
def _get_macos_platform_tag(self):
49-
"""Build macOS platform tag from MACOSX_DEPLOYMENT_TARGET env var."""
50-
deployment_target = os.environ.get("MACOSX_DEPLOYMENT_TARGET")
51-
if not deployment_target:
52-
# Fall back to current macOS version
53-
deployment_target = platform.mac_ver()[0]
54-
# Use only major.minor
55-
parts = deployment_target.split(".")
56-
deployment_target = f"{parts[0]}.{parts[1] if len(parts) > 1 else '0'}"
57-
58-
# Convert version to wheel tag format (e.g., "11.0" -> "11_0")
59-
version_tag = deployment_target.replace(".", "_")
60-
61-
# Get target architecture from ARCHFLAGS (set by cibuildwheel for cross-compilation)
62-
# or fall back to host machine architecture
63-
arch = self._get_macos_target_arch()
58+
cibuildwheel sets MACOSX_DEPLOYMENT_TARGET and ARCHFLAGS when building.
59+
We must use these rather than the host machine's values.
60+
"""
61+
target = os.environ.get("MACOSX_DEPLOYMENT_TARGET")
62+
if not target:
63+
# Fall back to current macOS version (for local dev builds)
64+
target = platform.mac_ver()[0]
65+
parts = target.split(".")
66+
target = f"{parts[0]}.{parts[1] if len(parts) > 1 else '0'}"
6467

68+
version_tag = target.replace(".", "_")
69+
arch = self._get_target_arch()
6570
return f"macosx_{version_tag}_{arch}"
6671

67-
def _get_macos_target_arch(self):
68-
"""Detect target architecture for macOS builds.
72+
def _get_target_arch(self):
73+
"""Detect target architecture, respecting ARCHFLAGS for cross-compilation.
6974
70-
Cibuildwheel sets ARCHFLAGS for cross-compilation (e.g., "-arch x86_64").
71-
Falls back to host machine architecture if not set.
75+
cibuildwheel sets ARCHFLAGS="-arch arm64" or "-arch x86_64" when
76+
cross-compiling on macOS.
7277
"""
7378
archflags = os.environ.get("ARCHFLAGS", "")
7479
if "-arch arm64" in archflags:
7580
return "arm64"
76-
elif "-arch x86_64" in archflags:
81+
if "-arch x86_64" in archflags:
7782
return "x86_64"
83+
return self._normalize_arch(platform.machine())
7884

79-
# Fall back to host architecture
80-
machine = platform.machine()
81-
if machine == "x86_64":
82-
return "x86_64"
83-
elif machine == "arm64":
84-
return "arm64"
85-
return machine
85+
def _normalize_arch(self, arch):
86+
"""Normalize architecture names to wheel tag format."""
87+
return {
88+
"AMD64": "amd64",
89+
"x86_64": "x86_64",
90+
"arm64": "arm64",
91+
"aarch64": "aarch64",
92+
}.get(arch, arch.lower())

livekit-rtc/pyproject.toml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,9 @@ include = ["/livekit", "/rust-sdks"]
5454

5555
[tool.cibuildwheel]
5656
build = "cp39-*"
57-
skip = "*-musllinux_*" # not supported (libwebrtc is using glibc)
58-
57+
skip = "*-musllinux_*" # not supported (libwebrtc requires glibc)
5958
before-build = "pip install requests && python rust-sdks/download_ffi.py --output livekit/rtc/resources"
60-
61-
manylinux-x86_64-image = "manylinux_2_28"
62-
manylinux-i686-image = "manylinux_2_28"
63-
manylinux-aarch64-image = "manylinux_2_28"
64-
manylinux-ppc64le-image = "manylinux_2_28"
65-
manylinux-s390x-image = "manylinux_2_28"
66-
manylinux-pypy_x86_64-image = "manylinux_2_28"
67-
manylinux-pypy_i686-image = "manylinux_2_28"
68-
manylinux-pypy_aarch64-image = "manylinux_2_28"
59+
# Note: manylinux_2_28 is the default in cibuildwheel 3.x, no explicit config needed
6960

7061
# macOS deployment targets must match the FFI binaries (see rust-sdks/.github/workflows/ffi-builds.yml)
7162
# x86_64 supports macOS 10.15+, arm64 requires macOS 11.0+

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ convention = "google"
6161

6262

6363
[tool.pytest.ini_options]
64+
testpaths = ["tests"]
6465
asyncio_mode = "auto"
6566
asyncio_default_fixture_loop_scope = "function"
6667
addopts = ["--import-mode=importlib", "--ignore=examples"]

0 commit comments

Comments
 (0)