├── .github ├── dependabot.yml └── workflows │ ├── pip.yml │ └── wheels.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── pyproject.toml ├── src ├── nanobind_example │ └── __init__.py └── nanobind_example_ext.cpp └── tests └── test_basic.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | groups: 9 | actions: 10 | patterns: 11 | - "*" 12 | -------------------------------------------------------------------------------- /.github/workflows/pip.yml: -------------------------------------------------------------------------------- 1 | name: Pip 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | build: 12 | name: Build with Pip 13 | runs-on: ${{ matrix.platform }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | platform: [windows-latest, macos-latest, ubuntu-latest] 18 | python-version: ["3.8", "3.12"] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - uses: actions/setup-python@v5 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | 27 | - name: Set min macOS version 28 | if: runner.os == 'macOS' 29 | run: | 30 | echo "MACOSX_DEPLOYMENT_TARGET=10.14" >> $GITHUB_ENV 31 | 32 | - name: Build and install 33 | run: | 34 | python -m pip install pytest 35 | pip install --verbose . 36 | 37 | - name: Test 38 | run: python -m pytest 39 | -------------------------------------------------------------------------------- /.github/workflows/wheels.yml: -------------------------------------------------------------------------------- 1 | name: Wheels 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | release: 10 | types: 11 | - published 12 | 13 | jobs: 14 | build_sdist: 15 | name: Build SDist 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | submodules: true 21 | 22 | - name: Build SDist 23 | run: pipx run build --sdist 24 | 25 | - name: Check metadata 26 | run: pipx run twine check dist/* 27 | 28 | - uses: actions/upload-artifact@v4 29 | with: 30 | name: dist-sdist 31 | path: dist/*.tar.gz 32 | 33 | 34 | build_wheels: 35 | name: Wheels on ${{ matrix.os }} 36 | runs-on: ${{ matrix.os }} 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | os: [ubuntu-latest, macos-13, macos-14, windows-latest] 41 | 42 | steps: 43 | - uses: actions/checkout@v4 44 | with: 45 | submodules: true 46 | 47 | - uses: pypa/cibuildwheel@v2.22 48 | 49 | - name: Verify clean directory 50 | run: git diff --exit-code 51 | shell: bash 52 | 53 | - name: Upload wheels 54 | uses: actions/upload-artifact@v4 55 | with: 56 | path: wheelhouse/*.whl 57 | name: dist-${{ matrix.os }} 58 | 59 | upload_all: 60 | name: Upload if release 61 | needs: [build_wheels, build_sdist] 62 | runs-on: ubuntu-latest 63 | if: github.event_name == 'release' && github.event.action == 'published' 64 | 65 | steps: 66 | - uses: actions/setup-python@v5 67 | - uses: actions/download-artifact@v4 68 | with: 69 | path: dist 70 | pattern: dist-* 71 | merge-multiple: true 72 | 73 | - uses: pypa/gh-action-pypi-publish@release/v1 74 | with: 75 | user: __token__ 76 | password: ${{ secrets.pypi_password }} 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nanobind_test.egg-info 2 | _skbuild 3 | /dist 4 | __pycache__ 5 | build 6 | *.pyd 7 | *.egg-info 8 | .vscode 9 | .vs 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.26) 2 | 3 | project(nanobind_example LANGUAGES CXX) 4 | 5 | if (NOT SKBUILD) 6 | message(WARNING "\ 7 | This CMake file is meant to be executed using 'scikit-build'. Running 8 | it directly will almost certainly not produce the desired result. If 9 | you are a user trying to install this package, please use the command 10 | below, which will install all necessary build dependencies, compile 11 | the package in an isolated environment, and then install it. 12 | ===================================================================== 13 | $ pip install . 14 | ===================================================================== 15 | If you are a software developer, and this is your own package, then 16 | it is usually much more efficient to install the build dependencies 17 | in your environment once and use the following command that avoids 18 | a costly creation of a new virtual environment at every compilation: 19 | ===================================================================== 20 | $ pip install nanobind scikit-build-core[pyproject] 21 | $ pip install --no-build-isolation -ve . 22 | ===================================================================== 23 | You may optionally add -Ceditable.rebuild=true to auto-rebuild when 24 | the package is imported. Otherwise, you need to re-run the above 25 | after editing C++ files.") 26 | endif() 27 | 28 | # Try to import all Python components potentially needed by nanobind 29 | find_package(Python 3.8 30 | REQUIRED COMPONENTS Interpreter Development.Module 31 | OPTIONAL_COMPONENTS Development.SABIModule) 32 | 33 | # Import nanobind through CMake's find_package mechanism 34 | find_package(nanobind CONFIG REQUIRED) 35 | 36 | # We are now ready to compile the actual extension module 37 | nanobind_add_module( 38 | # Name of the extension 39 | nanobind_example_ext 40 | 41 | # Target the stable ABI for Python 3.12+, which reduces 42 | # the number of binary wheels that must be built. This 43 | # does nothing on older Python versions 44 | STABLE_ABI 45 | 46 | # Build libnanobind statically and merge it into the 47 | # extension (which itself remains a shared library) 48 | # 49 | # If your project builds multiple extensions, you can 50 | # replace this flag by NB_SHARED to conserve space by 51 | # reusing a shared libnanobind across libraries 52 | NB_STATIC 53 | 54 | # Source code goes here 55 | src/nanobind_example_ext.cpp 56 | ) 57 | 58 | # Install directive for scikit-build-core 59 | install(TARGETS nanobind_example_ext LIBRARY DESTINATION nanobind_example) 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Wenzel Jakob , All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nanobind_example 2 | ================ 3 | 4 | | CI | status | 5 | |----------------------|--------| 6 | | pip builds | [![Pip Action Status][actions-pip-badge]][actions-pip-link] | 7 | | wheels | [![Wheel Action Status][actions-wheels-badge]][actions-wheels-link] | 8 | 9 | [actions-pip-link]: https://github.com/wjakob/nanobind_example/actions?query=workflow%3APip 10 | [actions-pip-badge]: https://github.com/wjakob/nanobind_example/workflows/Pip/badge.svg 11 | [actions-wheels-link]: https://github.com/wjakob/nanobind_example/actions?query=workflow%3AWheels 12 | [actions-wheels-badge]: https://github.com/wjakob/nanobind_example/workflows/Wheels/badge.svg 13 | 14 | 15 | This repository contains a tiny project showing how to create C++ bindings 16 | using [nanobind](https://github.com/wjakob/nanobind) and 17 | [scikit-build-core](https://scikit-build-core.readthedocs.io/en/latest/index.html). It 18 | was derived from the corresponding _pybind11_ [example 19 | project](https://github.com/pybind/scikit_build_example/) developed by 20 | [@henryiii](https://github.com/henryiii). 21 | 22 | Furthermore, the [bazel](https://github.com/wjakob/nanobind_example/tree/bazel) branch contains an example 23 | on how to build nanobind bindings extensions with Bazel using the [nanobind-bazel](https://github.com/nicholasjng/nanobind-bazel/) project. 24 | 25 | Installation 26 | ------------ 27 | 28 | 1. Clone this repository 29 | 2. Run `pip install ./nanobind_example` 30 | 31 | Afterwards, you should be able to issue the following commands (shown in an 32 | interactive Python session): 33 | 34 | ```pycon 35 | >>> import nanobind_example 36 | >>> nanobind_example.add(1, 2) 37 | 3 38 | ``` 39 | 40 | CI Examples 41 | ----------- 42 | 43 | The `.github/workflows` directory contains two continuous integration workflows 44 | for GitHub Actions. The first one (`pip`) runs automatically after each commit 45 | and ensures that packages can be built successfully and that tests pass. 46 | 47 | The `wheels` workflow uses 48 | [cibuildwheel](https://cibuildwheel.readthedocs.io/en/stable/) to automatically 49 | produce binary wheels for a large variety of platforms. If a `pypi_password` 50 | token is provided using GitHub Action's _secrets_ feature, this workflow can 51 | even automatically upload packages on PyPI. 52 | 53 | 54 | License 55 | ------- 56 | 57 | _nanobind_ and this example repository are both provided under a BSD-style 58 | license that can be found in the [LICENSE](./LICENSE) file. By using, 59 | distributing, or contributing to this project, you agree to the terms and 60 | conditions of this license. 61 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["scikit-build-core >=0.10", "nanobind >=1.3.2"] 3 | build-backend = "scikit_build_core.build" 4 | 5 | [project] 6 | name = "nanobind-example" 7 | version = "0.0.1" 8 | description = "An example minimal project that compiles bindings using nanobind and scikit-build" 9 | readme = "README.md" 10 | requires-python = ">=3.8" 11 | authors = [ 12 | { name = "Wenzel Jakob", email = "wenzel.jakob@epfl.ch" }, 13 | ] 14 | classifiers = [ 15 | "License :: OSI Approved :: BSD License", 16 | ] 17 | 18 | [project.urls] 19 | Homepage = "https://github.com/wjakob/nanobind_example" 20 | 21 | 22 | [tool.scikit-build] 23 | # Protect the configuration against future changes in scikit-build-core 24 | minimum-version = "build-system.requires" 25 | 26 | # Setuptools-style build caching in a local directory 27 | build-dir = "build/{wheel_tag}" 28 | 29 | # Build stable ABI wheels for CPython 3.12+ 30 | wheel.py-api = "cp312" 31 | 32 | [tool.cibuildwheel] 33 | # Necessary to see build output from the actual compilation 34 | build-verbosity = 1 35 | 36 | # Run pytest to ensure that the package was correctly built 37 | test-command = "pytest {project}/tests" 38 | test-requires = "pytest" 39 | 40 | # Don't test Python 3.8 wheels on macOS/arm64 41 | test-skip="cp38-macosx_*:arm64" 42 | 43 | # Needed for full C++17 support 44 | [tool.cibuildwheel.macos.environment] 45 | MACOSX_DEPLOYMENT_TARGET = "10.14" 46 | -------------------------------------------------------------------------------- /src/nanobind_example/__init__.py: -------------------------------------------------------------------------------- 1 | from .nanobind_example_ext import add, __doc__ 2 | -------------------------------------------------------------------------------- /src/nanobind_example_ext.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace nb = nanobind; 4 | 5 | using namespace nb::literals; 6 | 7 | NB_MODULE(nanobind_example_ext, m) { 8 | m.doc() = "This is a \"hello world\" example with nanobind"; 9 | m.def("add", [](int a, int b) { return a + b; }, "a"_a, "b"_a); 10 | } 11 | -------------------------------------------------------------------------------- /tests/test_basic.py: -------------------------------------------------------------------------------- 1 | import nanobind_example as m 2 | 3 | def test_add(): 4 | assert m.add(1, 2) == 3 5 | --------------------------------------------------------------------------------