Skip to content

Commit 01dfea9

Browse files
committed
[v0.1.2] 2025-09-18
🎉PyPI package distribution should now be working for all integrated plug-ins 🎉 *Enhanced Build System and GPU Runtime Detection* - Added robust GPU runtime detection with fail-fast behavior and comprehensive error reporting - Enhanced wheel building infrastructure with improved GitHub Actions workflows and timeout management - **Build System**: Improved plugin dependency resolution with explicit user request tracking - **Context API**: Enhanced error handling with consistent RuntimeError exceptions and better UUID validation - **RadiationModel**: Expanded camera system integration with comprehensive GPU capability detection - **Testing**: Added pytest markers for GPU-specific tests and enhanced cross-platform test coverage
1 parent 6579776 commit 01dfea9

63 files changed

Lines changed: 4526 additions & 1452 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-wheels.yml

Lines changed: 845 additions & 463 deletions
Large diffs are not rendered by default.

.github/workflows/pytest-linux-gpu.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
- name: Build PyHelios with all plugins (GPU included)
4646
run: |
4747
# Build with all available plugins including GPU-accelerated ones
48-
./build_scripts/build_helios --clean --plugins radiation,visualizer,weberpenntree
48+
./build_scripts/build_helios --clean
4949
5050
- name: Check plugin status
5151
run: |

.github/workflows/pytest-linux.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,16 @@ jobs:
2323

2424
- name: Install Helios dependencies
2525
run: |
26-
cd helios-core
27-
chmod +x utilities/dependencies.sh
28-
bash utilities/dependencies.sh VIS
26+
sudo apt-get update
27+
sudo apt-get install -y libomp-dev libx11-dev xorg-dev libgl1-mesa-dev libglu1-mesa-dev libxrandr-dev python3-dev pybind11-dev
2928
3029
- name: Install build dependencies only
3130
run: |
3231
python -m pip install --upgrade pip
3332
3433
- name: Build native libraries
3534
run: |
36-
python build_scripts/build_helios.py --buildmode release --plugins visualizer,weberpenntree --verbose
35+
python build_scripts/build_helios.py --buildmode release --nogpu --verbose
3736
3837
- name: Upload build artifacts
3938
uses: actions/upload-artifact@v4

.github/workflows/pytest-macos.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
3232
- name: Build native libraries
3333
run: |
34-
python build_scripts/build_helios.py --buildmode release --plugins visualizer,weberpenntree --verbose
34+
python build_scripts/build_helios.py --buildmode release --nogpu --verbose
3535
3636
- name: Upload build artifacts
3737
uses: actions/upload-artifact@v4

.github/workflows/pytest-windows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
env:
3333
CMAKE_RC_COMPILER: ""
3434
run: |
35-
python build_scripts/build_helios.py --buildmode release --plugins visualizer,weberpenntree --verbose
35+
python build_scripts/build_helios.py --buildmode release --nogpu --verbose
3636
3737
- name: Upload build artifacts
3838
uses: actions/upload-artifact@v4

CLAUDE.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,39 @@
22

33
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44

5+
## MANDATORY WORKFLOW - FOLLOW EVERY SESSION
6+
7+
### 1. Start Every Session
8+
- **TodoWrite**: Create todo list for complex/multi-step tasks (3+ steps or non-trivial tasks)
9+
- **Memory Check**: Search memory for relevant prior knowledge using `mcp__memory__search_nodes`
10+
- **Sub-Agent Selection**: Choose appropriate sub-agent(s) based on task type (see Sub-Agents section)
11+
12+
### 2. During Work
13+
- **TodoWrite**: Update progress frequently - mark tasks in_progress and completed immediately
14+
- **Memory Capture**: For any technical discoveries, solutions, or architectural insights, IMMEDIATELY document in memory using MCP tools
15+
16+
### 3. End Every Session
17+
- **Memory Documentation**: MANDATORY - Create memory entities for:
18+
- Technical issues discovered and their solutions
19+
- Design decisions made or conventions established
20+
- Architectural insights about PyHelios systems
21+
- Integration patterns or debugging approaches
22+
- **TodoWrite**: Mark final tasks as completed, clean up stale items
23+
- **Verification**: Confirm all memory writes were successful
24+
25+
### 4. Memory Guidelines
26+
**When to write memory (REQUIRED):**
27+
- New technical problems discovered and solved
28+
- Plugin integration insights or patterns
29+
- Testing architecture discoveries (like ctypes contamination)
30+
- Build system or configuration changes
31+
- Performance insights or optimization approaches
32+
33+
**How to write memory:**
34+
- Use `mcp__memory__create_entities` with specific entityType (technical_issue, solution, architecture, etc.)
35+
- Create meaningful relations with `mcp__memory__create_relations`
36+
- Write detailed observations that future sessions can reference
37+
538
## Sub-Agents
639

