Skip to content

Commit 3ef7955

Browse files
committed
[v0.1.0] 2025-09-06
πŸŽ‰++ PyPI Package Distribution ++ πŸŽ‰ - PyHelios now available on PyPI with `pip install pyhelios` ## Package Distribution - Added comprehensive wheel building infrastructure with GitHub Actions CI/CD - Cross-platform wheel support for Windows, macOS (x86_64 + ARM64), and Linux - Automated CUDA toolkit installation and multi-architecture GPU support - Smart platform detection for optimal plugin selection (macOS excludes GPU, Windows/Linux includes GPU when available) - Added wheel preparation script for native library packaging - Added MANIFEST.in for proper PyPI package structure ## Bug Fixes - Fixed PluginMetadata constructor calls in build_helios.py - Corrected plugin metadata parameter handling for GPU/visualization exclusions - Enhanced build system robustness for different plugin configurations
1 parent b6b7855 commit 3ef7955

12 files changed

Lines changed: 964 additions & 16 deletions

File tree

β€Ž.claude/agents/code-reviewer.mdβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ When reviewing code changes since the last commit, you will:
1515
3. Evaluate code against established best practices for the specific language and framework
1616
4. Look for potential security vulnerabilities, performance issues, and maintainability concerns
1717
5. Check for consistency with existing codebase patterns and conventions
18+
6. **ALWAYS check git status** to identify any new files that should be added to version control
1819

1920
**CRITICAL ISSUES TO CATCH:**
2021
- **Incomplete implementations**: Placeholder comments like "TODO", "FIXME", or unfinished logic
@@ -24,6 +25,9 @@ When reviewing code changes since the last commit, you will:
2425
- **Performance anti-patterns**: Inefficient algorithms, memory leaks, unnecessary database queries
2526
- **Error handling gaps**: Missing exception handling, inadequate error messages, swallowed exceptions
2627
- **Testing gaps**: Untestable code, missing edge case coverage, brittle test dependencies
28+
- **Git control gaps**: New files that should be added to version control but are missing from git
29+
- **Syntax errors**: Always run syntax checks on new Python files (python -m py_compile file.py)
30+
- **Missing imports**: Check that all required modules are imported in new files
2731

