Skip to content

Add a C ABI library option to the cppjswasm template - #221

Open
timkpaine wants to merge 4 commits into
mainfrom
cppjswasm-c-abi-library
Open

timkpaine wants to merge 4 commits into
mainfrom
cppjswasm-c-abi-library

Conversation

@timkpaine

@timkpaine timkpaine commented Nov 14, 2026 •

Copy link
Copy Markdown
Member

Description

add_c_abi now also applies to the cppjswasm template and gives it the layout the cpp template got in #220:

  • cpp/<name>/c_api.cpp plus the existing example.cpp build the generic library <module>/lib/lib<module>, declared in <module>/include/<module>/<module>.h.
  • cpp/python/extension.cpp is the pybind11 extension, linked against the library through depends.
  • cpp/CMakeLists.txt builds and installs the library without Python.
  • The package gets the get_* helpers and the Windows DLL directory, cibuildwheel gets the repair exclusions, and Pyodide builds use HATCH_CPP_LIBRARY_SOURCE=direct.
  • The JavaScript side is unchanged. It still compiles the C++ sources with Emscripten and does not use the C ABI.

Differences from the cpp template:

  • This template binds the Example class, so the C ABI has an opaque <module>_example handle with _new, _free, _stuff, and _set_stuff. Functions that allocate catch exceptions and report failure through the return value. The extension wraps the handle in a small class, so the Python surface is the same with and without the option.
  • The C API definitions in c_api.cpp are wrapped in // clang-format off and on. Each signature contains the module name twice, so clang-format would break the lines differently per project and a new project could fail make lint. I checked the generated C++ and Python formatting with a 3-character, a 30-character, and a 46-character module name.

Two changes also affect the output without add_c_abi:

  • extension_cpp.pyi now declares Example. The extension already exported it, and ty rejected the import in the new test.
  • js/build.mjs keeps the extension path in a constant. The path contains the module name and was repeated, so oxfmt wrapped two calls once the name passed 25 characters and pnpm lint failed for the cppjswasm-capi example.

The Windows consumer test and the delvewheel exclusion match #223.

Checked on macOS with the cppjswasm-capi example and SKIP_HATCH_JS=1: C++ and Python lint, make checks, and the five tests pass, and the package builds and imports in external mode against the CMake install and in direct mode. I did not run the JavaScript build, Windows, or Pyodide locally.

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Refactor / code cleanup
  • CI / build configuration
  • Other (describe below)

Checklist

  • Linting passes (make lint)
  • Tests pass (make test)
  • New tests added for new functionality
  • Documentation updated (if applicable)
  • Changelog / version bump (if applicable)

add_c_abi now also applies to the cppjswasm template, with the layout
the cpp template uses: cpp/<name> builds lib<module> with an extern "C"
surface, cpp/python holds the pybind11 extension that links it through
hatch-cpp depends, and cpp/CMakeLists.txt builds and installs the
library without Python. The JavaScript build is unchanged and still
compiles the C++ sources with Emscripten.

This template binds a class, so the C ABI has an opaque handle for
Example with create, free, and accessor functions, and the extension
wraps the handle. The C API definitions are excluded from clang-format
because their line breaks would depend on the module name's length.

extension_cpp.pyi now declares Example, which the extension already
exported. A cppjswasm-capi example and gen/test targets exercise the
option in CI.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>
Windows runners find MinGW gcc before cl, and the program needs the library directory on PATH either way.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>
cibuildwheel runs delvewheel on Windows by default, and it cannot find the DLL the wheel already ships.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>
build.mjs repeated the extension path, which contains the module name, so oxfmt wrapped two calls once the name passed 25 characters and pnpm lint failed for the cppjswasm-capi example. The path is now a constant.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant