hatch-cpp 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. hatch_cpp/__init__.py +5 -1
  2. hatch_cpp/plugin.py +37 -59
  3. hatch_cpp/structs.py +128 -69
  4. hatch_cpp/tests/test_project_basic/cpp/{basic-project → project}/basic.cpp +1 -1
  5. hatch_cpp/tests/test_project_basic/pyproject.toml +6 -34
  6. hatch_cpp/tests/test_project_nanobind/cpp/project/basic.cpp +2 -0
  7. hatch_cpp/tests/test_project_nanobind/cpp/project/basic.hpp +7 -0
  8. hatch_cpp/tests/test_project_nanobind/project/__init__.py +0 -0
  9. hatch_cpp/tests/test_project_nanobind/pyproject.toml +35 -0
  10. hatch_cpp/tests/test_project_override_classes/cpp/project/basic.cpp +5 -0
  11. hatch_cpp/tests/test_project_override_classes/cpp/project/basic.hpp +17 -0
  12. hatch_cpp/tests/test_project_override_classes/project/__init__.py +0 -0
  13. hatch_cpp/tests/test_project_override_classes/pyproject.toml +37 -0
  14. hatch_cpp/tests/test_project_pybind/cpp/project/basic.cpp +6 -0
  15. hatch_cpp/tests/test_project_pybind/cpp/project/basic.hpp +9 -0
  16. hatch_cpp/tests/test_project_pybind/project/__init__.py +0 -0
  17. hatch_cpp/tests/test_project_pybind/pyproject.toml +35 -0
  18. hatch_cpp/tests/test_projects.py +41 -0
  19. hatch_cpp/utils.py +12 -0
  20. {hatch_cpp-0.1.1.dist-info → hatch_cpp-0.1.2.dist-info}/METADATA +26 -1
  21. hatch_cpp-0.1.2.dist-info/RECORD +30 -0
  22. hatch_cpp-0.1.2.dist-info/entry_points.txt +2 -0
  23. hatch_cpp/__main__.py +0 -4
  24. hatch_cpp/tests/test_project_basic.py +0 -28
  25. hatch_cpp-0.1.1.dist-info/RECORD +0 -19
  26. hatch_cpp-0.1.1.dist-info/entry_points.txt +0 -5
  27. /hatch_cpp/tests/test_project_basic/cpp/{basic-project → project}/basic.hpp +0 -0
  28. /hatch_cpp/tests/test_project_basic/{basic_project → project}/__init__.py +0 -0
  29. {hatch_cpp-0.1.1.dist-info → hatch_cpp-0.1.2.dist-info}/WHEEL +0 -0
  30. {hatch_cpp-0.1.1.dist-info → hatch_cpp-0.1.2.dist-info}/licenses/LICENSE +0 -0
hatch_cpp/__init__.py CHANGED
@@ -1 +1,5 @@
1
- __version__ = "0.1.1"
1
+ __version__ = "0.1.2"
2
+
3
+ from .hooks import hatch_register_build_hook
4
+ from .plugin import HatchCppBuildHook
5
+ from .structs import *
hatch_cpp/plugin.py CHANGED
@@ -3,11 +3,11 @@ from __future__ import annotations
3
3
  import logging
4
4
  import os
5
5
  import typing as t
6
- from dataclasses import fields
7
6
 
8
7
  from hatchling.builders.hooks.plugin.interface import BuildHookInterface
9
8
 
10
- from .structs import HatchCppBuildConfig, HatchCppBuildPlan, HatchCppLibrary, HatchCppPlatform
9
+ from .structs import HatchCppBuildConfig, HatchCppBuildPlan
10
+ from .utils import import_string
11
11
 
12
12
  __all__ = ("HatchCppBuildHook",)
13
13
 
@@ -20,70 +20,48 @@ class HatchCppBuildHook(BuildHookInterface[HatchCppBuildConfig]):
20
20
 
21
21
  def initialize(self, version: str, _: dict[str, t.Any]) -> None:
22
22
  """Initialize the plugin."""
23
+ # Log some basic information
24
+ self._logger.info("Initializing hatch-cpp plugin version %s", version)
23
25
  self._logger.info("Running hatch-cpp")
24
26
 
27
+ # Only run if creating wheel
28
+ # TODO: Add support for specify sdist-plan
25
29
  if self.target_name != "wheel":
26
30
  self._logger.info("ignoring target name %s", self.target_name)
27
31
  return
28
32
 
33
+ # Skip if SKIP_HATCH_CPP is set
34
+ # TODO: Support CLI once https://github.com/pypa/hatch/pull/1743
29
35
  if os.getenv("SKIP_HATCH_CPP"):
30
36
  self._logger.info("Skipping the build hook since SKIP_HATCH_CPP was set")
31
37
  return
32
38
 
