diff --git a/hatch_cpp/structs.py b/hatch_cpp/structs.py index 75df2d8..5884988 100644 --- a/hatch_cpp/structs.py +++ b/hatch_cpp/structs.py @@ -102,8 +102,15 @@ def default() -> HatchCppPlatform: toolchain = "clang" elif "cl" in CC and "cl" in CXX: toolchain = "msvc" + # Fallback to platform defaults + elif platform == "linux": + toolchain = "gcc" + elif platform == "darwin": + toolchain = "clang" + elif platform == "win32": + toolchain = "msvc" else: - raise Exception(f"Unrecognized toolchain: {CC}, {CXX}") + toolchain = "gcc" # Customizations if which("ccache") and not environ.get("HATCH_CPP_DISABLE_CCACHE"): @@ -117,6 +124,12 @@ def default() -> HatchCppPlatform: # LD = which("ld.lld") return HatchCppPlatform(cc=CC, cxx=CXX, ld=LD, platform=platform, toolchain=toolchain) + @staticmethod + def platform_for_toolchain(toolchain: CompilerToolchain) -> HatchCppPlatform: + platform = HatchCppPlatform.default() + platform.toolchain = toolchain + return platform + def get_compile_flags(self, library: HatchCppLibrary, build_type: BuildType = "release") -> str: flags = "" @@ -241,11 +254,27 @@ class HatchCppBuildConfig(BaseModel): cmake: Optional[HatchCppCmakeConfiguration] = Field(default=None) platform: Optional[HatchCppPlatform] = Field(default_factory=HatchCppPlatform.default) - @model_validator(mode="after") - def check_toolchain_matches_args(self): - if self.cmake and self.libraries: + @model_validator(mode="wrap") + @classmethod + def validate_model(cls, data, handler): + if "toolchain" in data: + data["platform"] = HatchCppPlatform.platform_for_toolchain(data["toolchain"]) + data.pop("toolchain") + elif "platform" not in data: + data["platform"] = HatchCppPlatform.default() + if "cc" in data: + data["platform"].cc = data["cc"] + data.pop("cc") + if "cxx" in data: + data["platform"].cxx = data["cxx"] + data.pop("cxx") + if "ld" in data: + data["platform"].ld = data["ld"] + data.pop("ld") + model = handler(data) + if model.cmake and model.libraries: raise ValueError("Must not provide libraries when using cmake toolchain.") - return self + return model class HatchCppBuildPlan(HatchCppBuildConfig): diff --git a/hatch_cpp/tests/test_project_cmake/project/include/project/basic.hpp b/hatch_cpp/tests/test_project_cmake/project/include/project/basic.hpp new file mode 100644 index 0000000..65cb62e --- /dev/null +++ b/hatch_cpp/tests/test_project_cmake/project/include/project/basic.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "Python.h" + +PyObject* hello(PyObject*, PyObject*); + +static PyMethodDef extension_methods[] = { + {"hello", (PyCFunction)hello, METH_NOARGS}, + {nullptr, nullptr, 0, nullptr} +}; + +static PyModuleDef extension_module = { + PyModuleDef_HEAD_INIT, "extension", "extension", -1, extension_methods}; + +PyMODINIT_FUNC PyInit_extension(void) { + Py_Initialize(); + return PyModule_Create(&extension_module); +} diff --git a/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.cpp b/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.cpp new file mode 100644 index 0000000..db4432a --- /dev/null +++ b/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.cpp @@ -0,0 +1,5 @@ +#include "project/basic.hpp" + +PyObject* hello(PyObject*, PyObject*) { + return PyUnicode_FromString("A string"); +} diff --git a/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.hpp b/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.hpp new file mode 100644 index 0000000..65cb62e --- /dev/null +++ b/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "Python.h" + +PyObject* hello(PyObject*, PyObject*); + +static PyMethodDef extension_methods[] = { + {"hello", (PyCFunction)hello, METH_NOARGS}, + {nullptr, nullptr, 0, nullptr} +}; + +static PyModuleDef extension_module = { + PyModuleDef_HEAD_INIT, "extension", "extension", -1, extension_methods}; + +PyMODINIT_FUNC PyInit_extension(void) { + Py_Initialize(); + return PyModule_Create(&extension_module); +} diff --git a/hatch_cpp/tests/test_project_override_toolchain/project/__init__.py b/hatch_cpp/tests/test_project_override_toolchain/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hatch_cpp/tests/test_project_override_toolchain/pyproject.toml b/hatch_cpp/tests/test_project_override_toolchain/pyproject.toml new file mode 100644 index 0000000..b5c40ca --- /dev/null +++ b/hatch_cpp/tests/test_project_override_toolchain/pyproject.toml @@ -0,0 +1,38 @@ +[build-system] +requires = ["hatchling>=1.20"] +build-backend = "hatchling.build" + +[project] +name = "hatch-cpp-test-project-toolchain" +description = "Toolchain override 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"]} +] +toolchain = "gcc" +cc = "clang" +cxx = "clang++" diff --git a/hatch_cpp/tests/test_projects.py b/hatch_cpp/tests/test_projects.py index 7553c04..dd4c6fc 100644 --- a/hatch_cpp/tests/test_projects.py +++ b/hatch_cpp/tests/test_projects.py @@ -13,6 +13,8 @@ class TestProject: [ "test_project_basic", "test_project_override_classes", + "test_project_override_classes", + "test_project_override_toolchain", "test_project_pybind", "test_project_nanobind", "test_project_limited_api", @@ -29,8 +31,7 @@ def test_basic(self, project): # compile check_call( [ - "hatchling", - "build", + "hatch-build", "--hooks-only", ], cwd=f"hatch_cpp/tests/{project}", diff --git a/hatch_cpp/tests/test_structs.py b/hatch_cpp/tests/test_structs.py index fc36d9a..263b917 100644 --- a/hatch_cpp/tests/test_structs.py +++ b/hatch_cpp/tests/test_structs.py @@ -46,3 +46,11 @@ def test_cmake_args(self): assert f"-DHATCH_CPP_TEST_PROJECT_BASIC_PYTHON_VERSION=3.{version_info.minor}" in hatch_build_plan.commands[0] if hatch_build_plan.platform.platform == "darwin": assert "-DCMAKE_OSX_DEPLOYMENT_TARGET=11" in hatch_build_plan.commands[0] + + def test_platform_toolchain_override(self): + txt = (Path(__file__).parent / "test_project_override_toolchain" / "pyproject.toml").read_text() + toml = loads(txt) + hatch_build_config = HatchCppBuildConfig(name=toml["project"]["name"], **toml["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"]) + assert "clang" in hatch_build_config.platform.cc + assert "clang++" in hatch_build_config.platform.cxx + assert hatch_build_config.platform.toolchain == "gcc" diff --git a/pyproject.toml b/pyproject.toml index 6025877..e3b1ee4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ develop = [ "check-manifest", "codespell>=2.4,<2.5", "hatchling", + "hatch-build", "mdformat>=0.7.22,<0.8", "mdformat-tables>=1", "pytest",