From 849a02b200df2bda948902bd6acc343bd2fba74e Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Wed, 5 Nov 2025 22:08:21 -0500 Subject: [PATCH] More work on vcpkg toolchain, combining cmake and vcpkg toolchains --- .gitignore | 3 ++ hatch_cpp/config.py | 31 ++++++++++-- .../tests/test_project_cmake_vcpkg/vcpkg.json | 2 +- .../test_project_pybind_vcpkg/vcpkg.json | 2 +- hatch_cpp/toolchains/cmake.py | 16 +++++- hatch_cpp/toolchains/common.py | 2 + hatch_cpp/toolchains/vcpkg.py | 50 ++++++++++++++++--- 7 files changed, 90 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index ed2e334..9d04ba6 100644 --- a/.gitignore +++ b/.gitignore @@ -157,3 +157,6 @@ hatch_cpp/labextension # Rust target + +vcpkg +vcpkg_installed \ No newline at end of file diff --git a/hatch_cpp/config.py b/hatch_cpp/config.py index 8d27fa6..3d7ffe7 100644 --- a/hatch_cpp/config.py +++ b/hatch_cpp/config.py @@ -1,12 +1,13 @@ from __future__ import annotations +from logging import getLogger from os import system as system_call from pathlib import Path from typing import List, Optional from pydantic import BaseModel, Field, model_validator -from .toolchains import BuildType, HatchCppCmakeConfiguration, HatchCppLibrary, HatchCppPlatform, HatchCppVcpkgConfiguration +from .toolchains import BuildType, HatchCppCmakeConfiguration, HatchCppLibrary, HatchCppPlatform, HatchCppVcpkgConfiguration, Toolchain __all__ = ( "HatchCppBuildConfig", @@ -14,6 +15,9 @@ ) +_log = getLogger(__name__) + + class HatchCppBuildConfig(BaseModel): """Build config values for Hatch C++ Builder.""" @@ -22,7 +26,7 @@ class HatchCppBuildConfig(BaseModel): libraries: List[HatchCppLibrary] = Field(default_factory=list) cmake: Optional[HatchCppCmakeConfiguration] = Field(default=None) platform: Optional[HatchCppPlatform] = Field(default_factory=HatchCppPlatform.default) - vcpkg: Optional[HatchCppVcpkgConfiguration] = Field(default=None) + vcpkg: Optional[HatchCppVcpkgConfiguration] = Field(default_factory=HatchCppVcpkgConfiguration) @model_validator(mode="wrap") @classmethod @@ -41,6 +45,8 @@ def validate_model(cls, data, handler): if "ld" in data: data["platform"].ld = data["ld"] data.pop("ld") + if "vcpkg" in data and data["vcpkg"] == "false": + data["vcpkg"] = None model = handler(data) if model.cmake and model.libraries: raise ValueError("Must not provide libraries when using cmake toolchain.") @@ -51,20 +57,35 @@ class HatchCppBuildPlan(HatchCppBuildConfig): build_type: BuildType = "release" commands: List[str] = Field(default_factory=list) + _active_toolchains: List[Toolchain] = [] + def generate(self): self.commands = [] + # Evaluate toolchains if self.vcpkg and Path(self.vcpkg.vcpkg).exists(): - self.commands.extend(self.vcpkg.generate(self.platform)) - + self._active_toolchains.append("vcpkg") if self.libraries: + self._active_toolchains.append("vanilla") + elif self.cmake: + self._active_toolchains.append("cmake") + + # Collect toolchain commands + if "vcpkg" in self._active_toolchains: + self.commands.extend(self.vcpkg.generate(self)) + + if "vanilla" in self._active_toolchains: + if "vcpkg" in self._active_toolchains: + _log.warning("vcpkg toolchain is active; ensure that your compiler is configured to use vcpkg includes and libs.") + for library in self.libraries: compile_flags = self.platform.get_compile_flags(library, self.build_type) link_flags = self.platform.get_link_flags(library, self.build_type) self.commands.append( f"{self.platform.cc if library.language == 'c' else self.platform.cxx} {' '.join(library.sources)} {compile_flags} {link_flags}" ) - elif self.cmake: + + if "cmake" in self._active_toolchains: self.commands.extend(self.cmake.generate(self)) return self.commands diff --git a/hatch_cpp/tests/test_project_cmake_vcpkg/vcpkg.json b/hatch_cpp/tests/test_project_cmake_vcpkg/vcpkg.json index 10c9599..ace9c19 100644 --- a/hatch_cpp/tests/test_project_cmake_vcpkg/vcpkg.json +++ b/hatch_cpp/tests/test_project_cmake_vcpkg/vcpkg.json @@ -2,7 +2,7 @@ "name": "main", "version-string": "latest", "dependencies": [ - "arrow" + "nlohmann-json" ], "builtin-baseline": "b94ade01f19e4436d8c8a16a5c52e8c802ef67dd" } diff --git a/hatch_cpp/tests/test_project_pybind_vcpkg/vcpkg.json b/hatch_cpp/tests/test_project_pybind_vcpkg/vcpkg.json index 10c9599..ace9c19 100644 --- a/hatch_cpp/tests/test_project_pybind_vcpkg/vcpkg.json +++ b/hatch_cpp/tests/test_project_pybind_vcpkg/vcpkg.json @@ -2,7 +2,7 @@ "name": "main", "version-string": "latest", "dependencies": [ - "arrow" + "nlohmann-json" ], "builtin-baseline": "b94ade01f19e4436d8c8a16a5c52e8c802ef67dd" } diff --git a/hatch_cpp/toolchains/cmake.py b/hatch_cpp/toolchains/cmake.py index 0e66dc4..1b349af 100644 --- a/hatch_cpp/toolchains/cmake.py +++ b/hatch_cpp/toolchains/cmake.py @@ -11,6 +11,16 @@ __all__ = ("HatchCppCmakeConfiguration",) +DefaultMSVCGenerator = { + "12": "Visual Studio 12 2013", + "14": "Visual Studio 14 2015", + "14.0": "Visual Studio 14 2015", + "14.1": "Visual Studio 15 2017", + "14.2": "Visual Studio 16 2019", + "14.3": "Visual Studio 17 2022", + "14.4": "Visual Studio 17 2022", +} + class HatchCppCmakeConfiguration(BaseModel): root: Path @@ -33,6 +43,10 @@ def generate(self, config) -> Dict[str, Any]: # Append base command commands.append(f"cmake {Path(self.root).parent} -DCMAKE_BUILD_TYPE={config.build_type} -B {self.build}") + # Hook in to vcpkg if active + if "vcpkg" in config._active_toolchains: + commands[-1] += f" -DCMAKE_TOOLCHAIN_FILE={Path(config.vcpkg.vcpkg_root) / 'scripts' / 'buildsystems' / 'vcpkg.cmake'}" + # Setup install path if self.install: commands[-1] += f" -DCMAKE_INSTALL_PREFIX={self.install}" @@ -42,7 +56,7 @@ def generate(self, config) -> Dict[str, Any]: # TODO: CMAKE_CXX_COMPILER if config.platform.platform == "win32": # TODO: prefix? - commands[-1] += f' -G "{environ.get("GENERATOR", "Visual Studio 17 2022")}"' + commands[-1] += f' -G "{environ.get("CMAKE_GENERATOR", "Visual Studio 17 2022")}"' # Put in CMake flags args = self.cmake_args.copy() diff --git a/hatch_cpp/toolchains/common.py b/hatch_cpp/toolchains/common.py index 2ee094f..5922d64 100644 --- a/hatch_cpp/toolchains/common.py +++ b/hatch_cpp/toolchains/common.py @@ -13,6 +13,7 @@ __all__ = ( "BuildType", "CompilerToolchain", + "Toolchain", "Language", "Binding", "Platform", @@ -24,6 +25,7 @@ BuildType = Literal["debug", "release"] CompilerToolchain = Literal["gcc", "clang", "msvc"] +Toolchain = Literal["vcpkg", "cmake", "vanilla"] Language = Literal["c", "c++"] Binding = Literal["cpython", "pybind11", "nanobind", "generic"] Platform = Literal["linux", "darwin", "win32"] diff --git a/hatch_cpp/toolchains/vcpkg.py b/hatch_cpp/toolchains/vcpkg.py index 556f6a7..c5056dd 100644 --- a/hatch_cpp/toolchains/vcpkg.py +++ b/hatch_cpp/toolchains/vcpkg.py @@ -1,30 +1,64 @@ from __future__ import annotations from pathlib import Path +from platform import machine as platform_machine from sys import platform as sys_platform -from typing import Optional +from typing import Literal, Optional from pydantic import BaseModel, Field __all__ = ("HatchCppVcpkgConfiguration",) +VcpkgTriplet = Literal[ + "x64-android", + "x64-osx", + "x64-linux", + "x64-uwp", + "x64-windows", + "x64-windows-release", + "x64-windows-static", + "x64-windows-static-md", + "x86-windows", + "arm-neon-android", + "arm64-android", + "arm64-osx", + "arm64-uwp", + "arm64-windows", + "arm64-windows-static-md", +] +VcpkgPlatformDefaults = { + ("linux", "x86_64"): "x64-linux", + # ("linux", "arm64"): "", + ("darwin", "x86_64"): "x64-osx", + ("darwin", "arm64"): "arm64-osx", + ("win32", "x86_64"): "x64-windows-static-md", + ("win32", "arm64"): "arm64-windows-static-md", +} + + class HatchCppVcpkgConfiguration(BaseModel): vcpkg: Optional[str] = Field(default="vcpkg.json") vcpkg_root: Optional[Path] = Field(default=Path("vcpkg")) vcpkg_repo: Optional[str] = Field(default="https://github.com/microsoft/vcpkg.git") + vcpkg_triplet: Optional[VcpkgTriplet] = Field(default=None) + + # TODO: overlay def generate(self, config): commands = [] - if self.vcpkg and Path(self.vcpkg.vcpkg).exists(): - if not Path(self.vcpkg.vcpkg_root).exists(): - commands.append(f"git clone {self.vcpkg.vcpkg_repo} {self.vcpkg.vcpkg_root}") + if self.vcpkg_triplet is None: + self.vcpkg_triplet = VcpkgPlatformDefaults.get((sys_platform, platform_machine())) + if self.vcpkg_triplet is None: + raise ValueError(f"Could not determine vcpkg triplet for platform {sys_platform} and architecture {platform_machine()}") + + if self.vcpkg and Path(self.vcpkg).exists(): + if not Path(self.vcpkg_root).exists(): + commands.append(f"git clone {self.vcpkg_repo} {self.vcpkg_root}") commands.append( - f"./{self.vcpkg.vcpkg_root / 'bootstrap-vcpkg.sh' if sys_platform != 'win32' else self.vcpkg.vcpkg_root / 'sbootstrap-vcpkg.bat'}" + f"./{self.vcpkg_root / 'bootstrap-vcpkg.sh' if sys_platform != 'win32' else self.vcpkg_root / 'sbootstrap-vcpkg.bat'}" ) - commands.append( - f"./{self.vcpkg.vcpkg_root / 'vcpkg'} install --triplet {config.platform.platform}-{config.platform.toolchain} --manifest-root {Path(self.vcpkg.vcpkg).parent}" - ) + commands.append(f"./{self.vcpkg_root / 'vcpkg'} install --triplet {self.vcpkg_triplet}") return commands