740
- Make sure to familiarize yourself with the available sub-agents (@.claude/agents/) and use them efficiently:

MANIFEST.in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ include pyproject.toml
1313
include requirements*.txt
1414
include pytest.ini
1515

16+
# Include stub file for platform-specific wheels
17+
include pyhelios/_stub.c
18+
1619
# Include native libraries that get built
1720
recursive-include pyhelios/plugins *.dll *.so *.dylib
1821

22+
# Include PyHelios assets for wheel packaging
23+
recursive-include pyhelios/assets *
24+
1925
# Exclude test and development files
2026
recursive-exclude tests *
2127
recursive-exclude .github *

build_scripts/build_helios.py

Lines changed: 203 additions & 47 deletions
Large diffs are not rendered by default.

build_scripts/prepare_wheel.py

Lines changed: 221 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import subprocess
1414
import ctypes
1515

16-
def validate_library(lib_path: Path) -> bool:
16+
def validate_library(lib_path):
1717
"""
1818
Validate that library is a proper PyHelios library that can be loaded.
1919
@@ -53,7 +53,104 @@ def validate_library(lib_path: Path) -> bool:
5353
print(f"[ERROR] Error validating library {lib_path.name}: {e}")
5454
return False
5555

56-
def copy_assets_for_packaging(project_root: Path) -> None:
56+
def find_windows_dll_dependencies(lib_path, project_root):
57+
"""
58+
Find and collect all required DLL dependencies for Windows wheels.
59+
60+
Args:
61+
lib_path: Path to the main library (libhelios.dll)
62+
project_root: Path to project root directory
63+
64+
Returns:
65+
List of Path objects for all required DLLs
66+
"""
67+
if platform.system() != 'Windows':
68+
return []
69+
70+
print(f"\nScanning Windows DLL dependencies for {lib_path.name}...")
71+
72+
dependencies = []
73+
74+
# 1. OptiX DLL (if radiation plugin is built)
75+
# PyHelios CMake copies OptiX DLL to build/lib/ directory for wheel packaging
76+
build_lib_dir = project_root / 'pyhelios_build' / 'build' / 'lib'
77+
optix_dlls = ['optix.6.5.0.dll', 'optix.51.dll'] # Support both versions
78+
79+
for optix_dll in optix_dlls:
80+
optix_path = build_lib_dir / optix_dll
81+
if optix_path.exists():
82+
dependencies.append(optix_path)
83+
print(f"[FOUND] OptiX dependency: {optix_dll}")
84+
break
85+
86+
# 2. CUDA Runtime DLLs (from CI environment)
87+
# Check common CUDA installation paths
88+
cuda_paths = [
89+
'C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA',
90+
'C:\\Program Files (x86)\\NVIDIA GPU Computing Toolkit\\CUDA'
91+
]
92+
93+
cuda_dlls = ['cudart64_12.dll', 'cudart64_11.dll', 'cudart64_10.dll'] # Common versions
94+
for cuda_path in cuda_paths:
95+
cuda_root = Path(cuda_path)
96+
if cuda_root.exists():
97+
for version_dir in cuda_root.glob('v*'):
98+
bin_dir = version_dir / 'bin'
99+
if bin_dir.exists():
100+
for cuda_dll in cuda_dlls:
101+
cuda_dll_path = bin_dir / cuda_dll
102+
if cuda_dll_path.exists():
103+
dependencies.append(cuda_dll_path)
104+
print(f"[FOUND] CUDA Runtime: {cuda_dll}")
105+
break
106+
break
107+
break
108+
109+
# 3. Visual C++ Runtime (from Windows SDK/Visual Studio)
110+
vcruntime_dlls = ['vcruntime140.dll', 'msvcp140.dll', 'concrt140.dll']
111+
112+
# Check system directories and Visual Studio installations
113+
system_paths = [
114+
'C:\\Windows\\System32',
115+
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\VC\\Redist\\MSVC',
116+
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Redist\\MSVC',
117+
'C:\\Program Files\\Microsoft Visual Studio\\2019\\Enterprise\\VC\\Redist\\MSVC',
118+
'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Redist\\MSVC'
119+
]
120+
121+
for vc_dll in vcruntime_dlls:
122+
found = False
123+
for base_path in system_paths:
124+
base_path = Path(base_path)
125+
if base_path.exists():
126+
# Check direct path and subdirectories
127+
for dll_path in [base_path / vc_dll] + list(base_path.rglob(vc_dll)):
128+
if dll_path.exists() and dll_path.is_file():
129+
dependencies.append(dll_path)
130+
print(f"[FOUND] VC++ Runtime: {vc_dll}")
131+
found = True
132+
break
133+
if found:
134+
break
135+
136+
# 4. Additional OptiX dependencies if found in NVIDIA directories
137+
nvidia_paths = [
138+
'C:\\Program Files\\NVIDIA Corporation\\OptiX SDK 6.5.0\\bin64',
139+
'C:\\ProgramData\\NVIDIA Corporation\\OptiX\\cache'
140+
]
141+
142+
for nvidia_path in nvidia_paths:
143+
nvidia_path = Path(nvidia_path)
144+
if nvidia_path.exists():
145+
for optix_file in nvidia_path.glob('*.dll'):
146+
if 'optix' in optix_file.name.lower():
147+
dependencies.append(optix_file)
148+
print(f"[FOUND] Additional OptiX: {optix_file.name}")
149+
150+
print(f"Found {len(dependencies)} Windows DLL dependencies")
151+
return dependencies
152+
153+
def copy_assets_for_packaging(project_root):
57154
"""
58155
Copy Helios assets to pyhelios/assets/build for packaging in wheels.
59156
@@ -115,9 +212,45 @@ def copy_assets_for_packaging(project_root: Path) -> None:
115212
plugin_asset_dirs = {
116213
'weberpenntree': ['leaves', 'wood', 'xml'],
117214
'visualizer': ['textures', 'shaders'],
118-
'radiation': ['spectral_data'] if Path(plugins_src_dir / 'radiation' / 'spectral_data').exists() else []
119215
# NOTE: plantarchitecture and canopygenerator are not integrated with PyHelios - assets not needed
120216
}
217+
218+
# Add radiation assets only on platforms that build GPU plugins (Windows/Linux)
219+
if platform.system() != 'Darwin': # Exclude radiation on macOS
220+
radiation_assets = []
221+
222+
# Add spectral data if it exists
223+
if Path(plugins_src_dir / 'radiation' / 'spectral_data').exists():
224+
radiation_assets.append('spectral_data')
225+
226+
# Copy generated PTX files from build directory (critical for OptiX functionality)
227+
radiation_build_dir = build_dir / 'plugins' / 'radiation'
228+
if radiation_build_dir.exists():
229+
# Copy generated PTX files from build directory
230+
plugin_dest = dest_assets_dir / 'plugins' / 'radiation'
231+
plugin_dest.mkdir(parents=True, exist_ok=True)
232+
233+
ptx_files = list(radiation_build_dir.glob('*.ptx'))
234+
if ptx_files:
235+
ptx_copied = 0
236+
for ptx_file in ptx_files:
237+
try:
238+
shutil.copy2(ptx_file, plugin_dest / ptx_file.name)
239+
print(f"[OK] Copied PTX file: {ptx_file.name}")
240+
ptx_copied += 1
241+
except Exception as e:
242+
print(f"[ERROR] Failed to copy PTX file {ptx_file.name}: {e}")
243+
244+
if ptx_copied > 0:
245+
print(f"[OK] Successfully copied {ptx_copied} PTX files for radiation plugin")
246+
total_copied += ptx_copied
247+
else:
248+
print(f"[WARNING] No PTX files found in radiation build directory: {radiation_build_dir}")
249+
else:
250+
print(f"[WARNING] Radiation build directory not found: {radiation_build_dir}")
251+
252+
if radiation_assets:
253+
plugin_asset_dirs['radiation'] = radiation_assets
121254

