From 6b122ef8102717f869ed925d1777b48221ddc67e Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Thu, 9 Jan 2025 19:37:09 -0500 Subject: [PATCH 01/35] Expand CI, enable windows testing --- .copier-answers.yml | 2 +- .github/workflows/build.yml | 18 +++++++++++++----- Makefile | 2 +- hatch_cpp/plugin.py | 7 +++++-- hatch_cpp/structs.py | 7 ++----- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 3e3d361..f3bd003 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,6 +1,6 @@ # Changes here will be overwritten by Copier _commit: 81e8acd -_src_path: git@github.com:python-project-templates/base.git +_src_path: https://github.com/python-project-templates/base.git add_extension: python email: t.paine154@gmail.com github: python-project-templates diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d9a8ac7..b5df3cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,8 +29,8 @@ jobs: strategy: matrix: - os: [ubuntu-latest, macos-latest] - python-version: ["3.9"] + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 @@ -56,7 +56,15 @@ jobs: - name: Test run: make coverage - + if: matrix.os != 'windows-latest' + + - name: Test + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + make coverage + shell: cmd + if: matrix.os == 'windows-latest' + - name: Upload test results (Python) uses: actions/upload-artifact@v4 with: @@ -68,7 +76,7 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@v2 with: files: '**/junit.xml' - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' - name: Upload coverage uses: codecov/codecov-action@v5 @@ -82,4 +90,4 @@ jobs: with: name: dist-${{matrix.os}} path: dist - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' diff --git a/Makefile b/Makefile index 627b57c..1473fb6 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,7 @@ deep-clean: ## clean everything from the repository git clean -fdx clean: ## clean the repository - rm -rf .coverage coverage cover htmlcov logs build dist *.egg-info + rm -rf .coverage coverage cover htmlcov logs build dist *.egg-info hatch_cpp/tests/*/dist hatch_cpp/tests/*/build hatch_cpp/tests/*/*/*.so hatch_cpp/tests/*/*/*.pyd ############################################################################################ diff --git a/hatch_cpp/plugin.py b/hatch_cpp/plugin.py index fb88301..0ce6f53 100644 --- a/hatch_cpp/plugin.py +++ b/hatch_cpp/plugin.py @@ -43,10 +43,13 @@ def initialize(self, version: str, _: dict[str, t.Any]) -> None: libraries = [HatchCppLibrary(**library_kwargs) for library_kwargs in library_kwargs] platform = HatchCppPlatform.default() if config.toolchain == "raw": - # g++ basic-project/basic.cpp -I. -I/opt/homebrew/opt/python@3.11/Frameworks/Python.framework/Versions/3.11/include/python3.11/ -undefined dynamic_lookup -fPIC -shared -o extension.so build_plan = HatchCppBuildPlan(libraries=libraries, platform=platform) build_plan.generate() - build_plan.execute(verbose=config.verbose) + if config.verbose: + for command in build_plan.commands: + self._logger.info(command) + build_plan.execute() + # build_kwargs = config.build_kwargs # if version == "editable": # build_kwargs = config.editable_build_kwargs or build_kwargs diff --git a/hatch_cpp/structs.py b/hatch_cpp/structs.py index ccfab16..0412aef 100644 --- a/hatch_cpp/structs.py +++ b/hatch_cpp/structs.py @@ -117,8 +117,7 @@ def get_flags(self, library: HatchCppLibrary) -> str: flags += " " + " ".join(f"/LIBPATH:{lib}" for lib in library.library_dirs) flags += " " + " ".join(f"/D{macro}" for macro in library.define_macros) flags += " " + " ".join(f"/U{macro}" for macro in library.undef_macros) - flags += f" /Fo{library.name}.obj" - flags += f" /Fe{library.name}.pyd" + flags += f" /Fo{library.name}.pyd" # clean while flags.count(" "): flags = flags.replace(" ", " ") @@ -138,9 +137,7 @@ def generate(self): self.commands.append(f"{self.platform.cc} {' '.join(library.sources)} {flags}") return self.commands - def execute(self, verbose: bool = True): + def execute(self): for command in self.commands: - if verbose: - print(f"Running command: {command}") system(command) return self.commands From b7cbb9905816a5528faf51312d44965a5887a05b Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:45:01 -0500 Subject: [PATCH 02/35] Test importing built library --- .gitignore | 5 +++++ hatch_cpp/plugin.py | 1 + hatch_cpp/structs.py | 30 ++++++++++++++++++++------- hatch_cpp/tests/test_project_basic.py | 8 ++++++- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index a090202..858d10c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,12 @@ __pycache__/ *$py.class # C extensions +*.a *.so +*.obj +*.dll +*.exp +*.lib # Distribution / packaging .Python diff --git a/hatch_cpp/plugin.py b/hatch_cpp/plugin.py index 0ce6f53..0de620f 100644 --- a/hatch_cpp/plugin.py +++ b/hatch_cpp/plugin.py @@ -49,6 +49,7 @@ def initialize(self, version: str, _: dict[str, t.Any]) -> None: for command in build_plan.commands: self._logger.info(command) build_plan.execute() + build_plan.cleanup() # build_kwargs = config.build_kwargs # if version == "editable": diff --git a/hatch_cpp/structs.py b/hatch_cpp/structs.py index 0412aef..cc9c99c 100644 --- a/hatch_cpp/structs.py +++ b/hatch_cpp/structs.py @@ -2,7 +2,8 @@ from dataclasses import dataclass, field from os import environ, system -from sys import platform as sys_platform +from pathlib import Path +from sys import executable, platform as sys_platform from sysconfig import get_path from typing import Literal @@ -80,7 +81,7 @@ def default() -> HatchCppPlatform: raise Exception(f"Unrecognized toolchain: {CC}, {CXX}") return HatchCppPlatform(cc=CC, cxx=CXX, platform=platform, toolchain=toolchain) - def get_flags(self, library: HatchCppLibrary) -> str: + def get_compile_flags(self, library: HatchCppLibrary) -> str: flags = "" if self.toolchain == "gcc": flags = f"-I{get_path('include')}" @@ -109,20 +110,28 @@ def get_flags(self, library: HatchCppLibrary) -> str: elif self.toolchain == "msvc": flags = f"/I{get_path('include')} " flags += " ".join(f"/I{d}" for d in library.include_dirs) - flags += " /LD" flags += " " + " ".join(library.extra_compile_args) flags += " " + " ".join(library.extra_link_args) flags += " " + " ".join(library.extra_objects) - flags += " " + " ".join(f"{lib}.lib" for lib in library.libraries) - flags += " " + " ".join(f"/LIBPATH:{lib}" for lib in library.library_dirs) flags += " " + " ".join(f"/D{macro}" for macro in library.define_macros) flags += " " + " ".join(f"/U{macro}" for macro in library.undef_macros) - flags += f" /Fo{library.name}.pyd" + flags += " /EHsc /DWIN32 /LD" + flags += f" /Fo:{library.name}.obj" + flags += f" /Fe:{library.name}.pyd" + flags += " /link /DLL" + if (Path(executable).parent / "libs").exists(): + flags += f" /LIBPATH:{str(Path(executable).parent / 'libs')}" + flags += " " + " ".join(f"{lib}.lib" for lib in library.libraries) + flags += " " + " ".join(f"/LIBPATH:{lib}" for lib in library.library_dirs) # clean while flags.count(" "): flags = flags.replace(" ", " ") return flags + def get_link_flags(self, library: HatchCppLibrary) -> str: + flags = "" + return flags + @dataclass class HatchCppBuildPlan(object): @@ -133,7 +142,7 @@ class HatchCppBuildPlan(object): def generate(self): self.commands = [] for library in self.libraries: - flags = self.platform.get_flags(library) + flags = self.platform.get_compile_flags(library) self.commands.append(f"{self.platform.cc} {' '.join(library.sources)} {flags}") return self.commands @@ -141,3 +150,10 @@ def execute(self): for command in self.commands: system(command) return self.commands + + 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() diff --git a/hatch_cpp/tests/test_project_basic.py b/hatch_cpp/tests/test_project_basic.py index b70fff5..03de9f9 100644 --- a/hatch_cpp/tests/test_project_basic.py +++ b/hatch_cpp/tests/test_project_basic.py @@ -1,7 +1,8 @@ from os import listdir +from pathlib import Path from shutil import rmtree from subprocess import check_output -from sys import platform +from sys import path, platform class TestProject: @@ -20,3 +21,8 @@ def test_basic(self): assert "extension.pyd" in listdir("hatch_cpp/tests/test_project_basic/basic_project") else: assert "extension.so" in listdir("hatch_cpp/tests/test_project_basic/basic_project") + here = Path(__file__).parent / "test_project_basic" + path.insert(0, str(here)) + import basic_project.extension + + assert basic_project.extension.hello() == "A string" From b33981d94c10dc5cc1a01e1e72e814480f9db86b Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Fri, 10 Jan 2025 10:47:38 -0500 Subject: [PATCH 03/35] =?UTF-8?q?Bump=20version:=200.1.0=20=E2=86=92=200.1?= =?UTF-8?q?.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hatch_cpp/__init__.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hatch_cpp/__init__.py b/hatch_cpp/__init__.py index 3dc1f76..485f44a 100644 --- a/hatch_cpp/__init__.py +++ b/hatch_cpp/__init__.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.1" diff --git a/pyproject.toml b/pyproject.toml index c9344a7..451b580 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [{name = "the hatch-cpp authors", email = "t.paine154@gmail.com"}] description = "Hatch plugin for C++ builds" readme = "README.md" license = { text = "Apache-2.0" } -version = "0.1.0" +version = "0.1.1" requires-python = ">=3.9" keywords = [ "hatch", @@ -59,7 +59,7 @@ Repository = "https://github.com/python-project-templates/hatch-cpp" Homepage = "https://github.com/python-project-templates/hatch-cpp" [tool.bumpversion] -current_version = "0.1.0" +current_version = "0.1.1" commit = true tag = false From 6f59e855f9bff6288ef4d9b8f8e3d0d41c714d9c Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Fri, 10 Jan 2025 13:00:39 -0500 Subject: [PATCH 04/35] Switch from raw dataclasses to pydantic for validation --- hatch_cpp/plugin.py | 51 +++----------------------------- hatch_cpp/structs.py | 70 +++++++++++++++++++++----------------------- pyproject.toml | 1 + 3 files changed, 38 insertions(+), 84 deletions(-) diff --git a/hatch_cpp/plugin.py b/hatch_cpp/plugin.py index 0de620f..6b57f2d 100644 --- a/hatch_cpp/plugin.py +++ b/hatch_cpp/plugin.py @@ -3,11 +3,10 @@ import logging import os import typing as t -from dataclasses import fields from hatchling.builders.hooks.plugin.interface import BuildHookInterface -from .structs import HatchCppBuildConfig, HatchCppBuildPlan, HatchCppLibrary, HatchCppPlatform +from .structs import HatchCppBuildConfig, HatchCppBuildPlan __all__ = ("HatchCppBuildHook",) @@ -30,18 +29,10 @@ def initialize(self, version: str, _: dict[str, t.Any]) -> None: self._logger.info("Skipping the build hook since SKIP_HATCH_CPP was set") return - kwargs = {k.replace("-", "_"): v if not isinstance(v, bool) else str(v) for k, v in self.config.items()} - available_fields = [f.name for f in fields(HatchCppBuildConfig)] - for key in list(kwargs): - if key not in available_fields: - del kwargs[key] - config = HatchCppBuildConfig(**kwargs) + config = HatchCppBuildConfig(**self.config) - library_kwargs = [ - {k.replace("-", "_"): v if not isinstance(v, bool) else str(v) for k, v in library_kwargs.items()} for library_kwargs in config.libraries - ] - libraries = [HatchCppLibrary(**library_kwargs) for library_kwargs in library_kwargs] - platform = HatchCppPlatform.default() + libraries = config.libraries + platform = config.platform if config.toolchain == "raw": build_plan = HatchCppBuildPlan(libraries=libraries, platform=platform) build_plan.generate() @@ -51,39 +42,5 @@ def initialize(self, version: str, _: dict[str, t.Any]) -> None: build_plan.execute() build_plan.cleanup() - # build_kwargs = config.build_kwargs - # if version == "editable": - # build_kwargs = config.editable_build_kwargs or build_kwargs - - # should_skip_build = False - # if not config.build_function: - # log.warning("No build function found") - # should_skip_build = True - - # elif config.skip_if_exists and version == "standard": - # should_skip_build = should_skip(config.skip_if_exists) - # if should_skip_build: - # log.info("Skip-if-exists file(s) found") - - # # Get build function and call it with normalized parameter names. - # if not should_skip_build and config.build_function: - # build_func = get_build_func(config.build_function) - # build_kwargs = normalize_kwargs(build_kwargs) - # log.info("Building with %s", config.build_function) - # log.info("With kwargs: %s", build_kwargs) - # try: - # build_func(self.target_name, version, **build_kwargs) - # except Exception as e: - # if version == "editable" and config.optional_editable_build.lower() == "true": - # warnings.warn(f"Encountered build error:\n{e}", stacklevel=2) - # else: - # raise e - # else: - # log.info("Skipping build") - - # # Ensure targets in distributable dists. - # if version == "standard": - # ensure_targets(config.ensured_targets) - self._logger.info("Finished running hatch-cpp") return diff --git a/hatch_cpp/structs.py b/hatch_cpp/structs.py index cc9c99c..4241a7b 100644 --- a/hatch_cpp/structs.py +++ b/hatch_cpp/structs.py @@ -1,13 +1,12 @@ from __future__ import annotations -from dataclasses import dataclass, field from os import environ, system from pathlib import Path from sys import executable, platform as sys_platform from sysconfig import get_path -from typing import Literal +from typing import List, Literal, Optional -from hatchling.builders.config import BuilderConfig +from pydantic import BaseModel, Field __all__ = ( "HatchCppBuildConfig", @@ -25,42 +24,26 @@ } -@dataclass -class HatchCppBuildConfig(BuilderConfig): - """Build config values for Hatch C++ Builder.""" - - toolchain: str | None = field(default="raw") - libraries: list[dict[str, str]] = field(default_factory=list) - verbose: bool | None = field(default=False) - # build_function: str | None = None - # build_kwargs: t.Mapping[str, str] = field(default_factory=dict) - # editable_build_kwargs: t.Mapping[str, str] = field(default_factory=dict) - # ensured_targets: list[str] = field(default_factory=list) - # skip_if_exists: list[str] = field(default_factory=list) - - -@dataclass -class HatchCppLibrary(object): +class HatchCppLibrary(BaseModel): """A C++ library.""" name: str - sources: list[str] + sources: List[str] - include_dirs: list[str] = field(default_factory=list) - library_dirs: list[str] = field(default_factory=list) - libraries: list[str] = field(default_factory=list) - extra_compile_args: list[str] = field(default_factory=list) - extra_link_args: list[str] = field(default_factory=list) - extra_objects: list[str] = field(default_factory=list) - define_macros: list[str] = field(default_factory=list) - undef_macros: list[str] = field(default_factory=list) + 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") - export_symbols: list[str] = field(default_factory=list) - depends: list[str] = field(default_factory=list) + export_symbols: List[str] = Field(default_factory=list, alias="export-symbols") + depends: List[str] = Field(default_factory=list) -@dataclass -class HatchCppPlatform(object): +class HatchCppPlatform(BaseModel): cc: str cxx: str platform: Platform @@ -133,11 +116,10 @@ def get_link_flags(self, library: HatchCppLibrary) -> str: return flags -@dataclass -class HatchCppBuildPlan(object): - libraries: list[HatchCppLibrary] = field(default_factory=list) - platform: HatchCppPlatform = field(default_factory=HatchCppPlatform.default) - commands: list[str] = field(default_factory=list) +class HatchCppBuildPlan(BaseModel): + libraries: List[HatchCppLibrary] = Field(default_factory=list) + platform: HatchCppPlatform = Field(default_factory=HatchCppPlatform.default) + commands: List[str] = Field(default_factory=list) def generate(self): self.commands = [] @@ -157,3 +139,17 @@ def cleanup(self): temp_obj = Path(f"{library.name}.obj") if temp_obj.exists(): temp_obj.unlink() + + +class HatchCppBuildConfig(BaseModel): + """Build config values for Hatch C++ Builder.""" + + toolchain: Optional[str] = Field(default="raw") + libraries: List[HatchCppLibrary] = Field(default_factory=list) + verbose: Optional[bool] = Field(default=False) + platform: Optional[HatchCppPlatform] = Field(default_factory=HatchCppPlatform.default) + # build_function: str | None = None + # build_kwargs: t.Mapping[str, str] = field(default_factory=dict) + # editable_build_kwargs: t.Mapping[str, str] = field(default_factory=dict) + # ensured_targets: list[str] = field(default_factory=list) + # skip_if_exists: list[str] = field(default_factory=list) diff --git a/pyproject.toml b/pyproject.toml index 451b580..7e45b50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ classifiers = [ dependencies = [ "hatchling>=1.20", + "pydantic", ] [project.optional-dependencies] From 65a8618fd15aa4015208b99a60740602c51cc5e3 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Fri, 10 Jan 2025 13:30:19 -0500 Subject: [PATCH 05/35] Add overrides to config and build plan --- hatch_cpp/__init__.py | 4 ++ hatch_cpp/__main__.py | 4 -- hatch_cpp/plugin.py | 45 +++++++++---- hatch_cpp/structs.py | 5 +- .../basic_project/__init__.py | 0 .../cpp/basic-project/basic.cpp | 5 ++ .../cpp/basic-project/basic.hpp | 17 +++++ .../pyproject.toml | 65 +++++++++++++++++++ ...test_project_basic.py => test_projects.py} | 21 ++++++ hatch_cpp/utils.py | 12 ++++ pyproject.toml | 4 +- 11 files changed, 162 insertions(+), 20 deletions(-) delete mode 100644 hatch_cpp/__main__.py create mode 100644 hatch_cpp/tests/test_project_override_classes/basic_project/__init__.py create mode 100644 hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.cpp create mode 100644 hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.hpp create mode 100644 hatch_cpp/tests/test_project_override_classes/pyproject.toml rename hatch_cpp/tests/{test_project_basic.py => test_projects.py} (52%) diff --git a/hatch_cpp/__init__.py b/hatch_cpp/__init__.py index 485f44a..88f1932 100644 --- a/hatch_cpp/__init__.py +++ b/hatch_cpp/__init__.py @@ -1 +1,5 @@ __version__ = "0.1.1" + +from .hooks import hatch_register_build_hook +from .plugin import HatchCppBuildHook +from .structs import * diff --git a/hatch_cpp/__main__.py b/hatch_cpp/__main__.py deleted file mode 100644 index 9ae637f..0000000 --- a/hatch_cpp/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .cli import main - -if __name__ == "__main__": - main() diff --git a/hatch_cpp/plugin.py b/hatch_cpp/plugin.py index 6b57f2d..9ad7fb1 100644 --- a/hatch_cpp/plugin.py +++ b/hatch_cpp/plugin.py @@ -7,6 +7,7 @@ from hatchling.builders.hooks.plugin.interface import BuildHookInterface from .structs import HatchCppBuildConfig, HatchCppBuildPlan +from .utils import import_string __all__ = ("HatchCppBuildHook",) @@ -19,28 +20,48 @@ class HatchCppBuildHook(BuildHookInterface[HatchCppBuildConfig]): def initialize(self, version: str, _: dict[str, t.Any]) -> None: """Initialize the plugin.""" + # Log some basic information + self._logger.info("Initializing hatch-cpp plugin version %s", version) self._logger.info("Running hatch-cpp") + # Only run if creating wheel + # TODO: Add support for specify sdist-plan if self.target_name != "wheel": self._logger.info("ignoring target name %s", self.target_name) return + # Skip if SKIP_HATCH_CPP is set + # TODO: Support CLI once https://github.com/pypa/hatch/pull/1743 if os.getenv("SKIP_HATCH_CPP"): self._logger.info("Skipping the build hook since SKIP_HATCH_CPP was set") return - config = HatchCppBuildConfig(**self.config) + # Get build config class or use default + build_config_class = import_string(self.config["build-config-class"]) if "build-config-class" in self.config else HatchCppBuildConfig + # Instantiate build config + config = build_config_class(**self.config) + + # Grab libraries and platform libraries = config.libraries platform = config.platform - if config.toolchain == "raw": - build_plan = HatchCppBuildPlan(libraries=libraries, platform=platform) - build_plan.generate() - if config.verbose: - for command in build_plan.commands: - self._logger.info(command) - build_plan.execute() - build_plan.cleanup() - - self._logger.info("Finished running hatch-cpp") - return + + # Get build plan class or use default + build_plan_class = import_string(self.config["build-plan-class"]) if "build-plan-class" in self.config else HatchCppBuildPlan + + # Instantiate builder + build_plan = build_plan_class(libraries=libraries, platform=platform) + + # Generate commands + build_plan.generate() + + # Log commands if in verbose mode + if config.verbose: + for command in build_plan.commands: + self._logger.info(command) + + # Execute build plan + build_plan.execute() + + # Perform any cleanup actions + build_plan.cleanup() diff --git a/hatch_cpp/structs.py b/hatch_cpp/structs.py index 4241a7b..461fb8e 100644 --- a/hatch_cpp/structs.py +++ b/hatch_cpp/structs.py @@ -112,6 +112,7 @@ def get_compile_flags(self, library: HatchCppLibrary) -> str: return flags def get_link_flags(self, library: HatchCppLibrary) -> str: + # TODO flags = "" return flags @@ -144,10 +145,10 @@ def cleanup(self): class HatchCppBuildConfig(BaseModel): """Build config values for Hatch C++ Builder.""" - toolchain: Optional[str] = Field(default="raw") - libraries: List[HatchCppLibrary] = Field(default_factory=list) verbose: Optional[bool] = Field(default=False) + libraries: List[HatchCppLibrary] = Field(default_factory=list) platform: Optional[HatchCppPlatform] = Field(default_factory=HatchCppPlatform.default) + # build_function: str | None = None # build_kwargs: t.Mapping[str, str] = field(default_factory=dict) # editable_build_kwargs: t.Mapping[str, str] = field(default_factory=dict) diff --git a/hatch_cpp/tests/test_project_override_classes/basic_project/__init__.py b/hatch_cpp/tests/test_project_override_classes/basic_project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.cpp b/hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.cpp new file mode 100644 index 0000000..a7e840e --- /dev/null +++ b/hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.cpp @@ -0,0 +1,5 @@ +#include "basic-project/basic.hpp" + +PyObject* hello(PyObject*, PyObject*) { + return PyUnicode_FromString("A string"); +} diff --git a/hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.hpp b/hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.hpp new file mode 100644 index 0000000..65cb62e --- /dev/null +++ b/hatch_cpp/tests/test_project_override_classes/cpp/basic-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_classes/pyproject.toml b/hatch_cpp/tests/test_project_override_classes/pyproject.toml new file mode 100644 index 0000000..d7ffab9 --- /dev/null +++ b/hatch_cpp/tests/test_project_override_classes/pyproject.toml @@ -0,0 +1,65 @@ +[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 = [ + "basic_project/*.dll", + "basic_project/*.dylib", + "basic_project/*.so", +] + +[tool.hatch.build.sources] +src = "/" + +[tool.hatch.build.targets.sdist] +packages = ["basic_project"] + +[tool.hatch.build.targets.wheel] +packages = ["basic_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"]} +] + +# 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" diff --git a/hatch_cpp/tests/test_project_basic.py b/hatch_cpp/tests/test_projects.py similarity index 52% rename from hatch_cpp/tests/test_project_basic.py rename to hatch_cpp/tests/test_projects.py index 03de9f9..4d5a39c 100644 --- a/hatch_cpp/tests/test_project_basic.py +++ b/hatch_cpp/tests/test_projects.py @@ -26,3 +26,24 @@ def test_basic(self): import basic_project.extension assert basic_project.extension.hello() == "A string" + + def test_override_classes(self): + rmtree("hatch_cpp/tests/test_project_override_classes/basic_project/extension.so", ignore_errors=True) + rmtree("hatch_cpp/tests/test_project_override_classes/basic_project/extension.pyd", ignore_errors=True) + check_output( + [ + "hatchling", + "build", + "--hooks-only", + ], + cwd="hatch_cpp/tests/test_project_override_classes", + ) + if platform == "win32": + assert "extension.pyd" in listdir("hatch_cpp/tests/test_project_override_classes/basic_project") + else: + assert "extension.so" in listdir("hatch_cpp/tests/test_project_override_classes/basic_project") + here = Path(__file__).parent / "test_project_override_classes" + path.insert(0, str(here)) + import basic_project.extension + + assert basic_project.extension.hello() == "A string" diff --git a/hatch_cpp/utils.py b/hatch_cpp/utils.py index f95bf5e..fb209b2 100644 --- a/hatch_cpp/utils.py +++ b/hatch_cpp/utils.py @@ -1,5 +1,17 @@ from __future__ import annotations +from functools import lru_cache + +from pydantic import ImportString, TypeAdapter + +_import_string_adapter = TypeAdapter(ImportString) + + +@lru_cache(maxsize=None) +def import_string(input_string: str): + return _import_string_adapter.validate_python(input_string) + + # import multiprocessing # import os # import os.path diff --git a/pyproject.toml b/pyproject.toml index 7e45b50..452e4af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,8 +52,8 @@ develop = [ [project.entry-points.hatch] cpp = "hatch_cpp.hooks" -[project.scripts] -hatch-cpp = "hatch_cpp.cli:main" +# [project.scripts] +# hatch-cpp = "hatch_cpp.cli:main" [project.urls] Repository = "https://github.com/python-project-templates/hatch-cpp" From 8d0af69c44c06d95cee56a2d7b215935860bc0d1 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:10:27 -0500 Subject: [PATCH 06/35] Add support for other spawners and linkers --- README.md | 22 +++++++ hatch_cpp/plugin.py | 2 +- hatch_cpp/structs.py | 102 +++++++++++++++++++++---------- hatch_cpp/tests/test_projects.py | 6 +- pyproject.toml | 2 +- 5 files changed, 96 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index d47e19c..0850415 100644 --- a/README.md +++ b/README.md @@ -9,5 +9,27 @@ Hatch plugin for C++ builds ## Overview +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"]} +] +``` + +For more complete systems, see: +- [scikit-build-core](https://github.com/scikit-build/scikit-build-core) +- [setuptools](https://setuptools.pypa.io/en/latest/userguide/ext_modules.html) + +## Environment Variables +| Name | Default | Description | +|:-----|:--------|:------------| +|`CC`| | | +|`CXX`| | | +|`LD`| | | +|`HATCH_CPP_PLATFORM`| | | +|`HATCH_CPP_DISABLE_CCACHE`| | | + > [!NOTE] > This library was generated using [copier](https://copier.readthedocs.io/en/stable/) from the [Base Python Project Template repository](https://github.com/python-project-templates/base). diff --git a/hatch_cpp/plugin.py b/hatch_cpp/plugin.py index 9ad7fb1..469ee39 100644 --- a/hatch_cpp/plugin.py +++ b/hatch_cpp/plugin.py @@ -58,7 +58,7 @@ def initialize(self, version: str, _: dict[str, t.Any]) -> None: # Log commands if in verbose mode if config.verbose: for command in build_plan.commands: - self._logger.info(command) + self._logger.warning(command) # Execute build plan build_plan.execute() diff --git a/hatch_cpp/structs.py b/hatch_cpp/structs.py index 461fb8e..5bdf707 100644 --- a/hatch_cpp/structs.py +++ b/hatch_cpp/structs.py @@ -2,6 +2,7 @@ from os import environ, system from pathlib import Path +from shutil import which from sys import executable, platform as sys_platform from sysconfig import get_path from typing import List, Literal, Optional @@ -15,12 +16,14 @@ "HatchCppBuildPlan", ) -Platform = Literal["linux", "darwin", "win32"] +BuildType = Literal["debug", "release"] CompilerToolchain = Literal["gcc", "clang", "msvc"] +Language = Literal["c", "c++"] +Platform = Literal["linux", "darwin", "win32"] PlatformDefaults = { - "linux": {"CC": "gcc", "CXX": "g++"}, - "darwin": {"CC": "clang", "CXX": "clang++"}, - "win32": {"CC": "cl", "CXX": "cl"}, + "linux": {"CC": "gcc", "CXX": "g++", "LD": "ld"}, + "darwin": {"CC": "clang", "CXX": "clang++", "LD": "ld"}, + "win32": {"CC": "cl", "CXX": "cl", "LD": "link"}, } @@ -29,7 +32,7 @@ class HatchCppLibrary(BaseModel): name: str sources: List[str] - + language: Language = "c++" 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) @@ -46,6 +49,7 @@ class HatchCppLibrary(BaseModel): class HatchCppPlatform(BaseModel): cc: str cxx: str + ld: str platform: Platform toolchain: CompilerToolchain @@ -54,6 +58,7 @@ def default() -> HatchCppPlatform: platform = environ.get("HATCH_CPP_PLATFORM", sys_platform) CC = environ.get("CC", PlatformDefaults[platform]["CC"]) CXX = environ.get("CXX", PlatformDefaults[platform]["CXX"]) + LD = environ.get("LD", PlatformDefaults[platform]["LD"]) if "gcc" in CC and "g++" in CXX: toolchain = "gcc" elif "clang" in CC and "clang++" in CXX: @@ -62,34 +67,35 @@ def default() -> HatchCppPlatform: toolchain = "msvc" else: raise Exception(f"Unrecognized toolchain: {CC}, {CXX}") - return HatchCppPlatform(cc=CC, cxx=CXX, platform=platform, toolchain=toolchain) - def get_compile_flags(self, library: HatchCppLibrary) -> str: + # Customizations + if which("ccache") and not environ.get("HATCH_CPP_DISABLE_CCACHE"): + CC = f"ccache {CC}" + CXX = f"ccache {CXX}" + + # https://github.com/rui314/mold/issues/647 + # if which("ld.mold"): + # LD = which("ld.mold") + # elif which("ld.lld"): + # LD = which("ld.lld") + return HatchCppPlatform(cc=CC, cxx=CXX, ld=LD, platform=platform, toolchain=toolchain) + + def get_compile_flags(self, library: HatchCppLibrary, build_type: BuildType = "release") -> str: flags = "" if self.toolchain == "gcc": flags = f"-I{get_path('include')}" flags += " " + " ".join(f"-I{d}" for d in library.include_dirs) - flags += " -fPIC -shared" + flags += " -fPIC" flags += " " + " ".join(library.extra_compile_args) - flags += " " + " ".join(library.extra_link_args) - flags += " " + " ".join(library.extra_objects) - flags += " " + " ".join(f"-l{lib}" for lib in library.libraries) - flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs) flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros) flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros) - flags += f" -o {library.name}.so" elif self.toolchain == "clang": flags = f"-I{get_path('include')} " flags += " ".join(f"-I{d}" for d in library.include_dirs) - flags += " -undefined dynamic_lookup -fPIC -shared" + flags += " -fPIC" flags += " " + " ".join(library.extra_compile_args) - flags += " " + " ".join(library.extra_link_args) - flags += " " + " ".join(library.extra_objects) - flags += " " + " ".join(f"-l{lib}" for lib in library.libraries) - flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs) flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros) flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros) - flags += f" -o {library.name}.so" elif self.toolchain == "msvc": flags = f"/I{get_path('include')} " flags += " ".join(f"/I{d}" for d in library.include_dirs) @@ -98,7 +104,44 @@ def get_compile_flags(self, library: HatchCppLibrary) -> str: 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 /LD" + flags += " /EHsc /DWIN32" + # clean + while flags.count(" "): + flags = flags.replace(" ", " ") + return flags + + def get_link_flags(self, library: HatchCppLibrary, build_type: BuildType = "release") -> str: + flags = "" + if self.toolchain == "gcc": + flags += " -shared" + flags += " " + " ".join(library.extra_link_args) + flags += " " + " ".join(library.extra_objects) + flags += " " + " ".join(f"-l{lib}" for lib in library.libraries) + flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs) + flags += f" -o {library.name}.so" + if self.platform == "darwin": + flags += " -undefined dynamic_lookup" + if "mold" in self.ld: + flags += f" -fuse-ld={self.ld}" + elif "lld" in self.ld: + flags += " -fuse-ld=lld" + elif self.toolchain == "clang": + flags += " -shared" + flags += " " + " ".join(library.extra_link_args) + flags += " " + " ".join(library.extra_objects) + flags += " " + " ".join(f"-l{lib}" for lib in library.libraries) + flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs) + flags += f" -o {library.name}.so" + if self.platform == "darwin": + flags += " -undefined dynamic_lookup" + if "mold" in self.ld: + flags += f" -fuse-ld={self.ld}" + elif "lld" in self.ld: + flags += " -fuse-ld=lld" + elif self.toolchain == "msvc": + 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" @@ -111,13 +154,9 @@ def get_compile_flags(self, library: HatchCppLibrary) -> str: flags = flags.replace(" ", " ") return flags - def get_link_flags(self, library: HatchCppLibrary) -> str: - # TODO - flags = "" - return flags - class HatchCppBuildPlan(BaseModel): + build_type: BuildType = "release" libraries: List[HatchCppLibrary] = Field(default_factory=list) platform: HatchCppPlatform = Field(default_factory=HatchCppPlatform.default) commands: List[str] = Field(default_factory=list) @@ -125,8 +164,11 @@ class HatchCppBuildPlan(BaseModel): def generate(self): self.commands = [] for library in self.libraries: - flags = self.platform.get_compile_flags(library) - self.commands.append(f"{self.platform.cc} {' '.join(library.sources)} {flags}") + 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}" + ) return self.commands def execute(self): @@ -148,9 +190,3 @@ class HatchCppBuildConfig(BaseModel): verbose: Optional[bool] = Field(default=False) libraries: List[HatchCppLibrary] = Field(default_factory=list) platform: Optional[HatchCppPlatform] = Field(default_factory=HatchCppPlatform.default) - - # build_function: str | None = None - # build_kwargs: t.Mapping[str, str] = field(default_factory=dict) - # editable_build_kwargs: t.Mapping[str, str] = field(default_factory=dict) - # ensured_targets: list[str] = field(default_factory=list) - # skip_if_exists: list[str] = field(default_factory=list) diff --git a/hatch_cpp/tests/test_projects.py b/hatch_cpp/tests/test_projects.py index 4d5a39c..d05755a 100644 --- a/hatch_cpp/tests/test_projects.py +++ b/hatch_cpp/tests/test_projects.py @@ -1,7 +1,7 @@ from os import listdir from pathlib import Path from shutil import rmtree -from subprocess import check_output +from subprocess import check_call from sys import path, platform @@ -9,7 +9,7 @@ class TestProject: def test_basic(self): rmtree("hatch_cpp/tests/test_project_basic/basic_project/extension.so", ignore_errors=True) rmtree("hatch_cpp/tests/test_project_basic/basic_project/extension.pyd", ignore_errors=True) - check_output( + check_call( [ "hatchling", "build", @@ -30,7 +30,7 @@ def test_basic(self): def test_override_classes(self): rmtree("hatch_cpp/tests/test_project_override_classes/basic_project/extension.so", ignore_errors=True) rmtree("hatch_cpp/tests/test_project_override_classes/basic_project/extension.pyd", ignore_errors=True) - check_output( + check_call( [ "hatchling", "build", diff --git a/pyproject.toml b/pyproject.toml index 452e4af..0218232 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,7 +94,7 @@ exclude_also = [ "@(abc\\.)?abstractmethod", ] ignore_errors = true -fail_under = 75 +fail_under = 70 [tool.hatch.build] artifacts = [] From 0b79dc4addd0ab707390ffcc44d4a5b19efa6b0d Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:51:55 -0500 Subject: [PATCH 07/35] Add nanobind and pybind support --- README.md | 2 +- hatch_cpp/structs.py | 42 +++++++++++++--- .../cpp/{basic-project => project}/basic.cpp | 2 +- .../cpp/{basic-project => project}/basic.hpp | 0 .../{basic_project => project}/__init__.py | 0 .../tests/test_project_basic/pyproject.toml | 40 +++------------ .../cpp/project/basic.cpp | 2 + .../cpp/project/basic.hpp | 7 +++ .../project}/__init__.py | 0 .../test_project_nanobind/pyproject.toml | 35 +++++++++++++ .../cpp/{basic-project => project}/basic.cpp | 2 +- .../cpp/{basic-project => project}/basic.hpp | 0 .../project/__init__.py | 0 .../pyproject.toml | 40 +++------------ .../test_project_pybind/cpp/project/basic.cpp | 6 +++ .../test_project_pybind/cpp/project/basic.hpp | 9 ++++ .../test_project_pybind/project/__init__.py | 0 .../tests/test_project_pybind/pyproject.toml | 35 +++++++++++++ hatch_cpp/tests/test_projects.py | 50 ++++++++----------- pyproject.toml | 2 + 20 files changed, 166 insertions(+), 108 deletions(-) rename hatch_cpp/tests/test_project_basic/cpp/{basic-project => project}/basic.cpp (71%) rename hatch_cpp/tests/test_project_basic/cpp/{basic-project => project}/basic.hpp (100%) rename hatch_cpp/tests/test_project_basic/{basic_project => project}/__init__.py (100%) create mode 100644 hatch_cpp/tests/test_project_nanobind/cpp/project/basic.cpp create mode 100644 hatch_cpp/tests/test_project_nanobind/cpp/project/basic.hpp rename hatch_cpp/tests/{test_project_override_classes/basic_project => test_project_nanobind/project}/__init__.py (100%) create mode 100644 hatch_cpp/tests/test_project_nanobind/pyproject.toml rename hatch_cpp/tests/test_project_override_classes/cpp/{basic-project => project}/basic.cpp (71%) rename hatch_cpp/tests/test_project_override_classes/cpp/{basic-project => project}/basic.hpp (100%) create mode 100644 hatch_cpp/tests/test_project_override_classes/project/__init__.py create mode 100644 hatch_cpp/tests/test_project_pybind/cpp/project/basic.cpp create mode 100644 hatch_cpp/tests/test_project_pybind/cpp/project/basic.hpp create mode 100644 hatch_cpp/tests/test_project_pybind/project/__init__.py create mode 100644 hatch_cpp/tests/test_project_pybind/pyproject.toml diff --git a/README.md b/README.md index 0850415..1f26618 100644 --- a/README.md +++ b/README.md @@ -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"]} ] ``` diff --git a/hatch_cpp/structs.py b/hatch_cpp/structs.py index 5bdf707..26f846d 100644 --- a/hatch_cpp/structs.py +++ b/hatch_cpp/structs.py @@ -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"}, @@ -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") @@ -82,22 +89,42 @@ 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) @@ -105,6 +132,8 @@ def get_compile_flags(self, library: HatchCppLibrary, build_type: BuildType = "r 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(" ", " ") @@ -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(): @@ -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): diff --git a/hatch_cpp/tests/test_project_basic/cpp/basic-project/basic.cpp b/hatch_cpp/tests/test_project_basic/cpp/project/basic.cpp similarity index 71% rename from hatch_cpp/tests/test_project_basic/cpp/basic-project/basic.cpp rename to hatch_cpp/tests/test_project_basic/cpp/project/basic.cpp index a7e840e..db4432a 100644 --- a/hatch_cpp/tests/test_project_basic/cpp/basic-project/basic.cpp +++ b/hatch_cpp/tests/test_project_basic/cpp/project/basic.cpp @@ -1,4 +1,4 @@ -#include "basic-project/basic.hpp" +#include "project/basic.hpp" PyObject* hello(PyObject*, PyObject*) { return PyUnicode_FromString("A string"); diff --git a/hatch_cpp/tests/test_project_basic/cpp/basic-project/basic.hpp b/hatch_cpp/tests/test_project_basic/cpp/project/basic.hpp similarity index 100% rename from hatch_cpp/tests/test_project_basic/cpp/basic-project/basic.hpp rename to hatch_cpp/tests/test_project_basic/cpp/project/basic.hpp diff --git a/hatch_cpp/tests/test_project_basic/basic_project/__init__.py b/hatch_cpp/tests/test_project_basic/project/__init__.py similarity index 100% rename from hatch_cpp/tests/test_project_basic/basic_project/__init__.py rename to hatch_cpp/tests/test_project_basic/project/__init__.py diff --git a/hatch_cpp/tests/test_project_basic/pyproject.toml b/hatch_cpp/tests/test_project_basic/pyproject.toml index aea842d..d51683e 100644 --- a/hatch_cpp/tests/test_project_basic/pyproject.toml +++ b/hatch_cpp/tests/test_project_basic/pyproject.toml @@ -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" diff --git a/hatch_cpp/tests/test_project_nanobind/cpp/project/basic.cpp b/hatch_cpp/tests/test_project_nanobind/cpp/project/basic.cpp new file mode 100644 index 0000000..2ac7d56 --- /dev/null +++ b/hatch_cpp/tests/test_project_nanobind/cpp/project/basic.cpp @@ -0,0 +1,2 @@ +#include "project/basic.hpp" + diff --git a/hatch_cpp/tests/test_project_nanobind/cpp/project/basic.hpp b/hatch_cpp/tests/test_project_nanobind/cpp/project/basic.hpp new file mode 100644 index 0000000..1afa022 --- /dev/null +++ b/hatch_cpp/tests/test_project_nanobind/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_override_classes/basic_project/__init__.py b/hatch_cpp/tests/test_project_nanobind/project/__init__.py similarity index 100% rename from hatch_cpp/tests/test_project_override_classes/basic_project/__init__.py rename to hatch_cpp/tests/test_project_nanobind/project/__init__.py diff --git a/hatch_cpp/tests/test_project_nanobind/pyproject.toml b/hatch_cpp/tests/test_project_nanobind/pyproject.toml new file mode 100644 index 0000000..6a8f632 --- /dev/null +++ b/hatch_cpp/tests/test_project_nanobind/pyproject.toml @@ -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"}, +] diff --git a/hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.cpp b/hatch_cpp/tests/test_project_override_classes/cpp/project/basic.cpp similarity index 71% rename from hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.cpp rename to hatch_cpp/tests/test_project_override_classes/cpp/project/basic.cpp index a7e840e..db4432a 100644 --- a/hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.cpp +++ b/hatch_cpp/tests/test_project_override_classes/cpp/project/basic.cpp @@ -1,4 +1,4 @@ -#include "basic-project/basic.hpp" +#include "project/basic.hpp" PyObject* hello(PyObject*, PyObject*) { return PyUnicode_FromString("A string"); diff --git a/hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.hpp b/hatch_cpp/tests/test_project_override_classes/cpp/project/basic.hpp similarity index 100% rename from hatch_cpp/tests/test_project_override_classes/cpp/basic-project/basic.hpp rename to hatch_cpp/tests/test_project_override_classes/cpp/project/basic.hpp diff --git a/hatch_cpp/tests/test_project_override_classes/project/__init__.py b/hatch_cpp/tests/test_project_override_classes/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hatch_cpp/tests/test_project_override_classes/pyproject.toml b/hatch_cpp/tests/test_project_override_classes/pyproject.toml index d7ffab9..57fd83e 100644 --- a/hatch_cpp/tests/test_project_override_classes/pyproject.toml +++ b/hatch_cpp/tests/test_project_override_classes/pyproject.toml @@ -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" diff --git a/hatch_cpp/tests/test_project_pybind/cpp/project/basic.cpp b/hatch_cpp/tests/test_project_pybind/cpp/project/basic.cpp new file mode 100644 index 0000000..ebe96f8 --- /dev/null +++ b/hatch_cpp/tests/test_project_pybind/cpp/project/basic.cpp @@ -0,0 +1,6 @@ +#include "project/basic.hpp" + +std::string hello() { + return "A string"; +} + diff --git a/hatch_cpp/tests/test_project_pybind/cpp/project/basic.hpp b/hatch_cpp/tests/test_project_pybind/cpp/project/basic.hpp new file mode 100644 index 0000000..86053b2 --- /dev/null +++ b/hatch_cpp/tests/test_project_pybind/cpp/project/basic.hpp @@ -0,0 +1,9 @@ +#pragma once +#include +#include + +std::string hello(); + +PYBIND11_MODULE(extension, m) { + m.def("hello", &hello); +} \ No newline at end of file diff --git a/hatch_cpp/tests/test_project_pybind/project/__init__.py b/hatch_cpp/tests/test_project_pybind/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hatch_cpp/tests/test_project_pybind/pyproject.toml b/hatch_cpp/tests/test_project_pybind/pyproject.toml new file mode 100644 index 0000000..b24e6cd --- /dev/null +++ b/hatch_cpp/tests/test_project_pybind/pyproject.toml @@ -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"}, +] diff --git a/hatch_cpp/tests/test_projects.py b/hatch_cpp/tests/test_projects.py index d05755a..34e5bf7 100644 --- a/hatch_cpp/tests/test_projects.py +++ b/hatch_cpp/tests/test_projects.py @@ -2,48 +2,40 @@ from pathlib import Path from shutil import rmtree from subprocess import check_call -from sys import path, platform +from sys import modules, path, platform + +import pytest class TestProject: - def test_basic(self): - rmtree("hatch_cpp/tests/test_project_basic/basic_project/extension.so", ignore_errors=True) - rmtree("hatch_cpp/tests/test_project_basic/basic_project/extension.pyd", ignore_errors=True) + @pytest.mark.parametrize("project", ["test_project_basic", "test_project_override_classes", "test_project_pybind", "test_project_nanobind"]) + def test_basic(self, project): + # cleanup + 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( [ "hatchling", "build", "--hooks-only", ], - cwd="hatch_cpp/tests/test_project_basic", + cwd=f"hatch_cpp/tests/{project}", ) - if platform == "win32": - assert "extension.pyd" in listdir("hatch_cpp/tests/test_project_basic/basic_project") - else: - assert "extension.so" in listdir("hatch_cpp/tests/test_project_basic/basic_project") - here = Path(__file__).parent / "test_project_basic" - path.insert(0, str(here)) - import basic_project.extension - assert basic_project.extension.hello() == "A string" + # assert built - def test_override_classes(self): - rmtree("hatch_cpp/tests/test_project_override_classes/basic_project/extension.so", ignore_errors=True) - rmtree("hatch_cpp/tests/test_project_override_classes/basic_project/extension.pyd", ignore_errors=True) - check_call( - [ - "hatchling", - "build", - "--hooks-only", - ], - cwd="hatch_cpp/tests/test_project_override_classes", - ) if platform == "win32": - assert "extension.pyd" in listdir("hatch_cpp/tests/test_project_override_classes/basic_project") + assert "extension.pyd" in listdir(f"hatch_cpp/tests/{project}/project") else: - assert "extension.so" in listdir("hatch_cpp/tests/test_project_override_classes/basic_project") - here = Path(__file__).parent / "test_project_override_classes" + assert "extension.so" in listdir(f"hatch_cpp/tests/{project}/project") + + # import + here = Path(__file__).parent / project path.insert(0, str(here)) - import basic_project.extension + import project.extension - assert basic_project.extension.hello() == "A string" + assert project.extension.hello() == "A string" diff --git a/pyproject.toml b/pyproject.toml index 0218232..174e84e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,8 @@ develop = [ "twine", "wheel", # test + "nanobind", + "pybind11", "pytest", "pytest-cov", ] From 4efa073d7b93b5a0a528bc8332ac66329feacb1c Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:41:24 -0500 Subject: [PATCH 08/35] =?UTF-8?q?Bump=20version:=200.1.1=20=E2=86=92=200.1?= =?UTF-8?q?.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hatch_cpp/__init__.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hatch_cpp/__init__.py b/hatch_cpp/__init__.py index 88f1932..4007071 100644 --- a/hatch_cpp/__init__.py +++ b/hatch_cpp/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.1" +__version__ = "0.1.2" from .hooks import hatch_register_build_hook from .plugin import HatchCppBuildHook diff --git a/pyproject.toml b/pyproject.toml index 174e84e..34a0153 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [{name = "the hatch-cpp authors", email = "t.paine154@gmail.com"}] description = "Hatch plugin for C++ builds" readme = "README.md" license = { text = "Apache-2.0" } -version = "0.1.1" +version = "0.1.2" requires-python = ">=3.9" keywords = [ "hatch", @@ -62,7 +62,7 @@ Repository = "https://github.com/python-project-templates/hatch-cpp" Homepage = "https://github.com/python-project-templates/hatch-cpp" [tool.bumpversion] -current_version = "0.1.1" +current_version = "0.1.2" commit = true tag = false From 090cdd0fdb170495375a3a7fd6e4805ac50aa4e4 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:48:38 -0500 Subject: [PATCH 09/35] Form wheel name --- hatch_cpp/plugin.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/hatch_cpp/plugin.py b/hatch_cpp/plugin.py index 469ee39..7fcbfbf 100644 --- a/hatch_cpp/plugin.py +++ b/hatch_cpp/plugin.py @@ -2,6 +2,8 @@ import logging import os +import platform as sysplatform +import sys import typing as t from hatchling.builders.hooks.plugin.interface import BuildHookInterface @@ -18,7 +20,7 @@ class HatchCppBuildHook(BuildHookInterface[HatchCppBuildConfig]): PLUGIN_NAME = "hatch-cpp" _logger = logging.getLogger(__name__) - def initialize(self, version: str, _: dict[str, t.Any]) -> None: + def initialize(self, version: str, build_data: dict[str, t.Any]) -> None: """Initialize the plugin.""" # Log some basic information self._logger.info("Initializing hatch-cpp plugin version %s", version) @@ -30,6 +32,19 @@ def initialize(self, version: str, _: dict[str, t.Any]) -> None: self._logger.info("ignoring target name %s", self.target_name) return + build_data["pure_python"] = False + machine = sysplatform.machine() + version_major = sys.version_info.major + version_minor = sys.version_info.minor + # TODO abi3 + if "darwin" in sys.platform: + os_name = "macosx_11_0" + elif "linux" in sys.platform: + os_name = "linux" + else: + os_name = "win" + build_data["tag"] = f"cp{version_major}{version_minor}-cp{version_major}{version_minor}-{os_name}_{machine}" + # Skip if SKIP_HATCH_CPP is set # TODO: Support CLI once https://github.com/pypa/hatch/pull/1743 if os.getenv("SKIP_HATCH_CPP"): From 3173bb89b644a40b33185f8fd550fe980264e629 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:53:05 -0500 Subject: [PATCH 10/35] =?UTF-8?q?Bump=20version:=200.1.2=20=E2=86=92=200.1?= =?UTF-8?q?.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hatch_cpp/__init__.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hatch_cpp/__init__.py b/hatch_cpp/__init__.py index 4007071..6ec6cb1 100644 --- a/hatch_cpp/__init__.py +++ b/hatch_cpp/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.2" +__version__ = "0.1.3" from .hooks import hatch_register_build_hook from .plugin import HatchCppBuildHook diff --git a/pyproject.toml b/pyproject.toml index 34a0153..a49115b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [{name = "the hatch-cpp authors", email = "t.paine154@gmail.com"}] description = "Hatch plugin for C++ builds" readme = "README.md" license = { text = "Apache-2.0" } -version = "0.1.2" +version = "0.1.3" requires-python = ">=3.9" keywords = [ "hatch", @@ -62,7 +62,7 @@ Repository = "https://github.com/python-project-templates/hatch-cpp" Homepage = "https://github.com/python-project-templates/hatch-cpp" [tool.bumpversion] -current_version = "0.1.2" +current_version = "0.1.3" commit = true tag = false From a30986d7cd97344bcd13408d736c99e440fba026 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:16:10 -0500 Subject: [PATCH 11/35] Force include binaries --- hatch_cpp/plugin.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hatch_cpp/plugin.py b/hatch_cpp/plugin.py index 7fcbfbf..4166551 100644 --- a/hatch_cpp/plugin.py +++ b/hatch_cpp/plugin.py @@ -80,3 +80,11 @@ def initialize(self, version: str, build_data: dict[str, t.Any]) -> None: # Perform any cleanup actions build_plan.cleanup() + + # force include libraries + for library in libraries: + if build_plan.platform.platform == "win32": + suffix = "dll" + else: + suffix = "so" + build_data["force_include"][f"{library.name}.{suffix}"] = f"{library.name}.{suffix}" From 27d4793ccc10029d3dddc7569e54afbaee7d1051 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:19:43 -0500 Subject: [PATCH 12/35] =?UTF-8?q?Bump=20version:=200.1.3=20=E2=86=92=200.1?= =?UTF-8?q?.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hatch_cpp/__init__.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hatch_cpp/__init__.py b/hatch_cpp/__init__.py index 6ec6cb1..7b0b672 100644 --- a/hatch_cpp/__init__.py +++ b/hatch_cpp/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.3" +__version__ = "0.1.4" from .hooks import hatch_register_build_hook from .plugin import HatchCppBuildHook diff --git a/pyproject.toml b/pyproject.toml index a49115b..4e90653 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [{name = "the hatch-cpp authors", email = "t.paine154@gmail.com"}] description = "Hatch plugin for C++ builds" readme = "README.md" license = { text = "Apache-2.0" } -version = "0.1.3" +version = "0.1.4" requires-python = ">=3.9" keywords = [ "hatch", @@ -62,7 +62,7 @@ Repository = "https://github.com/python-project-templates/hatch-cpp" Homepage = "https://github.com/python-project-templates/hatch-cpp" [tool.bumpversion] -current_version = "0.1.3" +current_version = "0.1.4" commit = true tag = false From 7ab98620ec5e6a3de6176d59044b83d116fd3b52 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:08:38 -0500 Subject: [PATCH 13/35] Fix library qualified name on windows --- hatch_cpp/plugin.py | 7 ++----- hatch_cpp/structs.py | 9 +++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/hatch_cpp/plugin.py b/hatch_cpp/plugin.py index 4166551..2e29539 100644 --- a/hatch_cpp/plugin.py +++ b/hatch_cpp/plugin.py @@ -83,8 +83,5 @@ def initialize(self, version: str, build_data: dict[str, t.Any]) -> None: # force include libraries for library in libraries: - if build_plan.platform.platform == "win32": - suffix = "dll" - else: - suffix = "so" - build_data["force_include"][f"{library.name}.{suffix}"] = f"{library.name}.{suffix}" + name = library.get_qualified_name(build_plan.platform.platform) + build_data["force_include"][name] = name diff --git a/hatch_cpp/structs.py b/hatch_cpp/structs.py index 26f846d..1492720 100644 --- a/hatch_cpp/structs.py +++ b/hatch_cpp/structs.py @@ -52,6 +52,15 @@ class HatchCppLibrary(BaseModel): export_symbols: List[str] = Field(default_factory=list, alias="export-symbols") depends: List[str] = Field(default_factory=list) + def get_qualified_name(self, platform): + if platform == "win32": + suffix = "dll" if self.binding == "none" else "pyd" + elif platform == "darwin" and self.binding == "none": + suffix = "dylib" + else: + suffix = "so" + return f"{self.name}.{suffix}" + class HatchCppPlatform(BaseModel): cc: str From 6a54762309860cdcbfb2cbbdbcc07c7071bcf4bd Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:12:23 -0500 Subject: [PATCH 14/35] =?UTF-8?q?Bump=20version:=200.1.4=20=E2=86=92=200.1?= =?UTF-8?q?.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hatch_cpp/__init__.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hatch_cpp/__init__.py b/hatch_cpp/__init__.py index 7b0b672..02e0ebb 100644 --- a/hatch_cpp/__init__.py +++ b/hatch_cpp/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" from .hooks import hatch_register_build_hook from .plugin import HatchCppBuildHook diff --git a/pyproject.toml b/pyproject.toml index 4e90653..9b3ca91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [{name = "the hatch-cpp authors", email = "t.paine154@gmail.com"}] description = "Hatch plugin for C++ builds" readme = "README.md" license = { text = "Apache-2.0" } -version = "0.1.4" +version = "0.1.5" requires-python = ">=3.9" keywords = [ "hatch", @@ -62,7 +62,7 @@ Repository = "https://github.com/python-project-templates/hatch-cpp" Homepage = "https://github.com/python-project-templates/hatch-cpp" [tool.bumpversion] -current_version = "0.1.4" +current_version = "0.1.5" commit = true tag = false From 14525ea805cfc17a5e212d29f28cea7c0a8dd939 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 12 Jan 2025 23:13:03 -0500 Subject: [PATCH 15/35] Remove double build during dist --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1473fb6..f1ad97c 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,7 @@ dist-build: # build python dists dist-check: ## run python dist checker with twine python -m twine check dist/* -dist: clean build dist-build dist-check ## build all dists +dist: clean dist-build dist-check ## build all dists publish: dist # publish python assets From 256889704dea37b505b0dea6562799384ff16f5e Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 14 Jan 2025 09:56:07 -0500 Subject: [PATCH 16/35] Add support for limited abi, fixes #18 --- hatch_cpp/plugin.py | 30 +++++----- hatch_cpp/structs.py | 57 +++++++++++++------ hatch_cpp/tests/test_all.py | 2 - .../cpp/project/basic.cpp | 5 ++ .../cpp/project/basic.hpp | 17 ++++++ .../project/__init__.py | 0 .../test_project_limited_api/pyproject.toml | 35 ++++++++++++ .../test_project_nanobind/pyproject.toml | 2 +- .../pyproject.toml | 2 +- .../tests/test_project_pybind/pyproject.toml | 2 +- hatch_cpp/tests/test_projects.py | 13 +++-- hatch_cpp/tests/test_structs.py | 26 +++++++++ 12 files changed, 153 insertions(+), 38 deletions(-) delete mode 100644 hatch_cpp/tests/test_all.py create mode 100644 hatch_cpp/tests/test_project_limited_api/cpp/project/basic.cpp create mode 100644 hatch_cpp/tests/test_project_limited_api/cpp/project/basic.hpp create mode 100644 hatch_cpp/tests/test_project_limited_api/project/__init__.py create mode 100644 hatch_cpp/tests/test_project_limited_api/pyproject.toml create mode 100644 hatch_cpp/tests/test_structs.py diff --git a/hatch_cpp/plugin.py b/hatch_cpp/plugin.py index 2e29539..ed88546 100644 --- a/hatch_cpp/plugin.py +++ b/hatch_cpp/plugin.py @@ -32,19 +32,6 @@ def initialize(self, version: str, build_data: dict[str, t.Any]) -> None: self._logger.info("ignoring target name %s", self.target_name) return - build_data["pure_python"] = False - machine = sysplatform.machine() - version_major = sys.version_info.major - version_minor = sys.version_info.minor - # TODO abi3 - if "darwin" in sys.platform: - os_name = "macosx_11_0" - elif "linux" in sys.platform: - os_name = "linux" - else: - os_name = "win" - build_data["tag"] = f"cp{version_major}{version_minor}-cp{version_major}{version_minor}-{os_name}_{machine}" - # Skip if SKIP_HATCH_CPP is set # TODO: Support CLI once https://github.com/pypa/hatch/pull/1743 if os.getenv("SKIP_HATCH_CPP"): @@ -85,3 +72,20 @@ def initialize(self, version: str, build_data: dict[str, t.Any]) -> None: for library in libraries: name = library.get_qualified_name(build_plan.platform.platform) build_data["force_include"][name] = name + + if libraries: + build_data["pure_python"] = False + machine = sysplatform.machine() + version_major = sys.version_info.major + version_minor = sys.version_info.minor + # TODO abi3 + if "darwin" in sys.platform: + os_name = "macosx_11_0" + elif "linux" in sys.platform: + os_name = "linux" + else: + os_name = "win" + if all([lib.py_limited_api for lib in libraries]): + build_data["tag"] = f"cp{version_major}{version_minor}-abi3-{os_name}_{machine}" + else: + build_data["tag"] = f"cp{version_major}{version_minor}-cp{version_major}{version_minor}-{os_name}_{machine}" diff --git a/hatch_cpp/structs.py b/hatch_cpp/structs.py index 1492720..51de9da 100644 --- a/hatch_cpp/structs.py +++ b/hatch_cpp/structs.py @@ -2,12 +2,13 @@ from os import environ, system from pathlib import Path +from re import match from shutil import which from sys import executable, platform as sys_platform from sysconfig import get_path -from typing import List, Literal, Optional +from typing import Any, List, Literal, Optional -from pydantic import BaseModel, Field +from pydantic import AliasChoices, BaseModel, Field, field_validator, model_validator __all__ = ( "HatchCppBuildConfig", @@ -28,7 +29,7 @@ } -class HatchCppLibrary(BaseModel): +class HatchCppLibrary(BaseModel, validate_assignment=True): """A C++ library.""" name: str @@ -38,29 +39,47 @@ class HatchCppLibrary(BaseModel): 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") + include_dirs: List[str] = Field(default_factory=list, alias=AliasChoices("include_dirs", "include-dirs")) + library_dirs: List[str] = Field(default_factory=list, alias=AliasChoices("library_dirs", "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") + extra_compile_args: List[str] = Field(default_factory=list, alias=AliasChoices("extra_compile_args", "extra-compile-args")) + extra_link_args: List[str] = Field(default_factory=list, alias=AliasChoices("extra_link_args", "extra-link-args")) + extra_objects: List[str] = Field(default_factory=list, alias=AliasChoices("extra_objects", "extra-objects")) - define_macros: List[str] = Field(default_factory=list, alias="define-macros") - undef_macros: List[str] = Field(default_factory=list, alias="undef-macros") + define_macros: List[str] = Field(default_factory=list, alias=AliasChoices("define_macros", "define-macros")) + undef_macros: List[str] = Field(default_factory=list, alias=AliasChoices("undef_macros", "undef-macros")) - export_symbols: List[str] = Field(default_factory=list, alias="export-symbols") + export_symbols: List[str] = Field(default_factory=list, alias=AliasChoices("export_symbols", "export-symbols")) depends: List[str] = Field(default_factory=list) + py_limited_api: Optional[str] = Field(default="", alias=AliasChoices("py_limited_api", "py-limited-api")) + + @field_validator("py_limited_api", mode="before") + @classmethod + def check_py_limited_api(cls, value: Any) -> Any: + if value: + if not match(r"cp3\d", value): + raise ValueError("py-limited-api must be in the form of cp3X") + return value + def get_qualified_name(self, platform): if platform == "win32": suffix = "dll" if self.binding == "none" else "pyd" - elif platform == "darwin" and self.binding == "none": - suffix = "dylib" + elif platform == "darwin": + suffix = "dylib" if self.binding == "none" else "so" else: suffix = "so" + if self.py_limited_api and platform != "win32": + return f"{self.name}.abi3.{suffix}" return f"{self.name}.{suffix}" + @model_validator(mode="after") + def check_binding_and_py_limited_api(self): + if self.binding == "pybind11" and self.py_limited_api: + raise ValueError("pybind11 does not support Py_LIMITED_API") + return self + class HatchCppPlatform(BaseModel): cc: str @@ -117,6 +136,12 @@ def get_compile_flags(self, library: HatchCppLibrary, build_type: BuildType = "r 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 library.py_limited_api: + if library.binding == "pybind11": + raise ValueError("pybind11 does not support Py_LIMITED_API") + library.define_macros.append(f"Py_LIMITED_API=0x0{library.py_limited_api[2]}0{hex(int(library.py_limited_api[3:]))[2:]}00f0") + + # Toolchain-specific flags if self.toolchain == "gcc": flags += " " + " ".join(f"-I{d}" for d in library.include_dirs) flags += " -fPIC" @@ -156,7 +181,7 @@ def get_link_flags(self, library: HatchCppLibrary, build_type: BuildType = "rele flags += " " + " ".join(library.extra_objects) flags += " " + " ".join(f"-l{lib}" for lib in library.libraries) flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs) - flags += f" -o {library.name}.so" + flags += f" -o {library.get_qualified_name(self.platform)}" if self.platform == "darwin": flags += " -undefined dynamic_lookup" if "mold" in self.ld: @@ -169,7 +194,7 @@ def get_link_flags(self, library: HatchCppLibrary, build_type: BuildType = "rele flags += " " + " ".join(library.extra_objects) flags += " " + " ".join(f"-l{lib}" for lib in library.libraries) flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs) - flags += f" -o {library.name}.so" + flags += f" -o {library.get_qualified_name(self.platform)}" if self.platform == "darwin": flags += " -undefined dynamic_lookup" if "mold" in self.ld: @@ -180,7 +205,7 @@ 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" /Fe:{library.name}.pyd" + flags += f" /Fe:{library.get_qualified_name(self.platform)}" flags += " /link /DLL" if (Path(executable).parent / "libs").exists(): flags += f" /LIBPATH:{str(Path(executable).parent / 'libs')}" diff --git a/hatch_cpp/tests/test_all.py b/hatch_cpp/tests/test_all.py deleted file mode 100644 index 82959de..0000000 --- a/hatch_cpp/tests/test_all.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_import(): - pass diff --git a/hatch_cpp/tests/test_project_limited_api/cpp/project/basic.cpp b/hatch_cpp/tests/test_project_limited_api/cpp/project/basic.cpp new file mode 100644 index 0000000..db4432a --- /dev/null +++ b/hatch_cpp/tests/test_project_limited_api/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_limited_api/cpp/project/basic.hpp b/hatch_cpp/tests/test_project_limited_api/cpp/project/basic.hpp new file mode 100644 index 0000000..65cb62e --- /dev/null +++ b/hatch_cpp/tests/test_project_limited_api/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_limited_api/project/__init__.py b/hatch_cpp/tests/test_project_limited_api/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hatch_cpp/tests/test_project_limited_api/pyproject.toml b/hatch_cpp/tests/test_project_limited_api/pyproject.toml new file mode 100644 index 0000000..e1157e3 --- /dev/null +++ b/hatch_cpp/tests/test_project_limited_api/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["hatchling>=1.20"] +build-backend = "hatchling.build" + +[project] +name = "hatch-cpp-test-project-limtied-api" +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"], py-limited-api = "cp39"}, +] diff --git a/hatch_cpp/tests/test_project_nanobind/pyproject.toml b/hatch_cpp/tests/test_project_nanobind/pyproject.toml index 6a8f632..bd03189 100644 --- a/hatch_cpp/tests/test_project_nanobind/pyproject.toml +++ b/hatch_cpp/tests/test_project_nanobind/pyproject.toml @@ -3,7 +3,7 @@ requires = ["hatchling>=1.20"] build-backend = "hatchling.build" [project] -name = "hatch-cpp-test-project-basic" +name = "hatch-cpp-test-project-nanobind" description = "Basic test project for hatch-cpp" version = "0.1.0" requires-python = ">=3.9" diff --git a/hatch_cpp/tests/test_project_override_classes/pyproject.toml b/hatch_cpp/tests/test_project_override_classes/pyproject.toml index 57fd83e..90e3215 100644 --- a/hatch_cpp/tests/test_project_override_classes/pyproject.toml +++ b/hatch_cpp/tests/test_project_override_classes/pyproject.toml @@ -3,7 +3,7 @@ requires = ["hatchling>=1.20"] build-backend = "hatchling.build" [project] -name = "hatch-cpp-test-project-basic" +name = "hatch-cpp-test-project-override-classes" description = "Basic test project for hatch-cpp" version = "0.1.0" requires-python = ">=3.9" diff --git a/hatch_cpp/tests/test_project_pybind/pyproject.toml b/hatch_cpp/tests/test_project_pybind/pyproject.toml index b24e6cd..38e279e 100644 --- a/hatch_cpp/tests/test_project_pybind/pyproject.toml +++ b/hatch_cpp/tests/test_project_pybind/pyproject.toml @@ -3,7 +3,7 @@ requires = ["hatchling>=1.20"] build-backend = "hatchling.build" [project] -name = "hatch-cpp-test-project-basic" +name = "hatch-cpp-test-project-pybind" description = "Basic test project for hatch-cpp" version = "0.1.0" requires-python = ">=3.9" diff --git a/hatch_cpp/tests/test_projects.py b/hatch_cpp/tests/test_projects.py index 34e5bf7..52ea248 100644 --- a/hatch_cpp/tests/test_projects.py +++ b/hatch_cpp/tests/test_projects.py @@ -8,7 +8,9 @@ class TestProject: - @pytest.mark.parametrize("project", ["test_project_basic", "test_project_override_classes", "test_project_pybind", "test_project_nanobind"]) + @pytest.mark.parametrize( + "project", ["test_project_basic", "test_project_override_classes", "test_project_pybind", "test_project_nanobind", "test_project_limited_api"] + ) def test_basic(self, project): # cleanup rmtree(f"hatch_cpp/tests/{project}/project/extension.so", ignore_errors=True) @@ -28,10 +30,13 @@ def test_basic(self, project): # assert built - if platform == "win32": - assert "extension.pyd" in listdir(f"hatch_cpp/tests/{project}/project") + if project == "test_project_limited_api" and platform != "win32": + assert "extension.abi3.so" in listdir(f"hatch_cpp/tests/{project}/project") else: - assert "extension.so" in listdir(f"hatch_cpp/tests/{project}/project") + 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 diff --git a/hatch_cpp/tests/test_structs.py b/hatch_cpp/tests/test_structs.py new file mode 100644 index 0000000..0aacd31 --- /dev/null +++ b/hatch_cpp/tests/test_structs.py @@ -0,0 +1,26 @@ +import pytest +from pydantic import ValidationError + +from hatch_cpp.structs import HatchCppLibrary, HatchCppPlatform + + +class TestStructs: + def test_validate_py_limited_api(self): + with pytest.raises(ValidationError): + library = HatchCppLibrary( + name="test", + sources=["test.cpp"], + py_limited_api="42", + ) + library = HatchCppLibrary( + name="test", + sources=["test.cpp"], + py_limited_api="cp39", + ) + assert library.py_limited_api == "cp39" + platform = HatchCppPlatform.default() + flags = platform.get_compile_flags(library) + assert "-DPy_LIMITED_API=0x030900f0" in flags or "/DPy_LIMITED_API=0x030900f0" in flags + + with pytest.raises(ValidationError): + library.binding = "pybind11" From d7a5a40ce04a5fb628535d4095216826d04f7dc4 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:14:15 -0500 Subject: [PATCH 17/35] =?UTF-8?q?Bump=20version:=200.1.5=20=E2=86=92=200.1?= =?UTF-8?q?.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hatch_cpp/__init__.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hatch_cpp/__init__.py b/hatch_cpp/__init__.py index 02e0ebb..f6880d2 100644 --- a/hatch_cpp/__init__.py +++ b/hatch_cpp/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.5" +__version__ = "0.1.6" from .hooks import hatch_register_build_hook from .plugin import HatchCppBuildHook diff --git a/pyproject.toml b/pyproject.toml index 9b3ca91..82119f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [{name = "the hatch-cpp authors", email = "t.paine154@gmail.com"}] description = "Hatch plugin for C++ builds" readme = "README.md" license = { text = "Apache-2.0" } -version = "0.1.5" +version = "0.1.6" requires-python = ">=3.9" keywords = [ "hatch", @@ -62,7 +62,7 @@ Repository = "https://github.com/python-project-templates/hatch-cpp" Homepage = "https://github.com/python-project-templates/hatch-cpp" [tool.bumpversion] -current_version = "0.1.5" +current_version = "0.1.6" commit = true tag = false From 641f1b0354c48fb336d148c75308d272cf48345e Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:24:32 -0500 Subject: [PATCH 18/35] Enable basic CMake build --- .gitignore | 1 + hatch_cpp/plugin.py | 76 ++++++---- hatch_cpp/structs.py | 117 +++++++++++---- .../tests/test_project_cmake/CMakeLists.txt | 92 ++++++++++++ hatch_cpp/tests/test_project_cmake/Makefile | 140 ++++++++++++++++++ .../test_project_cmake/cpp/project/basic.cpp | 5 + .../test_project_cmake/cpp/project/basic.hpp | 17 +++ .../test_project_cmake/project/__init__.py | 0 .../tests/test_project_cmake/pyproject.toml | 39 +++++ hatch_cpp/tests/test_projects.py | 10 +- hatch_cpp/tests/test_structs.py | 24 ++- hatch_cpp/utils.py | 120 --------------- pyproject.toml | 1 + 13 files changed, 468 insertions(+), 174 deletions(-) create mode 100644 hatch_cpp/tests/test_project_cmake/CMakeLists.txt create mode 100644 hatch_cpp/tests/test_project_cmake/Makefile create mode 100644 hatch_cpp/tests/test_project_cmake/cpp/project/basic.cpp create mode 100644 hatch_cpp/tests/test_project_cmake/cpp/project/basic.hpp create mode 100644 hatch_cpp/tests/test_project_cmake/project/__init__.py create mode 100644 hatch_cpp/tests/test_project_cmake/pyproject.toml diff --git a/.gitignore b/.gitignore index 858d10c..5e96e1e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ dist/ downloads/ eggs/ .eggs/ +include/ lib/ lib64/ parts/ diff --git a/hatch_cpp/plugin.py b/hatch_cpp/plugin.py index ed88546..ab6ebf8 100644 --- a/hatch_cpp/plugin.py +++ b/hatch_cpp/plugin.py @@ -1,10 +1,11 @@ from __future__ import annotations -import logging -import os -import platform as sysplatform -import sys -import typing as t +from logging import getLogger +from os import getenv +from pathlib import Path +from platform import machine as platform_machine +from sys import platform as sys_platform, version_info +from typing import Any from hatchling.builders.hooks.plugin.interface import BuildHookInterface @@ -18,13 +19,14 @@ class HatchCppBuildHook(BuildHookInterface[HatchCppBuildConfig]): """The hatch-cpp build hook.""" PLUGIN_NAME = "hatch-cpp" - _logger = logging.getLogger(__name__) + _logger = getLogger(__name__) - def initialize(self, version: str, build_data: dict[str, t.Any]) -> None: + def initialize(self, version: str, build_data: dict[str, Any]) -> None: """Initialize the plugin.""" # Log some basic information + project_name = self.metadata.config["project"]["name"] self._logger.info("Initializing hatch-cpp plugin version %s", version) - self._logger.info("Running hatch-cpp") + self._logger.info(f"Running hatch-cpp: {project_name}") # Only run if creating wheel # TODO: Add support for specify sdist-plan @@ -34,7 +36,7 @@ def initialize(self, version: str, build_data: dict[str, t.Any]) -> None: # Skip if SKIP_HATCH_CPP is set # TODO: Support CLI once https://github.com/pypa/hatch/pull/1743 - if os.getenv("SKIP_HATCH_CPP"): + if getenv("SKIP_HATCH_CPP"): self._logger.info("Skipping the build hook since SKIP_HATCH_CPP was set") return @@ -42,17 +44,13 @@ def initialize(self, version: str, build_data: dict[str, t.Any]) -> None: build_config_class = import_string(self.config["build-config-class"]) if "build-config-class" in self.config else HatchCppBuildConfig # Instantiate build config - config = build_config_class(**self.config) - - # Grab libraries and platform - libraries = config.libraries - platform = config.platform + config = build_config_class(name=project_name, **self.config) # Get build plan class or use default build_plan_class = import_string(self.config["build-plan-class"]) if "build-plan-class" in self.config else HatchCppBuildPlan # Instantiate builder - build_plan = build_plan_class(libraries=libraries, platform=platform) + build_plan = build_plan_class(**config.model_dump()) # Generate commands build_plan.generate() @@ -68,24 +66,48 @@ def initialize(self, version: str, build_data: dict[str, t.Any]) -> None: # Perform any cleanup actions build_plan.cleanup() - # force include libraries - for library in libraries: - name = library.get_qualified_name(build_plan.platform.platform) - build_data["force_include"][name] = name + if build_plan.libraries: + # force include libraries + for library in build_plan.libraries: + name = library.get_qualified_name(build_plan.platform.platform) + build_data["force_include"][name] = name - if libraries: build_data["pure_python"] = False - machine = sysplatform.machine() - version_major = sys.version_info.major - version_minor = sys.version_info.minor - # TODO abi3 - if "darwin" in sys.platform: + machine = platform_machine() + version_major = version_info.major + version_minor = version_info.minor + if "darwin" in sys_platform: os_name = "macosx_11_0" - elif "linux" in sys.platform: + elif "linux" in sys_platform: os_name = "linux" else: os_name = "win" - if all([lib.py_limited_api for lib in libraries]): + if all([lib.py_limited_api for lib in build_plan.libraries]): build_data["tag"] = f"cp{version_major}{version_minor}-abi3-{os_name}_{machine}" else: build_data["tag"] = f"cp{version_major}{version_minor}-cp{version_major}{version_minor}-{os_name}_{machine}" + else: + build_data["pure_python"] = False + machine = platform_machine() + version_major = version_info.major + version_minor = version_info.minor + # TODO abi3 + if "darwin" in sys_platform: + os_name = "macosx_11_0" + elif "linux" in sys_platform: + os_name = "linux" + else: + os_name = "win" + build_data["tag"] = f"cp{version_major}{version_minor}-cp{version_major}{version_minor}-{os_name}_{machine}" + + # force include libraries + for path in Path(".").rglob("*"): + if path.is_dir(): + continue + if str(path).startswith(str(build_plan.cmake.build)) or str(path).startswith("dist"): + continue + if path.suffix in (".pyd", ".dll", ".so", ".dylib"): + build_data["force_include"][str(path)] = str(path) + + for path in build_data["force_include"]: + self._logger.warning(f"Force include: {path}") diff --git a/hatch_cpp/structs.py b/hatch_cpp/structs.py index 51de9da..75df2d8 100644 --- a/hatch_cpp/structs.py +++ b/hatch_cpp/structs.py @@ -1,12 +1,12 @@ from __future__ import annotations -from os import environ, system +from os import environ, system as system_call from pathlib import Path from re import match from shutil import which -from sys import executable, platform as sys_platform +from sys import executable, platform as sys_platform, version_info from sysconfig import get_path -from typing import Any, List, Literal, Optional +from typing import Any, Dict, List, Literal, Optional from pydantic import AliasChoices, BaseModel, Field, field_validator, model_validator @@ -20,7 +20,7 @@ BuildType = Literal["debug", "release"] CompilerToolchain = Literal["gcc", "clang", "msvc"] Language = Literal["c", "c++"] -Binding = Literal["cpython", "pybind11", "nanobind"] +Binding = Literal["cpython", "pybind11", "nanobind", "generic"] Platform = Literal["linux", "darwin", "win32"] PlatformDefaults = { "linux": {"CC": "gcc", "CXX": "g++", "LD": "ld"}, @@ -65,9 +65,9 @@ def check_py_limited_api(cls, value: Any) -> Any: def get_qualified_name(self, platform): if platform == "win32": - suffix = "dll" if self.binding == "none" else "pyd" + suffix = "dll" if self.binding == "generic" else "pyd" elif platform == "darwin": - suffix = "dylib" if self.binding == "none" else "so" + suffix = "dylib" if self.binding == "generic" else "so" else: suffix = "so" if self.py_limited_api and platform != "win32": @@ -78,6 +78,8 @@ def get_qualified_name(self, platform): def check_binding_and_py_limited_api(self): if self.binding == "pybind11" and self.py_limited_api: raise ValueError("pybind11 does not support Py_LIMITED_API") + if self.binding == "generic" and self.py_limited_api: + raise ValueError("Generic binding can not support Py_LIMITED_API") return self @@ -119,7 +121,8 @@ def get_compile_flags(self, library: HatchCppLibrary, build_type: BuildType = "r flags = "" # Python.h - library.include_dirs.append(get_path("include")) + if library.binding != "generic": + library.include_dirs.append(get_path("include")) if library.binding == "pybind11": import pybind11 @@ -217,36 +220,100 @@ def get_link_flags(self, library: HatchCppLibrary, build_type: BuildType = "rele return flags -class HatchCppBuildPlan(BaseModel): - build_type: BuildType = "release" +class HatchCppCmakeConfiguration(BaseModel): + root: Path + build: Path = Field(default_factory=lambda: Path("build")) + install: Optional[Path] = Field(default=None) + + cmake_arg_prefix: Optional[str] = Field(default=None) + 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) + + +class HatchCppBuildConfig(BaseModel): + """Build config values for Hatch C++ Builder.""" + + verbose: Optional[bool] = Field(default=False) + name: Optional[str] = Field(default=None) libraries: List[HatchCppLibrary] = Field(default_factory=list) - platform: HatchCppPlatform = Field(default_factory=HatchCppPlatform.default) + 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: + raise ValueError("Must not provide libraries when using cmake toolchain.") + return self + + +class HatchCppBuildPlan(HatchCppBuildConfig): + build_type: BuildType = "release" commands: List[str] = Field(default_factory=list) def generate(self): self.commands = [] - 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}" - ) + if self.libraries: + 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: + # Derive prefix + if self.cmake.cmake_arg_prefix is None: + self.cmake.cmake_arg_prefix = f"{self.name.replace('.', '_').replace('-', '_').upper()}_" + + # Append base command + self.commands.append(f"cmake {Path(self.cmake.root).parent} -DCMAKE_BUILD_TYPE={self.build_type} -B {self.cmake.build}") + + # Setup install path + if self.cmake.install: + self.commands[-1] += f" -DCMAKE_INSTALL_PREFIX={self.cmake.install}" + else: + self.commands[-1] += f" -DCMAKE_INSTALL_PREFIX={Path(self.cmake.root).parent}" + + # TODO: CMAKE_CXX_COMPILER + if self.platform.platform == "win32": + # TODO: prefix? + self.commands[-1] += f' -G "{environ.get("GENERATOR", "Visual Studio 17 2022")}"' + + # Put in CMake flags + args = self.cmake.cmake_args.copy() + for platform, env_args in self.cmake.cmake_env_args.items(): + if platform == self.platform.platform: + for key, value in env_args.items(): + args[key] = value + for key, value in args.items(): + self.commands[-1] += f" -D{self.cmake.cmake_arg_prefix}{key.upper()}={value}" + + # Include customs + if self.cmake.include_flags: + if self.cmake.include_flags.get("python_version", False): + self.commands[-1] += f" -D{self.cmake.cmake_arg_prefix}PYTHON_VERSION={version_info.major}.{version_info.minor}" + if self.cmake.include_flags.get("manylinux", False) and self.platform.platform == "linux": + self.commands[-1] += f" -D{self.cmake.cmake_arg_prefix}MANYLINUX=ON" + + # Include mac deployment target + if self.platform.platform == "darwin": + self.commands[-1] += f" -DCMAKE_OSX_DEPLOYMENT_TARGET={environ.get('OSX_DEPLOYMENT_TARGET', '11')}" + + # Append build command + self.commands.append(f"cmake --build {self.cmake.build} --config {self.build_type}") + + # Append install command + self.commands.append(f"cmake --install {self.cmake.build} --config {self.build_type}") + return self.commands def execute(self): for command in self.commands: - system(command) + system_call(command) return self.commands def cleanup(self): if self.platform.platform == "win32": for temp_obj in Path(".").glob("*.obj"): temp_obj.unlink() - - -class HatchCppBuildConfig(BaseModel): - """Build config values for Hatch C++ Builder.""" - - verbose: Optional[bool] = Field(default=False) - libraries: List[HatchCppLibrary] = Field(default_factory=list) - platform: Optional[HatchCppPlatform] = Field(default_factory=HatchCppPlatform.default) diff --git a/hatch_cpp/tests/test_project_cmake/CMakeLists.txt b/hatch_cpp/tests/test_project_cmake/CMakeLists.txt new file mode 100644 index 0000000..6344c70 --- /dev/null +++ b/hatch_cpp/tests/test_project_cmake/CMakeLists.txt @@ -0,0 +1,92 @@ +cmake_minimum_required(VERSION 3.20.0) +project(hatch-cpp-test-project-basic VERSION "0.1.0") +set(CMAKE_CXX_STANDARD 20) +include(CheckCCompilerFlag) +include(CheckLinkerFlag) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set(WIN32 ON) + set(MACOS OFF) + set(LINUX OFF) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(WIN32 OFF) + set(MACOS ON) + set(LINUX OFF) +else() + set(WIN32 OFF) + set(MACOS OFF) + set(LINUX ON) +endif() + +option(CMAKE_BUILD_TYPE "Release/Debug build" RELEASE) +option(HATCH_CPP_TEST_PROJECT_BASIC_BUILD_TESTS "Build tests" OFF) +option(HATCH_CPP_TEST_PROJECT_BASIC_MANYLINUX "Build for python's manylinux setup" OFF) + +string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) + +set(BUILD_SHARED_LIBS TRUE) +set(CMAKE_MACOSX_RPATH TRUE) +set(CMAKE_SKIP_RPATH FALSE) +set(CMAKE_SKIP_BUILD_RPATH FALSE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +set(CMAKE_INSTALL_NAME_DIR "@rpath") +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +string(REGEX REPLACE "[ ]*-O[^ ]+[ ]*" " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +string(REGEX REPLACE "[ ]*-Wl,-O2 -Wl,[^ ]+[ ]*" " " CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") +string(REGEX REPLACE "[ ]*-Wl,-O2 -Wl,[^ ]+[ ]*" " " CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") + + +if(MACOS) + set(CMAKE_THREAD_LIBS_INIT "-lpthread") + set(CMAKE_HAVE_THREADS_LIBRARY 1) + set(CMAKE_USE_WIN32_THREADS_INIT 0) + set(CMAKE_USE_PTHREADS_INIT 1) + set(THREADS_PREFER_PTHREAD_FLAG ON) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -undefined dynamic_lookup") +endif() + + +if(MACOS) + set(CMAKE_INSTALL_RPATH "@loader_path/") +elseif(LINUX) + set(CMAKE_INSTALL_RPATH "\$ORIGIN") +endif() + +if(WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc /MP /bigobj") + foreach(warning 4244 4251 4267 4275 4290 4786 4305 4996) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd${warning}") + endforeach(warning) +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ + -g \ + -Wall \ + -Werror \ + -Wno-deprecated-declarations \ + -Wno-deprecated \ + ") +endif() + + +find_package(Python ${CSP_PYTHON_VERSION} EXACT REQUIRED COMPONENTS Interpreter Development.Module) +link_directories(${Python_LIBRARY_DIRS}) +include_directories(${Python_INCLUDE_DIRS}) + +set(CMAKE_SHARED_LIBRARY_PREFIX "") +if(NOT WIN32) + set(CMAKE_SHARED_LIBRARY_SUFFIX .so) +else() + set(CMAKE_SHARED_LIBRARY_SUFFIX .pyd) +endif() + +include_directories("${CMAKE_SOURCE_DIR}/cpp") + +add_library(extension SHARED cpp/project/basic.cpp) +set_target_properties(extension PROPERTIES PUBLIC_HEADER cpp/project/basic.hpp) +install(TARGETS extension + PUBLIC_HEADER DESTINATION project/include/project + RUNTIME DESTINATION project/ + LIBRARY DESTINATION project/ + ) diff --git a/hatch_cpp/tests/test_project_cmake/Makefile b/hatch_cpp/tests/test_project_cmake/Makefile new file mode 100644 index 0000000..c265da9 --- /dev/null +++ b/hatch_cpp/tests/test_project_cmake/Makefile @@ -0,0 +1,140 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 3.31 + +# Default target executed when no arguments are given to make. +default_target: all +.PHONY : default_target + +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + +# Disable VCS-based implicit rules. +% : %,v + +# Disable VCS-based implicit rules. +% : RCS/% + +# Disable VCS-based implicit rules. +% : RCS/%,v + +# Disable VCS-based implicit rules. +% : SCCS/s.% + +# Disable VCS-based implicit rules. +% : s.% + +.SUFFIXES: .hpux_make_needs_suffix_list + +# Command-line flag to silence nested $(MAKE). +$(VERBOSE)MAKESILENT = -s + +#Suppress display of executed commands. +$(VERBOSE).SILENT: + +# A target that is always out of date. +cmake_force: +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /opt/homebrew/bin/cmake + +# The command to remove a file. +RM = /opt/homebrew/bin/cmake -E rm -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /Users/timkpaine/Developer/projects/templates/hatch-cpp/hatch_cpp/tests/test_project_cmake + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /Users/timkpaine/Developer/projects/templates/hatch-cpp/hatch_cpp/tests/test_project_cmake + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake cache editor..." + /opt/homebrew/bin/ccmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache +.PHONY : edit_cache/fast + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake to regenerate build system..." + /opt/homebrew/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache +.PHONY : rebuild_cache/fast + +# The main all target +all: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/timkpaine/Developer/projects/templates/hatch-cpp/hatch_cpp/tests/test_project_cmake/CMakeFiles /Users/timkpaine/Developer/projects/templates/hatch-cpp/hatch_cpp/tests/test_project_cmake//CMakeFiles/progress.marks + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/timkpaine/Developer/projects/templates/hatch-cpp/hatch_cpp/tests/test_project_cmake/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 clean +.PHONY : clean + +# The main clean target +clean/fast: clean +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... edit_cache" + @echo "... rebuild_cache" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/hatch_cpp/tests/test_project_cmake/cpp/project/basic.cpp b/hatch_cpp/tests/test_project_cmake/cpp/project/basic.cpp new file mode 100644 index 0000000..db4432a --- /dev/null +++ b/hatch_cpp/tests/test_project_cmake/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_cmake/cpp/project/basic.hpp b/hatch_cpp/tests/test_project_cmake/cpp/project/basic.hpp new file mode 100644 index 0000000..65cb62e --- /dev/null +++ b/hatch_cpp/tests/test_project_cmake/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_cmake/project/__init__.py b/hatch_cpp/tests/test_project_cmake/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hatch_cpp/tests/test_project_cmake/pyproject.toml b/hatch_cpp/tests/test_project_cmake/pyproject.toml new file mode 100644 index 0000000..8ad530e --- /dev/null +++ b/hatch_cpp/tests/test_project_cmake/pyproject.toml @@ -0,0 +1,39 @@ +[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 + +[tool.hatch.build.hooks.hatch-cpp.cmake] +root = "CMakeLists.txt" +cmake_args = {"BUILD_TESTS" = "OFF"} +include_flags = {"python_version" = true} +[tool.hatch.build.hooks.hatch-cpp.cmake.cmake_env_args] +linux = {"MANYLINUX" = "ON"} diff --git a/hatch_cpp/tests/test_projects.py b/hatch_cpp/tests/test_projects.py index 52ea248..7553c04 100644 --- a/hatch_cpp/tests/test_projects.py +++ b/hatch_cpp/tests/test_projects.py @@ -9,7 +9,15 @@ class TestProject: @pytest.mark.parametrize( - "project", ["test_project_basic", "test_project_override_classes", "test_project_pybind", "test_project_nanobind", "test_project_limited_api"] + "project", + [ + "test_project_basic", + "test_project_override_classes", + "test_project_pybind", + "test_project_nanobind", + "test_project_limited_api", + "test_project_cmake", + ], ) def test_basic(self, project): # cleanup diff --git a/hatch_cpp/tests/test_structs.py b/hatch_cpp/tests/test_structs.py index 0aacd31..fc36d9a 100644 --- a/hatch_cpp/tests/test_structs.py +++ b/hatch_cpp/tests/test_structs.py @@ -1,7 +1,11 @@ +from pathlib import Path +from sys import version_info + import pytest from pydantic import ValidationError +from toml import loads -from hatch_cpp.structs import HatchCppLibrary, HatchCppPlatform +from hatch_cpp.structs import HatchCppBuildConfig, HatchCppBuildPlan, HatchCppLibrary, HatchCppPlatform class TestStructs: @@ -24,3 +28,21 @@ def test_validate_py_limited_api(self): with pytest.raises(ValidationError): library.binding = "pybind11" + + def test_cmake_args(self): + txt = (Path(__file__).parent / "test_project_cmake" / "pyproject.toml").read_text() + toml = loads(txt) + hatch_build_config = HatchCppBuildConfig(name=toml["project"]["name"], **toml["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"]) + hatch_build_plan = HatchCppBuildPlan(**hatch_build_config.model_dump()) + hatch_build_plan.generate() + + assert hatch_build_plan.commands[0].startswith("cmake .") + assert hatch_build_plan.commands[1].startswith("cmake --build build") + assert hatch_build_plan.commands[2].startswith("cmake --install build") + + assert "-DCMAKE_BUILD_TYPE=release" in hatch_build_plan.commands[0] + assert "-B build" in hatch_build_plan.commands[0] + assert "-DHATCH_CPP_TEST_PROJECT_BASIC_BUILD_TESTS=OFF" in hatch_build_plan.commands[0] + 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] diff --git a/hatch_cpp/utils.py b/hatch_cpp/utils.py index fb209b2..0efb237 100644 --- a/hatch_cpp/utils.py +++ b/hatch_cpp/utils.py @@ -10,123 +10,3 @@ @lru_cache(maxsize=None) def import_string(input_string: str): return _import_string_adapter.validate_python(input_string) - - -# import multiprocessing -# import os -# import os.path -# import platform -# import subprocess -# import sys -# from shutil import which -# from skbuild import setup - -# CSP_USE_VCPKG = os.environ.get("CSP_USE_VCPKG", "1").lower() in ("1", "on") -# # Allow arg to override default / env -# if "--csp-no-vcpkg" in sys.argv: -# CSP_USE_VCPKG = False -# sys.argv.remove("--csp-no-vcpkg") - -# # CMake Options -# CMAKE_OPTIONS = ( -# ("CSP_BUILD_NO_CXX_ABI", "0"), -# ("CSP_BUILD_TESTS", "1"), -# ("CSP_MANYLINUX", "0"), -# ("CSP_BUILD_KAFKA_ADAPTER", "1"), -# ("CSP_BUILD_PARQUET_ADAPTER", "1"), -# ("CSP_BUILD_WS_CLIENT_ADAPTER", "1"), -# # NOTE: -# # - omit vcpkg, need to test for presence -# # - omit ccache, need to test for presence -# # - omit coverage/gprof, not implemented -# ) - -# if sys.platform == "linux": -# VCPKG_TRIPLET = "x64-linux" -# elif sys.platform == "win32": -# VCPKG_TRIPLET = "x64-windows-static-md" -# else: -# VCPKG_TRIPLET = None - -# # This will be used for e.g. the sdist -# if CSP_USE_VCPKG: -# if not os.path.exists("vcpkg"): -# subprocess.call(["git", "clone", "https://github.com/Microsoft/vcpkg.git"]) -# if not os.path.exists("vcpkg/ports"): -# subprocess.call(["git", "submodule", "update", "--init", "--recursive"]) -# if not os.path.exists("vcpkg/buildtrees"): -# subprocess.call(["git", "pull"], cwd="vcpkg") -# args = ["install"] -# if VCPKG_TRIPLET is not None: -# args.append(f"--triplet={VCPKG_TRIPLET}") - -# if os.name == "nt": -# subprocess.call(["bootstrap-vcpkg.bat"], cwd="vcpkg", shell=True) -# subprocess.call(["vcpkg.bat"] + args, cwd="vcpkg", shell=True) -# else: -# subprocess.call(["./bootstrap-vcpkg.sh"], cwd="vcpkg") -# subprocess.call(["./vcpkg"] + args, cwd="vcpkg") - - -# python_version = f"{sys.version_info.major}.{sys.version_info.minor}" -# cmake_args = [f"-DCSP_PYTHON_VERSION={python_version}"] -# vcpkg_toolchain_file = os.path.abspath( -# os.environ.get( -# "CSP_VCPKG_PATH", -# os.path.join("vcpkg/scripts/buildsystems/vcpkg.cmake"), -# ) -# ) - -# if CSP_USE_VCPKG and os.path.exists(vcpkg_toolchain_file): -# cmake_args.extend( -# [ -# "-DCMAKE_TOOLCHAIN_FILE={}".format(vcpkg_toolchain_file), -# "-DCSP_USE_VCPKG=ON", -# ] -# ) - -# if VCPKG_TRIPLET is not None: -# cmake_args.append(f"-DVCPKG_TARGET_TRIPLET={VCPKG_TRIPLET}") -# else: -# cmake_args.append("-DCSP_USE_VCPKG=OFF") - -# if "CXX" in os.environ: -# cmake_args.append(f"-DCMAKE_CXX_COMPILER={os.environ['CXX']}") - -# if "DEBUG" in os.environ: -# cmake_args.append("-DCMAKE_BUILD_TYPE=Debug") - -# if platform.system() == "Windows": -# import distutils.msvccompiler as dm - -# # https://wiki.python.org/moin/WindowsCompilers#Microsoft_Visual_C.2B-.2B-_14.0_with_Visual_Studio_2015_.28x86.2C_x64.2C_ARM.29 -# msvc = { -# "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", -# }.get(str(dm.get_build_version()), "Visual Studio 15 2017") -# cmake_args.extend( -# [ -# "-G", -# os.environ.get("CSP_GENERATOR", msvc), -# ] -# ) - -# for cmake_option, default in CMAKE_OPTIONS: -# if os.environ.get(cmake_option, default).lower() in ("1", "on"): -# cmake_args.append(f"-D{cmake_option}=ON") -# else: -# cmake_args.append(f"-D{cmake_option}=OFF") - -# if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ: -# os.environ["CMAKE_BUILD_PARALLEL_LEVEL"] = str(multiprocessing.cpu_count()) - -# if platform.system() == "Darwin": -# os.environ["MACOSX_DEPLOYMENT_TARGET"] = os.environ.get("OSX_DEPLOYMENT_TARGET", "10.15") -# cmake_args.append(f'-DCMAKE_OSX_DEPLOYMENT_TARGET={os.environ.get("OSX_DEPLOYMENT_TARGET", "10.15")}') - -# if which("ccache") and os.environ.get("CSP_USE_CCACHE", "") != "0": -# cmake_args.append("-DCSP_USE_CCACHE=On") diff --git a/pyproject.toml b/pyproject.toml index 82119f5..d03cfc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ develop = [ "pybind11", "pytest", "pytest-cov", + "toml", ] [project.entry-points.hatch] From b9b55b2d6eb436fd1f9fd40532d697a9271c5a11 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 14 Jan 2025 18:02:39 -0500 Subject: [PATCH 19/35] =?UTF-8?q?Bump=20version:=200.1.6=20=E2=86=92=200.1?= =?UTF-8?q?.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hatch_cpp/__init__.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hatch_cpp/__init__.py b/hatch_cpp/__init__.py index f6880d2..d3dfbab 100644 --- a/hatch_cpp/__init__.py +++ b/hatch_cpp/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.6" +__version__ = "0.1.7" from .hooks import hatch_register_build_hook from .plugin import HatchCppBuildHook diff --git a/pyproject.toml b/pyproject.toml index d03cfc4..cdbf5d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [{name = "the hatch-cpp authors", email = "t.paine154@gmail.com"}] description = "Hatch plugin for C++ builds" readme = "README.md" license = { text = "Apache-2.0" } -version = "0.1.6" +version = "0.1.7" requires-python = ">=3.9" keywords = [ "hatch", @@ -63,7 +63,7 @@ Repository = "https://github.com/python-project-templates/hatch-cpp" Homepage = "https://github.com/python-project-templates/hatch-cpp" [tool.bumpversion] -current_version = "0.1.6" +current_version = "0.1.7" commit = true tag = false From 5145f2803a1f9de8bdf1ae18942f31b21b4668a2 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 19 Jan 2025 05:17:20 +0000 Subject: [PATCH 20/35] Update from copier (2025-01-19T05:17:20) --- .copier-answers.yml | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index f3bd003..6476fe7 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 81e8acd +_commit: 7c534a1 _src_path: https://github.com/python-project-templates/base.git add_extension: python email: t.paine154@gmail.com diff --git a/pyproject.toml b/pyproject.toml index cdbf5d7..22fe31b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,6 @@ replace = 'version = "{new_version}"' ignore = [ ".copier-answers.yml", "Makefile", - "setup.py", "docs/**/*", ] From 51a7d7781ca628cf9fe5d7d6f2a35327a1030dcf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:15:12 +0000 Subject: [PATCH 21/35] Update ruff requirement from <0.9,>=0.3 to >=0.3,<0.10 Updates the requirements on [ruff](https://github.com/astral-sh/ruff) to permit the latest version. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.3.0...0.9.4) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 22fe31b..eb10078 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ develop = [ "build", "bump-my-version", "check-manifest", - "ruff>=0.3,<0.9", + "ruff>=0.3,<0.10", "twine", "wheel", # test From 48172d662fa66f20a51487539916f459ec060836 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 2 Feb 2025 05:18:13 +0000 Subject: [PATCH 22/35] Update from copier (2025-02-02T05:18:13) --- .copier-answers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 6476fe7..6d5b188 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 7c534a1 +_commit: '9689455' _src_path: https://github.com/python-project-templates/base.git add_extension: python email: t.paine154@gmail.com From a19cc2ee4ef604a91a768d6e06bbfff12df8aca7 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sat, 15 Feb 2025 23:54:40 -0500 Subject: [PATCH 23/35] workaround until https://github.com/wjakob/nanobind/commit/abd27e3b5565bc95f5091321f0f863fce8b5b95b --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eb10078..42dc4bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ develop = [ "twine", "wheel", # test - "nanobind", + "nanobind<2.5.0", # https://github.com/wjakob/nanobind/commit/abd27e3b5565bc95f5091321f0f863fce8b5b95b "pybind11", "pytest", "pytest-cov", From 78c005a90d6ea2bfaf3883568749700188b170c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 07:54:11 +0000 Subject: [PATCH 24/35] Update ruff requirement from <0.10,>=0.3 to >=0.3,<0.12 Updates the requirements on [ruff](https://github.com/astral-sh/ruff) to permit the latest version. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.3.0...0.11.2) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.11.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 42dc4bc..bba0d34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ develop = [ "build", "bump-my-version", "check-manifest", - "ruff>=0.3,<0.10", + "ruff>=0.3,<0.12", "twine", "wheel", # test From ba98c5c8f623c551dcd0e27f6aa12a4f27b71379 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 07:54:16 +0000 Subject: [PATCH 25/35] Update nanobind requirement from <2.5.0 to <2.7.0 Updates the requirements on [nanobind](https://github.com/wjakob/nanobind) to permit the latest version. - [Changelog](https://github.com/wjakob/nanobind/blob/master/docs/changelog.rst) - [Commits](https://github.com/wjakob/nanobind/compare/v0.0.1...v2.6.1) --- updated-dependencies: - dependency-name: nanobind dependency-version: 2.6.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 42dc4bc..09cf7d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ develop = [ "twine", "wheel", # test - "nanobind<2.5.0", # https://github.com/wjakob/nanobind/commit/abd27e3b5565bc95f5091321f0f863fce8b5b95b + "nanobind<2.7.0", # https://github.com/wjakob/nanobind/commit/abd27e3b5565bc95f5091321f0f863fce8b5b95b "pybind11", "pytest", "pytest-cov", From 906e5acc620956817c622f4e5fcc3590789cbba3 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 6 Apr 2025 05:19:15 +0000 Subject: [PATCH 26/35] Update from copier (2025-04-06T05:19:15) --- .copier-answers.yml | 2 +- .github/workflows/build.yml | 7 ++----- Makefile | 4 ++-- pyproject.toml | 1 + 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 6d5b188..dab7480 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: '9689455' +_commit: '6957226' _src_path: https://github.com/python-project-templates/base.git add_extension: python email: t.paine154@gmail.com diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5df3cb..ef66d9b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,12 +35,9 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + - uses: actions-ext/python/setup@main with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - cache-dependency-path: 'pyproject.toml' + version: ${{ matrix.python-version }} - name: Install dependencies run: make develop diff --git a/Makefile b/Makefile index f1ad97c..adac7f6 100644 --- a/Makefile +++ b/Makefile @@ -4,13 +4,13 @@ .PHONY: develop build install develop: ## install dependencies and build library - python -m pip install -e .[develop] + uv pip install -e .[develop] build: ## build the python library python -m build -n install: ## install library - python -m pip install . + uv pip install . ######### # LINTS # diff --git a/pyproject.toml b/pyproject.toml index d6c3f06..9d37394 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ develop = [ "check-manifest", "ruff>=0.3,<0.12", "twine", + "uv", "wheel", # test "nanobind<2.7.0", # https://github.com/wjakob/nanobind/commit/abd27e3b5565bc95f5091321f0f863fce8b5b95b From 1e7873a5718f8cf6aeaf707a62d7943ea1ff2cfd Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 13 Apr 2025 05:24:30 +0000 Subject: [PATCH 27/35] Update from copier (2025-04-13T05:24:30) --- .copier-answers.yml | 2 +- pyproject.toml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index dab7480..2bc0d63 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: '6957226' +_commit: 98c8f14 _src_path: https://github.com/python-project-templates/base.git add_extension: python email: t.paine154@gmail.com diff --git a/pyproject.toml b/pyproject.toml index 9d37394..7e19df6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,11 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", +<<<<<<< before updating "License :: OSI Approved :: Apache Software License", +======= + "Programming Language :: Python :: 3.13", +>>>>>>> after updating ] dependencies = [ From abf42d5ac229f972420151dcc8ad2af018e9f5fd Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Sun, 13 Apr 2025 13:38:43 -0400 Subject: [PATCH 28/35] Update pyproject.toml --- pyproject.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7e19df6..8362341 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,11 +28,8 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", -<<<<<<< before updating - "License :: OSI Approved :: Apache Software License", -======= "Programming Language :: Python :: 3.13", ->>>>>>> after updating + "License :: OSI Approved :: Apache Software License", ] dependencies = [ From 978da6a692ec919cf62544b1b15c26d64c34acd3 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 20 Apr 2025 05:20:29 +0000 Subject: [PATCH 29/35] Update from copier (2025-04-20T05:20:29) --- .copier-answers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 2bc0d63..116c448 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 98c8f14 +_commit: 122ea92 _src_path: https://github.com/python-project-templates/base.git add_extension: python email: t.paine154@gmail.com From 6e2ce7aede4d3f0e4f74f5c3ff2149125d78c1c8 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 27 Apr 2025 05:20:08 +0000 Subject: [PATCH 30/35] Update from copier (2025-04-27T05:20:08) --- .copier-answers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 116c448..45bf8ab 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 122ea92 +_commit: 839686f _src_path: https://github.com/python-project-templates/base.git add_extension: python email: t.paine154@gmail.com From 644989e8282c4964770a498996a66cc7a41c8ee7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 08:06:08 +0000 Subject: [PATCH 31/35] Update nanobind requirement from <2.7.0 to <2.8.0 Updates the requirements on [nanobind](https://github.com/wjakob/nanobind) to permit the latest version. - [Changelog](https://github.com/wjakob/nanobind/blob/master/docs/changelog.rst) - [Commits](https://github.com/wjakob/nanobind/compare/v0.0.1...v2.7.0) --- updated-dependencies: - dependency-name: nanobind dependency-version: 2.7.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8362341..f2cc332 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ develop = [ "uv", "wheel", # test - "nanobind<2.7.0", # https://github.com/wjakob/nanobind/commit/abd27e3b5565bc95f5091321f0f863fce8b5b95b + "nanobind<2.8.0", # https://github.com/wjakob/nanobind/commit/abd27e3b5565bc95f5091321f0f863fce8b5b95b "pybind11", "pytest", "pytest-cov", From 7227f567ebaf5cddb0a16221e12980870ab1975a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 4 May 2025 05:20:35 +0000 Subject: [PATCH 32/35] Update from copier (2025-05-04T05:20:35) --- .copier-answers.yml | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 45bf8ab..98e7012 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 839686f +_commit: c9dacb5 _src_path: https://github.com/python-project-templates/base.git add_extension: python email: t.paine154@gmail.com diff --git a/pyproject.toml b/pyproject.toml index f2cc332..1834fc9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,7 +114,6 @@ packages = ["hatch_cpp"] [tool.pytest.ini_options] addopts = ["-vvv", "--junitxml=junit.xml"] -asyncio_mode = "strict" testpaths = "hatch_cpp/tests" [tool.ruff] From 81b6d6dfb2d331d7d036ac751ba70ff179e5b798 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 19:17:12 +0000 Subject: [PATCH 33/35] Update from copier (2025-05-09T19:17:12) --- .copier-answers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 98e7012..354c327 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: c9dacb5 +_commit: a669f79 _src_path: https://github.com/python-project-templates/base.git add_extension: python email: t.paine154@gmail.com From b563e17d5448415456d61d606c8903f538883cf7 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 05:20:18 +0000 Subject: [PATCH 34/35] Update from copier (2025-05-11T05:20:18) --- .copier-answers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 354c327..0f8cdce 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: a669f79 +_commit: d56cb12 _src_path: https://github.com/python-project-templates/base.git add_extension: python email: t.paine154@gmail.com From d2908720b84bc7a84ea6d66b8e632910f2e4ea6f Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 14:48:19 +0000 Subject: [PATCH 35/35] Update from copier (2025-06-17T14:48:19) --- .copier-answers.yml => .copier-answers.yaml | 2 +- .gitattributes | 7 +++++++ .github/{dependabot.yml => dependabot.yaml} | 8 -------- .github/workflows/{build.yml => build.yaml} | 16 +++------------- .github/workflows/{copier.yml => copier.yaml} | 0 LICENSE | 4 ++++ README.md | 2 +- pyproject.toml | 2 +- 8 files changed, 17 insertions(+), 24 deletions(-) rename .copier-answers.yml => .copier-answers.yaml (95%) rename .github/{dependabot.yml => dependabot.yaml} (66%) rename .github/workflows/{build.yml => build.yaml} (73%) rename .github/workflows/{copier.yml => copier.yaml} (100%) diff --git a/.copier-answers.yml b/.copier-answers.yaml similarity index 95% rename from .copier-answers.yml rename to .copier-answers.yaml index 0f8cdce..1ad91e7 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yaml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: d56cb12 +_commit: 5c63cb2 _src_path: https://github.com/python-project-templates/base.git add_extension: python email: t.paine154@gmail.com diff --git a/.gitattributes b/.gitattributes index 092719d..8e05465 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,4 +3,11 @@ docs/* linguist-documentation *.ipynb linguist-documentation Makefile linguist-documentation +<<<<<<< before updating * text=auto eol=lf +======= +*.md text=auto eol=lf +*.py text=auto eol=lf +*.toml text=auto eol=lf +*.yaml text=auto eol=lf +>>>>>>> after updating diff --git a/.github/dependabot.yml b/.github/dependabot.yaml similarity index 66% rename from .github/dependabot.yml rename to .github/dependabot.yaml index 4d17b20..42cac77 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yaml @@ -14,11 +14,3 @@ updates: labels: - "lang: python" - "part: dependencies" - - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "monthly" - labels: - - "lang: javascript" - - "part: dependencies" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yaml similarity index 73% rename from .github/workflows/build.yml rename to .github/workflows/build.yaml index ef66d9b..109d0d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yaml @@ -29,8 +29,8 @@ jobs: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.9", "3.10", "3.11", "3.12"] + os: [ubuntu-latest] + python-version: ["3.11"] steps: - uses: actions/checkout@v4 @@ -53,15 +53,7 @@ jobs: - name: Test run: make coverage - if: matrix.os != 'windows-latest' - - - name: Test - run: | - call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - make coverage - shell: cmd - if: matrix.os == 'windows-latest' - + - name: Upload test results (Python) uses: actions/upload-artifact@v4 with: @@ -73,7 +65,6 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@v2 with: files: '**/junit.xml' - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' - name: Upload coverage uses: codecov/codecov-action@v5 @@ -87,4 +78,3 @@ jobs: with: name: dist-${{matrix.os}} path: dist - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' diff --git a/.github/workflows/copier.yml b/.github/workflows/copier.yaml similarity index 100% rename from .github/workflows/copier.yml rename to .github/workflows/copier.yaml diff --git a/LICENSE b/LICENSE index 261eeb9..95b5e23 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,11 @@ same "printed page" as the copyright notice for easier identification within third-party archives. +<<<<<<< before updating Copyright [yyyy] [name of copyright owner] +======= + Copyright 2025 the hatch-cpp authors +>>>>>>> after updating Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 1f26618..2d254b9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Hatch plugin for C++ builds -[![Build Status](https://github.com/python-project-templates/hatch-cpp/actions/workflows/build.yml/badge.svg?branch=main&event=push)](https://github.com/python-project-templates/hatch-cpp/actions/workflows/build.yml) +[![Build Status](https://github.com/python-project-templates/hatch-cpp/actions/workflows/build.yaml/badge.svg?branch=main&event=push)](https://github.com/python-project-templates/hatch-cpp/actions/workflows/build.yaml) [![codecov](https://codecov.io/gh/python-project-templates/hatch-cpp/branch/main/graph/badge.svg)](https://codecov.io/gh/python-project-templates/hatch-cpp) [![License](https://img.shields.io/github/license/python-project-templates/hatch-cpp)](https://github.com/python-project-templates/hatch-cpp) [![PyPI](https://img.shields.io/pypi/v/hatch-cpp.svg)](https://pypi.python.org/pypi/hatch-cpp) diff --git a/pyproject.toml b/pyproject.toml index 1834fc9..f727b50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ replace = 'version = "{new_version}"' [tool.check-manifest] ignore = [ - ".copier-answers.yml", + ".copier-answers.yaml", "Makefile", "docs/**/*", ]