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
3 changes: 2 additions & 1 deletion .copier-answers.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changes here will be overwritten by Copier
_commit: 02f55bd
_commit: c40b5eb
_src_path: https://github.com/python-project-templates/base.git
add_c_abi: true
add_docs: true
add_extension: cpp
add_pyodide: true
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,28 @@ A C++-Python project template

## Overview

## C ABI

`cpp/python-template-cpp` builds `libpython_template_cpp`, a C ABI over the C++ code, declared in
`python_template_cpp/include/python_template_cpp/python_template_cpp.h`. Wheels ship the library under `python_template_cpp/lib` and
the header under `python_template_cpp/include/python_template_cpp`, and the pybind11 extension in `cpp/python` links
against the library. The tests compile a C program against the installed package. Other
projects build against the installed wheel with `python_template_cpp.get_include()`,
`get_library_dirs()`, `get_libraries()`, and `get_prefix()`.

`HATCH_CPP_LIBRARY_SOURCE` chooses where the library comes from at build time:
`bundled` (the default, for wheels), `external` (a prebuilt library found
through `HATCH_CPP_LIBRARY_ROOT`, pkg-config, or the conda prefix), or `direct`
(compile the library sources into the extension).

`cpp/CMakeLists.txt` builds and installs the library without Python, with a CMake
package config and a pkg-config file, for conda, vcpkg, and system packages:

```bash
cmake -S cpp -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cmake --install build --prefix /path/to/prefix
```

> [!NOTE]
> This library was generated using [copier](https://copier.readthedocs.io/en/stable/) from the [Base Python Project Template repository](https://github.com/python-project-templates/base).
46 changes: 46 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Builds and installs libpython_template_cpp without Python, for conda, vcpkg, and system packages.
cmake_minimum_required(VERSION 3.20)
project(python_template_cpp VERSION 0.1.0 LANGUAGES CXX)

include(CMakePackageConfigHelpers)

add_library(python_template_cpp SHARED python-template-cpp/c_api.cpp python-template-cpp/example.cpp)
add_library(python_template_cpp::python_template_cpp ALIAS python_template_cpp)
target_compile_features(python_template_cpp PRIVATE cxx_std_17)
target_compile_definitions(python_template_cpp PRIVATE PYTHON_TEMPLATE_CPP_BUILD)
target_include_directories(
python_template_cpp
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../python_template_cpp/include> $<INSTALL_INTERFACE:include>
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
)
# SOVERSION is PYTHON_TEMPLATE_CPP_ABI_VERSION.
set_target_properties(
python_template_cpp
PROPERTIES VERSION ${PROJECT_VERSION}
SOVERSION 1
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
INSTALL_NAME_DIR "@rpath"
)

install(
TARGETS python_template_cpp
EXPORT python_template_cppTargets
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
install(DIRECTORY ../python_template_cpp/include/ DESTINATION include)
install(EXPORT python_template_cppTargets NAMESPACE python_template_cpp:: DESTINATION lib/cmake/python_template_cpp)

configure_package_config_file(
cmake/python_template_cppConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/python_template_cppConfig.cmake
INSTALL_DESTINATION lib/cmake/python_template_cpp
)
write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/python_template_cppConfigVersion.cmake COMPATIBILITY SameMinorVersion)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/python_template_cppConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/python_template_cppConfigVersion.cmake
DESTINATION lib/cmake/python_template_cpp
)

configure_file(cmake/python_template_cpp.pc.in ${CMAKE_CURRENT_BINARY_DIR}/python_template_cpp.pc @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/python_template_cpp.pc DESTINATION lib/pkgconfig)
9 changes: 9 additions & 0 deletions cpp/cmake/python_template_cpp.pc.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
prefix=${pcfiledir}/../..
libdir=${prefix}/lib
includedir=${prefix}/include

