From 6f3b89377295994944a28cf892c15a80bd909999 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 9 Nov 2025 17:46:59 -0500 Subject: [PATCH] Add support for hatch-build --- README.md | 14 ++++++ hatch_cpp/plugin.py | 4 ++ hatch_cpp/tests/test_hatch_build.py | 46 +++++++++++++++++++ .../tests/test_project_cmake/pyproject.toml | 2 +- .../cpp/project/basic.cpp | 2 + .../cpp/project/basic.hpp | 7 +++ .../project/__init__.py | 0 .../test_project_hatch_build/pyproject.toml | 36 +++++++++++++++ hatch_cpp/toolchains/cmake.py | 6 +-- pyproject.toml | 6 +-- 10 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 hatch_cpp/tests/test_hatch_build.py create mode 100644 hatch_cpp/tests/test_project_hatch_build/cpp/project/basic.cpp create mode 100644 hatch_cpp/tests/test_project_hatch_build/cpp/project/basic.hpp create mode 100644 hatch_cpp/tests/test_project_hatch_build/project/__init__.py create mode 100644 hatch_cpp/tests/test_project_hatch_build/pyproject.toml diff --git a/README.md b/README.md index 31715dd..3d69c21 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,20 @@ cmake_env_args = {} # env-specific cmake args to pass include_flags = {} # include flags to pass -D ``` +### CLI + +`hatch-cpp` is integrated with [`hatch-build`](https://github.com/python-project-templates/hatch-build) to allow easy configuration of options via command line: + +```bash +hatch-build \ + -- \ + --verbose \ + --platform linux \ + --vcpkg.vcpkg a/path/to/vcpkg.json \ + --libraries.0.binding pybind11 \ + --libraries.0.include-dirs cpp,another-dir +``` + ### Environment Variables `hatch-cpp` will respect standard environment variables for compiler control. diff --git a/hatch_cpp/plugin.py b/hatch_cpp/plugin.py index f1a32b0..3dfaec2 100644 --- a/hatch_cpp/plugin.py +++ b/hatch_cpp/plugin.py @@ -7,6 +7,7 @@ from sys import platform as sys_platform, version_info from typing import Any +from hatch_build import parse_extra_args_model from hatchling.builders.hooks.plugin.interface import BuildHookInterface from .config import HatchCppBuildConfig, HatchCppBuildPlan @@ -52,6 +53,9 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None: # Instantiate builder build_plan = build_plan_class(**config.model_dump()) + # Parse override args + parse_extra_args_model(build_plan) + # Generate commands build_plan.generate() diff --git a/hatch_cpp/tests/test_hatch_build.py b/hatch_cpp/tests/test_hatch_build.py new file mode 100644 index 0000000..b71e185 --- /dev/null +++ b/hatch_cpp/tests/test_hatch_build.py @@ -0,0 +1,46 @@ +from os import listdir +from pathlib import Path +from shutil import rmtree +from subprocess import check_call +from sys import modules, path, platform + + +class TestHatchBuild: + def test_hatch_build(self): + project = "test_project_hatch_build" + + rmtree(f"hatch_cpp/tests/{project}/project/extension.so", ignore_errors=True) + rmtree(f"hatch_cpp/tests/{project}/project/extension.pyd", ignore_errors=True) + modules.pop("project", None) + modules.pop("project.extension", None) + + # compile + check_call( + [ + "hatch-build", + "--hooks-only", + "--", + "--libraries.0.name=project/extension", + "--libraries.0.sources=cpp/project/basic.cpp", + "--libraries.0.include-dirs=cpp", + "--libraries.0.binding=nanobind", + ], + cwd=f"hatch_cpp/tests/{project}", + ) + + # assert built + + if project == "test_project_limited_api" and platform != "win32": + assert "extension.abi3.so" in listdir(f"hatch_cpp/tests/{project}/project") + else: + if platform == "win32": + assert "extension.pyd" in listdir(f"hatch_cpp/tests/{project}/project") + else: + assert "extension.so" in listdir(f"hatch_cpp/tests/{project}/project") + + # import + here = Path(__file__).parent / project + path.insert(0, str(here)) + import project.extension + + assert project.extension.hello() == "A string" diff --git a/hatch_cpp/tests/test_project_cmake/pyproject.toml b/hatch_cpp/tests/test_project_cmake/pyproject.toml index 8ad530e..51e9a66 100644 --- a/hatch_cpp/tests/test_project_cmake/pyproject.toml +++ b/hatch_cpp/tests/test_project_cmake/pyproject.toml @@ -29,7 +29,7 @@ packages = ["project"] packages = ["project"] [tool.hatch.build.hooks.hatch-cpp] -verbose = true +verbose = false [tool.hatch.build.hooks.hatch-cpp.cmake] root = "CMakeLists.txt" diff --git a/hatch_cpp/tests/test_project_hatch_build/cpp/project/basic.cpp b/hatch_cpp/tests/test_project_hatch_build/cpp/project/basic.cpp new file mode 100644 index 0000000..2ac7d56 --- /dev/null +++ b/hatch_cpp/tests/test_project_hatch_build/cpp/project/basic.cpp @@ -0,0 +1,2 @@ +#include "project/basic.hpp" + diff --git a/hatch_cpp/tests/test_project_hatch_build/cpp/project/basic.hpp b/hatch_cpp/tests/test_project_hatch_build/cpp/project/basic.hpp new file mode 100644 index 0000000..1afa022 --- /dev/null +++ b/hatch_cpp/tests/test_project_hatch_build/cpp/project/basic.hpp @@ -0,0 +1,7 @@ +#pragma once +#include +#include + +NB_MODULE(extension, m) { + m.def("hello", []() { return "A string"; }); +} diff --git a/hatch_cpp/tests/test_project_hatch_build/project/__init__.py b/hatch_cpp/tests/test_project_hatch_build/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hatch_cpp/tests/test_project_hatch_build/pyproject.toml b/hatch_cpp/tests/test_project_hatch_build/pyproject.toml new file mode 100644 index 0000000..e53ef12 --- /dev/null +++ b/hatch_cpp/tests/test_project_hatch_build/pyproject.toml @@ -0,0 +1,36 @@ +[build-system] +requires = ["hatchling>=1.20"] +build-backend = "hatchling.build" + +[project] +name = "hatch-cpp-test-project-nanobind" +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"}, + {name = "wrong", sources = ["wrong"], include-dirs = ["wrong"], binding = "generic"}, +] diff --git a/hatch_cpp/toolchains/cmake.py b/hatch_cpp/toolchains/cmake.py index 1b349af..a5d2f15 100644 --- a/hatch_cpp/toolchains/cmake.py +++ b/hatch_cpp/toolchains/cmake.py @@ -3,7 +3,7 @@ from os import environ from pathlib import Path from sys import version_info -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, Union from pydantic import BaseModel, Field @@ -23,7 +23,7 @@ class HatchCppCmakeConfiguration(BaseModel): - root: Path + root: Optional[Path] = None build: Path = Field(default_factory=lambda: Path("build")) install: Optional[Path] = Field(default=None) @@ -31,7 +31,7 @@ class HatchCppCmakeConfiguration(BaseModel): cmake_args: Dict[str, str] = Field(default_factory=dict) cmake_env_args: Dict[Platform, Dict[str, str]] = Field(default_factory=dict) - include_flags: Optional[Dict[str, Any]] = Field(default=None) + include_flags: Optional[Dict[str, Union[str, int, float, bool]]] = Field(default=None) def generate(self, config) -> Dict[str, Any]: commands = [] diff --git a/pyproject.toml b/pyproject.toml index 28b403d..35502e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ description = "Hatch plugin for C++ builds" readme = "README.md" license = { text = "Apache-2.0" } version = "0.1.9" -requires-python = ">=3.9" +requires-python = ">=3.10" keywords = [ "hatch", "python", @@ -25,7 +25,6 @@ classifiers = [ "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -35,6 +34,7 @@ classifiers = [ dependencies = [ "hatchling>=1.20", + "hatch-build>=0.4,<0.5", "pydantic", ] @@ -45,7 +45,7 @@ develop = [ "check-manifest", "codespell>=2.4,<2.5", "hatchling", - "hatch-build", + "hatch-build>=0.3.2", "mdformat>=0.7.22,<1.1", "mdformat-tables>=1", "pytest",