diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..83435fe0 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,55 @@ +name: docs + +# Build the Sphinx docs on every push/PR, and deploy to GitHub Pages on develop. +on: + push: + branches: [develop] + pull_request: + branches: [develop] + +# Allow one concurrent deployment. +concurrency: + group: pages + cancel-in-progress: false + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install docs dependencies + run: pip install ".[docs]" + + - name: Build HTML + # -W: fail on any warning so docstring/markup regressions are caught. + run: sphinx-build -b html -W --keep-going doc doc/_build/html + + - name: Upload Pages artifact + # Only the deploy job needs the artifact, but uploading on PRs too keeps + # the build honest (the artifact is just discarded). + uses: actions/upload-pages-artifact@v5 + with: + path: doc/_build/html + + deploy: + # Publish only from develop, not from pull requests. + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/README.md b/README.md index fcbf4ced..8a708466 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,26 @@ # cppwg -Automatically generate pybind11 Python wrapper code for C++ projects. +Automatically generate [pybind11](https://pybind11.readthedocs.io/) Python +wrapper code for C++ projects. + +cppwg reads your C++ source together with a YAML configuration file and emits the +pybind11 registration code for the classes, free functions, and template +instantiations you select — so you describe *what* to expose in config rather +than hand-writing wrapper code. + +## Documentation + +Full documentation is at **https://chaste.github.io/cppwg/**: + +- [Getting started](https://chaste.github.io/cppwg/getting-started.html) — install, run, and a worked example. +- [Configuration reference](https://chaste.github.io/cppwg/configuration.html) — every config option, with types and defaults. +- [Custom generators](https://chaste.github.io/cppwg/custom-generators.html) — inject hand-written binding code. +- [Tips & recipes](https://chaste.github.io/cppwg/tips.html). ## Installation -Install CastXML (required) and Clang (recommended). On Ubuntu, this would be: +Install CastXML (required) and Clang (recommended). On Ubuntu: ```bash sudo apt-get install castxml clang @@ -23,68 +38,9 @@ cd cppwg pip install . ``` -## Usage - -``` -usage: cppwg [-h] [-w WRAPPER_ROOT] [-p PACKAGE_INFO] [-c CASTXML_BINARY] - [-m CASTXML_COMPILER] [--std STD] [--castxml_cflags CASTXML_CFLAGS] - [-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. - -options: - -h, --help show this help message and exit - -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 - 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. - --castxml_cflags CASTXML_CFLAGS - Additional flags for the castxml clang frontend. Pass - values starting with "-" using "=" e.g. - --castxml_cflags="-Wno-deprecated". - -i, --includes [INCLUDES ...] - List of paths to include directories. - --overwrite Force rewrite of all wrapper files, even if unchanged. - -q, --quiet Disable informational messages. - -l, --logfile [LOGFILE] - Output log messages to a file. - -v, --version Print cppwg version. -``` - -## Example - -The project in `examples/shapes` demonstrates `cppwg` usage. We can walk through -the process with the `Rectangle` class in `examples/shapes/src/cpp/primitives` - -**Rectangle.hpp** - -```cpp -class Rectangle : public Shape<2> -{ -public: - Rectangle(double width=2.0, double height=1.0); - ~Rectangle(); - //... -}; -``` - -Cppwg needs a configuration file that has a list of classes to wrap and -describes the structure of the Python package to be created. - -There is an example configuration file in -`examples/shapes/wrapper/package_info.yaml`. +## Quick start -The extract below from the example configuration file describes a Python package -named `pyshapes` which has a `primitives` module that includes the `Rectangle` -class. +Describe the package to generate in a YAML config: ```yaml name: pyshapes @@ -94,9 +50,7 @@ modules: - name: Rectangle ``` -See `package_info.yaml` for more configuration options. - -To generate the wrappers: +Generate the wrappers: ```bash cd examples/shapes @@ -107,255 +61,24 @@ cppwg src/cpp \ --std c++17 ``` -For the `Rectangle` class, this creates two files in -`examples/shapes/wrapper/primitives`. - -**Rectangle.cppwg.hpp** - -```cpp -void register_Rectangle_class(pybind11::module &m); -``` - -**Rectangle.cppwg.cpp** - -```cpp -namespace py = pybind11; -void register_Rectangle_class(py::module &m) -{ - py::class_ >(m, "Rectangle") - .def(py::init(), py::arg("width")=2, py::arg("height")=1) - //... - ; -} -``` - -The wrapper for `Rectangle` is registered in the `primitives` module. - -**primitives.main.cpp** - -```cpp -PYBIND11_MODULE(_pyshapes_primitives, m) -{ - register_Rectangle_class(m); - //... -} -``` - -To compile the wrappers into a Python package: - -```bash -mkdir build && cd build -cmake .. -make -``` - -The compiled wrapper code can now be imported in Python: +Then compile them into a Python package and import it: ```python from pyshapes import Rectangle r = Rectangle(4, 5) ``` -## Tips - -- Use `examples/shapes` or `examples/cells` as a starting point. -- By default, cppwg only rewrites wrapper files whose content has changed, leaving - unchanged files untouched so build systems skip recompiling them. Pass - `--overwrite` to force a full rewrite of all wrapper files. -- A templated class's instantiations share a single wrapper file: `Foo<2>` and - `Foo<3>` are wrapped in one `Foo.cppwg.hpp` / `Foo.cppwg.cpp` pair (declaring - and defining a `register_Foo_2_class` and `register_Foo_3_class` each) rather - than a separate file pair per instantiation. This reduces the number of - generated files, and the translation units the build has to compile. -- To pass extra flags to the castxml clang frontend (e.g. to silence a - diagnostic), use `--castxml_cflags`. Values starting with `-` must use `=`, - e.g. `--castxml_cflags="-Wno-deprecated"`. -- To wrap a templated class for each of its explicit instantiations without - hand-writing `template_substitutions`, set `discover_template_instantiations` - (see `examples/shapes/wrapper/package_info.yaml`). cppwg finds the - instantiations (e.g. `template class Foo<2, 2>;`) in the source `.cpp` files. - Instantiations declared through a macro are found via CastXML; recovering a - **defaulted** trailing template argument from such a macro instantiation - requires **CastXML >= 0.6.0** (older versions drop it, e.g. naming - `Foo<2, 2>` as `Foo<2>`). See the `MacroMesh` class in `examples/cells`. - Discovery reads literal `template class` statements directly and only parses a - `.cpp` with CastXML when *all* of its instantiations are macro-generated. A - file that **mixes** literal and macro-generated instantiations is not fully - discovered — its macro-generated ones are missed — so configure those manually - with `template_substitutions`. -- To stop discovery wrapping instantiations you do not want (e.g. everything the - source instantiates at a spatial dimension of 1), set `discover_arg_excludes`, - a map of template parameter name to the argument values to drop: - - ```yaml - discover_arg_excludes: - DIM: [1] - ELEMENT_DIM: [1] - SPACE_DIM: [1] - ``` - - A discovered instantiation is dropped when the argument bound to a listed - parameter is one of its values, so `Foo` is dropped but - `Foo` is kept. Matching is **by parameter name**, so a value that - is a spatial dimension for one parameter but incidental for another — e.g. a - trailing `PROBLEM_DIM` of 1 in `Bar<2, 2, 1>` — is only excluded where it is - actually named. Only discovered instantiations are filtered; - `template_substitutions` are always wrapped as written. A key may be the bare - parameter name (`ELEMENT_DIM`) or carry a leading type as a - `template_substitutions` signature spells it (`unsigned ELEMENT_DIM`). Set it - at any level (package, module or class); it inherits down to classes. -- cppwg automatically drops a wrapped template instantiation whose wrapped - interface (a non-excluded method or constructor) takes or returns a **project - template type that is never instantiated** — which would otherwise fail to - link or import with an undefined symbol. Each drop is logged, e.g. - `Excluding Foo<1>: wrapped interface depends on uninstantiated type Bar<0>`. - This typically happens at a dimensional boundary — a low-dimensional element - (e.g. `Facet<1>`) whose faces are a never-instantiated `Facet<0>`. To keep - such a class, instantiate its dependency (see `Corner` in `examples/cells`); - otherwise the drop leaves the safe instantiations wrapped (see `Facet`). -- To stop C++ exceptions from crashing the Python interpreter, list their class - names under `exceptions` in the config. cppwg generates a pybind11 exception - translator for each. By default the message is read with `what()`; set - `message_method` on an entry to use a different accessor (e.g. `GetMessage`). - See `examples/shapes/wrapper/package_info.yaml` for an example. -- This only applies to thrown C++ exceptions. Errors from C dependencies that - use return codes (e.g. a PETSc `PetscErrorCode`) are not caught unless the - wrapped C++ code converts them into a C++ exception first (for PETSc, via - `PetscCallThrow()` in a C++-exception build, or by checking the code and - throwing). See `PetscUtils::ThrowPetscError` in the cells example. -- A wrapped signature may expose a type that pybind11 cannot convert on its own - (e.g. a PETSc `Vec`, a VTK `vtkSmartPointer<...>`, a boost ublas `c_vector`), - which needs a **type-caster header** included in the wrapper. Instead of adding - that header by hand under `source_includes` for every affected class, list your - casters once under `typecasters`, each with the `header` and the `types` it - handles: +See the [full walkthrough](https://chaste.github.io/cppwg/getting-started.html) +and the runnable `examples/shapes` and `examples/cells` projects for the +complete picture. - ```yaml - typecasters: - - header: caster_petsc.h - types: [Vec, Mat] - - header: PybindVTKTypeCaster.h - types: [vtkSmartPointer] - - header: PybindUblasTypeCaster.hpp - types: [boost::numeric::ublas::c_vector] - ``` +## Building the docs locally - cppwg then adds a caster's header to a class's wrapper only when that class's - wrapped method or constructor signatures actually use one of its `types` — and - to no other wrapper, keeping these heavy headers out of every wrapper that does - not need them (they are never added to the shared header collection). Because - matching is against the type as it appears in the generated signature, spell - each `type` as it is written there: as a whole token, case-sensitively, and - namespace-qualified where applicable (`boost::numeric::ublas::c_vector`, not - `vector`; `Vec` matches `::Vec` but not `c_vector`). You still add the caster - directories to your build's include paths, exactly as before. Free functions - are not covered — a free function using a caster type still needs the header - added manually. See `examples/cells/dynamic/config.yaml`. -- A wrapped signature may use another **project type** whose definition the - class's own header only forward-declares — e.g. a `MeshFactory` whose - `generateMesh()` returns a `PottsMesh` it never `#include`s. The wrapper then - needs that type's header (unless you use `common_include_file`, which pulls in - everything). Rather than listing it by hand under `source_includes`, set - `auto_includes: True` (at package, module or class level; it inherits down the - info tree, and is off by default): - - ```yaml - modules: - - name: mymod - classes: - - name: MeshFactory - auto_includes: True # resolves PottsMesh.hpp from the signature - ``` - - cppwg then scans each such class's wrapped method/constructor signatures, plus - the template arguments of its instantiations (e.g. a `Foo` needs - `Bar.hpp` even if `Bar` never appears in a method/constructor signature), and - for every **project type** it finds (a class defined under the module - `source_locations`) adds that class's header to the wrapper. Only project types - are resolved — library types (`std::`, boost, PETSc, VTK, …) are left alone — - and a type name that is defined in more than one header is treated as ambiguous - and left for a manual `source_includes`. It is a no-op with - `common_include_file`, and does not cover types that appear only in - hand-written `custom_generator` code (cppwg cannot see those) — a generator - that names such types can add their headers itself by overriding - `get_source_includes()` (see below). See the `MeshFactory` class in - `examples/cells/dynamic/config.yaml`. -- A `custom_generator` can emit code that references types cppwg never sees in - the parsed signatures (e.g. a template-method instantiation like - `AddCellWriter` built from a hard-coded list). Those headers - cannot be auto-included and would otherwise have to be repeated under - `source_includes`. Instead, override `get_source_includes()` on the generator - (a subclass of `cppwg.templates.custom.Custom`) to return the header names, - spelled as under `source_includes` — a bare name like `Foo.hpp` (cppwg adds - the quotes) or an angle-bracket string like ``. cppwg adds them to the - wrapper's `#include` block (deduplicated), so the generator and the includes - its code needs live in one place: - - ```python - class PopulationWriterCustomTemplate(cppwg.templates.custom.Custom): - WRITERS = ["CellAgesWriter", "CellIdWriter", ...] - - def get_class_cpp_def_code(self, class_name): - return "".join( - f'.def("AddCellWriter{w}", &{class_name}::AddCellWriter<{w}>)\n' - for w in self.WRITERS - ) - - def get_source_includes(self, *args, **kwargs): - return [f"{w}.hpp" for w in self.WRITERS] - ``` -- A wrapped class can inherit from a base class wrapped in a **different - module**, as long as that base is registered somewhere that gets imported - first. Opt in per module with `imports`, which lists the Python modules to - import at the start of the generated module (so their base types are - registered before this module's classes). Do not list a module in its own - `imports` (that would be a circular import). - - cppwg only emits an external base when it can confirm the base is registered, - to avoid generating a `py::class_<...>` with an unregistered base (e.g. a - framework/utility base), which fails at import. There are two cases: - - - **Base wrapped in another module of the same package** — detected - automatically; just `import` that module: - - ```yaml - modules: - - name: primitives # defines the base class, e.g. Rectangle - - name: composites - imports: - - pyshapes.primitives._pyshapes_primitives - classes: - - name: Square # inherits Rectangle, wrapped in `primitives` - ``` - - - **Base wrapped in another package** (unknown to this cppwg run) — also list - the base class name under `external_bases` so cppwg knows it is registered - by an imported module (names match without template arguments, and with or - without namespace qualification): +```bash +pip install ".[docs]" +sphinx-build -b html doc doc/_build/html +``` - ```yaml - modules: - - name: all - imports: - - ext_pkg._ext_mod - external_bases: - - AbstractFoo # MyFoo inherits AbstractFoo, wrapped in ext_pkg - classes: - - name: MyFoo - ``` +## License - See the - [pybind11 docs on partitioning code over multiple extension modules](https://pybind11.readthedocs.io/en/stable/advanced/misc.html#partitioning-code-over-multiple-extension-modules). -- To shrink wrappers, set `exclude_inherited_overrides: True` (package level). - cppwg then does not emit a binding for a method that merely overrides a virtual - method already wrapped on a wrapped base class: pybind11 inheritance plus C++ virtual - dispatch already expose it through the base binding, so the derived `.def` is - pure duplication. The virtual **trampoline** is still generated, so Python - subclasses can still override the method. A method is skipped only when a - wrapped, non-class-excluded base declares a virtual of the same name, argument - types and const-ness (the return type is not compared, so a covariant-return - override still matches); if the base lists the method under its - `excluded_methods` the override is kept, since the base does not wrap it. Off by - default. -- See the [pybind11 documentation](https://pybind11.readthedocs.io/) for help on pybind11 - wrapper code. +BSD 3-Clause. See [LICENSE](LICENSE). diff --git a/cppwg/info/package_info.py b/cppwg/info/package_info.py index 88a559a6..899346b2 100644 --- a/cppwg/info/package_info.py +++ b/cppwg/info/package_info.py @@ -237,14 +237,14 @@ def collect_source_files( Collect files matching the given patterns from the source root. Walk through the source root and return any files matching the provided - patterns e.g. "*.hpp", skipping restricted paths and generated wrapper + patterns e.g. ``*.hpp``, skipping restricted paths and generated wrapper files (e.g. .cppwg.hpp). The result is sorted by filename, then by full path, giving a deterministic order even when a basename is shared. Parameters ---------- patterns : list[str] - A list of filename patterns to match e.g. ["*.hpp"]. + A list of filename patterns to match e.g. ``["*.hpp"]``. restricted_paths : list[str] A list of restricted paths to skip when collecting files. @@ -287,7 +287,7 @@ def collect_source_headers(self, restricted_paths: list[str]) -> None: Collect header files from the source root. Walk through the source root and add any files matching the source file - patterns e.g. "*.hpp". + patterns e.g. ``*.hpp``. Parameters ---------- @@ -310,8 +310,8 @@ def collect_source_cpp(self, restricted_paths: list[str]) -> None: Collect implementation files from the source root. Walk through the source root and add any files matching the source cpp - patterns e.g. "*.cpp". These are scanned for explicit template - instantiations when `discover_template_instantiations` is enabled; they + patterns e.g. ``*.cpp``. These are scanned for explicit template + instantiations when ``discover_template_instantiations`` is enabled; they are not added to the header collection. Parameters diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index 91f521b6..07f1cdae 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -308,7 +308,7 @@ class that forwards them to Python. def bases_block(self, class_decl: "class_t") -> str: """ - Return the base-class list appended to the py::class_ declaration. + Return the base-class list appended to the ``py::class_`` declaration. Cross-module inheritance is opted into per module via `imports`. When set, a base class that is not wrapped in this module may still be @@ -692,7 +692,7 @@ def build_struct_enum_register(self, template_idx: int) -> str: """ Build the registration block for a struct-enum instantiation. - Handles a struct that wraps a single nested enum, for example: + Handles a struct that wraps a single nested enum, for example:: struct Foo { enum Value {A, B, C}; diff --git a/cppwg/writers/method_writer.py b/cppwg/writers/method_writer.py index 73119135..96a62505 100644 --- a/cppwg/writers/method_writer.py +++ b/cppwg/writers/method_writer.py @@ -159,8 +159,9 @@ def generate_wrapper(self) -> str: """ Generate the method wrapper code. - Example output: - .def("bar", (void(Foo::*)(double)) &Foo::bar, " ", py::arg("d") = 1.0) + Example output:: + + .def("bar", (void(Foo::*)(double)) &Foo::bar, " ", py::arg("d") = 1.0) Returns ------- @@ -260,16 +261,15 @@ def generate_virtual_override_wrapper(self) -> str: """ Generate wrapper code for overriding virtual methods. - Example output: - ``` - void bar(double d) const override { - PYBIND11_OVERRIDE_PURE( - bar, - Foo_2_2, - bar, - d); - } - ``` + Example output:: + + void bar(double d) const override { + PYBIND11_OVERRIDE_PURE( + bar, + Foo_2_2, + bar, + d); + } Returns ------- diff --git a/cppwg/writers/module_writer.py b/cppwg/writers/module_writer.py index fbf53b26..6de1fb8a 100644 --- a/cppwg/writers/module_writer.py +++ b/cppwg/writers/module_writer.py @@ -248,23 +248,21 @@ def write_module_wrapper(self) -> None: """ Generate the contents of the main cpp file for the module. - The main cpp file is named `_packagename_modulename.main.cppwg.cpp`. This - file contains the pybind11 module definition, within which the module's - classes and free functions are registered. + The main cpp file is named ``_packagename_modulename.main.cppwg.cpp``. + This file contains the pybind11 module definition, within which the + module's classes and free functions are registered. - Example output: + Example output:: - ``` - #include - #include "Foo.cppwg.hpp" - #include "Bar.cppwg.hpp" + #include + #include "Foo.cppwg.hpp" + #include "Bar.cppwg.hpp" - PYBIND11_MODULE(_packagename_modulename, m) - { - register_Foo_class(m); - register_Bar_class(m); - } - ``` + PYBIND11_MODULE(_packagename_modulename, m) + { + register_Foo_class(m); + register_Bar_class(m); + } """ cpp_string = self.wrapper_templates["module_main_cpp"].substitute( self.build_module_context() diff --git a/doc/api.rst b/doc/api.rst new file mode 100644 index 00000000..287b6dcc --- /dev/null +++ b/doc/api.rst @@ -0,0 +1,56 @@ +API reference +============= + +Autogenerated from the source docstrings. This documents cppwg's internals for +contributors; most users only need the :doc:`configuration`. + +Generator +--------- + +.. automodule:: cppwg.generators + :members: + +Config model +------------ + +.. automodule:: cppwg.info.package_info + :members: + +.. automodule:: cppwg.info.module_info + :members: + +.. automodule:: cppwg.info.class_info + :members: + +.. automodule:: cppwg.info.base_info + :members: + +Parsers +------- + +.. automodule:: cppwg.parsers.package_info_parser + :members: + +.. automodule:: cppwg.parsers.source_parser + :members: + +Writers +------- + +.. automodule:: cppwg.writers.class_writer + :members: + +.. automodule:: cppwg.writers.method_writer + :members: + +.. automodule:: cppwg.writers.constructor_writer + :members: + +.. automodule:: cppwg.writers.module_writer + :members: + +Custom generators +----------------- + +.. automodule:: cppwg.templates.custom + :members: diff --git a/doc/conf.py b/doc/conf.py index 70efb29f..d221fea1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -11,13 +11,12 @@ # All configuration values have a default; values that are commented out # serve to show the default. -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) +# The cppwg package lives one level up from this doc/ directory; add it to the +# path so autodoc can import it to pull in docstrings. +import os +import sys + +sys.path.insert(0, os.path.abspath("..")) # -- General configuration ------------------------------------------------ @@ -35,16 +34,27 @@ "sphinx.ext.mathjax", "sphinx.ext.viewcode", "sphinx.ext.githubpages", + "sphinx.ext.napoleon", # render the numpy-style docstrings used across cppwg + "myst_parser", # author pages in Markdown instead of reStructuredText ] +# MyST (Markdown) niceties: ::: fenced directives and (# anchor)= header targets. +myst_enable_extensions = ["colon_fence", "deflist"] +myst_heading_anchors = 3 + +# Keep the build green when optional import-time dependencies (pygccxml, +# pyyaml, ...) are unavailable on the docs builder. +autodoc_mock_imports = ["pygccxml", "yaml"] +autodoc_member_order = "bysource" + # Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] +templates_path = [] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = ".rst" +source_suffix = {".rst": "restructuredtext", ".md": "markdown"} # The master toctree document. master_doc = "index" @@ -52,23 +62,43 @@ # General information about the project. project = "cppwg" copyright = "2017, J. Grogan" -author = "J. Grogan" +author = "J. Grogan, Joe Bloggs, Chaste Developers " + +# The version info for the project, used as replacement for |version| and +# |release|. Single-sourced from the installed package metadata (which pip +# populates from pyproject.toml) so it can never drift from the release; falls +# back to parsing pyproject.toml directly for a bare, uninstalled checkout. +def _project_version() -> str: + from importlib import metadata + + try: + return metadata.version("cppwg") + except metadata.PackageNotFoundError: + # Bare, uninstalled checkout: read the version straight from pyproject. + # Parse with a small regex rather than tomllib, which is stdlib only on + # Python >= 3.11 while this project supports >= 3.10 (importing tomllib + # would crash the fallback on 3.10). The `version = "..."` line anchored + # to the start of a line is unambiguous (e.g. `target-version` does not + # match). + import re + + pyproject = os.path.join(os.path.dirname(__file__), os.pardir, "pyproject.toml") + with open(pyproject, encoding="utf-8") as fh: + match = re.search(r'(?m)^version\s*=\s*"([^"]+)"', fh.read()) + return match.group(1) if match else "0.0.0" + -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = "0.1" # The full version, including alpha/beta/rc tags. -release = "0.1.1" +release = _project_version() +# The short X.Y version. +version = ".".join(release.split(".")[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -87,7 +117,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = "alabaster" +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -98,22 +128,8 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +html_static_path = [] -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# This is required for the alabaster theme -# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -html_sidebars = { - "**": [ - "about.html", - "navigation.html", - "relations.html", # needs 'show_related': True theme option to display - "searchbox.html", - "donate.html", - ] -} # -- Options for HTMLHelp output ------------------------------------------ @@ -143,7 +159,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, "cppwg.tex", "cppwg Documentation", "J. Grogan", "manual"), + (master_doc, "cppwg.tex", "cppwg Documentation", author, "manual"), ] @@ -166,7 +182,7 @@ "cppwg Documentation", author, "cppwg", - "One line description of project.", + "An automatic Python wrapper generator for C++ code.", "Miscellaneous", ), ] diff --git a/doc/configuration.md b/doc/configuration.md new file mode 100644 index 00000000..b9600a6b --- /dev/null +++ b/doc/configuration.md @@ -0,0 +1,354 @@ +# Configuration reference + +cppwg is driven by a YAML configuration file (passed with `--package_info`). It +describes a **package** made of one or more **modules** (each compiled to its own +extension), and the **classes** and **free functions** each module wraps. + +```yaml +name: pyshapes # package +modules: + - name: primitives # module -> _pyshapes_primitives extension + source_locations: + - src/cpp/primitives + classes: + - name: Rectangle # class + excluded_methods: + - GetArea +``` + +## Option inheritance + +Options fall into two groups: + +- **Level-specific options** are only meaningful at one level (e.g. `modules` at + the package level, `imports` at the module level, `name_override` at the class + level). +- **Common options** may be set at the package, module, **or** class level and + **inherit downwards**: a value set on the package applies to every module and + class unless a lower level overrides it. This lets you set, say, + `smart_ptr_type` once for the whole package. + +The tables below mark common options accordingly. + +## Package options + +Set at the top level of the file. + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `name` | str | `cppwg_package` | Package name; prepended to every module's extension name (`_{package}_{module}`). | +| `modules` | list | – | The modules to build. See [Module options](#module-options). | +| `common_include_file` | bool | `True` | If true, every wrapper includes a single shared header collection instead of its own headers. Turn **off** so each wrapper includes only what it needs. | +| `exceptions` | list | `[]` | C++ exception classes to translate into Python. See [Exceptions](#exceptions). | +| `typecasters` | list | `[]` | Type-caster headers to auto-include by type. See [Type casters](#type-casters). | +| `exclude_default_args` | bool | `False` | If true, omit C++ default argument values from the generated bindings. | +| `exclude_inherited_overrides` | bool | `False` | If true, skip redundant `.def`s for methods that only override a virtual already wrapped on a wrapped base. See [Excluding inherited overrides](#excluding-inherited-overrides). | +| `source_hpp_patterns` | list[str] | `["*.hpp"]` | Glob patterns for the header files cppwg scans. | +| `source_cpp_patterns` | list[str] | `["*.cpp"]` | Glob patterns for the source files cppwg scans (used for template-instantiation discovery). | + +All [common options](#common-options) may also be set here. + +## Module options + +Each entry under `modules:`. + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `name` | str | `cppwg_module` | Module name; the extension is `_{package}_{name}`. | +| `source_locations` | list[str] | `[]` | Directories (relative to the source root) whose classes this module wraps. | +| `classes` | list | `[]` | Classes to wrap, or the string `CPPWG_ALL` to wrap every class found. See [Selecting what to wrap](#selecting-what-to-wrap). | +| `free_functions` | list | `[]` | Free functions to wrap, or `CPPWG_ALL`. | +| `imports` | list[str] | `[]` | Python modules to import at the start of this module, so their types are registered first. Required for cross-module inheritance. See [Cross-module inheritance](#cross-module-and-cross-package-inheritance). | +| `external_bases` | list[str] | `[]` | Base-class names registered by an imported **package**, so cppwg will emit them as bases. See [Cross-module inheritance](#cross-module-and-cross-package-inheritance). | + +All [common options](#common-options) may also be set here. + +## Class options + +Each entry under a module's `classes:`. + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `name` | str | – | The C++ class name (required). | +| `name_override` | str | `""` | Python name for the class, if different from the C++ name. | +| `source_file` | str | `""` | Header to attribute the class to, when the class name does not match its file name. | + +All [common options](#common-options) may also be set here. + +## Common options + +Settable at the package, module, or class level; they inherit downwards. + +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `excluded` | bool | `False` | Exclude the whole class from wrapping. | +| `excluded_methods` | list[str] | `[]` | Method names to skip. | +| `arg_type_excludes` | list[str] | `[]` | Skip any method/constructor with an argument type matching one of these. | +| `return_type_excludes` | list[str] | `[]` | Skip any method with a return type matching one of these. | +| `constructor_arg_type_excludes` | list[str] | `[]` | Skip any constructor with an argument type matching one of these. | +| `constructor_signature_excludes` | list[list[str]] | `[]` | Skip a constructor whose full argument-type list matches an entry. | +| `template_substitutions` | list | `[]` | Explicit template instantiations to wrap. See [Template instantiations](#template-instantiations). | +| `discover_template_instantiations` | bool | `None` | Auto-discover explicit instantiations from the source. See [Template instantiations](#template-instantiations). | +| `discover_arg_excludes` | dict | `{}` | Drop discovered instantiations by template-argument value. See [Template instantiations](#template-instantiations). | +| `auto_includes` | bool | `None` | Auto-resolve headers for project types used in wrapped signatures. See [Includes](#includes). | +| `source_includes` | list[str] | `[]` | Extra headers to add to the wrapper's `#include` block. | +| `smart_ptr_type` | str | `""` | Holder type declared for wrapped classes, e.g. `boost::shared_ptr`. Use one holder consistently across a hierarchy. | +| `pointer_call_policy` | str | `""` | Default pybind11 `return_value_policy` for methods returning a pointer, e.g. `reference`. | +| `reference_call_policy` | str | `""` | Default pybind11 `return_value_policy` for methods returning a reference, e.g. `reference_internal`. | +| `custom_generator` | str | `""` | Path to a Python template that injects extra binding code. See [Custom generators](custom-generators.md). | +| `prefix_text` | str | `""` | Text emitted at the top of each wrapper file (e.g. a licence header). | +| `prefix_code` | list[str] | `[]` | Lines emitted before a class's registration block. | +| `suffix_code` | list[str] | `[]` | Lines emitted after a class's `.def` chain, before the closing `;`. | + +`CPPWG_SOURCEROOT` in a path value (e.g. in `custom_generator`) is replaced with +the source root directory. + +--- + +## Selecting what to wrap + +List classes and free functions explicitly under a module, or wrap everything +found in the module's `source_locations` with the sentinel string `CPPWG_ALL`: + +```yaml +modules: + - name: all + source_locations: + - src/cpp + classes: CPPWG_ALL + free_functions: CPPWG_ALL +``` + +`CPPWG_ALL` must be the **whole** value — a list that merely contains it (e.g. +`[CPPWG_ALL, Foo]`) is treated as literal class names, not "wrap everything". + +## Template instantiations + +A templated class must be wrapped once per instantiation. There are two ways to +select them; they can be combined. + +### Explicit: `template_substitutions` + +List each instantiation as a `signature` (the template parameters) and the +`replacement` argument lists: + +```yaml +- name: AbstractElement + template_substitutions: + - signature: + replacement: [[1, 2], [2, 2], [2, 3], [3, 3]] +``` + +Each replacement's length must match the parameter count. + +### Automatic: `discover_template_instantiations` + +Set `discover_template_instantiations: True` to have cppwg find explicit +instantiations (e.g. `template class Foo<2, 2>;`) in the source `.cpp` files, +without hand-writing them. Instantiations declared through a macro are found via +CastXML; recovering a **defaulted** trailing template argument from such a macro +instantiation requires **CastXML >= 0.6.0** (older versions drop it, e.g. naming +`Foo<2, 2>` as `Foo<2>`). See the `MacroMesh` class in `examples/cells`. + +Discovery reads literal `template class` statements directly and only parses a +`.cpp` with CastXML when *all* of its instantiations are macro-generated. A file +that **mixes** literal and macro-generated instantiations is not fully +discovered — its macro-generated ones are missed — so configure those manually +with `template_substitutions`. + +### Filtering discovery: `discover_arg_excludes` + +To stop discovery from wrapping instantiations you do not want (e.g. everything +the source instantiates at a spatial dimension of 1), map a template-parameter +name to the argument values to drop: + +```yaml +discover_arg_excludes: + DIM: [1] + ELEMENT_DIM: [1] + SPACE_DIM: [1] +``` + +A discovered instantiation is dropped when the argument bound to a listed +parameter is one of its values, so `Foo` is dropped but +`Foo` is kept. Matching is **by parameter name**, so a value that is +a spatial dimension for one parameter but incidental for another — e.g. a +trailing `PROBLEM_DIM` of 1 in `Bar<2, 2, 1>` — is only excluded where it is +actually named. Only discovered instantiations are filtered; +`template_substitutions` are always wrapped as written. A key may be the bare +parameter name (`ELEMENT_DIM`) or carry a leading type as a +`template_substitutions` signature spells it (`unsigned ELEMENT_DIM`). + +### Uninstantiated-dependency dropping + +cppwg automatically drops a wrapped instantiation whose wrapped interface (a +non-excluded method or constructor) takes or returns a **project template type +that is never instantiated** — which would otherwise fail to link or import with +an undefined symbol. Each drop is logged, e.g. +`Excluding Foo<1>: wrapped interface depends on uninstantiated type Bar<0>`. This +typically happens at a dimensional boundary — a low-dimensional element (e.g. +`Facet<1>`) whose faces are a never-instantiated `Facet<0>`. To keep such a +class, instantiate its dependency (see `Corner` in `examples/cells`); otherwise +the drop leaves the safe instantiations wrapped (see `Facet`). + +## Excluding members + +Several options drop members that cannot or should not be wrapped. Type matches +are on the type as it appears in the C++ signature, as a whole token, +case-sensitively, and namespace-qualified where applicable. + +- `excluded: True` — drop the whole class. +- `excluded_methods` — drop methods by name. +- `return_type_excludes` / `arg_type_excludes` — drop methods (and, for args, + constructors) whose return/argument type matches. +- `constructor_arg_type_excludes` — drop constructors by an argument type (e.g. + a mesh reference the constructor stores a raw pointer to). +- `constructor_signature_excludes` — drop a constructor by its full argument-type + list, e.g. `- [unsigned]` to drop a single-`unsigned` constructor. + +## Includes + +Each non-common wrapper needs the headers for the types in its signatures. +cppwg offers three complementary mechanisms so you rarely list headers by hand. + +### `source_includes` + +Extra headers added verbatim to the wrapper's `#include` block. A bare name +(`Foo.hpp`) is quoted; an angle-bracket string (``) is emitted as-is. + +### `auto_includes` + +A wrapped signature may use another **project type** whose definition the class's +own header only forward-declares — e.g. a `MeshFactory` whose +`generateMesh()` returns a `PottsMesh` it never `#include`s. Set +`auto_includes: True` (at any level; it inherits down and is off by default): + +```yaml +modules: + - name: mymod + classes: + - name: MeshFactory + auto_includes: True # resolves PottsMesh.hpp from the signature +``` + +cppwg scans each such class's wrapped method/constructor signatures, plus the +template arguments of its instantiations (e.g. a `Foo` needs `Bar.hpp` +even if `Bar` never appears in a method/constructor signature), and for every +**project type** it finds (a class defined under the module `source_locations`) +adds that class's header. Only project types are resolved — library types +(`std::`, boost, PETSc, VTK, …) are left alone — and a type name defined in more +than one header is treated as ambiguous and left for a manual `source_includes`. +It is a no-op with `common_include_file`, and does not cover types that appear +only in hand-written `custom_generator` code. See the `MeshFactory` class in +`examples/cells/dynamic/config.yaml`. + +### Type casters + +A wrapped signature may expose a type pybind11 cannot convert on its own (a PETSc +`Vec`, a VTK `vtkSmartPointer<...>`, a boost ublas `c_vector`), which needs a +**type-caster header** in the wrapper. List your casters once under +`typecasters`, each with the `header` and the `types` it handles: + +```yaml +typecasters: + - header: caster_petsc.h + types: [Vec, Mat] + - header: PybindVTKTypeCaster.h + types: [vtkSmartPointer] + - header: PybindUblasTypeCaster.hpp + types: [boost::numeric::ublas::c_vector] +``` + +cppwg adds a caster's header to a wrapper only when that class's wrapped +signatures actually use one of its `types` — and to no other wrapper, keeping +these heavy headers out of wrappers that do not need them (they are never added +to the shared header collection). Because matching is against the type as it +appears in the generated signature, spell each `type` as written there: as a +whole token, case-sensitively, and namespace-qualified (`boost::numeric::ublas::c_vector`, +not `vector`; `Vec` matches `::Vec` but not `c_vector`). You still add the caster +directories to your build's include paths. Free functions are not covered — a +free function using a caster type still needs the header added manually. See +`examples/cells/dynamic/config.yaml`. + +A caster converts a type only at the **call boundary** (arguments and returns), +not for a type stored in a container or as object state, and not for non-const +reference (out-)parameters. Signatures using a caster type in one of those shapes +still have to be excluded. + +## Cross-module and cross-package inheritance + +A wrapped class can inherit from a base wrapped in a **different module**, as long +as that base is registered somewhere imported first. Opt in per module with +`imports`, which lists the Python modules to import at the start of the generated +module. Do not list a module in its own `imports` (a circular import). + +cppwg only emits an external base when it can confirm the base is registered, to +avoid a `py::class_<...>` with an unregistered base (which fails at import). Two +cases: + +**Base wrapped in another module of the same package** — detected automatically; +just `import` that module: + +```yaml +modules: + - name: primitives # defines the base class, e.g. Rectangle + - name: composites + imports: + - pyshapes.primitives._pyshapes_primitives + classes: + - name: Square # inherits Rectangle, wrapped in `primitives` +``` + +**Base wrapped in another package** (unknown to this cppwg run) — also list the +base class name under `external_bases` so cppwg knows it is registered by an +imported module (names match without template arguments, and with or without +namespace qualification): + +```yaml +modules: + - name: all + imports: + - ext_pkg._ext_mod + external_bases: + - AbstractFoo # MyFoo inherits AbstractFoo, wrapped in ext_pkg + classes: + - name: MyFoo +``` + +See the [pybind11 docs on partitioning code over multiple extension modules](https://pybind11.readthedocs.io/en/stable/advanced/misc.html#partitioning-code-over-multiple-extension-modules). + +## Excluding inherited overrides + +Set `exclude_inherited_overrides: True` (package level) to shrink wrappers. +cppwg then does not emit a binding for a method that merely overrides a virtual +already wrapped on a wrapped base class: pybind11 inheritance plus C++ virtual +dispatch already expose it through the base binding, so the derived `.def` is +pure duplication. The virtual **trampoline** is still generated, so Python +subclasses can still override the method. + +A method is skipped only when a wrapped, non-class-excluded base declares a +virtual of the same name, argument types and const-ness (the return type is not +compared, so a covariant-return override still matches). It is kept when: the +base itself excludes the method from wrapping (by name or by type), the base is +in another module that is not `imports`-linked, or another same-name overload on +the class would still be bound (which would otherwise shadow the base). Off by +default. + +## Exceptions + +To stop C++ exceptions from crashing the Python interpreter, list their class +names under `exceptions`. cppwg generates a pybind11 exception translator for +each. By default the message is read with `what()`; set `message_method` to use a +different accessor: + +```yaml +exceptions: + - name: Exception + message_method: GetMessage +``` + +This only applies to thrown C++ exceptions. Errors from C dependencies that use +return codes (e.g. a PETSc `PetscErrorCode`) are not caught unless the wrapped +C++ code converts them into a C++ exception first (for PETSc, via +`PetscCallThrow()` in a C++-exception build, or by checking the code and +throwing). See `PetscUtils::ThrowPetscError` in the cells example. diff --git a/doc/custom-generators.md b/doc/custom-generators.md new file mode 100644 index 00000000..a8fa3be0 --- /dev/null +++ b/doc/custom-generators.md @@ -0,0 +1,72 @@ +# Custom generators + +Some bindings cannot be produced from the parsed C++ signatures alone — for +example a `.def` built from a hard-coded list, or a template-method instantiation +like `AddCellWriter`. A **custom generator** lets you inject +hand-written binding code for a class while cppwg generates the rest. + +Point a class at a generator with `custom_generator`: + +```yaml +- name: MeshBasedCellPopulation + custom_generator: "CPPWG_SOURCEROOT/wrapper/PopulationWriterCustomTemplate.py" +``` + +`CPPWG_SOURCEROOT` is replaced with the source root directory. The file defines a +subclass of `cppwg.templates.custom.Custom`, overriding the hooks it needs. + +## Hooks + +| Hook | Injects | Where | +| --- | --- | --- | +| `get_class_cpp_pre_code(class_name)` | Code before the registration block | Before `py::class_<...>` | +| `get_class_cpp_def_code(class_name)` | Extra `.def(...)` lines | Into the `.def` chain | +| `get_source_includes()` | Header names the emitted code needs | The wrapper `#include` block | + +`class_name` is the wrapper alias for the current instantiation (e.g. +`Foo_2_2`), so pre-code and def-code can refer to the class by that name. + +## Injecting `.def` code + +```python +import cppwg.templates.custom + + +class PopulationWriterCustomTemplate(cppwg.templates.custom.Custom): + WRITERS = ["CellAgesWriter", "CellIdWriter"] + + def get_class_cpp_def_code(self, class_name): + return "".join( + f'.def("AddCellWriter{w}", &{class_name}::AddCellWriter<{w}>)\n' + for w in self.WRITERS + ) +``` + +## Declaring the headers your code needs + +Code emitted by a generator can reference types cppwg never sees in the parsed +signatures, so [`auto_includes`](configuration.md#includes) cannot resolve their +headers and they would otherwise have to be repeated under `source_includes`. +Override `get_source_includes()` to return the header names — spelled as under +`source_includes`, a bare name like `Foo.hpp` (cppwg adds the quotes) or an +angle-bracket string like ``. cppwg adds them to the wrapper's `#include` +block (deduplicated), so the generator and the includes its code needs live in +one place: + +```python +class PopulationWriterCustomTemplate(cppwg.templates.custom.Custom): + WRITERS = ["CellAgesWriter", "CellIdWriter"] + + def get_class_cpp_def_code(self, class_name): + return "".join( + f'.def("AddCellWriter{w}", &{class_name}::AddCellWriter<{w}>)\n' + for w in self.WRITERS + ) + + def get_source_includes(self, *args, **kwargs): + return [f"{w}.hpp" for w in self.WRITERS] +``` + +See the `MutableElement`, `ReplicatableVector`, and population classes in +`examples/cells/dynamic/config.yaml` and their generator templates for full +examples. diff --git a/doc/getting-started.md b/doc/getting-started.md new file mode 100644 index 00000000..4f20953a --- /dev/null +++ b/doc/getting-started.md @@ -0,0 +1,150 @@ +# Getting started + +## Installation + +Install CastXML (required) and Clang (recommended). On Ubuntu: + +```bash +sudo apt-get install castxml clang +``` + +Clone the repository and install cppwg: + +```bash +git clone https://github.com/Chaste/cppwg.git +cd cppwg +pip install . +``` + +## Command-line usage + +```text +usage: cppwg [-h] [-w WRAPPER_ROOT] [-p PACKAGE_INFO] [-c CASTXML_BINARY] + [-m CASTXML_COMPILER] [--std STD] [--castxml_cflags CASTXML_CFLAGS] + [-i [INCLUDES ...]] [--overwrite] [-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. + +options: + -h, --help show this help message and exit + -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 + 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. + --castxml_cflags CASTXML_CFLAGS + Additional flags for the castxml clang frontend. Pass + values starting with "-" using "=" e.g. + --castxml_cflags="-Wno-deprecated". + -i, --includes [INCLUDES ...] + List of paths to include directories. + --overwrite Force rewrite of all wrapper files, even if unchanged. + -q, --quiet Disable informational messages. + -l, --logfile [LOGFILE] + Output log messages to a file. + -v, --version Print cppwg version. +``` + +## A worked example + +The project in `examples/shapes` demonstrates cppwg usage. We can walk through +the process with the `Rectangle` class in `examples/shapes/src/cpp/primitives`. + +**Rectangle.hpp** + +```cpp +class Rectangle : public Shape<2> +{ +public: + Rectangle(double width=2.0, double height=1.0); + ~Rectangle(); + //... +}; +``` + +cppwg needs a configuration file that lists the classes to wrap and describes the +structure of the Python package to be created. There is an example at +`examples/shapes/wrapper/package_info.yaml`. + +The extract below describes a Python package named `pyshapes` with a +`primitives` module that includes the `Rectangle` class: + +```yaml +name: pyshapes +modules: + - name: primitives + classes: + - name: Rectangle +``` + +See the [configuration reference](configuration.md) for all available options. + +To generate the wrappers: + +```bash +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/primitives \ + --std c++17 +``` + +For the `Rectangle` class, this creates two files in +`examples/shapes/wrapper/primitives`. + +**Rectangle.cppwg.hpp** + +```cpp +void register_Rectangle_class(pybind11::module &m); +``` + +**Rectangle.cppwg.cpp** + +```cpp +namespace py = pybind11; +void register_Rectangle_class(py::module &m) +{ + py::class_ >(m, "Rectangle") + .def(py::init(), py::arg("width")=2, py::arg("height")=1) + //... + ; +} +``` + +The wrapper for `Rectangle` is registered in the `primitives` module. + +**primitives.main.cpp** + +```cpp +PYBIND11_MODULE(_pyshapes_primitives, m) +{ + register_Rectangle_class(m); + //... +} +``` + +To compile the wrappers into a Python package: + +```bash +mkdir build && cd build +cmake .. +make +``` + +The compiled wrapper code can now be imported in Python: + +```python +from pyshapes import Rectangle +r = Rectangle(4, 5) +``` + +Use `examples/shapes` or `examples/cells` as a starting point for your own +project. diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 00000000..0cf408cf --- /dev/null +++ b/doc/index.md @@ -0,0 +1,35 @@ +# cppwg + +Automatically generate [pybind11](https://pybind11.readthedocs.io/) Python +wrapper code for C++ projects. + +cppwg reads your C++ source together with a YAML configuration file and emits the +pybind11 registration code for the classes, free functions, and template +instantiations you select — so you describe *what* to expose in config rather +than hand-writing wrapper code. + +```{toctree} +:maxdepth: 2 +:caption: Contents + +getting-started +configuration +custom-generators +tips +api +``` + +## Quick links + +- [Getting started](getting-started.md) — install, run, and a full worked example. +- [Configuration reference](configuration.md) — every config option, with types + and defaults. +- [Custom generators](custom-generators.md) — inject hand-written binding code. +- [Tips & recipes](tips.md) — build behaviour, discovery, and troubleshooting. +- [API reference](api.rst) — autogenerated from the source docstrings. + +## Indices + +- {ref}`genindex` +- {ref}`modindex` +- {ref}`search` diff --git a/doc/index.rst b/doc/index.rst deleted file mode 100644 index b927f83f..00000000 --- a/doc/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. cppwg documentation master file, created by - sphinx-quickstart on Fri Jul 7 09:26:24 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to cppwg's documentation! -================================= - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/doc/tips.md b/doc/tips.md new file mode 100644 index 00000000..73344cb0 --- /dev/null +++ b/doc/tips.md @@ -0,0 +1,33 @@ +# Tips & recipes + +- Use `examples/shapes` or `examples/cells` as a starting point. + +- **Incremental rewrites.** By default cppwg only rewrites wrapper files whose + content has changed, leaving unchanged files untouched so build systems skip + recompiling them. Pass `--overwrite` to force a full rewrite of all wrapper + files. + +- **One file per templated class.** A templated class's instantiations share a + single wrapper file: `Foo<2>` and `Foo<3>` are wrapped in one `Foo.cppwg.hpp` / + `Foo.cppwg.cpp` pair (declaring and defining a `register_Foo_2_class` and + `register_Foo_3_class` each) rather than a separate file pair per + instantiation. This reduces the number of generated files and the translation + units the build has to compile. + +- **CastXML flags.** To pass extra flags to the castxml clang frontend (e.g. to + silence a diagnostic), use `--castxml_cflags`. Values starting with `-` must + use `=`, e.g. `--castxml_cflags="-Wno-deprecated"`. + +- **Consistent smart-pointer holders.** pybind11 requires the same holder type + across a class hierarchy. Set `smart_ptr_type` once at the package level rather + than per class, so a base and its derived classes cannot end up with mismatched + holders (which fails to compile). + +- **Unresolved / linker errors after generation** usually mean a wrapped + signature references a type whose instantiation was never produced. See + [uninstantiated-dependency dropping](configuration.md#template-instantiations) + — cppwg drops such instantiations and logs why; instantiate the dependency to + keep the class. + +- See the [pybind11 documentation](https://pybind11.readthedocs.io/) for help + writing and understanding the generated wrapper code. diff --git a/pyproject.toml b/pyproject.toml index 0835fa5b..570eb428 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ dev = [ docs = [ "sphinx", "sphinx-rtd-theme", - "numpydoc", + "myst-parser", ] [project.scripts]