Skip to content
Merged
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 .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ exclude =
__pycache__,
.git,
.github,
.venv,
build,
cppwg/templates,
doc,
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/test-cells-conda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install cmake git
sudo apt-get install castxml clang cmake git

- name: Setup Miniconda Python ${{ matrix.python-version }}
uses: conda-incubator/setup-miniconda@v3
Expand All @@ -39,7 +39,8 @@ jobs:
use-mamba: true
miniforge-version: latest
python-version: ${{ matrix.python-version }}
channels: conda-forge
channels: conda-forge,defaults
conda-remove-defaults: "false"

- name: Install cppwg
run: |
Expand Down Expand Up @@ -80,7 +81,8 @@ jobs:
run: mamba install boa conda-build conda-verify

- name: Build
run: conda mambabuild recipe -m variants/python${{ matrix.python-version }}.yaml
run: |
conda mambabuild recipe -m variants/python${{ matrix.python-version }}.yaml
working-directory: examples/cells/conda

- name: Install
Expand Down
76 changes: 59 additions & 17 deletions .github/workflows/test-cells-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@ on:
branches:
- "**"

concurrency:
group: test-cells-ubuntu-${{ github.ref }}
cancel-in-progress: true

jobs:
test-cells-ubuntu:
runs-on: ubuntu-22.04
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: ["ubuntu-22.04", "ubuntu-24.04"]

concurrency:
group: test-cells-ubuntu-${{ github.ref }}-${{ matrix.os }}
cancel-in-progress: true

defaults:
run:
shell: bash -el {0} # -l needed to activate pipx

steps:
- name: Checkout
Expand All @@ -21,25 +30,53 @@ jobs:
- name: Install system dependencies
run: |
sudo apt-get update

for vtk_ver in $(seq 7 9); do
available=1
dpkg -s "libvtk${vtk_ver}-dev" || available=0
if [ "${available}" -eq 1 ]; then
break
fi
done
echo "VTK version: ${vtk_ver}"

sudo apt-get install \
castxml \
clang \
cmake \
git \
libboost-all-dev \
libpetsc-real3.15 \
libpetsc-real3.15-dbg \
libpetsc-real3.15-dev \
libvtk9-dev \
libpetsc-real-dev \
libvtk${vtk_ver}-dev \
mpi-default-bin \
mpi-default-dev \
pipx \
python3-mpi4py \
python3-petsc4py-real3.15 \
python3-vtk9 \
vtk9
python3-petsc4py-real \
python3-pip \
python3-vtk${vtk_ver}

pipx ensurepath

# Check installed package versions
dpkg-query -W \
castxml \
clang \
cmake \
git \
libboost-all-dev \
libpetsc-real-dev \
libvtk${vtk_ver}-dev \
mpi-default-bin \
mpi-default-dev \
pipx \
python3-mpi4py \
python3-petsc4py-real \
python3-pip \
python3-vtk${vtk_ver}

- name: Install cppwg
run: |
python -m pip install --upgrade pip
python -m pip install .
run: pipx install .

- name: Configure
run: |
Expand All @@ -61,9 +98,14 @@ jobs:
working-directory: examples/cells

- name: Build
run: python -m pip install -v .
run: |
python3 -m venv --system-site-packages .venv
. .venv/bin/activate
python3 -m pip install -v .
working-directory: examples/cells

- name: Test
run: python -m unittest discover tests
run: |
. .venv/bin/activate
python3 -m unittest discover tests
working-directory: examples/cells
2 changes: 1 addition & 1 deletion .github/workflows/test-shapes-pip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install cmake git
sudo apt-get install castxml clang cmake git

- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
Expand Down
33 changes: 20 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ Automatically generate pybind11 Python wrapper code for C++ projects.

## Installation

Install CastXML (required) and Clang (recommended). On Ubuntu, this would be:

```bash
sudo apt-get install castxml clang
```

Clone the repository and install cppwg:

