hatch-cpp 0.1.0__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 -55
  3. hatch_cpp/structs.py +138 -66
  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.0.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 -22
  25. hatch_cpp-0.1.0.dist-info/RECORD +0 -19
  26. hatch_cpp-0.1.0.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.0.dist-info → hatch_cpp-0.1.2.dist-info}/WHEEL +0 -0
  30. {hatch_cpp-0.1.0.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.0"
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,66 +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
- # 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
47
- build_plan = HatchCppBuildPlan(libraries=libraries, platform=platform)
48
- build_plan.generate()
49
- build_plan.execute(verbose=config.verbose)
50
- # build_kwargs = config.build_kwargs
51
- # if version == "editable":
52
- # build_kwargs = config.editable_build_kwargs or build_kwargs
53
-
54
- # should_skip_build = False
55
- # if not config.build_function:
56
- # log.warning("No build function found")
57
- # should_skip_build = True
58
-
59
- # elif config.skip_if_exists and version == "standard":
60
- # should_skip_build = should_skip(config.skip_if_exists)
61
- # if should_skip_build:
62
- # log.info("Skip-if-exists file(s) found")
63
-
64
- # # Get build function and call it with normalized parameter names.
65
- # if not should_skip_build and config.build_function:
66
- # build_func = get_build_func(config.build_function)
67
- # build_kwargs = normalize_kwargs(build_kwargs)
68
- # log.info("Building with %s", config.build_function)
69
- # log.info("With kwargs: %s", build_kwargs)
70
- # try:
71
- # build_func(self.target_name, version, **build_kwargs)
72
- # except Exception as e:
73
- # if version == "editable" and config.optional_editable_build.lower() == "true":
74
- # warnings.warn(f"Encountered build error:\n{e}", stacklevel=2)
75
- # else:
76
- # raise e
77
- # else:
78
- # log.info("Skipping build")
79
-
80
- # # Ensure targets in distributable dists.
81
- # if version == "standard":
82
- # ensure_targets(config.ensured_targets)
83
-
84
- self._logger.info("Finished running hatch-cpp")
85
- 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,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass, field
4
3
  from os import environ, system
5
- from sys import platform as sys_platform
4
+ from pathlib import Path
5
+ from shutil import which
6
+ from sys import executable, platform as sys_platform
6
7
  from sysconfig import get_path
7
- from typing import Literal
8
+ from typing import List, Literal, Optional
8
9
 
9
- from hatchling.builders.config import BuilderConfig
10
+ from pydantic import BaseModel, Field
10
11
 
11
12
  __all__ = (
12
13
  "HatchCppBuildConfig",
@@ -15,53 +16,47 @@ __all__ = (
15
16
  "HatchCppBuildPlan",
16
17
  )
17
18
 
18
- Platform = Literal["linux", "darwin", "win32"]
19
+ BuildType = Literal["debug", "release"]
19
20
  CompilerToolchain = Literal["gcc", "clang", "msvc"]
21
+ Language = Literal["c", "c++"]
22
+ Binding = Literal["cpython", "pybind11", "nanobind"]
23
+ Platform = Literal["linux", "darwin", "win32"]
20
24
  PlatformDefaults = {
21
- "linux": {"CC": "gcc", "CXX": "g++"},
22
- "darwin": {"CC": "clang", "CXX": "clang++"},
23
- "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"},
24
28
  }
25
29
 
26
30
 
27
- @dataclass
28
- class HatchCppBuildConfig(BuilderConfig):
29
- """Build config values for Hatch C++ Builder."""
31
+ class HatchCppLibrary(BaseModel):
32
+ """A C++ library."""
30
33
 
31
- toolchain: str | None = field(default="raw")
32
- libraries: list[dict[str, str]] = field(default_factory=list)
33
- verbose: bool | None = field(default=False)
34
- # build_function: str | None = None
35
- # build_kwargs: t.Mapping[str, str] = field(default_factory=dict)
36
- # editable_build_kwargs: t.Mapping[str, str] = field(default_factory=dict)
37
- # ensured_targets: list[str] = field(default_factory=list)
38
- # skip_if_exists: list[str] = field(default_factory=list)
34
+ name: str
35
+ sources: List[str]
36
+ language: Language = "c++"
39
37
 
38
+ binding: Binding = "cpython"
39
+ std: Optional[str] = None
40
40
 
41
- @dataclass
42
- class HatchCppLibrary(object):
43
- """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)
44
44
 
45
- name: str
46
- 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")
47
48
 
48
- include_dirs: list[str] = field(default_factory=list)
49
- library_dirs: list[str] = field(default_factory=list)
50
- libraries: list[str] = field(default_factory=list)
51
- extra_compile_args: list[str] = field(default_factory=list)
52
- extra_link_args: list[str] = field(default_factory=list)
53
- extra_objects: list[str] = field(default_factory=list)
54
- define_macros: list[str] = field(default_factory=list)
55
- 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")
56
51
 
57
- export_symbols: list[str] = field(default_factory=list)
58
- 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)
59
54
 
60
55
 
61
- @dataclass
62
- class HatchCppPlatform(object):
56
+ class HatchCppPlatform(BaseModel):
63
57
  cc: str
64
58
  cxx: str
59
+ ld: str
65
60
  platform: Platform
66
61
  toolchain: CompilerToolchain
67
62
 
@@ -70,6 +65,7 @@ class HatchCppPlatform(object):
70
65
  platform = environ.get("HATCH_CPP_PLATFORM", sys_platform)
71
66
  CC = environ.get("CC", PlatformDefaults[platform]["CC"])
72
67
  CXX = environ.get("CXX", PlatformDefaults[platform]["CXX"])
68
+ LD = environ.get("LD", PlatformDefaults[platform]["LD"])
73
69
  if "gcc" in CC and "g++" in CXX:
74
70
  toolchain = "gcc"
75
71
  elif "clang" in CC and "clang++" in CXX:
@@ -78,69 +74,145 @@ class HatchCppPlatform(object):
78
74
  toolchain = "msvc"
79
75
  else:
80
76
  raise Exception(f"Unrecognized toolchain: {CC}, {CXX}")
81
- return HatchCppPlatform(cc=CC, cxx=CXX, platform=platform, toolchain=toolchain)
82
77
 
83
- def get_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:
84
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
+
85
111
  if self.toolchain == "gcc":
86
- flags = f"-I{get_path('include')}"
87
112
  flags += " " + " ".join(f"-I{d}" for d in library.include_dirs)
88
- flags += " -fPIC -shared"
113
+ flags += " -fPIC"
89
114
  flags += " " + " ".join(library.extra_compile_args)
115
+ flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros)
116
+ flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros)
117
+ if library.std:
118
+ flags += f" -std={library.std}"
119
+ elif self.toolchain == "clang":
120
+ flags += " ".join(f"-I{d}" for d in library.include_dirs)
121
+ flags += " -fPIC"
122
+ flags += " " + " ".join(library.extra_compile_args)
123
+ flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros)
124
+ flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros)
125
+ if library.std:
126
+ flags += f" -std={library.std}"
127
+ elif self.toolchain == "msvc":
128
+ flags += " ".join(f"/I{d}" for d in library.include_dirs)
129
+ flags += " " + " ".join(library.extra_compile_args)
130
+ flags += " " + " ".join(library.extra_link_args)
131
+ flags += " " + " ".join(library.extra_objects)
132
+ flags += " " + " ".join(f"/D{macro}" for macro in library.define_macros)
133
+ flags += " " + " ".join(f"/U{macro}" for macro in library.undef_macros)
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"
90
146
  flags += " " + " ".join(library.extra_link_args)
91
147
  flags += " " + " ".join(library.extra_objects)
92
148
  flags += " " + " ".join(f"-l{lib}" for lib in library.libraries)
93
149
  flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs)
94
- flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros)
95
- flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros)
96
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"
97
157
  elif self.toolchain == "clang":
98
- flags = f"-I{get_path('include')} "
99
- flags += " ".join(f"-I{d}" for d in library.include_dirs)
100
- flags += " -undefined dynamic_lookup -fPIC -shared"
101
- flags += " " + " ".join(library.extra_compile_args)
158
+ flags += " -shared"
102
159
  flags += " " + " ".join(library.extra_link_args)
103
160
  flags += " " + " ".join(library.extra_objects)
104
161
  flags += " " + " ".join(f"-l{lib}" for lib in library.libraries)
105
162
  flags += " " + " ".join(f"-L{lib}" for lib in library.library_dirs)
106
- flags += " " + " ".join(f"-D{macro}" for macro in library.define_macros)
107
- flags += " " + " ".join(f"-U{macro}" for macro in library.undef_macros)
108
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"
109
170
  elif self.toolchain == "msvc":
110
- flags = f"/I{get_path('include')} "
111
- flags += " ".join(f"/I{d}" for d in library.include_dirs)
112
- flags += " /LD"
113
- flags += " " + " ".join(library.extra_compile_args)
114
171
  flags += " " + " ".join(library.extra_link_args)
115
172
  flags += " " + " ".join(library.extra_objects)
173
+ flags += " /LD"
174
+ flags += f" /Fe:{library.name}.pyd"
175
+ flags += " /link /DLL"
176
+ if (Path(executable).parent / "libs").exists():
177
+ flags += f" /LIBPATH:{str(Path(executable).parent / 'libs')}"
116
178
  flags += " " + " ".join(f"{lib}.lib" for lib in library.libraries)
117
179
  flags += " " + " ".join(f"/LIBPATH:{lib}" for lib in library.library_dirs)
118
- flags += " " + " ".join(f"/D{macro}" for macro in library.define_macros)
119
- flags += " " + " ".join(f"/U{macro}" for macro in library.undef_macros)
120
- flags += f" /Fo{library.name}.obj"
121
- flags += f" /Fe{library.name}.pyd"
122
180
  # clean
123
181
  while flags.count(" "):
124
182
  flags = flags.replace(" ", " ")
125
183
  return flags
126
184
 
127
185
 
128
- @dataclass
129
- class HatchCppBuildPlan(object):
130
- libraries: list[HatchCppLibrary] = field(default_factory=list)
131
- platform: HatchCppPlatform = field(default_factory=HatchCppPlatform.default)
132
- 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)
133
191
 
134
192
  def generate(self):
135
193
  self.commands = []
136
194
  for library in self.libraries:
137
- flags = self.platform.get_flags(library)
138
- 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
+ )
139
200
  return self.commands
140
201
 
141
- def execute(self, verbose: bool = True):
202
+ def execute(self):
142
203
  for command in self.commands:
143
- if verbose:
144
- print(f"Running command: {command}")
145
204
  system(command)
146
205
  return self.commands
206
+
207
+ def cleanup(self):
208
+ if self.platform.platform == "win32":
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.0
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,22 +0,0 @@
1
- from os import listdir
2
- from shutil import rmtree
3
- from subprocess import check_output
4
- from sys import platform
5
-
6
-
7
- class TestProject:
8
- def test_basic(self):
9
- rmtree("hatch_cpp/tests/test_project_basic/basic_project/extension.so", ignore_errors=True)
10
- rmtree("hatch_cpp/tests/test_project_basic/basic_project/extension.pyd", ignore_errors=True)
11
- check_output(
12
- [
13
- "hatchling",
14
- "build",
15
- "--hooks-only",
16
- ],
17
- cwd="hatch_cpp/tests/test_project_basic",
18
- )
19
- if platform == "win32":
20
- assert "extension.pyd" in listdir("hatch_cpp/tests/test_project_basic/basic_project")
21
- else:
22
- assert "extension.so" in listdir("hatch_cpp/tests/test_project_basic/basic_project")
@@ -1,19 +0,0 @@
1
- hatch_cpp/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,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=2skvxNUCfLVbgKo_DKvTAvpU6Rmolhm5NKSzcVXAPI0,3576
5
- hatch_cpp/structs.py,sha256=Jk6NBdbsVTwVg78XltfyKJet-hptLy8VcoZI2n8cELg,5939
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=jswtsv_FtMKLLAWqZGQiZX3VgW0UoxUicqQ5O0yzI04,800
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.0.dist-info/METADATA,sha256=IJ8GoRmY9b7HGAapdCeWARYmTpJeZf2WBM0G0naMoHI,2278
16
- hatch_cpp-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- hatch_cpp-0.1.0.dist-info/entry_points.txt,sha256=lP2xDFRH6dca9Ld3hE91Syr_q6-hkJhGmtQ5zukJr9E,80
18
- hatch_cpp-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
19
- hatch_cpp-0.1.0.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