Skip to content

Commit caf5455

Browse files
committed
packaging: add svv-accelerated companion wheel and accel extras; auto-prefer accelerators; build both wheels in CI
1 parent 0711311 commit caf5455

File tree

3 files changed

+110
-37
lines changed

3 files changed

+110
-37
lines changed

.github/workflows/test-upload.yml

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ jobs:
3535
- name: Upgrade pip, setuptools, wheel
3636
run: python -m pip install --upgrade setuptools wheel
3737

38-
- name: Build wheels
38+
- name: Build wheels (pure-Python svv)
39+
env:
40+
# Ensure we do NOT build extensions for the main wheel
41+
SVV_BUILD_EXTENSIONS: "0"
3942
run: cibuildwheel --output-dir dist
4043

4144
- name: Upload artifacts
@@ -62,22 +65,47 @@ jobs:
6265
name: cibw-sdist
6366
path: dist/*.tar.gz
6467

65-
upload_testpypi:
66-
needs: [build_wheels, build_sdist]
68+
69+
build_wheels_accelerated:
70+
runs-on: ${{ matrix.os }}
71+
strategy:
72+
matrix:
73+
os: [macOS-latest, ubuntu-latest, windows-latest]
74+
env:
75+
CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-*"
76+
CIBW_BUILD_VERBOSITY: 3
77+
# Install build deps before building each wheel
78+
CIBW_BEFORE_BUILD: "pip install -U pip setuptools wheel cython numpy cmake"
79+
# Tell setup.py to build the companion 'svv-accelerated' distribution
80+
CIBW_ENVIRONMENT: "PIP_NO_CACHE_DIR=1 SVV_ACCEL_COMPANION=1"
81+
steps:
82+
- name: Check out code
83+
uses: actions/checkout@v4
84+
85+
- name: Install cibuildwheel
86+
run: pip install --upgrade pip && pip install cibuildwheel
87+
88+
- name: Build wheels (svv-accelerated)
89+
run: cibuildwheel --output-dir dist
90+
91+
- name: Upload artifacts
92+
uses: actions/upload-artifact@v4
93+
with:
94+
name: cibw-wheels-accelerated-${{ matrix.os }}
95+
path: dist
96+
97+
upload_testpypi_all:
98+
needs: [build_wheels, build_wheels_accelerated, build_sdist]
6799
runs-on: ubuntu-latest
68-
steps:
100+
steps:
69101
- uses: actions/download-artifact@v4
70102
with:
71-
# unpacks all CIBW artifacts into dist/
72103
pattern: cibw-*
73104
path: dist
74105
merge-multiple: true
75-
76-
- name: Install build dependencies
77-
run: |
78-
pip install --upgrade pip && pip install twine
79-
80-
- name: Upload to TestPyPI
106+
- name: Install twine
107+
run: pip install --upgrade pip && pip install twine
108+
- name: Upload both wheels to TestPyPI
81109
run: twine upload --repository testpypi dist/*
82110
env:
83111
TWINE_USERNAME: __token__

setup.py

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ def env_flag(name: str, default: bool = False) -> bool:
4242
return default
4343
return str(val).strip().lower() in {"1", "true", "yes", "y", "on"}
4444

45+
ACCEL_COMPANION = env_flag("SVV_ACCEL_COMPANION", False)
46+
4547
def get_filename_without_ext(abs_path):
4648
base_name = os.path.basename(abs_path) # e.g. "file.txt"
4749
file_name_no_ext = os.path.splitext(base_name)[0] # e.g. "file"
@@ -460,34 +462,40 @@ def get_extra_compile_args():
460462

461463
def _build_extensions():
462464
"""Return list of Extension objects if we are building with accelerators, else []."""
463-
build_accel = env_flag("SVV_BUILD_EXTENSIONS", False) or env_flag("SVV_WITH_CYTHON", False)
465+
# Two modes:
466+
# - Normal package (svv): build only when explicitly requested via env flag
467+
# - Companion package (svv-accelerated): always build extensions (wheels-only)
468+
build_accel = env_flag("SVV_BUILD_EXTENSIONS", False) or env_flag("SVV_WITH_CYTHON", False) or ACCEL_COMPANION
464469
if not (build_accel and HAS_CYTHON and HAS_NUMPY):
465470
return []
466471
include_dirs = [_np.get_include()] if HAS_NUMPY else []
472+
prefix = 'svv_accel.' if ACCEL_COMPANION else 'svv.'
473+
def mod(name):
474+
return prefix + name
467475
return [
468-
Extension('svv.domain.routines.c_allocate', ['svv/domain/routines/c_allocate.pyx'],
476+
Extension(mod('domain.routines.c_allocate'), ['svv/domain/routines/c_allocate.pyx'],
469477
include_dirs=include_dirs, language='c++'),
470-
Extension('svv.domain.routines.c_sample', ['svv/domain/routines/c_sample.pyx'],
478+
Extension(mod('domain.routines.c_sample'), ['svv/domain/routines/c_sample.pyx'],
471479
include_dirs=include_dirs, language='c++'),
472-
Extension('svv.utils.spatial.c_distance', ['svv/utils/spatial/c_distance.pyx'],
480+
Extension(mod('utils.spatial.c_distance'), ['svv/utils/spatial/c_distance.pyx'],
473481
include_dirs=include_dirs, language='c++'),
474-
Extension('svv.tree.utils.c_angle', ['svv/tree/utils/c_angle.pyx'],
482+
Extension(mod('tree.utils.c_angle'), ['svv/tree/utils/c_angle.pyx'],
475483
include_dirs=include_dirs, language='c++'),
476-
Extension('svv.tree.utils.c_basis', ['svv/tree/utils/c_basis.pyx'],
484+
Extension(mod('tree.utils.c_basis'), ['svv/tree/utils/c_basis.pyx'],
477485
include_dirs=include_dirs, language='c++'),
478-
Extension('svv.tree.utils.c_close', ['svv/tree/utils/c_close.pyx'],
486+
Extension(mod('tree.utils.c_close'), ['svv/tree/utils/c_close.pyx'],
479487
include_dirs=include_dirs, language='c++'),
480-
Extension('svv.tree.utils.c_local_optimize', ['svv/tree/utils/c_local_optimize.pyx'],
488+
Extension(mod('tree.utils.c_local_optimize'), ['svv/tree/utils/c_local_optimize.pyx'],
481489
include_dirs=include_dirs, language='c++'),
482-
Extension('svv.tree.utils.c_obb', ['svv/tree/utils/c_obb.pyx'],
490+
Extension(mod('tree.utils.c_obb'), ['svv/tree/utils/c_obb.pyx'],
483491
include_dirs=include_dirs, language='c++'),
484-
Extension('svv.tree.utils.c_update', ['svv/tree/utils/c_update.pyx'],
492+
Extension(mod('tree.utils.c_update'), ['svv/tree/utils/c_update.pyx'],
485493
include_dirs=include_dirs, language='c++'),
486-
Extension('svv.tree.utils.c_extend', ['svv/tree/utils/c_extend.pyx'],
494+
Extension(mod('tree.utils.c_extend'), ['svv/tree/utils/c_extend.pyx'],
487495
include_dirs=include_dirs, language='c++'),
488-
Extension('svv.simulation.utils.close_segments', ['svv/simulation/utils/close_segments.pyx'],
496+
Extension(mod('simulation.utils.close_segments'), ['svv/simulation/utils/close_segments.pyx'],
489497
include_dirs=include_dirs, language='c++'),
490-
Extension('svv.simulation.utils.extract', ['svv/simulation/utils/extract.pyx'],
498+
Extension(mod('simulation.utils.extract'), ['svv/simulation/utils/extract.pyx'],
491499
include_dirs=include_dirs, language='c++'),
492500
]
493501

@@ -517,7 +525,10 @@ def read_version():
517525
'Operating System :: Unix',
518526
'Operating System :: MacOS']
519527

520-
PACKAGES = find_packages(include=['svv', 'svv.*'])
528+
if ACCEL_COMPANION:
529+
PACKAGES = find_packages(include=['svv_accel']) or ['svv_accel']
530+
else:
531+
PACKAGES = find_packages(include=['svv', 'svv.*'])
521532

522533

523534
def parse_requirements(path="requirements.txt"):
@@ -535,8 +546,8 @@ def parse_requirements(path="requirements.txt"):
535546

536547
REQUIREMENTS = parse_requirements()
537548
_build_accel_flag = env_flag("SVV_BUILD_EXTENSIONS", False) or env_flag("SVV_WITH_CYTHON", False)
538-
# Filter out Cython from runtime requirements unless explicitly building extensions
539-
if not _build_accel_flag:
549+
if not _build_accel_flag and not ACCEL_COMPANION:
550+
# For the main package, filter out Cython unless explicitly requested
540551
REQUIREMENTS = [r for r in REQUIREMENTS if not r.strip().lower().startswith('cython')]
541552

542553

@@ -549,7 +560,7 @@ def parse_requirements(path="requirements.txt"):
549560
extensions = _build_extensions()
550561

551562
setup_info = dict(
552-
name='svv',
563+
name=('svv-accelerated' if ACCEL_COMPANION else 'svv'),
553564
version=VERSION,
554565
author='Zachary Sexton',
555566
author_email='[email protected]',
@@ -562,17 +573,18 @@ def parse_requirements(path="requirements.txt"):
562573
long_description=DESCRIPTION,
563574
long_description_content_type="text/markdown",
564575
ext_modules=cythonize(extensions) if (extensions and HAS_CYTHON) else [],
565-
package_data={'svv.bin': ['*']},
576+
package_data=( {'svv.bin': ['*']} if not ACCEL_COMPANION else {} ),
577+
exclude_package_data=(
578+
{"svv": ["*.so", "*.pyd", "*.dylib", "*.dll"]} if not ACCEL_COMPANION else {}
579+
),
566580
include_package_data=True,
567581
zip_safe=False,
568582
install_requires=REQUIREMENTS,
569-
extras_require={
570-
'all': [
571-
'Cython>=3.0.7',
572-
'numpy',
573-
'cmake>=3.15'
574-
]
575-
},
583+
extras_require=(
584+
# For the main package, make [accel] and [accelerated] pull in the companion wheel
585+
{'accel': [f'svv-accelerated=={VERSION}'], 'accelerated': [f'svv-accelerated=={VERSION}']}
586+
if not ACCEL_COMPANION else {}
587+
),
576588
cmdclass={
577589
'build_ext': DownloadAndBuildExt,
578590
'bdist_wheel': BDistWheelCmd,

svv/__init__.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,34 @@
1-
__version__ = "0.0.32"
1+
__version__ = "0.0.32"
2+
3+
# If the optional companion package with compiled accelerators is installed
4+
# (svv-accelerated), prefer those modules transparently by aliasing them into
5+
# svv.* import paths. This lets users simply `pip install svv[accel]` to get
6+
# faster implementations without changing imports in their code.
7+
try:
8+
import importlib, sys
9+
# Try importing the top-level companion namespace first.
10+
importlib.import_module('svv_accel')
11+
_ACCEL = {
12+
'svv.domain.routines.c_allocate': 'svv_accel.domain.routines.c_allocate',
13+
'svv.domain.routines.c_sample': 'svv_accel.domain.routines.c_sample',
14+
'svv.utils.spatial.c_distance': 'svv_accel.utils.spatial.c_distance',
15+
'svv.tree.utils.c_angle': 'svv_accel.tree.utils.c_angle',
16+
'svv.tree.utils.c_basis': 'svv_accel.tree.utils.c_basis',
17+
'svv.tree.utils.c_close': 'svv_accel.tree.utils.c_close',
18+
'svv.tree.utils.c_local_optimize': 'svv_accel.tree.utils.c_local_optimize',
19+
'svv.tree.utils.c_obb': 'svv_accel.tree.utils.c_obb',
20+
'svv.tree.utils.c_update': 'svv_accel.tree.utils.c_update',
21+
'svv.tree.utils.c_extend': 'svv_accel.tree.utils.c_extend',
22+
'svv.simulation.utils.close_segments': 'svv_accel.simulation.utils.close_segments',
23+
'svv.simulation.utils.extract': 'svv_accel.simulation.utils.extract',
24+
}
25+
for target, source in _ACCEL.items():
26+
try:
27+
mod = importlib.import_module(source)
28+
sys.modules[target] = mod
29+
except Exception:
30+
# If a specific accelerator module is missing, leave fallback in place
31+
pass
32+
except Exception:
33+
# Companion package not installed; keep pure-Python fallbacks/compiled svv.* if present
34+
pass

0 commit comments

Comments
 (0)