```bash
Expand All @@ -19,30 +25,30 @@ pip install .
## Usage

```
usage: cppwg [-h] [-w WRAPPER_ROOT] [-p PACKAGE_INFO] [-c CASTXML_BINARY]
[--std STD] [-i [INCLUDES ...]] [-q] [-l [LOGFILE]] [-v]
SOURCE_ROOT
usage: cppwg [-h] [-w WRAPPER_ROOT] [-p PACKAGE_INFO] [-c CASTXML_BINARY]
[-m CASTXML_COMPILER] [--std STD] [-i [INCLUDES ...]] [-q]
[-l [LOGFILE]] [-v] SOURCE_ROOT

Generate Python Wrappers for C++ code

positional arguments:
SOURCE_ROOT Path to the root directory of the input C++ source
code.
SOURCE_ROOT Path to the root directory of the input C++ source code.

options:
-h, --help show this help message and exit
-w WRAPPER_ROOT, --wrapper_root WRAPPER_ROOT
Path to the output directory for the Pybind11 wrapper
code.
-p PACKAGE_INFO, --package_info PACKAGE_INFO
-w, --wrapper_root WRAPPER_ROOT
Path to the output directory for the Pybind11 wrapper code.
-p, --package_info PACKAGE_INFO
Path to the package info file.
-c CASTXML_BINARY, --castxml_binary CASTXML_BINARY
-c, --castxml_binary CASTXML_BINARY
Path to the castxml executable.
-m, --castxml_compiler CASTXML_COMPILER
Path to a compiler to be used by castxml.
--std STD C++ standard e.g. c++17.
-i [INCLUDES ...], --includes [INCLUDES ...]
-i, --includes [INCLUDES ...]
List of paths to include directories.
-q, --quiet Disable informational messages.
-l [LOGFILE], --logfile [LOGFILE]
-l, --logfile [LOGFILE]
Output log messages to a file.
-v, --version Print cppwg version.
```
Expand Down Expand Up @@ -91,7 +97,8 @@ cd examples/shapes
cppwg src/cpp \
--wrapper_root wrapper \
--package_info wrapper/package_info.yaml \
--includes src/cpp/geometry src/cpp/math_funcs src/cpp/mesh src/cpp/primitives
--includes src/cpp/geometry src/cpp/math_funcs src/cpp/primitives \
--std c++17
```

For the `Rectangle` class, this creates two files in
Expand Down
8 changes: 8 additions & 0 deletions cppwg/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ def parse_args() -> argparse.Namespace:
help="Path to the castxml executable.",
)

parser.add_argument(
"-m",
"--castxml_compiler",
type=str,
help="Path to a compiler to be used by castxml.",
)

# Note: we're passing in std directly because syntax like
# --castxml_cflags "-std=c++17" isn't supported by argparse because of
# the initial "-" in the argument. See https://bugs.python.org/issue9334
Expand Down Expand Up @@ -112,6 +119,7 @@ def generate(args: argparse.Namespace) -> None:
package_info_path=args.package_info,
castxml_binary=args.castxml_binary,
castxml_cflags=castxml_cflags,
castxml_compiler=args.castxml_compiler,
)

generator.generate()
Expand Down
16 changes: 15 additions & 1 deletion cppwg/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
import os
import re
import shutil
import subprocess
import uuid
from pathlib import Path
Expand Down Expand Up @@ -40,6 +41,8 @@ class CppWrapperGenerator:
The path to the castxml binary
castxml_cflags : str
Optional cflags to be passed to castxml e.g. "-std=c++17"
castxml_compiler : str
Optional compiler path to be passed to CastXML
package_info_path : str
The path to the package info yaml config file; defaults to "package_info.yaml"
source_ns : pygccxml.declarations.namespace_t
Expand All @@ -56,6 +59,7 @@ def __init__(
castxml_binary: Optional[str] = None,
package_info_path: Optional[str] = None,
castxml_cflags: Optional[str] = None,
castxml_compiler: Optional[str] = None,
):
logger = logging.getLogger()