122255
# Process each plugin directory
123256
for plugin_dir in plugins_src_dir.iterdir():
@@ -161,7 +294,10 @@ def copy_assets_for_packaging(project_root: Path) -> None:
161294
print(f"[OK] Successfully copied {total_copied} total assets for packaging")
162295
else:
163296
print("Warning: No assets found to copy")
164-
297+
298+
# Note: Asset directories should NOT have __init__.py files as they are data directories,
299+
# not Python packages. setuptools handles them correctly via package_data configuration.
300+
165301
print(f"[OK] Assets packaged in {dest_assets_dir}")
166302

167303
def build_and_prepare(build_args):
@@ -317,7 +453,66 @@ def build_and_prepare(build_args):
317453
# Continue anyway - some failures might be acceptable
318454

319455
print(f"[OK] Successfully prepared {copied_count} libraries for packaging")
320-
456+
457+
# Windows-specific: Bundle additional DLL dependencies
458+
if system == 'Windows' and copied_count > 0:
459+
print(f"\n=== Windows DLL Dependency Bundling ===")
460+
461+
# Find the main library (usually libhelios.dll or helios.dll)
462+
main_library = None
463+
for lib_file in found_libraries:
464+
if 'helios' in lib_file.name.lower() and lib_file.suffix == '.dll':
465+
main_library = lib_file
466+
break
467+
468+
if main_library:
469+
# Find all required DLL dependencies
470+
dependencies = find_windows_dll_dependencies(main_library, project_root)
471+
472+
dependency_copied = 0
473+
dependency_failed = 0
474+
475+
for dep_dll in dependencies:
476+
try:
477+
dest_dll = plugins_dir / dep_dll.name
478+
479+
# Skip if already exists (avoid overwriting main libraries)
480+
if dest_dll.exists():
481+
print(f"[SKIP] Dependency already bundled: {dep_dll.name}")
482+
continue
483+
484+
shutil.copy2(dep_dll, dest_dll)
485+
print(f"[OK] Bundled dependency: {dep_dll.name}")
486+
dependency_copied += 1
487+
488+
except (OSError, PermissionError) as e:
489+
print(f"[ERROR] Failed to bundle critical dependency {dep_dll.name}: {e}")
490+
print(f"This dependency is required for libhelios.dll to load properly on Windows systems")
491+
print(f"without development tools installed. The wheel will not work correctly.")
492+
dependency_failed += 1
493+
494+
print(f"\n=== Dependency Bundle Summary ===")
495+
print(f"Found: {len(dependencies)} DLL dependencies")
496+
print(f"Bundled: {dependency_copied} dependencies")
497+
print(f"Failed: {dependency_failed} dependencies")
498+
499+
# Fail-fast: If critical dependencies are missing, the wheel is broken
500+
if len(dependencies) > 0 and dependency_copied == 0:
501+
print(f"[ERROR] CRITICAL: No Windows DLL dependencies were bundled!")
502+
print(f"This means the wheel will fail to load on systems without development tools.")
503+
print(f"Required dependencies: {[dep.name for dep in dependencies]}")
504+
print(f"The wheel build cannot continue with missing critical dependencies.")
505+
sys.exit(1)
506+
elif dependency_failed > 0:
507+
print(f"[ERROR] CRITICAL: {dependency_failed} critical dependencies could not be bundled!")
508+
print(f"The wheel will not work properly on clean Windows systems.")
509+
print(f"All dependencies must be bundled for the wheel to function correctly.")
510+
sys.exit(1)
511+
else:
512+
print(f"[OK] All {dependency_copied} Windows DLL dependencies bundled successfully")
513+
else:
514+
print(f"[WARNING] Could not find main Helios library for dependency analysis")
515+
321516
# Copy assets for packaging
322517
copy_assets_for_packaging(project_root)
323518

