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
1 change: 1 addition & 0 deletions .actrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-P ubuntu-latest=catthehacker/ubuntu:act-latest
1 change: 1 addition & 0 deletions .github/actions/generate-build-matrix/generate_matrix.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Generates a build matrix for CI based on the trigger event and user input."""

import json
import os
import re
Expand Down
55 changes: 55 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,58 @@ All Markdown files must strictly follow these markdownlint rules:
- **MD034**: No bare URLs (for example, use a markdown link like `[text](destination)` instead of a plain URL)
- **MD036**: Use # headings, not **Bold:** for titles
- **MD040**: Always specify code block language (for example, use '```bash', '```python', '```text', etc.)

## Development & Testing Workflows

### Build and Test

- **Environment**: Always source `setup-env.sh` before building or testing. This applies to all environments (Dev Container, local machine, HPC).
- **Configuration**:
- **Presets**: Prefer `CMakePresets.json` workflows (e.g., `cmake --preset default`).
- **Generator**: Prefer `Ninja` over `Makefiles` when available (`-G Ninja`).
- **Build**:
- **Parallelism**: Always use multiple cores. Ninja does this by default. For `make`, use `cmake --build build -j $(nproc)`.
- **Test**:
- **Parallelism**: Run tests in parallel using `ctest -j $(nproc)` or `ctest --parallel <N>`.
- **Selection**: Run specific tests with `ctest -R "regex"` (e.g., `ctest -R "py:*"`).
- **Debugging**: Use `ctest --output-on-failure` to see logs for failed tests.

### Python Integration

- **Naming**: Avoid naming Python test scripts `types.py` or other names that shadow standard library modules. This causes obscure import errors (e.g., `ModuleNotFoundError: No module named 'numpy'`).
- **PYTHONPATH**: When running tests in Spack environments, ensure `PYTHONPATH` includes `site-packages`. In CMake, explicitly add `Python_SITELIB` and `Python_SITEARCH` to `TEST_PYTHONPATH`.
- **Test Structure**:
- **C++ Driver**: Provides data streams (e.g., `test/python/driver.cpp`).
- **Jsonnet Config**: Wires the graph (e.g., `test/python/pytypes.jsonnet`).
- **Python Script**: Implements algorithms (e.g., `test/python/test_types.py`).
- **Type Conversion**: `plugins/python/src/modulewrap.cpp` handles C++ $\leftrightarrow$ Python conversion.
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LaTeX syntax in markdown file may not render correctly in all contexts. Consider using the Unicode bidirectional arrow character (↔) or plain text description "to/from" instead of LaTeX math notation.

Suggested change
- **Type Conversion**: `plugins/python/src/modulewrap.cpp` handles C++ $\leftrightarrow$ Python conversion.
- **Type Conversion**: `plugins/python/src/modulewrap.cpp` handles C++ Python conversion.

Copilot uses AI. Check for mistakes.
- **Mechanism**: Uses string comparison of type names (e.g., `"float64]]"`). This is brittle.
- **Requirement**: Ensure converters exist for all types used in tests (e.g., `float`, `double`, `unsigned int`, and their vector equivalents).
- **Warning**: Exact type matches are required. `numpy.float32` != `float`.

### Coverage Analysis

- **Tooling**: The project uses LLVM source-based coverage.
- **Requirement**: The `phlex` binary must catch exceptions in `main` to ensure coverage data is flushed to disk even when tests fail/crash.
- **Generation**:
- **CMake Targets**: `coverage-xml`, `coverage-html` (if configured).
- **Manual**:
1. Run tests with `LLVM_PROFILE_FILE` set (e.g., `export LLVM_PROFILE_FILE="profraw/%m-%p.profraw"`).
2. Merge profiles: `llvm-profdata merge -sparse profraw/*.profraw -o coverage.profdata`.
3. Generate report: `llvm-cov show -instr-profile=coverage.profdata -format=html ...`

### Local GitHub Actions Testing (`act`)

- **Tool**: Use `act` to run GitHub Actions workflows locally.
- **Configuration**: Ensure `.actrc` exists in the workspace root with the following content to use a compatible runner image:
```text
-P ubuntu-latest=catthehacker/ubuntu:act-latest
```
- **Usage**:
- List jobs: `act -l`
- Run specific job: `act -j <job_name>` (e.g., `act -j python-check`)
- Run specific event: `act pull_request`
- **Troubleshooting**:
- **Docker Socket**: `act` requires access to the Docker socket. In dev containers, this may require specific mount configurations or permissions.
- **Artifacts**: `act` creates a `phlex-src` directory (or similar) for checkout. Ensure this is cleaned up or ignored by tools like `mypy`.

16 changes: 9 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ option(ENABLE_CLANG_TIDY "Enable clang-tidy checks during build" OFF)
add_compile_options(-Wall -Werror -Wunused -Wunused-parameter -pedantic)

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.1"
AND CMAKE_COMPILER_VERSION VERSION_LESS "15"
)
# GCC 14.1 issues many false positives re. array-bounds and
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.1")
# GCC 14.1+ issues many false positives re. array-bounds and
# stringop-overflow
add_compile_options(-Wno-array-bounds -Wno-stringop-overflow)
add_compile_options(
-Wno-array-bounds -Wno-stringop-overflow -Wno-maybe-uninitialized
)
endif()
endif()

Expand Down Expand Up @@ -104,7 +104,8 @@ if(ENABLE_TSAN)
-g
-O1
# Ensure no optimizations interfere with TSan
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-omit-frame-pointer -fno-optimize-sibling-calls>"
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-omit-frame-pointer>"
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-optimize-sibling-calls>"
)
add_link_options(-fsanitize=thread)
else()
Expand All @@ -128,7 +129,8 @@ if(ENABLE_ASAN)
-g
-O1
# Ensure no optimizations interfere with ASan
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-omit-frame-pointer -fno-optimize-sibling-calls>"
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-omit-frame-pointer>"
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-fno-optimize-sibling-calls>"
)
add_link_options(-fsanitize=address)
else()
Expand Down
2 changes: 1 addition & 1 deletion ci/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ set -euo pipefail
# Use pip to install tooling that is not packaged in Spack
. /entrypoint.sh

PYTHONDONTWRITEBYTECODE=1 pip --isolated --no-input --disable-pip-version-check --no-cache-dir install --prefix /usr/local ruff
PYTHONDONTWRITEBYTECODE=1 pip --isolated --no-input --disable-pip-version-check --no-cache-dir install --prefix /usr/local ruff mypy
rm -rf ~/.spack
INSTALL_PYTHON_TOOLS

Expand Down
11 changes: 9 additions & 2 deletions plugins/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ if(Python_FOUND)
${Python_EXECUTABLE} -c "import sys
try:
import ${MODULE_NAME}
from packaging.version import parse as parse_version
installed_version = getattr(${MODULE_NAME}, '__version__', None)
if parse_version(installed_version) >= parse_version('${MIN_VERSION}'):
if not installed_version:
sys.exit(2)

def parse(v):
return tuple(map(int, v.split('.')[:3]))

if parse(installed_version) >= parse('${MIN_VERSION}'):
sys.exit(0)
else:
sys.exit(2) # Version too low
except ImportError:
sys.exit(1)
except Exception:
sys.exit(1)"
RESULT_VARIABLE _module_check_result
)
Expand Down
55 changes: 55 additions & 0 deletions plugins/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Phlex Python Plugin Architecture

This directory contains the C++ source code for the Phlex Python plugin, which enables Phlex to execute Python code as part of its computation graph.

## Architecture Overview

The integration is built on the **Python C API** (not `pybind11`) to maintain strict control over the interpreter lifecycle and memory management.

### 1. The "Type Bridge" (`modulewrap.cpp`)

The core of the integration is the type conversion layer in `src/modulewrap.cpp`. This layer is responsible for:
- Converting Phlex `Product` objects (C++) into Python objects (e.g., `PyObject*`, `numpy.ndarray`).
- Converting Python return values back into Phlex `Product` objects.

**Critical Implementation Detail:**
The type mapping relies on **string comparison** of type names.
- **Mechanism**: The C++ code checks `type_name() == "float64]]"` to identify a 2D array of doubles.
- **Brittleness**: This is a fragile contract. If the type name changes (e.g., `numpy` changes its string representation) or if a user provides a slightly different type (e.g., `float` vs `np.float32`), the bridge may fail.
- **Extension**: When adding support for new types, you must explicitly add converters in `modulewrap.cpp` for both scalar and vector/array versions.

### 2. Hybrid Configuration

Phlex uses a hybrid configuration model involving three languages:

1. **Jsonnet** (`*.jsonnet`): Defines the computation graph structure. It specifies:
- The nodes in the graph.
- The Python module/class to load for specific nodes.
- Configuration parameters passed to the Python object.
2. **C++ Driver**: The executable that:
- Parses the Jsonnet configuration.
- Initializes the Phlex core.
- Loads the Python interpreter and the specified plugin.
3. **Python Code** (`*.py`): Implements the algorithmic logic.

### 3. Environment & Testing

Because the Python interpreter is embedded within the C++ application, the runtime environment is critical.

- **PYTHONPATH**: Must be set correctly to include:
- The build directory (for generated modules).
- The source directory (for user scripts).
- System/Spack `site-packages` (for dependencies like `numpy`).
- **Naming Collisions**:
- **Warning**: Do not name test files `types.py`, `test.py`, `code.py`, or other names that shadow standard library modules.
- **Consequence**: Shadowing can cause obscure failures in internal libraries (e.g., `numpy` failing to import because it tries to import `types` from the standard library but gets your local file instead).

## Development Guidelines

1. **Adding New Types**:
- Update `src/modulewrap.cpp` to handle the new C++ type.
- Add a corresponding test case in `test/python/` to verify the round-trip conversion.
2. **Testing**:
- Use `ctest` to run tests.
- Tests are integration tests: they run the full C++ application which loads the Python script.
- Debugging: Use `ctest --output-on-failure` to see Python exceptions.
2 changes: 2 additions & 0 deletions plugins/python/src/lifelinewrap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ static int ll_clear(py_lifeline_t* pyobj)

static void ll_dealloc(py_lifeline_t* pyobj)
{
PyObject_GC_UnTrack(pyobj);
Py_CLEAR(pyobj->m_view);
typedef std::shared_ptr<void> generic_shared_t;
pyobj->m_source.~generic_shared_t();
Py_TYPE(pyobj)->tp_free((PyObject*)pyobj);
}

// clang-format off
Expand Down
Loading
Loading