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
4 changes: 3 additions & 1 deletion .github/scripts/ci_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ def classify(paths):
selected.add("cpp_tidy")
elif path.startswith("scripts/") and path.endswith(".sh"):
selected.add("shell")
if path == "scripts/check-python.sh":
if path == "scripts/build-macos-openmp.sh":
selected.add("wheels")
elif path == "scripts/check-python.sh":
selected.add("python_quality")
elif path == "scripts/check-tidy.sh":
selected.add("cpp_tidy")
Expand Down
1 change: 1 addition & 0 deletions .github/scripts/test_ci_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def test_language_specific_changes(self):
},
".github/scripts/prepare_release.py": {"python_quality"},
"scripts/check-python.sh": {"shell", "python_quality"},
"scripts/build-macos-openmp.sh": {"shell", "wheels"},
"scripts/check-tidy.sh": {"shell", "cpp_tidy"},
"scripts/check-format.sh": {"shell", "cpp_format"},
"scripts/check-includes.sh": {"shell"},
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ jobs:
!cancelled() && needs.prepare.result == 'success' &&
needs.prepare.outputs.build == 'true' &&
(needs.changes.result != 'success' || needs.changes.outputs.wheels == 'true')
runs-on: macos-14
runs-on: macos-15
timeout-minutes: 90
strategy:
fail-fast: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ jobs:
name: Native Apple Silicon C++
needs: changes
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.cpp == 'true') }}
runs-on: macos-14
runs-on: macos-15
timeout-minutes: 45
env:
OMP_NUM_THREADS: "2"
Expand Down
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,15 @@ test-environment = { RABITQ_TEST_WHEEL = "1", OMP_NUM_THREADS = "2" }

[tool.cibuildwheel.macos]
archs = ["arm64"]
before-all = "brew install libomp"
environment = { CMAKE_ARGS = "-DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF -DOpenMP_ROOT=$(brew --prefix libomp)", MACOSX_DEPLOYMENT_TARGET = "14.0" }
before-all = 'bash "{project}/scripts/build-macos-openmp.sh" "$RABITQ_OPENMP_PREFIX"'
repair-wheel-command = "delocate-wheel --require-archs arm64 -w {dest_dir} -v {wheel}"
test-environment = { RABITQ_TEST_WHEEL = "1", OMP_NUM_THREADS = "2" }

[tool.cibuildwheel.macos.environment]
MACOSX_DEPLOYMENT_TARGET = "14.0"
RABITQ_OPENMP_PREFIX = "$(pwd)/build/wheel-openmp"
CMAKE_ARGS = '-DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF -DOpenMP_ROOT="$RABITQ_OPENMP_PREFIX" -DCMAKE_OSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET'

[tool.cibuildwheel.windows]
before-build = "python -m pip install delvewheel"
repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}"
Expand Down
26 changes: 26 additions & 0 deletions scripts/build-macos-openmp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash

set -euo pipefail

readonly install_prefix="${1:?OpenMP install prefix is required}"
readonly deployment_target="${MACOSX_DEPLOYMENT_TARGET:?macOS deployment target is required}"
source_dir="$(mktemp -d)"
readonly source_dir
trap 'rm -rf "$source_dir"' EXIT

# Homebrew bottles target the runner OS. Build its checksum-verified source
# ourselves so the bundled runtime supports the wheel's older deployment target.
brew fetch --build-from-source libomp
tar -xf "$(brew --cache --build-from-source libomp)" \
-C "$source_dir" --strip-components=1
cmake -S "$source_dir/runtimes" -B "$source_dir/build" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$install_prefix" \
-DCMAKE_OSX_ARCHITECTURES=arm64 \
-DCMAKE_OSX_DEPLOYMENT_TARGET="$deployment_target" \
-DLLVM_ENABLE_RUNTIMES=openmp \
-DOPENMP_INSTALL_LIBDIR=lib \
-DOPENMP_ENABLE_OMPT_TOOLS=OFF \
-DLIBOMP_INSTALL_ALIASES=OFF
cmake --build "$source_dir/build" --parallel "$(sysctl -n hw.logicalcpu)"
cmake --install "$source_dir/build"
6 changes: 5 additions & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,13 @@ CMake excludes AVX source files on ARM64. Reference tests run through generic an
NEON kernels; explicit AVX backend tests are guarded or skipped.

For Python source builds, pass `-Ccmake.define.OpenMP_ROOT=<libomp-prefix>` as well
as the native-optimization setting below. Release CI uses native `macos-14` runners,
as the native-optimization setting below. Release CI uses native `macos-15` runners,
builds CPython 3.11–3.14 ARM64 wheels, bundles OpenMP with delocate, then installs and
runs the full Python suite for each wheel. The minimum wheel OS target is macOS 14.
The wheel build compiles Homebrew's OpenMP source with that deployment target via
`scripts/build-macos-openmp.sh`; the runner's prebuilt OpenMP bottle may require a
newer macOS. The installed-wheel check verifies both the extension and bundled
runtime support macOS 14.
Set `RABITQ_TEST_WHEEL=1` when testing a repaired wheel locally to also check its
ARM64 extension and bundled OpenMP loader path.

Expand Down
8 changes: 8 additions & 0 deletions tests/python/test_wheel_macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import os
import platform
import re
import subprocess
from pathlib import Path

Expand Down Expand Up @@ -37,3 +38,10 @@ def test_native_arm64_wheel_bundles_openmp():
subprocess.check_output(["lipo", "-archs", str(runtime)], text=True).strip()
== "arm64"
)
for binary in (path, runtime):
load_commands = subprocess.check_output(["otool", "-l", str(binary)], text=True)
minimum_versions = re.findall(r"\bminos (\d+)\.(\d+)", load_commands)
assert minimum_versions, load_commands
assert all(
(int(major), int(minor)) <= (14, 0) for major, minor in minimum_versions
), f"{binary} requires macOS newer than 14.0: {minimum_versions}"
Loading