Skip to content

Add explicit PyTorch dependency with platform-specific constraints #5

Add explicit PyTorch dependency with platform-specific constraints

Add explicit PyTorch dependency with platform-specific constraints #5

name: Cross-Platform Compatibility Tests
on:
push:
branches: [ package ]
pull_request:
branches: [ package ]
workflow_dispatch: # Allow manual triggering
jobs:
test-cross-platform:
strategy:
fail-fast: false # Continue testing other platforms even if one fails
matrix:
include:
# Windows x86_64
- os: windows-latest
python-version: "3.10"
platform-name: "Windows-py310"
- os: windows-latest
python-version: "3.11"
platform-name: "Windows-py311"
- os: windows-latest
python-version: "3.12"
platform-name: "Windows-py312"
# macOS x86_64 (Intel)
- os: macos-13 # Intel-based
python-version: "3.10"
platform-name: "macOS-Intel-py310"
- os: macos-13
python-version: "3.11"
platform-name: "macOS-Intel-py311"
# macOS ARM64 (Apple Silicon)
- os: macos-14 # Apple Silicon
python-version: "3.10"
platform-name: "macOS-ARM64-py310"
- os: macos-14
python-version: "3.11"
platform-name: "macOS-ARM64-py311"
- os: macos-14
python-version: "3.12"
platform-name: "macOS-ARM64-py312"
# Linux x86_64
- os: ubuntu-20.04
python-version: "3.10"
platform-name: "Linux-py310-ubuntu20"
- os: ubuntu-22.04
python-version: "3.11"
platform-name: "Linux-py311-ubuntu22"
- os: ubuntu-22.04
python-version: "3.12"
platform-name: "Linux-py312-ubuntu22"
runs-on: ${{ matrix.os }}
name: Test ${{ matrix.platform-name }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Display platform information
run: |
python -c "
import platform, sys
print('=== PLATFORM INFO ===')
print(f'Platform: {platform.platform()}')
print(f'System: {platform.system()}')
print(f'Machine: {platform.machine()}')
print(f'Processor: {platform.processor()}')
print(f'Architecture: {platform.architecture()}')
print(f'Python: {sys.version}')
print(f'sys.platform: {sys.platform}')
print('=====================')
"
- name: Cache pip dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/requirements.txt', '**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-
- name: Install system dependencies (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y libgl1-mesa-glx libglib2.0-0 libsm6 libxext6 libxrender-dev libgomp1
- name: Install system dependencies (macOS)
if: startsWith(matrix.os, 'macos')
run: |
# Install any macOS-specific dependencies if needed
brew update || true
- name: Upgrade pip and install build tools
run: |
python -m pip install --upgrade pip setuptools wheel
- name: Install Poetry
run: |
python -m pip install poetry
- name: Configure Poetry
run: |
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
- name: Install dependencies with Poetry
run: |
# Remove lock file using cross-platform Python command and install without lock file constraints
python -c "import os; os.remove('poetry.lock') if os.path.exists('poetry.lock') else None"
poetry install --only=main --no-cache
- name: Verify environment markers worked correctly
run: |
poetry run python -c "
import numpy as np
import platform
print('=== ENVIRONMENT MARKERS TEST ===')
print(f'NumPy version: {np.__version__}')
print(f'Platform: {platform.system()} {platform.machine()}')
# Verify version ranges based on our environment markers
if platform.system() == 'Windows':
assert '1.26.0' <= np.__version__ < '1.26.5', f'Windows should have numpy 1.26.0-1.26.5, got {np.__version__}'
print('✅ Windows NumPy version constraint satisfied')
else:
assert '1.26.0' <= np.__version__ < '1.27.0', f'Non-Windows should have numpy 1.26.0-1.27.0, got {np.__version__}'
print('✅ Non-Windows NumPy version constraint satisfied')
print('✅ Environment markers working correctly!')
print('===============================')
"
- name: Test NumPy binary compatibility
run: |
poetry run python -c "
import numpy as np
print('=== NUMPY BINARY COMPATIBILITY TEST ===')
# Test operations that commonly trigger binary incompatibility
print('Testing dtype operations...')
arr = np.array([1.0, 2.0, 3.0], dtype=np.float64)
dt = np.dtype('float64')
test_array = np.zeros(100, dtype=dt)
print('Testing array operations...')
result = np.mean(arr)
matrix = np.random.random((50, 50))
matrix_op = np.dot(matrix, matrix.T)
print('Testing advanced operations...')
# These operations often trigger the 'dtype size changed' error
complex_array = np.array([[1+2j, 3+4j], [5+6j, 7+8j]], dtype=np.complex128)
fft_result = np.fft.fft2(complex_array)
print(f'✅ NumPy {np.__version__} - Binary compatibility test PASSED!')
print('=========================================')
"
- name: Test Pandas-NumPy interaction
run: |
poetry run python -c "
import pandas as pd
import numpy as np
print('=== PANDAS-NUMPY INTERACTION TEST ===')
# Create DataFrame with numpy operations (common failure point)
df = pd.DataFrame({
'A': np.random.random(100),
'B': np.random.normal(0, 1, 100),
'C': np.arange(100, dtype=np.float64)
})
# Operations that commonly trigger binary issues
print('Testing DataFrame operations...')
stats = df.describe()
grouped = df.groupby(pd.cut(df['A'], 5)).mean()
# Dtype operations (critical test)
print('Testing dtype conversions...')
df_typed = df.astype({'A': 'float32', 'B': 'float64'})
print(f'✅ Pandas {pd.__version__} - NumPy interaction test PASSED!')
print('====================================')
"
- name: Test PyTorch compatibility
run: |
poetry run python -c "
import torch
print('=== PYTORCH COMPATIBILITY TEST ===')
# Basic tensor operations
print('Testing tensor operations...')
x = torch.randn(10, 10)
y = torch.randn(10, 10)
z = torch.mm(x, y)
# Test NumPy interop (common issue area)
print('Testing PyTorch-NumPy interop...')
np_array = z.detach().numpy()
torch_from_np = torch.from_numpy(np_array)
cuda_available = torch.cuda.is_available()
print(f'✅ PyTorch {torch.__version__} compatibility test PASSED! (CUDA: {cuda_available})')
print('=================================')
"
- name: Test Ultralytics import (Critical Test)
run: |
poetry run python -c "
print('=== ULTRALYTICS IMPORT TEST ===')
print('This is the main test - Ultralytics caused the original numpy binary incompatibility')
try:
from ultralytics import YOLO, __version__
print(f'✅ Ultralytics {__version__} import SUCCESSFUL!')
# Test that we can instantiate YOLO class (without loading weights)
print('Testing YOLO class instantiation...')
# Note: We don't load actual weights to avoid long download times in CI
print('✅ YOLO class accessible!')
except Exception as e:
print(f'❌ Ultralytics import FAILED: {e}')
raise
print('✅ Ultralytics compatibility test PASSED!')
print('==============================')
"
- name: Test scikit-learn compatibility
run: |
poetry run python -c "
import sklearn
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
print('=== SCIKIT-LEARN COMPATIBILITY TEST ===')
print('Testing ML operations...')
X, y = make_classification(n_samples=100, n_features=20, n_classes=2, random_state=42)
clf = RandomForestClassifier(n_estimators=10, random_state=42)
clf.fit(X, y)
predictions = clf.predict(X)
print(f'✅ scikit-learn {sklearn.__version__} compatibility test PASSED!')
print('======================================')
"
- name: Install and test bplusplus package
run: |
# Install the package in development mode
poetry run pip install -e .
# Test imports
poetry run python -c "
import bplusplus
print('=== BPLUSPLUS PACKAGE TEST ===')
print('Testing bplusplus module imports...')
# Test individual modules
modules = ['collect', 'prepare', 'train', 'test', 'inference']
successful_modules = []
for module in modules:
try:
exec(f'from bplusplus import {module}')
successful_modules.append(module)
print(f' ✅ bplusplus.{module}')
except ImportError as e:
print(f' ❌ bplusplus.{module}: {e}')
print(f'✅ Bplusplus package test PASSED! ({len(successful_modules)}/{len(modules)} modules available)')
# Final compatibility verification
print('=== FINAL VERIFICATION ===')
print('Testing that all critical dependencies work together...')
import numpy as np
import pandas as pd
import torch
from ultralytics import YOLO
import sklearn
# Create a small test that uses all libraries together
data = np.random.random((10, 5))
df = pd.DataFrame(data)
tensor = torch.from_numpy(data.astype(np.float32))
print('✅ ALL DEPENDENCIES WORK TOGETHER!')
print('✅ CROSS-PLATFORM COMPATIBILITY VERIFIED!')
print('========================')
"
# Summary job that runs after all platform tests
test-summary:
needs: test-cross-platform
runs-on: ubuntu-latest
if: always()
steps:
- name: Test Results Summary
run: |
echo "🎯 CROSS-PLATFORM TEST SUMMARY"
echo "============================="
# Check if all tests passed
if [ "${{ needs.test-cross-platform.result }}" == "success" ]; then
echo "🎉 SUCCESS: All platforms passed!"
echo ""
echo "✅ Windows (Python 3.10, 3.11, 3.12)"
echo "✅ macOS Intel (Python 3.10, 3.11)"
echo "✅ macOS Apple Silicon (Python 3.10, 3.11, 3.12)"
echo "✅ Linux Ubuntu (Python 3.10, 3.11, 3.12)"
echo ""
echo "Your bplusplus package is compatible across all platforms!"
echo "Users can safely install on Windows, macOS, and Linux."
else
echo "❌ FAILURE: Some platform tests failed"
echo ""
echo "Check the individual job results above to see which platforms failed."
echo "Common issues:"
echo "- NumPy binary incompatibility (environment markers not working)"
echo "- Missing system dependencies"
echo "- Architecture-specific wheel availability"
echo ""
exit 1
fi