Skip to content
Open
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
103 changes: 89 additions & 14 deletions .github/workflows/build-wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ jobs:
- os: macos-14
platform: macos
arch: arm64
- os: windows-2022
platform: windows
arch: x86_64

runs-on: ${{ matrix.os }}
env:
Expand Down Expand Up @@ -48,25 +51,48 @@ jobs:
restore-keys: |
cibw-${{ runner.os }}-${{ matrix.platform }}-${{ matrix.arch }}-

- name: Build wheels
- name: Build wheels (Linux)
if: matrix.platform == 'linux'
env:
CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-*"
CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux_*"
CIBW_ARCHS: "auto64"
CIBW_BEFORE_BUILD: "bash {project}/scripts/prepare_build.sh"
CIBW_TEST_SKIP: "*"
CIBW_ENVIRONMENT: "RAYFORCE_GITHUB=https://github.com/RayforceDB/rayforce.git"
CIBW_MACOS_ENVIRONMENT: "MACOSX_DEPLOYMENT_TARGET=11.0"
CIBW_CACHE_PATH: ".cibw"
CIBW_MANYLINUX_X86_64_IMAGE: "quay.io/pypa/manylinux_2_28_x86_64:latest"
CIBW_BEFORE_ALL_LINUX: "yum install -y git gcc clang gcc-c++ make"
run: cibuildwheel --platform linux --archs x86_64 --output-dir wheelhouse

run: |
if [ "${{ matrix.platform }}" = "linux" ]; then
cibuildwheel --platform linux --archs x86_64 --output-dir wheelhouse
else
cibuildwheel --platform macos --archs arm64 --output-dir wheelhouse
fi
- name: Build wheels (macOS)
if: matrix.platform == 'macos'
env:
CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-*"
CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux_*"
CIBW_ARCHS: "auto64"
CIBW_BEFORE_BUILD: "bash {project}/scripts/prepare_build.sh"
CIBW_TEST_SKIP: "*"
CIBW_ENVIRONMENT: "RAYFORCE_GITHUB=https://github.com/RayforceDB/rayforce.git"
CIBW_MACOS_ENVIRONMENT: "MACOSX_DEPLOYMENT_TARGET=11.0"
CIBW_CACHE_PATH: ".cibw"
run: cibuildwheel --platform macos --archs arm64 --output-dir wheelhouse

- name: Build wheels (Windows)
if: matrix.platform == 'windows'
env:
CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-*"
CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux_*"
CIBW_ARCHS: "auto64"
CIBW_BEFORE_ALL_WINDOWS: >-
C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm mingw-w64-x86_64-clang mingw-w64-x86_64-lld make"
CIBW_BEFORE_BUILD_WINDOWS: "powershell -ExecutionPolicy Bypass -File {project}/scripts/prepare_build_windows.ps1"
CIBW_TEST_SKIP: "*"
CIBW_ENVIRONMENT_WINDOWS: >-
RAYFORCE_GITHUB=https://github.com/RayforceDB/rayforce.git
PATH="C:\\msys64\\mingw64\\bin;C:\\msys64\\usr\\bin;$PATH"
CIBW_CACHE_PATH: ".cibw"
run: cibuildwheel --platform windows --archs AMD64 --output-dir wheelhouse

- name: Upload wheel artifacts
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -113,6 +139,22 @@ jobs:
python-version: '3.14'
platform: macos
arch: arm64
- os: windows-2022
python-version: '3.11'
platform: windows
arch: x86_64
- os: windows-2022
python-version: '3.12'
platform: windows
arch: x86_64
- os: windows-2022
python-version: '3.13'
platform: windows
arch: x86_64
- os: windows-2022
python-version: '3.14'
platform: windows
arch: x86_64

runs-on: ${{ matrix.os }}

Expand All @@ -122,7 +164,7 @@ jobs:
with:
submodules: false

- name: Verify AVX2 support (required for rayforce)
- name: Verify AVX2 support (Linux)
if: runner.os == 'Linux'
run: |
echo "Checking CPU features..."
Expand All @@ -133,6 +175,15 @@ jobs:
exit 1
fi

- name: Verify AVX2 support (Windows)
if: runner.os == 'Windows'
shell: powershell
run: |
$cpu = Get-CimInstance Win32_Processor
Write-Host "CPU: $($cpu.Name)"
# GitHub Actions Windows runners use modern Xeon/EPYC CPUs with AVX2
Write-Host "AVX2 support assumed on GitHub Actions Windows runners"

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
Expand All @@ -150,18 +201,27 @@ jobs:
python -m pip install --upgrade pip
pip install pytest pytest-asyncio pytest-timeout websockets pandas>=2.0.0 polars>=0.19.0 pyarrow sqlglot