33
- kwargs = {k.replace("-", "_"): v if not isinstance(v, bool) else str(v) for k, v in self.config.items()}
34
- available_fields = [f.name for f in fields(HatchCppBuildConfig)]
35
- for key in list(kwargs):
36
- if key not in available_fields:
37
- del kwargs[key]
38
- config = HatchCppBuildConfig(**kwargs)
39
-
40
- library_kwargs = [
41
- {k.replace("-", "_"): v if not isinstance(v, bool) else str(v) for k, v in library_kwargs.items()} for library_kwargs in config.libraries
42
- ]
43
- libraries = [HatchCppLibrary(**library_kwargs) for library_kwargs in library_kwargs]
44
- platform = HatchCppPlatform.default()
45
- if config.toolchain == "raw":
46
- build_plan = HatchCppBuildPlan(libraries=libraries, platform=platform)
47
- build_plan.generate()
48
- if config.verbose:
49
- for command in build_plan.commands:
50
- self._logger.info(command)
51
- build_plan.execute()
52
- build_plan.cleanup()
53
-
54
- # build_kwargs = config.build_kwargs
55
- # if version == "editable":
56
- # build_kwargs = config.editable_build_kwargs or build_kwargs
57
-
58
- # should_skip_build = False
59
- # if not config.build_function:
60
- # log.warning("No build function found")
61
- # should_skip_build = True
62
-
63
- # elif config.skip_if_exists and version == "standard":
64
- # should_skip_build = should_skip(config.skip_if_exists)
65
- # if should_skip_build:
66
- # log.info("Skip-if-exists file(s) found")
67
-
68
- # # Get build function and call it with normalized parameter names.
69
- # if not should_skip_build and config.build_function:
70
- # build_func = get_build_func(config.build_function)
71
- # build_kwargs = normalize_kwargs(build_kwargs)
72
- # log.info("Building with %s", config.build_function)
73
- # log.info("With kwargs: %s", build_kwargs)
74
- # try:
75
- # build_func(self.target_name, version, **build_kwargs)
76
- # except Exception as e:
77
- # if version == "editable" and config.optional_editable_build.lower() == "true":
78
- # warnings.warn(f"Encountered build error:\n{e}", stacklevel=2)
79
- # else:
80
- # raise e
81
- # else:
82
- # log.info("Skipping build")
83
-
84
- # # Ensure targets in distributable dists.
85
- # if version == "standard":
86
- # ensure_targets(config.ensured_targets)
87
-
88
- self._logger.info("Finished running hatch-cpp")
89
- return
39
+ # Get build config class or use default
40
+ build_config_class = import_string(self.config["build-config-class"]) if "build-config-class" in self.config else HatchCppBuildConfig
41
+
42
+ # Instantiate build config
43
+ config = build_config_class(**self.config)
44
+
45
+ # Grab libraries and platform
46
+ libraries = config.libraries
47
+ platform = config.platform
48
+
49
+ # Get build plan class or use default
50
+ build_plan_class = import_string(self.config["build-plan-class"]) if "build-plan-class" in self.config else HatchCppBuildPlan
51
+
52
+ # Instantiate builder
53
+ build_plan = build_plan_class(libraries=libraries, platform=platform)
54
+
55
+ # Generate commands
56
+ build_plan.generate()
57
+
58
+ # Log commands if in verbose mode
59
+ if config.verbose:
60
+ for command in build_plan.commands:
61
+ self._logger.warning(command)
62
+
63
+ # Execute build plan
64
+ build_plan.execute()
65
+
66
+ # Perform any cleanup actions
67
+ build_plan.cleanup()
hatch_cpp/structs.py CHANGED
@@ -1,13 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass, field
4
3
  from os import environ, system
5
4
  from pathlib import Path
5
+ from shutil import which
6
6
  from sys import executable, platform as sys_platform
7
7
  from sysconfig import get_path
8
- from typing import Literal
8
+ from typing import List, Literal, Optional
9
9
 
10
- from hatchling.builders.config import BuilderConfig
10
+ from pydantic import BaseModel, Field
11
11
 