Name: python_template_cpp
Description: A C++-Python project template
Version: @PROJECT_VERSION@
Libs: -L${libdir} -lpython_template_cpp
Cflags: -I${includedir}
4 changes: 4 additions & 0 deletions cpp/cmake/python_template_cppConfig.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/python_template_cppTargets.cmake")
check_required_components(python_template_cpp)
7 changes: 7 additions & 0 deletions cpp/python-template-cpp/c_api.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include "python_template_cpp/python_template_cpp.h"

#include "python-template-cpp/example.hpp"

uint32_t python_template_cpp_abi_version(void) { return PYTHON_TEMPLATE_CPP_ABI_VERSION; }

int32_t python_template_cpp_add(int32_t i, int32_t j) { return python_template_cpp::add(i, j); }
7 changes: 7 additions & 0 deletions cpp/python-template-cpp/example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include "python-template-cpp/example.hpp"

namespace python_template_cpp {

int add(int i, int j) { return i + j; }

}
7 changes: 7 additions & 0 deletions cpp/python-template-cpp/example.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

namespace python_template_cpp {

int add(int i, int j);

}
3 changes: 0 additions & 3 deletions cpp/python-template-cpp/extension.cpp

This file was deleted.

12 changes: 0 additions & 12 deletions cpp/python-template-cpp/extension.hpp

This file was deleted.

9 changes: 9 additions & 0 deletions cpp/python/extension.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <pybind11/pybind11.h>

#include "python_template_cpp/python_template_cpp.h"

PYBIND11_MODULE(extension, m) {
m.doc() = "pybind11 example plugin";

m.def("add", &python_template_cpp_add, "A function that adds two numbers");
}
41 changes: 36 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[build-system]
requires = [
"hatchling",
"hatch-cpp>=0.5.0",
"hatch-cpp>=0.6.0",
"pybind11",
]
build-backend = "hatchling.build"
Expand Down Expand Up @@ -39,7 +39,7 @@ develop = [
"check-dist",
"cibuildwheel",
"codespell",
"hatch-cpp>=0.5.0",
"hatch-cpp>=0.6.0",
"hatchling",
"mdformat",
"mdformat-myst",
Expand Down Expand Up @@ -76,6 +76,11 @@ filename = "pyproject.toml"
search = 'version = "{current_version}"'
replace = 'version = "{new_version}"'

[[tool.bumpversion.files]]
filename = "cpp/CMakeLists.txt"
search = "VERSION {current_version}"
replace = "VERSION {new_version}"

[tool.cibuildwheel]
build = "cp311-* cp312-* cp313-*"
test-command = "pytest -vvv {project}/python_template_cpp/tests"
Expand All @@ -85,19 +90,26 @@ test-extras = "develop"
manylinux-aarch64-image = "manylinux_2_28"
manylinux-x86_64-image = "manylinux_2_28"
skip = "*i686* *musllinux*"
# Keep the bundled C ABI library where the extension's rpath expects it.
repair-wheel-command = "auditwheel repair --exclude 'libpython_template_cpp.so' -w {dest_dir} {wheel}"

[tool.cibuildwheel.macos]
environment = {MACOSX_DEPLOYMENT_TARGET="11.0"}
archs = "arm64"
repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} --exclude libpython_template_cpp.dylib -w {dest_dir} -v {wheel}"

[tool.cibuildwheel.windows]
archs = "AMD64"
skip = "*win32 *arm64"
# The C ABI library ships inside the wheel; delvewheel must not look for it on PATH.
repair-wheel-command = "delvewheel repair --exclude libpython_template_cpp.dll -w {dest_dir} {wheel}"

[tool.cibuildwheel.pyodide]
test-command = "python -m pytest -vvv --pyargs python_template_cpp.tests"
test-extras = []
test-requires = ["pytest"]
# Emscripten has no shared libraries to split; compile the library in.
environment = {HATCH_CPP_LIBRARY_SOURCE="direct"}

[tool.coverage.run]
branch = true
Expand All @@ -119,9 +131,27 @@ src = "/"

[tool.hatch.build.hooks.hatch-cpp]
verbose = true
libraries = [
{name = "python_template_cpp/extension", sources = ["cpp/python-template-cpp/extension.cpp"], include-dirs = ["cpp"], binding="pybind11"}
]