- name: Remove local rayforce package to force pip installation
- name: Remove local rayforce package (Unix)
if: runner.os != 'Windows'
run: |
# Remove local rayforce directory to ensure we test against installed wheel
rm -rf rayforce/__pycache__
rm -rf rayforce/*.so rayforce/*.dylib rayforce/*.dll
rm -rf rayforce/*.so rayforce/*.dylib rayforce/*.dll rayforce/*.pyd
rm -rf rayforce/plugins/*.so rayforce/plugins/*.dylib rayforce/plugins/*.dll
# Move rayforce directory out of Python path temporarily
if [ -d "rayforce" ]; then
mv rayforce rayforce.local
fi

- name: Install and test wheel
- name: Remove local rayforce package (Windows)
if: runner.os == 'Windows'
shell: powershell
run: |
if (Test-Path "rayforce\__pycache__") { Remove-Item -Recurse -Force "rayforce\__pycache__" }
Get-ChildItem -Path "rayforce" -Include "*.so","*.dylib","*.dll","*.pyd" -ErrorAction SilentlyContinue | Remove-Item -Force
Get-ChildItem -Path "rayforce\plugins" -Include "*.so","*.dylib","*.dll" -ErrorAction SilentlyContinue | Remove-Item -Force
if (Test-Path "rayforce") { Rename-Item "rayforce" "rayforce.local" }

- name: Install and test wheel (Unix)
if: runner.os != 'Windows'
run: |
PYVER=$(echo '${{ matrix.python-version }}' | tr -d '.')
WHEEL_FILE=$(find wheels -name "*cp${PYVER}*.whl" | head -1)
Expand All @@ -174,6 +234,21 @@ jobs:
pip install "$WHEEL_FILE" --force-reinstall --no-deps
python -m pytest -m "" -x -vv --durations=20 tests/

- name: Install and test wheel (Windows)
if: runner.os == 'Windows'
shell: powershell
run: |
$pyver = '${{ matrix.python-version }}' -replace '\.',''
$wheel = Get-ChildItem -Path wheels -Filter "*cp${pyver}*win_amd64*.whl" | Select-Object -First 1
if (-not $wheel) {
Write-Error "No wheel file found for Python ${{ matrix.python-version }}"
Get-ChildItem -Path wheels -Filter "*.whl"
exit 1
}
Write-Host "Installing wheel: $($wheel.FullName)"
pip install $wheel.FullName --force-reinstall --no-deps
python -m pytest -m "" -x -vv --durations=20 tests/

publish:
name: Publish to PyPI
needs: test_wheels
Expand Down
82 changes: 73 additions & 9 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,56 @@
name: Coverage

on:
pull_request:
push:
branches: [master]

jobs:
coverage:
name: Coverage
runs-on: ubuntu-latest
name: Coverage (${{ matrix.os }}, Python ${{ matrix.python-version }})
strategy:
fail-fast: false
matrix:
include:
# TODO: re-enable linux and macos after Windows support is confirmed
# - os: ubuntu-22.04
# python-version: '3.11'
# platform: linux
# - os: ubuntu-22.04
# python-version: '3.12'
# platform: linux
# - os: ubuntu-22.04
# python-version: '3.13'
# platform: linux
# - os: ubuntu-22.04
# python-version: '3.14'
# platform: linux
# - os: macos-14
# python-version: '3.11'
# platform: macos
# - os: macos-14
# python-version: '3.12'
# platform: macos
# - os: macos-14
# python-version: '3.13'
# platform: macos
# - os: macos-14
# python-version: '3.14'
# platform: macos
- os: windows-2022
python-version: '3.11'
platform: windows
- os: windows-2022
python-version: '3.12'
platform: windows
- os: windows-2022
python-version: '3.13'
platform: windows
- os: windows-2022
python-version: '3.14'
platform: windows

runs-on: ${{ matrix.os }}

env:
RAYFORCE_GITHUB: "https://github.com/RayforceDB/rayforce.git"
Expand All @@ -18,25 +61,46 @@ jobs:
with:
submodules: false

- name: Set up Python 3.14
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: '3.14'
python-version: ${{ matrix.python-version }}
cache: 'pip'

- name: Install system dependencies
- name: Install system dependencies (Linux)
if: matrix.platform == 'linux'
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq git gcc clang g++ make

- name: Install system dependencies (macOS)
if: matrix.platform == 'macos'
run: |
echo "Xcode CLI tools provide clang and make"

- name: Install system dependencies (Windows)
if: matrix.platform == 'windows'
shell: powershell
run: |
C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm mingw-w64-x86_64-clang mingw-w64-x86_64-lld make"

- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel setuptools-scm

- name: Build rayforce binaries
- name: Build rayforce binaries (Unix)
if: matrix.platform != 'windows'
run: make app

- name: Build rayforce binaries (Windows)
if: matrix.platform == 'windows'
shell: powershell
env:
PATH: "C:\\msys64\\mingw64\\bin;C:\\msys64\\usr\\bin;${{ env.PATH }}"
run: |
& scripts/prepare_build_windows.ps1

- name: Install test dependencies
run: |
pip install -e ".[dev,test-plugins]"
Expand All @@ -54,13 +118,13 @@ jobs:
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
flags: ${{ matrix.platform }}-py${{ matrix.python-version }}
name: coverage-${{ matrix.platform }}-py${{ matrix.python-version }}
fail_ci_if_error: false

- name: Upload coverage HTML report
uses: actions/upload-artifact@v4
with:
name: coverage-report
name: coverage-report-${{ matrix.platform }}-py${{ matrix.python-version }}
path: htmlcov/
retention-days: 30
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ include README.md
include LICENSE
recursive-include rayforce *.so
recursive-include rayforce *.dylib
recursive-include rayforce *.dll
recursive-include rayforce *.pyd
recursive-include rayforce/plugins *.so
recursive-include rayforce/plugins *.dylib
recursive-include rayforce/plugins *.dll
recursive-include rayforce *.pyi
include rayforce/bin/rayforce
include rayforce/bin/rayforce.exe
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ifeq ($(UNAME_S),Darwin)
RAYKX_LIB_NAME = libraykx.dylib
RELEASE_LDFLAGS = $(shell python3 -c "import sysconfig; print(sysconfig.get_config_var('LDFLAGS') or '')")
PYTHON_VERSION = $(shell python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
SHARED_COMPILE_FLAGS = -lpython$(PYTHON_VERSION)
SHARED_COMPILE_FLAGS = -undefined dynamic_lookup
else ifeq ($(UNAME_S),Linux)
RAYKX_LIB_NAME = libraykx.so
RELEASE_LDFLAGS = $$(RELEASE_LDFLAGS)
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ before-all = [
"yum install -y clang llvm",
]

[tool.cibuildwheel.windows]
before-all = "C:\\msys64\\usr\\bin\\bash -lc \"pacman -S --noconfirm mingw-w64-x86_64-clang mingw-w64-x86_64-lld make\""
before-build = "powershell -ExecutionPolicy Bypass -File {project}/scripts/prepare_build_windows.ps1"
environment = { PATH = "C:\\msys64\\mingw64\\bin;C:\\msys64\\usr\\bin;$PATH", RAYFORCE_GITHUB = "https://github.com/RayforceDB/rayforce.git" }

[tool.ruff]
show-fixes = true
target-version = "py314"
Expand Down
20 changes: 15 additions & 5 deletions rayforce/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

import ctypes
import os
from pathlib import Path
import sys

Expand All @@ -19,16 +20,25 @@
lib_name = "_rayforce_c.so"
raykx_lib_name = "libraykx.dylib"
elif sys.platform == "win32":
lib_name = "rayforce.dll"
lib_name = "_rayforce_c.pyd"
raykx_lib_name = "raykx.dll"
else:
raise ImportError(f"Platform not supported: {sys.platform}")

lib_path = Path(__file__).resolve().parent / lib_name
raykx_lib_path = Path(__file__).resolve().parent / "plugins" / raykx_lib_name
_pkg_dir = Path(__file__).resolve().parent
lib_path = _pkg_dir / lib_name
raykx_lib_path = _pkg_dir / "plugins" / raykx_lib_name
if lib_path.exists() and raykx_lib_path.exists():
try:
ctypes.CDLL(str(lib_path), mode=ctypes.RTLD_GLOBAL)
ctypes.CDLL(str(raykx_lib_path), mode=ctypes.RTLD_GLOBAL)
if sys.platform == "win32":
# Add package dirs to DLL search path (Python 3.8+ restricts DLL loading)
os.add_dll_directory(str(_pkg_dir))
os.add_dll_directory(str(_pkg_dir / "plugins"))
load_mode = 0
else:
load_mode = ctypes.RTLD_GLOBAL
ctypes.CDLL(str(lib_path), mode=load_mode)
ctypes.CDLL(str(raykx_lib_path), mode=load_mode)
except Exception as e:
raise ImportError(f"Error loading CDLL: {e}") from e
else:
Expand Down
2 changes: 2 additions & 0 deletions rayforce/capi/rayforce_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
#include "vary.h"
#include <Python.h>
#include <string.h>
#ifndef _WIN32
#include <unistd.h>
#endif

#ifndef memcpy
extern void *memcpy(void *dest, const void *src, size_t n);
Expand Down
Loading
Loading