2832
**BEST PRACTICES ENFORCEMENT:**
2933
- Code readability and self-documenting practices
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
name: Build Wheels
2+
3+
on:
4+
push:
5+
branches: [ master, main ]
6+
tags: [ 'v*' ]
7+
pull_request:
8+
branches: [ master, main ]
9+
workflow_dispatch:
10+
11+
jobs:
12+
build_wheels:
13+
name: Build wheels on ${{ matrix.os }}
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
matrix:
17+
include:
18+
- os: ubuntu-24.04
19+
arch: x86_64
20+
- os: windows-2022
21+
arch: AMD64
22+
- os: macos-13
23+
arch: x86_64
24+
- os: macos-14
25+
arch: arm64
26+
27+
steps:
28+
- name: Checkout PyHelios
29+
uses: actions/checkout@v4
30+
with:
31+
submodules: recursive
32+
fetch-depth: 0 # Required for setuptools-scm
33+
34+
- name: Set up Python
35+
uses: actions/setup-python@v4
36+
with:
37+
python-version: '3.8' # Use lowest supported version for compatibility
38+
39+
- name: Setup MSVC (Windows)
40+
if: runner.os == 'Windows'
41+
uses: ilammy/msvc-dev-cmd@v1
42+
43+
- name: Install Helios dependencies (Linux)
44+
if: runner.os == 'Linux'
45+
run: |
46+
cd helios-core/utilities
47+
sudo bash dependencies.sh ALL
48+
49+
- name: Install Helios dependencies (macOS)
50+
if: runner.os == 'macOS'
51+
run: |
52+
cd helios-core/utilities
53+
bash dependencies.sh ALL
54+
55+
- name: Install CUDA Toolkit (Windows)
56+
if: runner.os == 'Windows'
57+
shell: powershell
58+
run: |
59+
# Download CUDA 12.6 installer
60+
Invoke-WebRequest -Uri "https://developer.download.nvidia.com/compute/cuda/12.6.2/network_installers/cuda_12.6.2_windows_network.exe" -OutFile "cuda_installer.exe"
61+
# Install CUDA toolkit components needed for compilation
62+
Start-Process -FilePath ".\cuda_installer.exe" -ArgumentList "-s","nvcc_12.6","cudart_12.6","nvrtc_12.6","nvrtc_dev_12.6","visual_studio_integration_12.6" -Wait
63+
# Add CUDA to PATH for subsequent steps
64+
echo "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.6\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
65+
66+
- name: Install cibuildwheel and repair tools
67+
run: |
68+
python -m pip install --upgrade pip
69+
python -m pip install cibuildwheel
70+
# Install platform-specific wheel repair tools
71+
if [ "${{ runner.os }}" = "Linux" ]; then
72+
python -m pip install auditwheel
73+
elif [ "${{ runner.os }}" = "macOS" ]; then
74+
python -m pip install delocate
75+
fi
76+
77+
- name: Build wheels
78+
run: python -m cibuildwheel --output-dir wheelhouse
79+
env:
80+
# Build for Python 3.8+ on all platforms
81+
CIBW_BUILD: cp38-* cp39-* cp310-* cp311-* cp312-*
82+
83+
# Skip 32-bit builds and PyPy
84+
CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux* pp*"
85+
86+
# Architecture configuration based on runner
87+
CIBW_ARCHS: ${{ matrix.arch }}
88+
89+
# Platform-specific build commands with broad CUDA compatibility
90+
CIBW_BEFORE_BUILD_MACOS: "python build_scripts/prepare_wheel.py --buildmode release --nogpu --verbose"
91+
CIBW_BEFORE_BUILD_WINDOWS: "set CMAKE_RC_COMPILER= && set PYHELIOS_CUDA_ARCHITECTURES=50;60;70;75;80;86;90 && python build_scripts/prepare_wheel.py --buildmode release --verbose"
92+
CIBW_BEFORE_BUILD_LINUX: "export PYHELIOS_CUDA_ARCHITECTURES=50;60;70;75;80;86;90 && python build_scripts/prepare_wheel.py --buildmode release --verbose"
93+
94+
# Test wheel installation
95+
CIBW_TEST_COMMAND: "python -c 'import pyhelios; print(f\"PyHelios {pyhelios.__version__} imported successfully\")'"
96+
CIBW_TEST_REQUIRES: "pytest"
97+
98+
# Repair wheels to bundle dependencies
99+
CIBW_REPAIR_WHEEL_COMMAND_MACOS: "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
100+
CIBW_REPAIR_WHEEL_COMMAND_LINUX: "auditwheel repair -w {dest_dir} {wheel}"
101+
102+
- name: Debug build failure
103+
if: failure()
104+
run: |
105+
echo "=== Build Failure Diagnostics ==="
106+
echo "Build directory contents:"
107+
find pyhelios_build -name "*.so" -o -name "*.dll" -o -name "*.dylib" 2>/dev/null || echo "No build directory found"
108+
echo ""
109+
echo "Plugin directory contents:"
110+
ls -la pyhelios/plugins/ 2>/dev/null || echo "No plugins directory found"
111+
echo ""
112+
echo "Wheel directory contents:"
113+
ls -la wheelhouse/ 2>/dev/null || echo "No wheelhouse directory found"
114+
echo ""
115+
echo "Python environment:"
116+
python --version
117+
pip list | grep -E "(cibuildwheel|auditwheel|delocate)" || echo "Wheel tools not found"
118+
119+
- name: Upload wheels as artifacts
120+
uses: actions/upload-artifact@v4
121+
with:
122+
name: wheels-${{ matrix.os }}-${{ matrix.arch }}
123+
path: wheelhouse/*.whl
124+
retention-days: 7
125+
126+
test_wheels:
127+
name: Test wheels on ${{ matrix.os }} Python ${{ matrix.python-version }}
128+
runs-on: ${{ matrix.os }}
129+
needs: build_wheels
130+
strategy:
131+
matrix:
132+
include:
133+
- os: ubuntu-24.04
134+
python-version: '3.8'
135+
- os: ubuntu-24.04
136+
python-version: '3.11'
137+
- os: windows-2022
138+
python-version: '3.8'
139+
- os: windows-2022
140+
python-version: '3.11'
141+
- os: macos-13
142+
python-version: '3.8'
143+
- os: macos-13
144+
python-version: '3.11'
145+
- os: macos-14
146+
python-version: '3.8'
147+
- os: macos-14
148+
python-version: '3.11'
149+
150+
steps:
151+
- name: Set up Python ${{ matrix.python-version }}
152+
uses: actions/setup-python@v4
153+
with:
154+
python-version: ${{ matrix.python-version }}
155+
156+
- name: Download wheels
157+
uses: actions/download-artifact@v4
158+
with:
159+
pattern: wheels-*
160+
merge-multiple: true
161+
path: wheelhouse
162+
163+
- name: Install wheel
164+
run: |
165+
python -m pip install --upgrade pip
166+
python -m pip install --find-links wheelhouse --no-index pyhelios
167+
168+
- name: Test basic functionality
169+
run: |
170+
python -c "
171+
import pyhelios
172+
from pyhelios import Context, WeberPennTree, WPTType
173+
from pyhelios.types import vec2, vec3, RGBcolor
174+
print(f'PyHelios version: {pyhelios.__version__}')
175+
176+
# Test basic Context operations
177+
context = Context()
178+
center = vec3(1, 2, 3)
179+
color = RGBcolor(0.5, 0.5, 0.5)
180+
uuid = context.addPatch(center=center, size=vec2(1, 1), color=color)
181+
print(f'Added patch with UUID: {uuid}')
182+
183+
# Test plugin availability reporting
184+
from pyhelios.plugins import print_plugin_status
185+
print_plugin_status()
186+
"
187+
188+
publish:
189+
name: Publish to PyPI
190+
runs-on: ubuntu-latest
191+
needs: [build_wheels, test_wheels]
192+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
193+
environment:
194+
name: pypi
195+
url: https://pypi.org/p/pyhelios
196+
permissions:
197+
id-token: write # Required for trusted publishing
198+
199+
steps:
200+
- name: Download all wheels
201+
uses: actions/download-artifact@v4
202+
with:
203+
pattern: wheels-*
204+
merge-multiple: true
205+
path: wheelhouse
206+
207+
- name: Publish to PyPI
208+
uses: pypa/gh-action-pypi-publish@release/v1
209+
with:
210+
packages-dir: wheelhouse/
211+
verify-metadata: false # Skip metadata verification due to dynamic versioning

β€Ž.gitignoreβ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ build/
1212
# Auto-generated version file (setuptools-scm)
1313
pyhelios/_version.py
1414

15+
# Auto-generated documentation
16+
docs/generated/
17+
1518
# IDEs and editors
1619
.vscode/
1720
.idea/

β€ŽMANIFEST.inβ€Ž

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Include build scripts
2+
include build_scripts/*.py
3+
include build_scripts/build_helios
4+
5+
# Include documentation
6+
include README.md
7+
include docs/CHANGELOG.md
8+
graft docs/examples
9+
recursive-exclude docs/generated *
10+
11+
# Include configuration files
12+
include pyproject.toml
13+
include requirements*.txt
14+
include pytest.ini
15+
16+
# Include native libraries that get built
17+
recursive-include pyhelios/plugins *.dll *.so *.dylib
18+
19+
# Exclude test and development files
20+
recursive-exclude tests *
21+
recursive-exclude .github *
22+
recursive-exclude pyhelios_build *
23+
exclude test_*.py
24+
exclude .gitignore
25+
exclude .gitmodules

β€ŽREADME.mdβ€Ž

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<br><br>
22

3-
[![Test Linux](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-linux.yml/badge.svg?branch=master)](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-linux.yml) [![Test Windows](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-windows.yml/badge.svg?branch=master)](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-windows.yml) [![Test MacOS](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-macos.yml/badge.svg?branch=master)](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-macos.yml)
3+
[![Build Wheels](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/build-wheels.yml/badge.svg?branch=master)](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/build-wheels.yml) [![Test Linux](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-linux.yml/badge.svg?branch=master)](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-linux.yml) [![Test Windows](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-windows.yml/badge.svg?branch=master)](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-windows.yml) [![Test MacOS](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-macos.yml/badge.svg?branch=master)](https://github.com/PlantSimulationLab/PyHelios/actions/workflows/pytest-macos.yml)
44

55
<div align="center">
66
<img src="https://raw.githubusercontent.com/PlantSimulationLab/PyHelios/master/docs/images/PyHelios_logo_whiteborder.png" alt="" width="300" />
@@ -22,6 +22,21 @@ See the Helios C++ documentation for a more in-depth description of Helios: http
2222

2323
### Installation
2424

25+
**Easy Install (Recommended):**
26+
```bash
27+
pip install pyhelios
28+
```
29+
30+
This installs pre-built PyHelios with platform-appropriate plugins:
31+
- **macOS**: All plugins except GPU-accelerated ones (automatically detected)
32+
- **Windows/Linux**: All plugins including GPU acceleration (when hardware supports it)
33+
34+
PyHelios will gracefully handle GPU features - if you don't have CUDA-capable hardware, GPU plugins will display helpful error messages with setup instructions.
35+
36+
### Build from Source
37+
38+
If you need to customize plugins or build from source:
39+
2540
#### Windows
2641

2742
**Prerequisites:**
@@ -82,6 +97,35 @@ source helios-core/utilities/dependencies.sh
8297
pip install -e .
8398
```
8499

100+
### GPU Features Setup
101+
102+
If you want to use GPU-accelerated features (radiation modeling, aerial LiDAR), ensure you have:
103+
104+
**Requirements:**
105+
- NVIDIA GPU with CUDA support
106+
- NVIDIA drivers installed
107+
- CUDA Toolkit (version 11.8 or 12.x)
108+
109+
**Verification:**
110+
```bash
111+
nvidia-smi # Should show GPU information
112+
nvcc --version # Should show CUDA compiler version
113+
```
114+
115+
**Testing GPU Features:**
116+
```python
117+
from pyhelios import Context, RadiationModel
118+
119+
context = Context()
120+
try:
121+
radiation = RadiationModel(context)
122+
print("GPU radiation modeling available!")
123+
except RuntimeError as e:
124+
print(f"GPU features unavailable: {e}")
125+
```
126+
127+
If GPU features fail, PyHelios will provide specific guidance on installation and setup requirements.
128+
85129
### First Example
86130

87131
```python

β€Žbuild_scripts/build_helios.pyβ€Ž

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,20 +1453,20 @@ def parse_plugin_selection(args) -> List[str]:
14531453
if args.nogpu:
14541454
# Remove GPU-dependent plugins
14551455
gpu_plugins = [p for p in selected_plugins
1456-
if PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [], [])).gpu_required]
1456+
if PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [])).gpu_required]
14571457
selected_plugins = [p for p in selected_plugins
1458-
if not PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [], [])).gpu_required]
1458+
if not PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [])).gpu_required]
14591459
if gpu_plugins:
14601460
print(f"[EXCLUDED] GPU-dependent plugins excluded (--nogpu): {gpu_plugins}")
14611461

14621462
if args.novis:
14631463
# Remove visualization plugins
14641464
vis_plugins = [p for p in selected_plugins
14651465
if any(dep in ["opengl", "glfw", "imgui"]
1466-
for dep in PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [], [])).system_dependencies)]
1466+
for dep in PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [])).system_dependencies)]
14671467
selected_plugins = [p for p in selected_plugins
14681468
if not any(dep in ["opengl", "glfw", "imgui"]
1469-
for dep in PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [], [])).system_dependencies)]
1469+
for dep in PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [])).system_dependencies)]
14701470
if vis_plugins:
14711471
print(f"[EXCLUDED] Visualization plugins excluded (--novis): {vis_plugins}")
14721472

@@ -1481,9 +1481,9 @@ def parse_plugin_selection(args) -> List[str]:
14811481
# 4. Check environment variables for additional exclusions
14821482
if os.environ.get('PYHELIOS_EXCLUDE_GPU', '').lower() in ['1', 'true', 'yes']:
14831483
env_gpu_plugins = [p for p in selected_plugins
1484-
if PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [], [])).gpu_required]
1484+
if PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [])).gpu_required]
14851485
selected_plugins = [p for p in selected_plugins
1486-
if not PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [], [])).gpu_required]
1486+
if not PLUGIN_METADATA.get(p, PluginMetadata("", "", [], [], [], False, True, [])).gpu_required]
14871487
if env_gpu_plugins:
14881488
print(f"[EXCLUDED] GPU-dependent plugins excluded (PYHELIOS_EXCLUDE_GPU): {env_gpu_plugins}")
14891489

0 commit comments

Comments
Β (0)