Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions .github/workflows/build-rtc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,13 @@ jobs:
with:
submodules: true

- uses: actions/setup-python@v4

- name: Install cibuildwheel
if: runner.os != 'macOS'
run: python3 -m pip install cibuildwheel==2.17.0

- name: Install cibuildwheel on macOS
if: runner.os == 'macOS'
run: python3 -m pip install --break-system-packages cibuildwheel==2.17.0
- uses: actions/setup-python@v5
id: setup-python
with:
python-version: "3.11"

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

Expand Down
97 changes: 52 additions & 45 deletions livekit-rtc/hatch_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""Custom build hook for platform-specific wheel tagging.

This hook generates py3-none-{platform} wheels because the native FFI libraries
(.so/.dylib/.dll) don't use the Python C API - they're loaded via ctypes at
runtime. This makes them compatible with any Python 3.x version.

Why not use sysconfig.get_platform()?
- On macOS, it returns the Python interpreter's compile-time deployment target,
not the MACOSX_DEPLOYMENT_TARGET from the environment that cibuildwheel sets.

Why not let hatchling infer the tag?
- hatchling doesn't recognize bundled .so/.dylib/.dll as platform-specific
unless we explicitly set pure_python=False and provide the tag.
"""

import os
import platform
import sys
Expand All @@ -21,65 +36,57 @@

class CustomBuildHook(BuildHookInterface):
def initialize(self, version, build_data):
"""Force platform-specific wheel with py3-none tag.

The native libraries (.so, .dylib, .dll) are not Python C extensions -
they're standalone FFI libraries loaded at runtime. This means they
don't depend on a specific CPython ABI, so we use py3-none to indicate
compatibility with any Python 3.x version while keeping the platform tag.
"""
build_data["pure_python"] = False
build_data["infer_tag"] = False
build_data["tag"] = f"py3-none-{self._get_platform_tag()}"

def _get_platform_tag(self):
"""Get the wheel platform tag for the current/target platform."""
if sys.platform == "darwin":
plat_tag = self._get_macos_platform_tag()
return self._get_macos_tag()
elif sys.platform == "linux":
# Return linux tag; cibuildwheel's auditwheel converts to manylinux
return f"linux_{platform.machine()}"
elif sys.platform == "win32":
return f"win_{self._normalize_arch(platform.machine())}"
else:
from packaging.tags import sys_tags
return f"{platform.system().lower()}_{platform.machine()}"

tag = next(
t
for t in sys_tags()
if "manylinux" not in t.platform and "musllinux" not in t.platform
)
plat_tag = tag.platform
def _get_macos_tag(self):
"""Build macOS platform tag respecting cross-compilation settings.

build_data["tag"] = f"py3-none-{plat_tag}"

def _get_macos_platform_tag(self):
"""Build macOS platform tag from MACOSX_DEPLOYMENT_TARGET env var."""
deployment_target = os.environ.get("MACOSX_DEPLOYMENT_TARGET")
if not deployment_target:
# Fall back to current macOS version
deployment_target = platform.mac_ver()[0]
# Use only major.minor
parts = deployment_target.split(".")
deployment_target = f"{parts[0]}.{parts[1] if len(parts) > 1 else '0'}"

# Convert version to wheel tag format (e.g., "11.0" -> "11_0")
version_tag = deployment_target.replace(".", "_")

# Get target architecture from ARCHFLAGS (set by cibuildwheel for cross-compilation)
# or fall back to host machine architecture
arch = self._get_macos_target_arch()
cibuildwheel sets MACOSX_DEPLOYMENT_TARGET and ARCHFLAGS when building.
We must use these rather than the host machine's values.
"""
target = os.environ.get("MACOSX_DEPLOYMENT_TARGET")
if not target:
# Fall back to current macOS version (for local dev builds)
target = platform.mac_ver()[0]
parts = target.split(".")
target = f"{parts[0]}.{parts[1] if len(parts) > 1 else '0'}"

version_tag = target.replace(".", "_")
arch = self._get_target_arch()
return f"macosx_{version_tag}_{arch}"

def _get_macos_target_arch(self):
"""Detect target architecture for macOS builds.
def _get_target_arch(self):
"""Detect target architecture, respecting ARCHFLAGS for cross-compilation.

Cibuildwheel sets ARCHFLAGS for cross-compilation (e.g., "-arch x86_64").
Falls back to host machine architecture if not set.
cibuildwheel sets ARCHFLAGS="-arch arm64" or "-arch x86_64" when
cross-compiling on macOS.
"""
archflags = os.environ.get("ARCHFLAGS", "")
if "-arch arm64" in archflags:
return "arm64"
elif "-arch x86_64" in archflags:
if "-arch x86_64" in archflags:
return "x86_64"
return self._normalize_arch(platform.machine())

# Fall back to host architecture
machine = platform.machine()
if machine == "x86_64":
return "x86_64"
elif machine == "arm64":
return "arm64"
return machine
def _normalize_arch(self, arch):
"""Normalize architecture names to wheel tag format."""
return {
"AMD64": "amd64",
"x86_64": "x86_64",
"arm64": "arm64",
"aarch64": "aarch64",
}.get(arch, arch.lower())
12 changes: 2 additions & 10 deletions livekit-rtc/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,9 @@ include = ["/livekit", "/rust-sdks"]

[tool.cibuildwheel]
build = "cp39-*"
skip = "*-musllinux_*" # not supported (libwebrtc is using glibc)
skip = "*-musllinux_*" # not supported (libwebrtc requires glibc)
before-build = "pip install requests && python rust-sdks/download_ffi.py --output livekit/rtc/resources"

manylinux-x86_64-image = "manylinux_2_28"
manylinux-i686-image = "manylinux_2_28"
manylinux-aarch64-image = "manylinux_2_28"
manylinux-ppc64le-image = "manylinux_2_28"
manylinux-s390x-image = "manylinux_2_28"
manylinux-pypy_x86_64-image = "manylinux_2_28"
manylinux-pypy_i686-image = "manylinux_2_28"
manylinux-pypy_aarch64-image = "manylinux_2_28"
# Note: manylinux_2_28 is the default in cibuildwheel 3.x, no explicit config needed

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