Expand Down Expand Up @@ -100,6 +104,16 @@ def __init__(
if castxml_cflags:
self.castxml_cflags = f"{self.castxml_cflags} {castxml_cflags}"

# Try to set castxml compiler
if castxml_compiler:
self.castxml_compiler = castxml_compiler
else:
compiler_path = shutil.which("clang++")
if compiler_path:
self.castxml_compiler = compiler_path
else:
self.castxml_compiler = None

# Sanitize source_root
self.source_root: str = os.path.abspath(source_root)
if not os.path.isdir(self.source_root):
Expand Down Expand Up @@ -195,7 +209,6 @@ def log_unknown_classes(self) -> None:

# Check for uninstantiated class templates not parsed by pygccxml
for hpp_file_path in self.package_info.source_hpp_files:

class_list = utils.find_classes_in_source_file(hpp_file_path)

for _, class_name, _ in class_list:
Expand All @@ -216,6 +229,7 @@ def parse_headers(self) -> None:
self.castxml_binary,
self.source_includes,
self.castxml_cflags,
self.castxml_compiler,
)
self.source_ns = source_parser.parse()

Expand Down
52 changes: 16 additions & 36 deletions cppwg/info/class_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class CppClassInfo(CppEntityInfo):
"""

def __init__(self, name: str, class_config: Optional[Dict[str, Any]] = None):

super().__init__(name, class_config)

self.base_decls: List["declaration_t"] = [] # noqa: F821
Expand Down Expand Up @@ -173,41 +172,23 @@ def update_from_ns(self, source_ns: "namespace_t") -> None: # noqa: F821
if self.excluded:
return

for class_cpp_name in self.cpp_names:
class_name = class_cpp_name.replace(" ", "") # e.g. Foo<2,2>

for class_cpp_name, class_py_name in zip(self.cpp_names, self.py_names):
try:
class_decl = source_ns.class_(class_name)

except declaration_not_found_t as e1:
if "=" not in self.template_signature:
logger.error(f"Could not find declaration for class {class_name}")
raise e1

# If class has default args, try to compress the template signature
logger.warning(
f"Could not find declaration for class {class_name}: trying a partial match."
)

# Try to find the class without default template args
# e.g. for template <int A, int B=A> class Foo {};
# Look for Foo<2> instead of Foo<2,2>
pos = 0
for i, s in enumerate(self.template_signature.split(",")):
if "=" in s:
pos = i
break

class_name = ",".join(class_name.split(",")[0:pos]) + " >"

try:
class_decl = source_ns.class_(class_name)

except declaration_not_found_t as e2:
logger.error(f"Could not find declaration for class {class_name}")
raise e2

logger.info(f"Found {class_name}")
cpp_name = class_cpp_name.replace(" ", "") # e.g. Foo<2,2,1>
class_decl = source_ns.class_(cpp_name)

except declaration_not_found_t:
# Parsed names for templated classes which have default args
# may vary between CastXML versions and compiler versions.
# Try to look up the class name via the typedef e.g. for
# `template <int A, int B=A, int C=1> class Foo {};`
# the parsed name for Foo<2,2,1> could be Foo<2,2>, or Foo<2>
# but the typedef name will always be Foo_2_2_1
py_name = class_py_name.replace(" ", "") # e.g. Foo_2_2_1
typedef_decl = source_ns.typedef(py_name)
class_decl = typedef_decl.decl_type.declaration

logger.info(f"Found {class_decl.name} for {class_cpp_name}")

self.decls.append(class_decl)

Expand Down Expand Up @@ -298,7 +279,6 @@ class instantiation. For example, class "Foo" with template arguments

template_string = ""
for idx, arg in enumerate(template_arg_list):

# Do standard name replacements
arg_str = str(arg)
for name, replacement in self.name_replacements.items():
Expand Down
1 change: 0 additions & 1 deletion cppwg/info/cpp_entity_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ class CppEntityInfo(BaseInfo):
"""

def __init__(self, name: str, entity_config: Optional[Dict[str, Any]] = None):

super().__init__(name, entity_config)

self.name_override: str = ""
Expand Down
1 change: 0 additions & 1 deletion cppwg/info/free_function_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ class CppFreeFunctionInfo(CppEntityInfo):
def __init__(
self, name: str, free_function_config: Optional[Dict[str, Any]] = None
):

super().__init__(name, free_function_config)

def update_from_ns(self, source_ns: "namespace_t") -> None: # noqa: F821
Expand Down
1 change: 0 additions & 1 deletion cppwg/info/method_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class CppMethodInfo(CppEntityInfo):
"""

def __init__(self, name: str, _) -> None:

super().__init__(name)

self.class_info: Optional["CppClassInfo"] = None # noqa: F821
Expand Down
Loading