12
12
  __all__ = (
13
13
  "HatchCppBuildConfig",
@@ -16,53 +16,47 @@ __all__ = (
16
16
  "HatchCppBuildPlan",
17
17
  )
18
18
 
19
- Platform = Literal["linux", "darwin", "win32"]
19
+ BuildType = Literal["debug", "release"]
20
20
  CompilerToolchain = Literal["gcc", "clang", "msvc"]
21
+ Language = Literal["c", "c++"]
22
+ Binding = Literal["cpython", "pybind11", "nanobind"]
23
+ Platform = Literal["linux", "darwin", "win32"]
21
24
  PlatformDefaults = {
22
- "linux": {"CC": "gcc", "CXX": "g++"},
23
- "darwin": {"CC": "clang", "CXX": "clang++"},
24
- "win32": {"CC": "cl", "CXX": "cl"},
25
+ "linux": {"CC": "gcc", "CXX": "g++", "LD": "ld"},
26
+ "darwin": {"CC": "clang", "CXX": "clang++", "LD": "ld"},
27
+ "win32": {"CC": "cl", "CXX": "cl", "LD": "link"},
25
28
  }
26
29
 
27
30
 
28
- @dataclass
29
- class HatchCppBuildConfig(BuilderConfig):
30
- """Build config values for Hatch C++ Builder."""
31
+ class HatchCppLibrary(BaseModel):
32
+ """A C++ library."""
31
33
 
32
- toolchain: str | None = field(default="raw")
33
- libraries: list[dict[str, str]] = field(default_factory=list)
34
- verbose: bool | None = field(default=False)
35
- # build_function: str | None = None
36
- # build_kwargs: t.Mapping[str, str] = field(default_factory=dict)
37
- # editable_build_kwargs: t.Mapping[str, str] = field(default_factory=dict)
38
- # ensured_targets: list[str] = field(default_factory=list)
39
- # skip_if_exists: list[str] = field(default_factory=list)
34
+ name: str
35
+ sources: List[str]
36
+ language: Language = "c++"
40
37
 
38
+ binding: Binding = "cpython"
39
+ std: Optional[str] = None
41
40
 
42
- @dataclass
43
- class HatchCppLibrary(object):
44
- """A C++ library."""
41
+ include_dirs: List[str] = Field(default_factory=list, alias="include-dirs")
42
+ library_dirs: List[str] = Field(default_factory=list, alias="library-dirs")
43
+ libraries: List[str] = Field(default_factory=list)
45
44
 
46
- name: str
47
- sources: list[str]
45
+ extra_compile_args: List[str] = Field(default_factory=list, alias="extra-compile-args")
46
+ extra_link_args: List[str] = Field(default_factory=list, alias="extra-link-args")
47
+ extra_objects: List[str] = Field(default_factory=list, alias="extra-objects")
48
48
 
49
- include_dirs: list[str] = field(default_factory=list)
50
- library_dirs: list[str] = field(default_factory=list)
51
- libraries: list[str] = field(default_factory=list)
52
- extra_compile_args: list[str] = field(default_factory=list)
53
- extra_link_args: list[str] = field(default_factory=list)
54
- extra_objects: list[str] = field(default_factory=list)
55
- define_macros: list[str] = field(default_factory=list)
56
- undef_macros: list[str] = field(default_factory=list)
49
+ define_macros: List[str] = Field(default_factory=list, alias="define-macros")
50
+ undef_macros: List[str] = Field(default_factory=list, alias="undef-macros")
57
51
 
58
- export_symbols: list[str] = field(default_factory=list)
59
- depends: list[str] = field(default_factory=list)
52
+ export_symbols: List[str] = Field(default_factory=list, alias="export-symbols")
53
+ depends: List[str] = Field(default_factory=list)
60
54
 
61
55
 
62
- @dataclass
63
- class HatchCppPlatform(object):
56
+ class HatchCppPlatform(BaseModel):
64
57
  cc: str
65
58
  cxx: str
59
+ ld: str
66
60
  platform: Platform
67
61
  toolchain: CompilerToolchain
68
62
 
@@ -71,6 +65,7 @@ class HatchCppPlatform(object):
71
65
  platform = environ.get("HATCH_CPP_PLATFORM", sys_platform)
72
66
  CC = environ.get("CC", PlatformDefaults[platform]["CC"])
73
67
  CXX = environ.get("CXX", PlatformDefaults[platform]["CXX"])
68
+ LD = environ.get("LD", PlatformDefaults[platform]["LD"])
74
69
  if "gcc" in CC and "g++" in CXX:
75
70
  toolchain = "gcc"
76
71
  elif "clang" in CC and "clang++" in CXX:
@@ -79,44 +74,103 @@ class HatchCppPlatform(object):
79
74
  toolchain = "msvc"
80
75
  else:
81
76
  raise Exception(f"Unrecognized toolchain: {CC}, {CXX}")
82
- return HatchCppPlatform(cc=CC, cxx=CXX, platform=platform, toolchain=toolchain)
83
77
 
84
- def get_compile_flags(self, library: HatchCppLibrary) -> str:
78
+ # Customizations
79
+ if which("ccache") and not environ.get("HATCH_CPP_DISABLE_CCACHE"):
80
+ CC = f"ccache {CC}"
81
+ CXX = f"ccache {CXX}"
82
+
83
+ # https://github.com/rui314/mold/issues/647
84
+ # if which("ld.mold"):
85
+ # LD = which("ld.mold")
86
+ # elif which("ld.lld"):
87
+ # LD = which("ld.lld")
88
+ return HatchCppPlatform(cc=CC, cxx=CXX, ld=LD, platform=platform, toolchain=toolchain)
89
+
90
+ def get_compile_flags(self, library: HatchCppLibrary, build_type: BuildType = "release") -> str:
85
91
  flags = ""
92
+
93
+ # Python.h
94
+ library.include_dirs.append(get_path("include"))
95
+
96
+ if library.binding == "pybind11":
97
+ import pybind11
98
+
99
+ library.include_dirs.append(pybind11.get_include())
100
+ if not library.std:
101
+ library.std = "c++11"
102
+ elif library.binding == "nanobind":
103
+ import nanobind
104
+
105
+ library.include_dirs.append(nanobind.include_dir())
106
+ if not library.std:
107
+ library.std = "c++17"
108
+ library.sources.append(str(Path(nanobind.include_dir()).parent / "src" / "nb_combined.cpp"))
109
+ library.include_dirs.append(str((Path(nanobind.include_dir()).parent / "ext" / "robin_map" / "include")))
110
+
86
111
  if self.toolchain == "gcc":
87
- flags = f"-I{get_path('include')}"
88
112
  flags += " " + " ".join(f"-I{d}" for d in library.include_dirs)
89
- flags += " -fPIC -shared"
113
+ flags += " -fPIC"
90
114
  flags += " " + " ".join(library.extra_compile_args)
91
- flags += " " + " ".join(library.extra_link_args)
92
- flags += " " + " ".join(library.extra_objects)
93
- flags += " " + " ".join(f"-l{lib}" for lib in library.libraries)
94
- flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs)
95
115
  flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros)
