Skip to content

Commit

Permalink
Merge pull request #12 from python-project-templates/tkp/pyd
Browse files Browse the repository at this point in the history
Add nanobind and pybind support
  • Loading branch information
timkpaine authored Jan 10, 2025
2 parents 25c7895 + 0b79dc4 commit 801e8b6
Show file tree
Hide file tree
Showing 20 changed files with 166 additions and 108 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A simple, extensible C++ build plugin for [hatch](https://hatch.pypa.io/latest/)
```toml
[tool.hatch.build.hooks.hatch-cpp]
libraries = [
{name = "basic_project/extension", sources = ["cpp/basic-project/basic.cpp"], include-dirs = ["cpp"]}
{name = "project/extension", sources = ["cpp/project/basic.cpp"], include-dirs = ["cpp"]}
]
```

Expand Down
42 changes: 34 additions & 8 deletions hatch_cpp/structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
BuildType = Literal["debug", "release"]
CompilerToolchain = Literal["gcc", "clang", "msvc"]
Language = Literal["c", "c++"]
Binding = Literal["cpython", "pybind11", "nanobind"]
Platform = Literal["linux", "darwin", "win32"]
PlatformDefaults = {
"linux": {"CC": "gcc", "CXX": "g++", "LD": "ld"},
Expand All @@ -33,12 +34,18 @@ class HatchCppLibrary(BaseModel):
name: str
sources: List[str]
language: Language = "c++"

binding: Binding = "cpython"
std: Optional[str] = None

include_dirs: List[str] = Field(default_factory=list, alias="include-dirs")
library_dirs: List[str] = Field(default_factory=list, alias="library-dirs")
libraries: List[str] = Field(default_factory=list)

extra_compile_args: List[str] = Field(default_factory=list, alias="extra-compile-args")
extra_link_args: List[str] = Field(default_factory=list, alias="extra-link-args")
extra_objects: List[str] = Field(default_factory=list, alias="extra-objects")

define_macros: List[str] = Field(default_factory=list, alias="define-macros")
undef_macros: List[str] = Field(default_factory=list, alias="undef-macros")

Expand Down Expand Up @@ -82,29 +89,51 @@ def default() -> HatchCppPlatform:

def get_compile_flags(self, library: HatchCppLibrary, build_type: BuildType = "release") -> str:
flags = ""

# Python.h
library.include_dirs.append(get_path("include"))

if library.binding == "pybind11":
import pybind11

library.include_dirs.append(pybind11.get_include())
if not library.std:
library.std = "c++11"
elif library.binding == "nanobind":
import nanobind

library.include_dirs.append(nanobind.include_dir())
if not library.std:
library.std = "c++17"
library.sources.append(str(Path(nanobind.include_dir()).parent / "src" / "nb_combined.cpp"))
library.include_dirs.append(str((Path(nanobind.include_dir()).parent / "ext" / "robin_map" / "include")))

if self.toolchain == "gcc":
flags = f"-I{get_path('include')}"
flags += " " + " ".join(f"-I{d}" for d in library.include_dirs)
flags += " -fPIC"
flags += " " + " ".join(library.extra_compile_args)
flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros)
flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros)
if library.std:
flags += f" -std={library.std}"
elif self.toolchain == "clang":
flags = f"-I{get_path('include')} "
flags += " ".join(f"-I{d}" for d in library.include_dirs)
flags += " -fPIC"
flags += " " + " ".join(library.extra_compile_args)
flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros)
flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros)
if library.std:
flags += f" -std={library.std}"
elif self.toolchain == "msvc":
flags = f"/I{get_path('include')} "
flags += " ".join(f"/I{d}" for d in library.include_dirs)
flags += " " + " ".join(library.extra_compile_args)
flags += " " + " ".join(library.extra_link_args)
flags += " " + " ".join(library.extra_objects)
flags += " " + " ".join(f"/D{macro}" for macro in library.define_macros)
flags += " " + " ".join(f"/U{macro}" for macro in library.undef_macros)
flags += " /EHsc /DWIN32"
if library.std:
flags += f" /std:{library.std}"
# clean
while flags.count(" "):
flags = flags.replace(" ", " ")
Expand Down Expand Up @@ -142,7 +171,6 @@ def get_link_flags(self, library: HatchCppLibrary, build_type: BuildType = "rele
flags += " " + " ".join(library.extra_link_args)
flags += " " + " ".join(library.extra_objects)
flags += " /LD"
flags += f" /Fo:{library.name}.obj"
flags += f" /Fe:{library.name}.pyd"
flags += " /link /DLL"
if (Path(executable).parent / "libs").exists():
Expand Down Expand Up @@ -178,10 +206,8 @@ def execute(self):

def cleanup(self):
if self.platform.platform == "win32":
for library in self.libraries:
temp_obj = Path(f"{library.name}.obj")
if temp_obj.exists():
temp_obj.unlink()
for temp_obj in Path(".").glob("*.obj"):
temp_obj.unlink()


class HatchCppBuildConfig(BaseModel):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "basic-project/basic.hpp"
#include "project/basic.hpp"

PyObject* hello(PyObject*, PyObject*) {
return PyUnicode_FromString("A string");
Expand Down
40 changes: 6 additions & 34 deletions hatch_cpp/tests/test_project_basic/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,50 +14,22 @@ dependencies = [

[tool.hatch.build]
artifacts = [
"basic_project/*.dll",
"basic_project/*.dylib",
"basic_project/*.so",
"project/*.dll",
"project/*.dylib",
"project/*.so",
]

[tool.hatch.build.sources]
src = "/"

[tool.hatch.build.targets.sdist]
packages = ["basic_project"]
packages = ["project"]

[tool.hatch.build.targets.wheel]
packages = ["basic_project"]
packages = ["project"]

[tool.hatch.build.hooks.hatch-cpp]
verbose = true
libraries = [
{name = "basic_project/extension", sources = ["cpp/basic-project/basic.cpp"], include-dirs = ["cpp"]}
{name = "project/extension", sources = ["cpp/project/basic.cpp"], include-dirs = ["cpp"]}
]

# build-function = "hatch_cpp.cpp_builder"

# [tool.hatch.build.hooks.defaults]
# build-type = "release"

# [tool.hatch.build.hooks.env-vars]
# TODO: these will all be available via
# CLI after https://github.com/pypa/hatch/pull/1743
# e.g. --hatch-cpp-build-type=debug
# build-type = "BUILD_TYPE"
# ccache = "USE_CCACHE"
# manylinux = "MANYLINUX"
# vcpkg = "USE_VCPKG"

# [tool.hatch.build.hooks.cmake]

# [tool.hatch.build.hooks.vcpkg]
# triplets = {linux="x64-linux", macos="x64-osx", windows="x64-windows-static-md"}
# clone = true
# update = true

# [tool.hatch.build.hooks.hatch-cpp.build-kwargs]
# path = "cpp"

[tool.pytest.ini_options]
asyncio_mode = "strict"
testpaths = "basic_project/tests"
2 changes: 2 additions & 0 deletions hatch_cpp/tests/test_project_nanobind/cpp/project/basic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#include "project/basic.hpp"

7 changes: 7 additions & 0 deletions hatch_cpp/tests/test_project_nanobind/cpp/project/basic.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once
#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h>

NB_MODULE(extension, m) {
m.def("hello", []() { return "A string"; });
}
35 changes: 35 additions & 0 deletions hatch_cpp/tests/test_project_nanobind/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[build-system]
requires = ["hatchling>=1.20"]
build-backend = "hatchling.build"

[project]
name = "hatch-cpp-test-project-basic"
description = "Basic test project for hatch-cpp"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = [
"hatchling>=1.20",
"hatch-cpp",
]

[tool.hatch.build]
artifacts = [
"project/*.dll",
"project/*.dylib",
"project/*.so",
]

[tool.hatch.build.sources]
src = "/"

[tool.hatch.build.targets.sdist]
packages = ["project"]

[tool.hatch.build.targets.wheel]
packages = ["project"]

[tool.hatch.build.hooks.hatch-cpp]
verbose = true
libraries = [
{name = "project/extension", sources = ["cpp/project/basic.cpp"], include-dirs = ["cpp"], binding = "nanobind"},
]
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "basic-project/basic.hpp"
#include "project/basic.hpp"

PyObject* hello(PyObject*, PyObject*) {
return PyUnicode_FromString("A string");
Expand Down
Empty file.
40 changes: 6 additions & 34 deletions hatch_cpp/tests/test_project_override_classes/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,24 @@ dependencies = [

[tool.hatch.build]
artifacts = [
"basic_project/*.dll",
"basic_project/*.dylib",
"basic_project/*.so",
"project/*.dll",
"project/*.dylib",
"project/*.so",
]

[tool.hatch.build.sources]
src = "/"

[tool.hatch.build.targets.sdist]
packages = ["basic_project"]
packages = ["project"]

[tool.hatch.build.targets.wheel]
packages = ["basic_project"]
packages = ["project"]

[tool.hatch.build.hooks.hatch-cpp]
build-config-class = "hatch_cpp.HatchCppBuildConfig"
build-plan-class = "hatch_cpp.HatchCppBuildPlan"
verbose = true
libraries = [
{name = "basic_project/extension", sources = ["cpp/basic-project/basic.cpp"], include-dirs = ["cpp"]}
{name = "project/extension", sources = ["cpp/project/basic.cpp"], include-dirs = ["cpp"]}
]

# build-function = "hatch_cpp.cpp_builder"

# [tool.hatch.build.hooks.defaults]
# build-type = "release"

# [tool.hatch.build.hooks.env-vars]
# TODO: these will all be available via
# CLI after https://github.com/pypa/hatch/pull/1743
# e.g. --hatch-cpp-build-type=debug
# build-type = "BUILD_TYPE"
# ccache = "USE_CCACHE"
# manylinux = "MANYLINUX"
# vcpkg = "USE_VCPKG"

# [tool.hatch.build.hooks.cmake]

# [tool.hatch.build.hooks.vcpkg]
# triplets = {linux="x64-linux", macos="x64-osx", windows="x64-windows-static-md"}
# clone = true
# update = true

# [tool.hatch.build.hooks.hatch-cpp.build-kwargs]
# path = "cpp"

[tool.pytest.ini_options]
asyncio_mode = "strict"
testpaths = "basic_project/tests"
6 changes: 6 additions & 0 deletions hatch_cpp/tests/test_project_pybind/cpp/project/basic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "project/basic.hpp"

std::string hello() {
return "A string";
}

9 changes: 9 additions & 0 deletions hatch_cpp/tests/test_project_pybind/cpp/project/basic.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once
#include <pybind11/pybind11.h>
#include <string>

std::string hello();

PYBIND11_MODULE(extension, m) {
m.def("hello", &hello);
}
Empty file.
35 changes: 35 additions & 0 deletions hatch_cpp/tests/test_project_pybind/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[build-system]
requires = ["hatchling>=1.20"]
build-backend = "hatchling.build"

[project]
name = "hatch-cpp-test-project-basic"
description = "Basic test project for hatch-cpp"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = [
"hatchling>=1.20",
"hatch-cpp",
]

[tool.hatch.build]
artifacts = [
"project/*.dll",
"project/*.dylib",
"project/*.so",
]

[tool.hatch.build.sources]
src = "/"

[tool.hatch.build.targets.sdist]
packages = ["project"]

[tool.hatch.build.targets.wheel]
packages = ["project"]

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

0 comments on commit 801e8b6

Please sign in to comment.