Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
export CMAKE_PREFIX_PATH=${{ github.workspace }}/pybind11/pybind11/share/cmake
export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc)

uv pip install -v -e .
uv pip install -v -e ".[dev]"

py.test

Expand All @@ -92,6 +92,6 @@ jobs:
$env:CMAKE_PREFIX_PATH="${{ github.workspace }}\pybind11\pybind11\share\cmake"
$env:CMAKE_BUILD_PARALLEL_LEVEL = [Environment]::ProcessorCount

uv pip install -v -e .
uv pip install -v -e ".[dev]"

py.test
3 changes: 3 additions & 0 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ jobs:
env:
CIBW_BUILD: "cp313-*"
CIBW_SKIP: "*musllinux* *win32*"
CIBW_TEST_COMMAND: pytest {project}/tests
CIBW_TEST_EXTRAS: dev

CIBW_BEFORE_BUILD: rm -rf {project}/build
CIBW_BEFORE_ALL_WINDOWS: bash tools\cibw_before.sh
CIBW_ENVIRONMENT_WINDOWS: "CMAKE_PREFIX_PATH=D:/a/pycpp/pycpp/pybind11/pybind11/share/cmake"

Expand Down
17 changes: 14 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
cmake_minimum_required(VERSION 3.4...3.18)


# required to find dependencies
if(NOT DEFINED ENV{VCPKG_HOST_TRIPLET})
message(FATAL_ERROR "Environment variable 'VCPKG_HOST_TRIPLET' is not set")
endif()

project(cppcore)

set(VCPKG_HOST_TRIPLET $ENV{VCPKG_HOST_TRIPLET})
Expand Down Expand Up @@ -27,9 +33,14 @@ find_library(NLOPT_LIB nlopt PATHS "${NLOPT_DIR_LIBRARY_DIR}")

pybind11_add_module(cppcore src/cpp/main.cpp)

include_directories(${CMAKE_SOURCE_DIR}/src/cpp ${EIGEN_PATH} ${GSL_INCLUDE_DIR} ${NLOPT_DIR_INCLUDE_DIR})
target_include_directories(cppcore PRIVATE ${GSL_INCLUDE_DIR} ${NLOPT_DIR_INCLUDE_DIR})
target_link_libraries(cppcore PRIVATE ${GSL_LIB} ${GSLCBLAS_LIB} ${NLOPT_LIB})
target_include_directories(cppcore PRIVATE ${CMAKE_SOURCE_DIR}/src/cpp)
target_include_directories(cppcore PRIVATE ${EIGEN_PATH})
target_include_directories(cppcore PRIVATE ${GSL_INCLUDE_DIR})
target_include_directories(cppcore PRIVATE ${NLOPT_DIR_INCLUDE_DIR})

target_link_libraries(cppcore PRIVATE ${NLOPT_LIB})
target_link_libraries(cppcore PRIVATE ${GSL_LIB})
target_link_libraries(cppcore PRIVATE ${GSLCBLAS_LIB})