96
116
  flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros)
97
- flags += f" -o {library.name}.so"
117
+ if library.std:
118
+ flags += f" -std={library.std}"
98
119
  elif self.toolchain == "clang":
99
- flags = f"-I{get_path('include')} "
100
120
  flags += " ".join(f"-I{d}" for d in library.include_dirs)
101
- flags += " -undefined dynamic_lookup -fPIC -shared"
121
+ flags += " -fPIC"
102
122
  flags += " " + " ".join(library.extra_compile_args)
103
- flags += " " + " ".join(library.extra_link_args)
104
- flags += " " + " ".join(library.extra_objects)
105
- flags += " " + " ".join(f"-l{lib}" for lib in library.libraries)
106
- flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs)
107
123
  flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros)
108
124
  flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros)
109
- flags += f" -o {library.name}.so"
125
+ if library.std:
126
+ flags += f" -std={library.std}"
110
127
  elif self.toolchain == "msvc":
111
- flags = f"/I{get_path('include')} "
112
128
  flags += " ".join(f"/I{d}" for d in library.include_dirs)
113
129
  flags += " " + " ".join(library.extra_compile_args)
114
130
  flags += " " + " ".join(library.extra_link_args)
115
131
  flags += " " + " ".join(library.extra_objects)
116
132
  flags += " " + " ".join(f"/D{macro}" for macro in library.define_macros)
117
133
  flags += " " + " ".join(f"/U{macro}" for macro in library.undef_macros)
118
- flags += " /EHsc /DWIN32 /LD"
119
- flags += f" /Fo:{library.name}.obj"
134
+ flags += " /EHsc /DWIN32"
135
+ if library.std:
136
+ flags += f" /std:{library.std}"
137
+ # clean
138
+ while flags.count(" "):
139
+ flags = flags.replace(" ", " ")
140
+ return flags
141
+
142
+ def get_link_flags(self, library: HatchCppLibrary, build_type: BuildType = "release") -> str:
143
+ flags = ""
144
+ if self.toolchain == "gcc":
145
+ flags += " -shared"
146
+ flags += " " + " ".join(library.extra_link_args)
147
+ flags += " " + " ".join(library.extra_objects)
148
+ flags += " " + " ".join(f"-l{lib}" for lib in library.libraries)
149
+ flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs)
150
+ flags += f" -o {library.name}.so"
151
+ if self.platform == "darwin":
152
+ flags += " -undefined dynamic_lookup"
153
+ if "mold" in self.ld:
154
+ flags += f" -fuse-ld={self.ld}"
155
+ elif "lld" in self.ld:
156
+ flags += " -fuse-ld=lld"
157
+ elif self.toolchain == "clang":
158
+ flags += " -shared"
159
+ flags += " " + " ".join(library.extra_link_args)
160
+ flags += " " + " ".join(library.extra_objects)
161
+ flags += " " + " ".join(f"-l{lib}" for lib in library.libraries)
162
+ flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs)
163
+ flags += f" -o {library.name}.so"
164
+ if self.platform == "darwin":
165
+ flags += " -undefined dynamic_lookup"
166
+ if "mold" in self.ld:
167
+ flags += f" -fuse-ld={self.ld}"
168
+ elif "lld" in self.ld:
169
+ flags += " -fuse-ld=lld"
170
+ elif self.toolchain == "msvc":
171
+ flags += " " + " ".join(library.extra_link_args)
172
+ flags += " " + " ".join(library.extra_objects)
173
+ flags += " /LD"
120
174
  flags += f" /Fe:{library.name}.pyd"
121
175
  flags += " /link /DLL"
122
176
  if (Path(executable).parent / "libs").exists():
@@ -128,22 +182,21 @@ class HatchCppPlatform(object):
128
182
  flags = flags.replace(" ", " ")
129
183
  return flags
130
184
 
131
- def get_link_flags(self, library: HatchCppLibrary) -> str:
132
- flags = ""
133
- return flags
134
-
135
185
 
136
- @dataclass
137
- class HatchCppBuildPlan(object):
138
- libraries: list[HatchCppLibrary] = field(default_factory=list)
139
- platform: HatchCppPlatform = field(default_factory=HatchCppPlatform.default)
140
- commands: list[str] = field(default_factory=list)
186
+ class HatchCppBuildPlan(BaseModel):
187
+ build_type: BuildType = "release"
188
+ libraries: List[HatchCppLibrary] = Field(default_factory=list)
189
+ platform: HatchCppPlatform = Field(default_factory=HatchCppPlatform.default)
190
+ commands: List[str] = Field(default_factory=list)
141
191
 
142
192
  def generate(self):
143
193
  self.commands = []
144
194
  for library in self.libraries:
145
- flags = self.platform.get_compile_flags(library)
146
- self.commands.append(f"{self.platform.cc} {' '.join(library.sources)} {flags}")
195
+ compile_flags = self.platform.get_compile_flags(library, self.build_type)
196
+ link_flags = self.platform.get_link_flags(library, self.build_type)
197
+ self.commands.append(
198
+ f"{self.platform.cc if library.language == 'c' else self.platform.cxx} {' '.join(library.sources)} {compile_flags} {link_flags}"
199
+ )
147
200
  return self.commands
148
201
 
149
202
  def execute(self):