@@ -327,15 +522,33 @@ def main():
327522
print("prepare_wheel.py - Build PyHelios native libraries and prepare for wheel packaging")
328523
print()
329524
print("Usage: python prepare_wheel.py <build_args...>")
330-
print("Example: python prepare_wheel.py --buildmode release --exclude radiation,aeriallidar --verbose")
525+
print("Examples:")
526+
print(" python prepare_wheel.py --buildmode release --nogpu --verbose")
527+
print(" python prepare_wheel.py --plugins weberpenntree,visualizer")
528+
print(" python prepare_wheel.py --exclude radiation --buildmode debug")
331529
print()
332530
print("This script:")
333531
print(" 1. Calls build_scripts/build_helios.py with the provided arguments")
334532
print(" 2. Copies built libraries to pyhelios/plugins/ for wheel packaging")
335-
print(" 3. Validates libraries can be loaded properly")
533+
print(" 3. Copies required assets to pyhelios/assets/build/")
534+
print(" 4. Validates libraries can be loaded properly")
336535
print()
337-
print("For build argument options, run:")
536+
print("Common build arguments:")
537+
print(" --buildmode {debug,release,relwithdebinfo} CMake build type")
538+
print(" --nogpu Exclude GPU plugins")
539+
print(" --novis Exclude visualization plugins")
540+
print(" --plugins <plugin1,plugin2,...> Specific plugins to build")
541+
print(" --exclude <plugin1,plugin2,...> Plugins to exclude")
542+
print(" --clean Clean build artifacts first")
543+
print(" --verbose Verbose output")
544+
print()
545+
print("For complete build argument options, run:")
338546
print(" python build_scripts/build_helios.py --help")
547+
print()
548+
print("For list of integrated plugins, run:")
549+
print(" python build_scripts/build_helios.py --list-plugins")
550+
print("For list of all helios-core plugins, run:")
551+
print(" python build_scripts/build_helios.py --list-all-plugins")
339552
sys.exit(0 if '--help' in sys.argv or '-h' in sys.argv else 1)
340553

341554
build_args = sys.argv[1:]

0 commit comments

Comments
 (0)