target_compile_definitions(
cppcore
Expand Down
103 changes: 96 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
# cppcore for pybind11
# A Python Package with a C++ extension

MacOS installation:
Explorations in how to use Python, C++, and bindings between the two systems. Build compiled code in multiple environments via GitHub Actions. Key technologies used include [pybind11](https://pybind11.readthedocs.io/en/stable/), [vcpkg](https://github.com/microsoft/vcpkg), and [cibuildwheel](https://cibuildwheel.readthedocs.io/en/stable/).

The project does the following:

- Builds a simple C++ package
- Includes third party C++ packages installed via the vcpkg package manager
- Builds a python interface to the C++ package
- Has some tests to ensure the C++ package works correctly from python
- Automatically builds the packages on Mac, Linux, and Windows

## Quickstart

For this particular example, we install 3 scientific C++ packages: as dependencies for our library:

* [Eigen3](https://vcpkg.io/en/package/eigen3.html) (headers only template library)
* [GNU Scientific Library](https://vcpkg.io/en/package/gsl.html)
* [NLopt](https://vcpkg.io/en/package/nlopt.html)

Though you could customize this template as needed to install other packages from vcpkg.

### MacOS/Linux:

```bash
# check out a single commit from a git repository
mkdir vcpkg
cd vcpkg
git init
Expand All @@ -11,14 +32,82 @@ git fetch --depth 1 origin c9c17dcea3016bc241df0422e82b8aea212dcb93
git checkout FETCH_HEAD
cd ..

export VCPKG_HOST_TRIPLET=arm64-osx-dynamic
# build static dependencies; use the appropriate triplet (e.g., x64-linux, arm64-osx)
export VCPKG_HOST_TRIPLET="arm64-osx"
./vcpkg/bootstrap-vcpkg.sh
./vcpkg/vcpkg install --host-triplet=$VCPKG_HOST_TRIPLET


# install pybind11 at the root so for our build phase
uv pip install pybind11==3.0.0 --target=./pybind11
export VCPKG_HOST_TRIPLET=arm64-osx-dynamic
export CMAKE_PREFIX_PATH=/Users/andyshapiro/dev/pycpp/pybind11/pybind11/share/cmake

# set environment variables for building python extension
export CMAKE_PREFIX_PATH="$(readlink -f ./pybind11/pybind11/share/cmake)"
export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc)
uv pip install -e . && pytest

echo "$VCPKG_HOST_TRIPLET"
echo "$CMAKE_PREFIX_PATH"
echo "$CMAKE_BUILD_PARALLEL_LEVEL"

# create a new python virtual environment
uv venv --python=3.13
source .venv/bin/activate

# compile and install the package
uv pip install -v -e ".[dev]"

# generate typing stubs for C++ file
stubgen -p demo.cppcore -o src

# test
pytest
```

### Windows:

On Windows, install Visual Studio (2019 or 2022) and the C++ build packages (this is not related to Visual Studio Code). All commands below must be run within a Developer PowerShell for VS environment in order to use the appropriate compiler toolchain.

We'll build static dependencies ([x64-windows-static-md](https://learn.microsoft.com/en-us/vcpkg/users/platforms/windows)) for C++ to make them easier to add to our custom Python module.

```ps1
# check out a single commit from a git repository
mkdir vcpkg
cd vcpkg
git init
git remote add origin git@github.com:microsoft/vcpkg.git
git fetch --depth 1 origin c9c17dcea3016bc241df0422e82b8aea212dcb93
git checkout FETCH_HEAD
cd ..

# build static dependencies
$env:VCPKG_HOST_TRIPLET="x64-windows-static-md"
.\vcpkg\bootstrap-vcpkg.bat
.\vcpkg\vcpkg install --host-triplet="$env:VCPKG_HOST_TRIPLET"

# install pybind11 at the root so for our build phase
uv pip install pybind11==3.0.0 --target=./pybind11

# set environment variables for building python extension
$env:CMAKE_PREFIX_PATH=Resolve-Path "./pybind11/pybind11/share/cmake" | Select-Object -ExpandProperty Path
$env:CMAKE_BUILD_PARALLEL_LEVEL=[Environment]::ProcessorCount

echo "$env:VCPKG_HOST_TRIPLET"
echo "$env:CMAKE_PREFIX_PATH"
echo "$env:CMAKE_BUILD_PARALLEL_LEVEL"

# create a new python virtual environment
uv venv --python=3.13
.venv/Scripts/activate

# compile and install the package
uv pip install -v -e ".[dev]"

# generate typing stubs for C++ file
stubgen -p demo.cppcore -o src

# test
pytest
```

## Distributable Python Wheels

See the GitHub Actions.
18 changes: 8 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ authors = [
requires-python = ">=3.13"
dependencies = [
"numpy",
"pytest",
]

[project.optional-dependencies]
dev = [
"pytest~=8.4.1",
"mypy~=1.17.1",
]

[tool.setuptools.dynamic]
Expand All @@ -20,18 +25,11 @@ include = ["demo*"]

[build-system]
requires = [
"setuptools",
"pybind11"
"setuptools",
"pybind11~=3.0.0"
]
build-backend = "setuptools.build_meta"

[tool.pytest.ini_options]
testpaths = "tests"
python_files = ["test_*.py"]

[tool.cibuildwheel]
test-command = "pytest {project}/tests"
test-extras = ["test"]
test-skip = ["*universal2:arm64"]
# Setuptools bug causes collision between pypy and cpython artifacts
before-build = "rm -rf {project}/build"
16 changes: 16 additions & 0 deletions src/demo/cppcore.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import collections.abc
import numpy
import numpy.typing
import typing

def add(arg0: typing.SupportsInt, arg1: typing.SupportsInt) -> int: ...
def eigen_matmul(
arg0: typing.Annotated[numpy.typing.ArrayLike, numpy.float64],
arg1: typing.Annotated[numpy.typing.ArrayLike, numpy.float64],
) -> numpy.typing.NDArray[numpy.float64]: ...
def gsl_bessel(arg0: typing.SupportsFloat) -> float: ...
def nlopt_optimize(
lower_bounds: collections.abc.Sequence[typing.SupportsFloat],
upper_bounds: collections.abc.Sequence[typing.SupportsFloat],
) -> list[float]: ...
def subtract(arg0: typing.SupportsInt, arg1: typing.SupportsInt) -> int: ...