# libpython_template_cpp: the C ABI library, shipped under python_template_cpp/lib. Its header is in python_template_cpp/include.
[[tool.hatch.build.hooks.hatch-cpp.libraries]]
name = "python_template_cpp/lib/libpython_template_cpp"
sources = ["cpp/python-template-cpp/c_api.cpp", "cpp/python-template-cpp/example.cpp"]
include-dirs = ["cpp", "python_template_cpp/include"]
binding = "generic"
define-macros = ["PYTHON_TEMPLATE_CPP_BUILD"]
extra-compile-args-linux = ["-fvisibility=hidden"]
extra-compile-args-darwin = ["-fvisibility=hidden"]
expected-headers = ["python_template_cpp/include/python_template_cpp/python_template_cpp.h"]
header-symbol-prefix = "python_template_cpp_"
expected-abi-strings = ["PYTHON_TEMPLATE_CPP_ABI_VERSION 1"]

# The pybind11 extension, linked against the library by default (library-source = bundled).
[[tool.hatch.build.hooks.hatch-cpp.libraries]]
name = "python_template_cpp/extension"
sources = ["cpp/python/extension.cpp"]
include-dirs = ["python_template_cpp/include"]
binding = "pybind11"
depends = ["python_template_cpp/lib/libpython_template_cpp"]

[tool.hatch.build.targets.sdist]
exclude = ["*.dll", "*.dylib", "*.so", "*.pyd"]
Expand All @@ -140,6 +170,7 @@ artifacts = [
"python_template_cpp/*.dll",
"python_template_cpp/*.dylib",
"python_template_cpp/*.so",
"python_template_cpp/lib/*",
]
packages = [
"python_template_cpp",
Expand Down
24 changes: 24 additions & 0 deletions python_template_cpp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import os
import os.path

if os.name == "nt" and os.path.isdir(os.path.join(os.path.dirname(__file__), "lib")):
# Windows has no rpath, so the extension finds libpython_template_cpp.dll through this directory.
_DLL_DIRECTORY = os.add_dll_directory(os.path.join(os.path.dirname(__file__), "lib"))

from .extension import *

__version__ = "0.1.0"
Expand All @@ -16,3 +20,23 @@ def bin_path():

def lib_path():
return os.path.abspath(os.path.join(os.path.dirname(__file__), "lib"))


def get_prefix() -> str:
"""The installed package directory, which holds ``include`` and ``lib`` for libpython_template_cpp."""
return os.path.abspath(os.path.dirname(__file__))


def get_include() -> str:
"""The directory holding ``python_template_cpp/python_template_cpp.h``."""
return include_path()


def get_library_dirs() -> list[str]:
"""Directories holding the bundled libpython_template_cpp shared library."""
return [lib_path()]


def get_libraries() -> list[str]:
"""Link names for libpython_template_cpp, for ``-l`` or ``target_link_libraries``."""
return ["libpython_template_cpp" if os.name == "nt" else "python_template_cpp"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* C ABI of libpython_template_cpp. Every exported function is declared here. */
#pragma once

#include <stdint.h>

#define PYTHON_TEMPLATE_CPP_ABI_VERSION 1

#if defined(_WIN32) && defined(PYTHON_TEMPLATE_CPP_BUILD)
#define PYTHON_TEMPLATE_CPP_API __declspec(dllexport)
#elif defined(_WIN32)
#define PYTHON_TEMPLATE_CPP_API __declspec(dllimport)
#else
#define PYTHON_TEMPLATE_CPP_API __attribute__((visibility("default")))
#endif

#ifdef __cplusplus
extern "C" {
#endif

/* The ABI version the library was built with. Compare it with PYTHON_TEMPLATE_CPP_ABI_VERSION. */
PYTHON_TEMPLATE_CPP_API uint32_t python_template_cpp_abi_version(void);

/* Adds two numbers. */
PYTHON_TEMPLATE_CPP_API int32_t python_template_cpp_add(int32_t i, int32_t j);

#ifdef __cplusplus
}
#endif
Loading
Loading