@@ -153,7 +206,13 @@ class HatchCppBuildPlan(object):
153
206
 
154
207
  def cleanup(self):
155
208
  if self.platform.platform == "win32":
156
- for library in self.libraries:
157
- temp_obj = Path(f"{library.name}.obj")
158
- if temp_obj.exists():
159
- temp_obj.unlink()
209
+ for temp_obj in Path(".").glob("*.obj"):
210
+ temp_obj.unlink()
211
+
212
+
213
+ class HatchCppBuildConfig(BaseModel):
214
+ """Build config values for Hatch C++ Builder."""
215
+
216
+ verbose: Optional[bool] = Field(default=False)
217
+ libraries: List[HatchCppLibrary] = Field(default_factory=list)
218
+ platform: Optional[HatchCppPlatform] = Field(default_factory=HatchCppPlatform.default)
@@ -1,4 +1,4 @@
1
- #include "basic-project/basic.hpp"
1
+ #include "project/basic.hpp"
2
2
 
3
3
  PyObject* hello(PyObject*, PyObject*) {
4
4
  return PyUnicode_FromString("A string");
@@ -14,50 +14,22 @@ dependencies = [
14
14
 
15
15
  [tool.hatch.build]
16
16
  artifacts = [
17
- "basic_project/*.dll",
18
- "basic_project/*.dylib",
19
- "basic_project/*.so",
17
+ "project/*.dll",
18
+ "project/*.dylib",
19
+ "project/*.so",
20
20
  ]
21
21
 
22
22
  [tool.hatch.build.sources]
23
23
  src = "/"
24
24
 
25
25
  [tool.hatch.build.targets.sdist]
26
- packages = ["basic_project"]
26
+ packages = ["project"]
27
27
 
28
28
  [tool.hatch.build.targets.wheel]
29
- packages = ["basic_project"]
29
+ packages = ["project"]
30
30
 
31
31
  [tool.hatch.build.hooks.hatch-cpp]
32
32
  verbose = true
33
33
  libraries = [
34
- {name = "basic_project/extension", sources = ["cpp/basic-project/basic.cpp"], include-dirs = ["cpp"]}
34
+ {name = "project/extension", sources = ["cpp/project/basic.cpp"], include-dirs = ["cpp"]}
35
35
  ]
36
-
37
- # build-function = "hatch_cpp.cpp_builder"
38
-
39
- # [tool.hatch.build.hooks.defaults]
40
- # build-type = "release"
41
-
42
- # [tool.hatch.build.hooks.env-vars]
43
- # TODO: these will all be available via
44
- # CLI after https://github.com/pypa/hatch/pull/1743
45
- # e.g. --hatch-cpp-build-type=debug
46
- # build-type = "BUILD_TYPE"
47
- # ccache = "USE_CCACHE"
48
- # manylinux = "MANYLINUX"
49
- # vcpkg = "USE_VCPKG"
50
-
51
- # [tool.hatch.build.hooks.cmake]
52
-
53
- # [tool.hatch.build.hooks.vcpkg]
54
- # triplets = {linux="x64-linux", macos="x64-osx", windows="x64-windows-static-md"}
55
- # clone = true
56
- # update = true
57
-
58
- # [tool.hatch.build.hooks.hatch-cpp.build-kwargs]
59
- # path = "cpp"
60
-
61
- [tool.pytest.ini_options]
62
- asyncio_mode = "strict"
63
- testpaths = "basic_project/tests"
@@ -0,0 +1,2 @@
1
+ #include "project/basic.hpp"
2
+
@@ -0,0 +1,7 @@
1
+ #pragma once
2
+ #include <nanobind/nanobind.h>
3
+ #include <nanobind/stl/string.h>
4
+
5
+ NB_MODULE(extension, m) {
6
+ m.def("hello", []() { return "A string"; });
7
+ }
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.20"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "hatch-cpp-test-project-basic"
7
+ description = "Basic test project for hatch-cpp"
8
+ version = "0.1.0"
9
+ requires-python = ">=3.9"
10
+ dependencies = [
11
+ "hatchling>=1.20",
12
+ "hatch-cpp",
13
+ ]
14
+
15
+ [tool.hatch.build]
16
+ artifacts = [
17
+ "project/*.dll",
18
+ "project/*.dylib",
19
+ "project/*.so",
20
+ ]
21
+
22
+ [tool.hatch.build.sources]
23
+ src = "/"
24
+
25
+ [tool.hatch.build.targets.sdist]
26
+ packages = ["project"]
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["project"]
30
+
31
+ [tool.hatch.build.hooks.hatch-cpp]
32
+ verbose = true
33
+ libraries = [
34
+ {name = "project/extension", sources = ["cpp/project/basic.cpp"], include-dirs = ["cpp"], binding = "nanobind"},
35
+ ]
@@ -0,0 +1,5 @@
1
+ #include "project/basic.hpp"
2
+
3
+ PyObject* hello(PyObject*, PyObject*) {
4
+ return PyUnicode_FromString("A string");
5
+ }
@@ -0,0 +1,17 @@
1
+ #pragma once
2
+ #include "Python.h"
3
+
4
+ PyObject* hello(PyObject*, PyObject*);
5
+
6
+ static PyMethodDef extension_methods[] = {
7
+ {"hello", (PyCFunction)hello, METH_NOARGS},
8
+ {nullptr, nullptr, 0, nullptr}
9
+ };
10
+
11
+ static PyModuleDef extension_module = {
12
+ PyModuleDef_HEAD_INIT, "extension", "extension", -1, extension_methods};
13
+
14
+ PyMODINIT_FUNC PyInit_extension(void) {
15
+ Py_Initialize();
16
+ return PyModule_Create(&extension_module);
17
+ }
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.20"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "hatch-cpp-test-project-basic"
7
+ description = "Basic test project for hatch-cpp"
8
+ version = "0.1.0"
9
+ requires-python = ">=3.9"
10
+ dependencies = [
11
+ "hatchling>=1.20",
12
+ "hatch-cpp",
13
+ ]
14
+
15
+ [tool.hatch.build]
16
+ artifacts = [
17
+ "project/*.dll",
18
+ "project/*.dylib",
19
+ "project/*.so",
20
+ ]
21
+
22
+ [tool.hatch.build.sources]
23
+ src = "/"
24
+
25
+ [tool.hatch.build.targets.sdist]
26
+ packages = ["project"]
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["project"]
30
+
31
+ [tool.hatch.build.hooks.hatch-cpp]
32
+ build-config-class = "hatch_cpp.HatchCppBuildConfig"
33
+ build-plan-class = "hatch_cpp.HatchCppBuildPlan"
34
+ verbose = true
35
+ libraries = [
36
+ {name = "project/extension", sources = ["cpp/project/basic.cpp"], include-dirs = ["cpp"]}
37
+ ]
@@ -0,0 +1,6 @@
1
+ #include "project/basic.hpp"
2
+
3
+ std::string hello() {
4
+ return "A string";
5
+ }
6
+
@@ -0,0 +1,9 @@
1
+ #pragma once
2
+ #include <pybind11/pybind11.h>
3
+ #include <string>
4
+
5
+ std::string hello();
6
+
7
+ PYBIND11_MODULE(extension, m) {
8
+ m.def("hello", &hello);
9
+ }
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.20"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "hatch-cpp-test-project-basic"
7
+ description = "Basic test project for hatch-cpp"
8
+ version = "0.1.0"
9
+ requires-python = ">=3.9"
10
+ dependencies = [
11
+ "hatchling>=1.20",
12
+ "hatch-cpp",
13
+ ]
14
+
15
+ [tool.hatch.build]
16
+ artifacts = [
17
+ "project/*.dll",
18
+ "project/*.dylib",
19
+ "project/*.so",
20
+ ]
21
+
22
+ [tool.hatch.build.sources]
23
+ src = "/"
24
+
25
+ [tool.hatch.build.targets.sdist]
26
+ packages = ["project"]
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["project"]
30
+
31
+ [tool.hatch.build.hooks.hatch-cpp]
32
+ verbose = true
33
+ libraries = [
34
+ {name = "project/extension", sources = ["cpp/project/basic.cpp"], include-dirs = ["cpp"], binding="pybind11"},
35
+ ]
@@ -0,0 +1,41 @@
1
+ from os import listdir
2
+ from pathlib import Path
3
+ from shutil import rmtree
4
+ from subprocess import check_call
5
+ from sys import modules, path, platform
6
+
7
+ import pytest
8
+
9
+
10
+ class TestProject:
11
+ @pytest.mark.parametrize("project", ["test_project_basic", "test_project_override_classes", "test_project_pybind", "test_project_nanobind"])
12
+ def test_basic(self, project):
13
+ # cleanup
14
+ rmtree(f"hatch_cpp/tests/{project}/project/extension.so", ignore_errors=True)
15
+ rmtree(f"hatch_cpp/tests/{project}/project/extension.pyd", ignore_errors=True)
16
+ modules.pop("project", None)
17
+ modules.pop("project.extension", None)
18
+
19
+ # compile
20
+ check_call(
21
+ [
22
+ "hatchling",
23
+ "build",
24
+ "--hooks-only",
25
+ ],
26
+ cwd=f"hatch_cpp/tests/{project}",
27
+ )
28
+
29
+ # assert built
30
+
31
+ if platform == "win32":
32
+ assert "extension.pyd" in listdir(f"hatch_cpp/tests/{project}/project")
33
+ else:
34
+ assert "extension.so" in listdir(f"hatch_cpp/tests/{project}/project")
35
+
36
+ # import
37
+ here = Path(__file__).parent / project
38
+ path.insert(0, str(here))
39
+ import project.extension
40
+
41
+ assert project.extension.hello() == "A string"
hatch_cpp/utils.py CHANGED
@@ -1,5 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from functools import lru_cache
4
+
5
+ from pydantic import ImportString, TypeAdapter
6
+
7
+ _import_string_adapter = TypeAdapter(ImportString)
8
+
9
+
10
+ @lru_cache(maxsize=None)
11
+ def import_string(input_string: str):
12
+ return _import_string_adapter.validate_python(input_string)
13
+
14
+
3
15
  # import multiprocessing
4
16
  # import os
5
17
  # import os.path
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hatch-cpp
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Hatch plugin for C++ builds
5
5
  Project-URL: Repository, https://github.com/python-project-templates/hatch-cpp
6
6
  Project-URL: Homepage, https://github.com/python-project-templates/hatch-cpp
@@ -20,10 +20,13 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
20
20
  Classifier: Programming Language :: Python :: Implementation :: PyPy
21
21
  Requires-Python: >=3.9
22
22
  Requires-Dist: hatchling>=1.20
23
+ Requires-Dist: pydantic
23
24
  Provides-Extra: develop
24
25
  Requires-Dist: build; extra == 'develop'
25
26
  Requires-Dist: bump-my-version; extra == 'develop'
26
27
  Requires-Dist: check-manifest; extra == 'develop'
28
+ Requires-Dist: nanobind; extra == 'develop'
29
+ Requires-Dist: pybind11; extra == 'develop'
27
30
  Requires-Dist: pytest; extra == 'develop'
28
31
  Requires-Dist: pytest-cov; extra == 'develop'
29
32
  Requires-Dist: ruff<0.9,>=0.3; extra == 'develop'
@@ -42,5 +45,27 @@ Hatch plugin for C++ builds
42
45
 
43
46
  ## Overview
44
47
 
48
+ A simple, extensible C++ build plugin for [hatch](https://hatch.pypa.io/latest/).
49
+
50
+ ```toml
51
+ [tool.hatch.build.hooks.hatch-cpp]
52
+ libraries = [
53
+ {name = "project/extension", sources = ["cpp/project/basic.cpp"], include-dirs = ["cpp"]}
54
+ ]
55
+ ```
56
+
57
+ For more complete systems, see:
58
+ - [scikit-build-core](https://github.com/scikit-build/scikit-build-core)
59
+ - [setuptools](https://setuptools.pypa.io/en/latest/userguide/ext_modules.html)
60
+
61
+ ## Environment Variables
62
+ | Name | Default | Description |
63
+ |:-----|:--------|:------------|
64
+ |`CC`| | |
65
+ |`CXX`| | |
66
+ |`LD`| | |
67
+ |`HATCH_CPP_PLATFORM`| | |
68
+ |`HATCH_CPP_DISABLE_CCACHE`| | |
69
+
45
70
  > [!NOTE]
46
71
  > 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).
@@ -0,0 +1,30 @@
1
+ hatch_cpp/__init__.py,sha256=TkBacTiDIMCQw8W4szqksyUlwA-f-XuC7FxVqE5SSRQ,129
2
+ hatch_cpp/hooks.py,sha256=SQkF5WJIgzw-8rvlTzuQvBqdP6K3fHgnh6CZOZFag50,203
3
+ hatch_cpp/plugin.py,sha256=ZH-RmbaC9z9GMbOu9qWzWe6ht42noZLqBzj7VT-GGYQ,2240
4
+ hatch_cpp/structs.py,sha256=haFLBwb3T1YOC-ZjsbPB_X2Hv8whp4WMu3MrAIMdLkg,8857
5
+ hatch_cpp/utils.py,sha256=topOiT6biVzisNAEKHvYzU-JdA56B3LA61jbU9fjAEQ,4444
6
+ hatch_cpp/tests/test_all.py,sha256=zxMSmbNDdS_pIhjBR9IQfnJLtehCmbHqJXWC40Y8TXg,28
7
+ hatch_cpp/tests/test_projects.py,sha256=HW-4IExYEAgats0_-RL-HZr3uvJdNVEMJ083wWjFyWg,1275
8
+ hatch_cpp/tests/test_project_basic/pyproject.toml,sha256=eqM1UVpNmJWDfsuO18ZG_VOV9I4tAWgsM5Dhf49X8Nc,694
9
+ hatch_cpp/tests/test_project_basic/cpp/project/basic.cpp,sha256=gQ2nmdLqIdgaqxSKvkN_vbp6Iv_pAoVIHETXPRnALb0,117
10
+ hatch_cpp/tests/test_project_basic/cpp/project/basic.hpp,sha256=SO5GhPj8k3RzWrfH37lFSDc8w1Vf3yqTUhxmr1hnoko,422
11
+ hatch_cpp/tests/test_project_basic/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ hatch_cpp/tests/test_project_nanobind/pyproject.toml,sha256=PyqfQJomnY0ebavNiDBSlDU6z1f7XHR5_PE7RaxYHhQ,717
13
+ hatch_cpp/tests/test_project_nanobind/cpp/project/basic.cpp,sha256=L4v9AUveWWQX8Z2GMRREsEGLz-A_NCeuX0KiyppDmbc,30
14
+ hatch_cpp/tests/test_project_nanobind/cpp/project/basic.hpp,sha256=AK2FKdlqBwH5jnRUdJXSbWvRr-gSVsUKSG0PiYNsW7A,155
15
+ hatch_cpp/tests/test_project_nanobind/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ hatch_cpp/tests/test_project_override_classes/pyproject.toml,sha256=cfsaxEltYqcqJo9LKrd8VoTzafjue3cRj38UZsjF5-M,796
17
+ hatch_cpp/tests/test_project_override_classes/cpp/project/basic.cpp,sha256=gQ2nmdLqIdgaqxSKvkN_vbp6Iv_pAoVIHETXPRnALb0,117
18
+ hatch_cpp/tests/test_project_override_classes/cpp/project/basic.hpp,sha256=SO5GhPj8k3RzWrfH37lFSDc8w1Vf3yqTUhxmr1hnoko,422
19
+ hatch_cpp/tests/test_project_override_classes/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ hatch_cpp/tests/test_project_pybind/pyproject.toml,sha256=buYJAAdAanoOpJTj3JHfzSTgjjPPZ47ribtO07lODHY,715
21
+ hatch_cpp/tests/test_project_pybind/cpp/project/basic.cpp,sha256=MT3eCSQKr4guI3XZHV8kw8PoBGC6LEK8ClxnNMBnvnI,78
22
+ hatch_cpp/tests/test_project_pybind/cpp/project/basic.hpp,sha256=LZSfCfhLY_91MBOxtnvDk7DcceO-9GhCHKpMnAjGe18,146
23
+ hatch_cpp/tests/test_project_pybind/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ hatch_cpp/toolchains/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ hatch_cpp/toolchains/cmake.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ hatch_cpp-0.1.2.dist-info/METADATA,sha256=bLAzn6Fm5bSdr0wQgeQj4akpUViQTRro4FxaGxU-Epo,3003
27
+ hatch_cpp-0.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
+ hatch_cpp-0.1.2.dist-info/entry_points.txt,sha256=RgXfjpD4iwomJQK5n7FnPkXzb5fOnF5v3DI5hbkgVcw,30
29
+ hatch_cpp-0.1.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
30
+ hatch_cpp-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [hatch]
2
+ cpp = hatch_cpp.hooks
hatch_cpp/__main__.py DELETED
@@ -1,4 +0,0 @@
1
- from .cli import main
2
-
3
- if __name__ == "__main__":
4
- main()
@@ -1,28 +0,0 @@
1
- from os import listdir
2
- from pathlib import Path
3
- from shutil import rmtree
4
- from subprocess import check_output
5
- from sys import path, platform
6
-
7
-
8
- class TestProject:
9
- def test_basic(self):
10
- rmtree("hatch_cpp/tests/test_project_basic/basic_project/extension.so", ignore_errors=True)
11
- rmtree("hatch_cpp/tests/test_project_basic/basic_project/extension.pyd", ignore_errors=True)
12
- check_output(
13
- [
14
- "hatchling",
15
- "build",
16
- "--hooks-only",
17
- ],
18
- cwd="hatch_cpp/tests/test_project_basic",
19
- )
20
- if platform == "win32":
21
- assert "extension.pyd" in listdir("hatch_cpp/tests/test_project_basic/basic_project")
22
- else:
23
- assert "extension.so" in listdir("hatch_cpp/tests/test_project_basic/basic_project")
24
- here = Path(__file__).parent / "test_project_basic"
25
- path.insert(0, str(here))
26
- import basic_project.extension
27
-
28
- assert basic_project.extension.hello() == "A string"
@@ -1,19 +0,0 @@
1
- hatch_cpp/__init__.py,sha256=rnObPjuBcEStqSO0S6gsdS_ot8ITOQjVj_-P1LUUYpg,22
2
- hatch_cpp/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
3
- hatch_cpp/hooks.py,sha256=SQkF5WJIgzw-8rvlTzuQvBqdP6K3fHgnh6CZOZFag50,203
4
- hatch_cpp/plugin.py,sha256=deTyrXUhddxmeOcGQ_U9tkxIukJ55zBP5yGOLr_gtcs,3521
5
- hatch_cpp/structs.py,sha256=JmW0fTzqO66lOY680g71wdVEX4uIg7u169i3vcFJvVA,6430
6
- hatch_cpp/utils.py,sha256=qeIjS0rul_Kz8Nzji6IWPGQ4Y7jBmIW51hsXczs0j1w,4181
7
- hatch_cpp/tests/test_all.py,sha256=zxMSmbNDdS_pIhjBR9IQfnJLtehCmbHqJXWC40Y8TXg,28
8
- hatch_cpp/tests/test_project_basic.py,sha256=TaMUZirQnNQIrWXkHlHqTUntR9VStprim-G2A9BX5yY,1026
9
- hatch_cpp/tests/test_project_basic/pyproject.toml,sha256=QKKt4UPNvQqMwRfI7_GtksrDSfcEnDkWb-0pimi08Ck,1440
10
- hatch_cpp/tests/test_project_basic/basic_project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- hatch_cpp/tests/test_project_basic/cpp/basic-project/basic.cpp,sha256=SzFhtdj02DiJXp2bTAfLpsuOwhRQHghPATaL3SWq3us,123
12
- hatch_cpp/tests/test_project_basic/cpp/basic-project/basic.hpp,sha256=SO5GhPj8k3RzWrfH37lFSDc8w1Vf3yqTUhxmr1hnoko,422
13
- hatch_cpp/toolchains/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- hatch_cpp/toolchains/cmake.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- hatch_cpp-0.1.1.dist-info/METADATA,sha256=8pwQ3a83aP-uvRQ-F8gzliWqPirWCCQ-HWYHLkEhcj8,2278
16
- hatch_cpp-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- hatch_cpp-0.1.1.dist-info/entry_points.txt,sha256=lP2xDFRH6dca9Ld3hE91Syr_q6-hkJhGmtQ5zukJr9E,80
18
- hatch_cpp-0.1.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
19
- hatch_cpp-0.1.1.dist-info/RECORD,,
@@ -1,5 +0,0 @@
1
- [console_scripts]
2
- hatch-cpp = hatch_cpp.cli:main
3
-
4
- [hatch]
5
- cpp = hatch_cpp.hooks