hatch-cpp 0.3.5__py3-none-any.whl → 0.3.7__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.
hatch_cpp/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.3.5"
1
+ __version__ = "0.3.7"
2
2
 
3
3
  from .config import *
4
4
  from .hooks import *
hatch_cpp/config.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from os import system as system_call
3
+ from os import environ, system as system_call
4
4
  from pathlib import Path
5
5
  from typing import List, Optional
6
6
 
@@ -63,12 +63,27 @@ class HatchCppBuildPlan(HatchCppBuildConfig):
63
63
  def generate(self):
64
64
  self.commands = []
65
65
 
66
+ # Check for env var overrides
67
+ vcpkg_override = environ.get("HATCH_CPP_VCPKG")
68
+ cmake_override = environ.get("HATCH_CPP_CMAKE")
69
+
66
70
  # Evaluate toolchains
67
- if self.vcpkg and Path(self.vcpkg.vcpkg).exists():
71
+ if vcpkg_override == "1":
72
+ if self.vcpkg:
73
+ self._active_toolchains.append("vcpkg")
74
+ else:
75
+ log.warning("HATCH_CPP_VCPKG=1 set but no vcpkg configuration found; ignoring.")
76
+ elif vcpkg_override != "0" and self.vcpkg and Path(self.vcpkg.vcpkg).exists():
68
77
  self._active_toolchains.append("vcpkg")
78
+
69
79
  if self.libraries:
70
80
  self._active_toolchains.append("vanilla")
71
- elif self.cmake:
81
+ elif cmake_override == "1":
82
+ if self.cmake:
83
+ self._active_toolchains.append("cmake")
84
+ else:
85
+ log.warning("HATCH_CPP_CMAKE=1 set but no cmake configuration found; ignoring.")
86
+ elif cmake_override != "0" and self.cmake:
72
87
  self._active_toolchains.append("cmake")
73
88
 
74
89
  # Collect toolchain commands
@@ -345,7 +345,7 @@ class TestPlatformFlagsIntegration:
345
345
 
346
346
  flags = platform.get_link_flags(library)
347
347
  assert "-shared" in flags
348
- assert "-Wl,-rpath,$ORIGIN/lib" in flags
348
+ assert r"-Wl,-rpath,\$ORIGIN/lib" in flags
349
349
 
350
350
  def test_darwin_platform_uses_darwin_specific_fields(self):
351
351
  """Test that darwin platform uses darwin-specific fields."""
@@ -1,11 +1,14 @@
1
+ from os import environ
1
2
  from pathlib import Path
2
3
  from sys import version_info
4
+ from unittest.mock import patch
3
5
 
4
6
  import pytest
5
7
  from pydantic import ValidationError
6
8
  from toml import loads
7
9
 
8
10
  from hatch_cpp import HatchCppBuildConfig, HatchCppBuildPlan, HatchCppLibrary, HatchCppPlatform
11
+ from hatch_cpp.toolchains.common import _normalize_rpath
9
12
 
10
13
 
11
14
  class TestStructs:
@@ -54,3 +57,184 @@ class TestStructs:
54
57
  assert "clang" in hatch_build_config.platform.cc
55
58
  assert "clang++" in hatch_build_config.platform.cxx
56
59
  assert hatch_build_config.platform.toolchain == "gcc"
60
+
61
+ def test_cmake_args_env_variable(self):
62
+ """Test that CMAKE_ARGS environment variable is respected."""
63
+ txt = (Path(__file__).parent / "test_project_cmake" / "pyproject.toml").read_text()
64
+ toml_data = loads(txt)
65
+ hatch_build_config = HatchCppBuildConfig(name=toml_data["project"]["name"], **toml_data["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"])
66
+ hatch_build_plan = HatchCppBuildPlan(**hatch_build_config.model_dump())
67
+
68
+ with patch.dict(environ, {"CMAKE_ARGS": "-DFOO=bar -DBAZ=qux"}):
69
+ hatch_build_plan.generate()
70
+ assert "-DFOO=bar" in hatch_build_plan.commands[0]
71
+ assert "-DBAZ=qux" in hatch_build_plan.commands[0]
72
+
73
+ def test_cmake_args_env_variable_empty(self):
74
+ """Test that an empty CMAKE_ARGS does not add extra whitespace."""
75
+ txt = (Path(__file__).parent / "test_project_cmake" / "pyproject.toml").read_text()
76
+ toml_data = loads(txt)
77
+ hatch_build_config = HatchCppBuildConfig(name=toml_data["project"]["name"], **toml_data["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"])
78
+ hatch_build_plan = HatchCppBuildPlan(**hatch_build_config.model_dump())
79
+
80
+ with patch.dict(environ, {"CMAKE_ARGS": ""}):
81
+ hatch_build_plan.generate()
82
+ # Should not have trailing whitespace from empty CMAKE_ARGS
83
+ assert not hatch_build_plan.commands[0].endswith(" ")
84
+
85
+ def test_cmake_generator_env_variable(self):
86
+ """Test that CMAKE_GENERATOR environment variable is respected on non-Windows platforms."""
87
+ txt = (Path(__file__).parent / "test_project_cmake" / "pyproject.toml").read_text()
88
+ toml_data = loads(txt)
89
+ hatch_build_config = HatchCppBuildConfig(name=toml_data["project"]["name"], **toml_data["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"])
90
+ hatch_build_plan = HatchCppBuildPlan(**hatch_build_config.model_dump())
91
+
92
+ with patch.dict(environ, {"CMAKE_GENERATOR": "Ninja"}):
93
+ hatch_build_plan.generate()
94
+ assert '-G "Ninja"' in hatch_build_plan.commands[0]
95
+
96
+ def test_cmake_generator_env_variable_unset(self):
97
+ """Test that no -G flag is added on non-Windows when CMAKE_GENERATOR is not set."""
98
+ txt = (Path(__file__).parent / "test_project_cmake" / "pyproject.toml").read_text()
99
+ toml_data = loads(txt)
100
+ hatch_build_config = HatchCppBuildConfig(name=toml_data["project"]["name"], **toml_data["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"])
101
+ hatch_build_plan = HatchCppBuildPlan(**hatch_build_config.model_dump())
102
+
103
+ with patch.dict(environ, {}, clear=False):
104
+ # Remove CMAKE_GENERATOR if present
105
+ environ.pop("CMAKE_GENERATOR", None)
106
+ hatch_build_plan.generate()
107
+ if hatch_build_plan.platform.platform != "win32":
108
+ assert "-G " not in hatch_build_plan.commands[0]
109
+
110
+ def test_hatch_cpp_cmake_env_force_off(self):
111
+ """Test that HATCH_CPP_CMAKE=0 disables cmake even when cmake config is present."""
112
+ txt = (Path(__file__).parent / "test_project_cmake" / "pyproject.toml").read_text()
113
+ toml_data = loads(txt)
114
+ hatch_build_config = HatchCppBuildConfig(name=toml_data["project"]["name"], **toml_data["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"])
115
+ hatch_build_plan = HatchCppBuildPlan(**hatch_build_config.model_dump())
116
+
117
+ assert hatch_build_plan.cmake is not None
118
+ with patch.dict(environ, {"HATCH_CPP_CMAKE": "0"}):
119
+ hatch_build_plan.generate()
120
+ # cmake should not be active, so no cmake commands generated
121
+ assert len(hatch_build_plan.commands) == 0
122
+ assert "cmake" not in hatch_build_plan._active_toolchains
123
+
124
+ def test_hatch_cpp_cmake_env_force_on(self):
125
+ """Test that HATCH_CPP_CMAKE=1 enables cmake when cmake config is present."""
126
+ txt = (Path(__file__).parent / "test_project_cmake" / "pyproject.toml").read_text()
127
+ toml_data = loads(txt)
128
+ hatch_build_config = HatchCppBuildConfig(name=toml_data["project"]["name"], **toml_data["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"])
129
+ hatch_build_plan = HatchCppBuildPlan(**hatch_build_config.model_dump())
130
+
131
+ assert hatch_build_plan.cmake is not None
132
+ with patch.dict(environ, {"HATCH_CPP_CMAKE": "1"}):
133
+ hatch_build_plan.generate()
134
+ assert "cmake" in hatch_build_plan._active_toolchains
135
+
136
+ def test_hatch_cpp_cmake_env_force_on_no_config(self):
137
+ """Test that HATCH_CPP_CMAKE=1 warns and skips when no cmake config exists."""
138
+ txt = (Path(__file__).parent / "test_project_cmake" / "pyproject.toml").read_text()
139
+ toml_data = loads(txt)
140
+ config_data = toml_data["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"].copy()
141
+ config_data.pop("cmake", None)
142
+ hatch_build_config = HatchCppBuildConfig(name=toml_data["project"]["name"], **config_data)
143
+ hatch_build_plan = HatchCppBuildPlan(**hatch_build_config.model_dump())
144
+
145
+ assert hatch_build_plan.cmake is None
146
+ with patch.dict(environ, {"HATCH_CPP_CMAKE": "1"}):
147
+ hatch_build_plan.generate()
148
+ # cmake should NOT be activated when there's no config
149
+ assert "cmake" not in hatch_build_plan._active_toolchains
150
+
151
+ def test_hatch_cpp_vcpkg_env_force_off(self):
152
+ """Test that HATCH_CPP_VCPKG=0 disables vcpkg even when vcpkg.json exists."""
153
+ txt = (Path(__file__).parent / "test_project_cmake_vcpkg" / "pyproject.toml").read_text()
154
+ toml_data = loads(txt)
155
+ hatch_build_config = HatchCppBuildConfig(name=toml_data["project"]["name"], **toml_data["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"])
156
+ hatch_build_plan = HatchCppBuildPlan(**hatch_build_config.model_dump())
157
+
158
+ with patch.dict(environ, {"HATCH_CPP_VCPKG": "0"}):
159
+ hatch_build_plan.generate()
160
+ assert "vcpkg" not in hatch_build_plan._active_toolchains
161
+
162
+ def test_hatch_cpp_vcpkg_env_force_on(self):
163
+ """Test that HATCH_CPP_VCPKG=1 enables vcpkg even when vcpkg.json doesn't exist."""
164
+ txt = (Path(__file__).parent / "test_project_cmake" / "pyproject.toml").read_text()
165
+ toml_data = loads(txt)
166
+ hatch_build_config = HatchCppBuildConfig(name=toml_data["project"]["name"], **toml_data["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"])
167
+ hatch_build_plan = HatchCppBuildPlan(**hatch_build_config.model_dump())
168
+
169
+ with patch.dict(environ, {"HATCH_CPP_VCPKG": "1"}):
170
+ hatch_build_plan.generate()
171
+ assert "vcpkg" in hatch_build_plan._active_toolchains
172
+
173
+
174
+ class TestNormalizeRpath:
175
+ def test_origin_to_loader_path_on_darwin(self):
176
+ """$ORIGIN should be translated to @loader_path on macOS."""
177
+ assert _normalize_rpath("-Wl,-rpath,$ORIGIN", "darwin") == "-Wl,-rpath,@loader_path"
178
+
179
+ def test_loader_path_to_origin_on_linux(self):
180
+ """@loader_path should be translated to (escaped) $ORIGIN on Linux."""
181
+ result = _normalize_rpath("-Wl,-rpath,@loader_path", "linux")
182
+ assert result == r"-Wl,-rpath,\$ORIGIN"
183
+
184
+ def test_origin_escaped_on_linux(self):
185
+ """$ORIGIN should be escaped as \\$ORIGIN on Linux for shell safety."""
186
+ result = _normalize_rpath("-Wl,-rpath,$ORIGIN", "linux")
187
+ assert result == r"-Wl,-rpath,\$ORIGIN"
188
+
189
+ def test_already_escaped_origin_on_darwin(self):
190
+ """Already-escaped \\$ORIGIN should still translate to @loader_path on macOS."""
191
+ assert _normalize_rpath(r"-Wl,-rpath,\$ORIGIN", "darwin") == "-Wl,-rpath,@loader_path"
192
+
193
+ def test_no_rpath_unchanged(self):
194
+ """Args without rpath values should pass through unchanged."""
195
+ assert _normalize_rpath("-lfoo", "linux") == "-lfoo"
196
+ assert _normalize_rpath("-lfoo", "darwin") == "-lfoo"
197
+
198
+ def test_win32_no_transform(self):
199
+ """Windows should not transform rpath values."""
200
+ assert _normalize_rpath("$ORIGIN", "win32") == "$ORIGIN"
201
+ assert _normalize_rpath("@loader_path", "win32") == "@loader_path"
202
+
203
+ def test_link_flags_rpath_translation_darwin(self):
204
+ """Full integration: extra_link_args with $ORIGIN produce @loader_path on macOS."""
205
+ library = HatchCppLibrary(
206
+ name="test",
207
+ sources=["test.cpp"],
208
+ binding="generic",
209
+ extra_link_args=["-Wl,-rpath,$ORIGIN"],
210
+ )
211
+ platform = HatchCppPlatform(
212
+ cc="clang",
213
+ cxx="clang++",
214
+ ld="ld",
215
+ platform="darwin",
216
+ toolchain="clang",
217
+ disable_ccache=True,
218
+ )
219
+ flags = platform.get_link_flags(library)
220
+ assert "@loader_path" in flags
221
+ assert "$ORIGIN" not in flags
222
+
223
+ def test_link_flags_rpath_escaped_linux(self):
224
+ """Full integration: extra_link_args with $ORIGIN are shell-escaped on Linux."""
225
+ library = HatchCppLibrary(
226
+ name="test",
227
+ sources=["test.cpp"],
228
+ binding="generic",
229
+ extra_link_args=["-Wl,-rpath,$ORIGIN"],
230
+ )
231
+ platform = HatchCppPlatform(
232
+ cc="gcc",
233
+ cxx="g++",
234
+ ld="ld",
235
+ platform="linux",
236
+ toolchain="gcc",
237
+ disable_ccache=True,
238
+ )
239
+ flags = platform.get_link_flags(library)
240
+ assert r"\$ORIGIN" in flags
@@ -156,14 +156,66 @@ class TestVcpkgGenerate:
156
156
  def test_generate_skips_clone_when_vcpkg_root_exists(self, tmp_path, monkeypatch):
157
157
  monkeypatch.chdir(tmp_path)
158
158
  self._make_vcpkg_env(tmp_path)
159
- (tmp_path / "vcpkg").mkdir()
159
+ vcpkg_root = tmp_path / "vcpkg"
160
+ vcpkg_root.mkdir()
161
+ # Existing bootstrap script and executable mean no clone/bootstrap is needed.
162
+ (vcpkg_root / "bootstrap-vcpkg.sh").write_text("#!/bin/sh\nexit 0\n")
163
+ (vcpkg_root / "vcpkg").write_text("#!/bin/sh\nexit 0\n")
160
164
 
161
165
  cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
166
+ monkeypatch.setattr(cfg, "_is_vcpkg_working", lambda: True)
162
167
  commands = cfg.generate(None)
163
168
 
164
- # When vcpkg_root already exists, no clone or checkout happens
169
+ # When vcpkg_root already exists with a working executable, no clone or bootstrap happens.
165
170
  assert not any("git clone" in cmd for cmd in commands)
166
171
  assert not any("checkout" in cmd for cmd in commands)
172
+ assert not any("bootstrap-vcpkg" in cmd for cmd in commands)
173
+ assert any("vcpkg" in cmd and "install" in cmd for cmd in commands)
174
+
175
+ def test_generate_reclones_when_vcpkg_root_exists_but_empty(self, tmp_path, monkeypatch):
176
+ monkeypatch.chdir(tmp_path)
177
+ self._make_vcpkg_env(tmp_path)
178
+ (tmp_path / "vcpkg").mkdir()
179
+
180
+ cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
181
+ commands = cfg.generate(None)
182
+
183
+ assert any(cmd.startswith('rm -rf "vcpkg"') for cmd in commands)
184
+ assert any("git clone" in cmd for cmd in commands)
185
+ assert any("checkout 2024.01.12" in cmd for cmd in commands)
186
+ assert any("bootstrap-vcpkg" in cmd for cmd in commands)
187
+ assert any("vcpkg" in cmd and "install" in cmd for cmd in commands)
188
+
189
+ def test_generate_bootstraps_when_vcpkg_executable_missing(self, tmp_path, monkeypatch):
190
+ monkeypatch.chdir(tmp_path)
191
+ self._make_vcpkg_env(tmp_path)
192
+ vcpkg_root = tmp_path / "vcpkg"
193
+ vcpkg_root.mkdir()
194
+ (vcpkg_root / "bootstrap-vcpkg.sh").write_text("#!/bin/sh\nexit 0\n")
195
+
196
+ cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
197
+ commands = cfg.generate(None)
198
+
199
+ assert not any("git clone" in cmd for cmd in commands)
200
+ assert any("bootstrap-vcpkg" in cmd for cmd in commands)
201
+ assert any("vcpkg" in cmd and "install" in cmd for cmd in commands)
202
+
203
+ def test_generate_reclones_when_vcpkg_exists_but_not_working(self, tmp_path, monkeypatch):
204
+ monkeypatch.chdir(tmp_path)
205
+ self._make_vcpkg_env(tmp_path)
206
+ vcpkg_root = tmp_path / "vcpkg"
207
+ vcpkg_root.mkdir()
208
+ (vcpkg_root / "bootstrap-vcpkg.sh").write_text("#!/bin/sh\nexit 0\n")
209
+ (vcpkg_root / "vcpkg").write_text("#!/bin/sh\nexit 1\n")
210
+
211
+ cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
212
+ monkeypatch.setattr(cfg, "_is_vcpkg_working", lambda: False)
213
+ commands = cfg.generate(None)
214
+
215
+ assert any(cmd.startswith('rm -rf "vcpkg"') for cmd in commands)
216
+ assert any("git clone" in cmd for cmd in commands)
217
+ assert any("checkout 2024.01.12" in cmd for cmd in commands)
218
+ assert any("bootstrap-vcpkg" in cmd for cmd in commands)
167
219
  assert any("vcpkg" in cmd and "install" in cmd for cmd in commands)
168
220
 
169
221
  def test_generate_no_vcpkg_json(self, tmp_path, monkeypatch):
@@ -54,9 +54,14 @@ class HatchCppCmakeConfiguration(BaseModel):
54
54
  commands[-1] += f" -DCMAKE_INSTALL_PREFIX={Path(self.root).parent}"
55
55
 
56
56
  # TODO: CMAKE_CXX_COMPILER
57
+ # Respect CMAKE_GENERATOR environment variable
58
+ cmake_generator = environ.get("CMAKE_GENERATOR", "")
57
59
  if config.platform.platform == "win32":
58
- # TODO: prefix?
59
- commands[-1] += f' -G "{environ.get("CMAKE_GENERATOR", "Visual Studio 17 2022")}"'
60
+ if not cmake_generator:
61
+ cmake_generator = "Visual Studio 17 2022"
62
+ commands[-1] += f' -G "{cmake_generator}"'
63
+ elif cmake_generator:
64
+ commands[-1] += f' -G "{cmake_generator}"'
60
65
 
61
66
  # Put in CMake flags
62
67
  args = self.cmake_args.copy()
@@ -78,6 +83,11 @@ class HatchCppCmakeConfiguration(BaseModel):
78
83
  if config.platform.platform == "darwin":
79
84
  commands[-1] += f" -DCMAKE_OSX_DEPLOYMENT_TARGET={environ.get('OSX_DEPLOYMENT_TARGET', '11')}"
80
85
 
86
+ # Respect CMAKE_ARGS environment variable
87
+ cmake_args_env = environ.get("CMAKE_ARGS", "").strip()
88
+ if cmake_args_env:
89
+ commands[-1] += " " + cmake_args_env
90
+
81
91
  # Append build command
82
92
  commands.append(f"cmake --build {self.build} --config {config.build_type}")
83
93
 
@@ -20,6 +20,7 @@ __all__ = (
20
20
  "PlatformDefaults",
21
21
  "HatchCppLibrary",
22
22
  "HatchCppPlatform",
23
+ "_normalize_rpath",
23
24
  )
24
25
 
25
26
 
@@ -207,6 +208,28 @@ class HatchCppLibrary(BaseModel, validate_assignment=True):
207
208
  return macros
208
209
 
209
210
 
211
+ def _normalize_rpath(value: str, platform: Platform) -> str:
212
+ r"""Translate and escape rpath values for the target platform.
213
+
214
+ - On macOS (darwin): ``$ORIGIN`` is replaced with ``@loader_path``.
215
+ - On Linux: ``@loader_path`` is replaced with ``$ORIGIN``, and
216
+ ``$ORIGIN`` is escaped as ``\$ORIGIN`` so that ``os.system()``
217
+ (which invokes a shell) passes it through literally.
218
+ - On Windows: no transformation is applied (Windows does not use
219
+ rpath).
220
+ """
221
+ if platform == "darwin":
222
+ # Handle already-escaped \$ORIGIN first, then plain $ORIGIN
223
+ value = value.replace(r"\$ORIGIN", "@loader_path")
224
+ value = value.replace("$ORIGIN", "@loader_path")
225
+ elif platform == "linux":
226
+ # Translate macOS rpath to Linux equivalent
227
+ value = value.replace("@loader_path", "$ORIGIN")
228
+ # Escape $ORIGIN for shell safety (os.system runs through bash)
229
+ value = value.replace("$ORIGIN", r"\$ORIGIN")
230
+ return value
231
+
232
+
210
233
  class HatchCppPlatform(BaseModel):
211
234
  cc: str
212
235
  cxx: str
@@ -338,6 +361,9 @@ class HatchCppPlatform(BaseModel):
338
361
  effective_libraries = library.get_effective_libraries(self.platform)
339
362
  effective_library_dirs = library.get_effective_library_dirs(self.platform)
340
363
 
364
+ # Normalize rpath values ($ORIGIN <-> @loader_path) and escape for shell
365
+ effective_link_args = [_normalize_rpath(arg, self.platform) for arg in effective_link_args]
366
+
341
367
  if self.toolchain == "gcc":
342
368
  flags += " -shared"
343
369
  flags += " " + " ".join(effective_link_args)
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import configparser
4
+ import subprocess
4
5
  from pathlib import Path
5
6
  from platform import machine as platform_machine
6
7
  from sys import platform as sys_platform
@@ -78,6 +79,39 @@ class HatchCppVcpkgConfiguration(BaseModel):
78
79
  return self.vcpkg_ref
79
80
  return _read_vcpkg_ref_from_gitmodules(self.vcpkg_root)
80
81
 
82
+ def _bootstrap_script_path(self) -> Path:
83
+ return self.vcpkg_root / ("bootstrap-vcpkg.bat" if sys_platform == "win32" else "bootstrap-vcpkg.sh")
84
+
85
+ def _vcpkg_executable_path(self) -> Path:
86
+ if sys_platform == "win32":
87
+ return self.vcpkg_root / "vcpkg.exe"
88
+ return self.vcpkg_root / "vcpkg"
89
+
90
+ def _delete_dir_command(self, path: Path) -> str:
91
+ if sys_platform == "win32":
92
+ return f'rmdir /s /q "{path}"'
93
+ return f'rm -rf "{path}"'
94
+
95
+ def _is_vcpkg_working(self) -> bool:
96
+ vcpkg_executable = self._vcpkg_executable_path()
97
+ if not vcpkg_executable.exists():
98
+ return False
99
+ try:
100
+ result = subprocess.run([str(vcpkg_executable), "version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False)
101
+ return result.returncode == 0
102
+ except OSError:
103
+ return False
104
+
105
+ def _clone_checkout_bootstrap_commands(self) -> list[str]:
106
+ commands = [f"git clone {self.vcpkg_repo} {self.vcpkg_root}"]
107
+
108
+ ref = self._resolve_vcpkg_ref()
109
+ if ref is not None:
110
+ commands.append(f"git -C {self.vcpkg_root} checkout {ref}")
111
+
112
+ commands.append(f"./{self._bootstrap_script_path()}")
113
+ return commands
114
+
81
115
  def generate(self, config):
82
116
  commands = []
83
117
 
@@ -87,14 +121,24 @@ class HatchCppVcpkgConfiguration(BaseModel):
87
121
  raise ValueError(f"Could not determine vcpkg triplet for platform {sys_platform} and architecture {platform_machine()}")
88
122
 
89
123
  if self.vcpkg and Path(self.vcpkg).exists():
90
- if not Path(self.vcpkg_root).exists():
91
- commands.append(f"git clone {self.vcpkg_repo} {self.vcpkg_root}")
92
-
93
- ref = self._resolve_vcpkg_ref()
94
- if ref is not None:
95
- commands.append(f"git -C {self.vcpkg_root} checkout {ref}")
124
+ vcpkg_root = Path(self.vcpkg_root)
125
+ bootstrap_script = self._bootstrap_script_path()
126
+
127
+ if not vcpkg_root.exists():
128
+ commands.extend(self._clone_checkout_bootstrap_commands())
129
+ else:
130
+ is_empty_dir = vcpkg_root.is_dir() and not any(vcpkg_root.iterdir())
131
+ if is_empty_dir:
132
+ commands.append(self._delete_dir_command(vcpkg_root))
133
+ commands.extend(self._clone_checkout_bootstrap_commands())
134
+ else:
135
+ vcpkg_executable = self._vcpkg_executable_path()
136
+ if not vcpkg_executable.exists():
137
+ commands.append(f"./{bootstrap_script}")
138
+ elif not self._is_vcpkg_working():
139
+ commands.append(self._delete_dir_command(vcpkg_root))
140
+ commands.extend(self._clone_checkout_bootstrap_commands())
96
141
 
97
- commands.append(f"./{self.vcpkg_root / 'bootstrap-vcpkg.sh' if sys_platform != 'win32' else self.vcpkg_root / 'bootstrap-vcpkg.bat'}")
98
142
  commands.append(f"./{self.vcpkg_root / 'vcpkg'} install --triplet {self.vcpkg_triplet}")
99
143
 
100
144
  return commands
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hatch-cpp
3
- Version: 0.3.5
3
+ Version: 0.3.7
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
@@ -33,7 +33,7 @@ Requires-Dist: hatch-build>=0.3.2; extra == 'develop'
33
33
  Requires-Dist: hatchling; extra == 'develop'
34
34
  Requires-Dist: mdformat; extra == 'develop'
35
35
  Requires-Dist: mdformat-tables>=1; extra == 'develop'
36
- Requires-Dist: nanobind<2.12.0; extra == 'develop'
36
+ Requires-Dist: nanobind<2.13.0; extra == 'develop'
37
37
  Requires-Dist: pybind11; extra == 'develop'
38
38
  Requires-Dist: pytest; extra == 'develop'
39
39
  Requires-Dist: pytest-cov; extra == 'develop'
@@ -1,13 +1,13 @@
1
- hatch_cpp/__init__.py,sha256=oGC85OzqpNweL1rFPBDebJVlVz3i-HmX54zLu5_A8gU,114
2
- hatch_cpp/config.py,sha256=6oO8J2k91r88DwZwDJpxtxt2F5xZZ-N65IqSyabwOg4,3831
1
+ hatch_cpp/__init__.py,sha256=GipF1UFyt6YVB3duYASeWgalyTJ30FHcGUrwBQRmGKc,114
2
+ hatch_cpp/config.py,sha256=aRmcRyrPR7LsEOBjrF9pkOM3ldiykuYRmD73CB3k9MY,4512
3
3
  hatch_cpp/hooks.py,sha256=SQkF5WJIgzw-8rvlTzuQvBqdP6K3fHgnh6CZOZFag50,203
4
4
  hatch_cpp/plugin.py,sha256=NcIDi7c9KH-_RkPeeCgWKoH5InIjw2eECswazwvGl_c,4304
5
5
  hatch_cpp/utils.py,sha256=lxd3U3aMYsTS6DYMc1y17MR16LzgRzv92f_xh87LSg8,297
6
6
  hatch_cpp/tests/test_hatch_build.py,sha256=q2IKTFi3Pq99UsOyL84V-C9VtFUs3ZcA9UlA8lpRW54,1553
7
- hatch_cpp/tests/test_platform_specific.py,sha256=8EFHEv86e0CD1bIruHgLb28yf2pSRF1VgvcX2tK5bRo,20124
7
+ hatch_cpp/tests/test_platform_specific.py,sha256=SRKAojh6PkwKZnEj8SjvfFa_Dy8NSKw4R82ol8O1TZs,20126
8
8
  hatch_cpp/tests/test_projects.py,sha256=vs-kHdiHM7pCqM3oGQsSryq5blRi0HyK5QFfHxlJDi8,1773
9
- hatch_cpp/tests/test_structs.py,sha256=cpzbPLneEbEONzXmPS1S9PL9mbhya4udc4eqTpU9w6w,2576
10
- hatch_cpp/tests/test_vcpkg_ref.py,sha256=OEb4u3Ny-OYNYLanjGaG7-dL4WW48EYdbsxeDN0sqwM,7371
9
+ hatch_cpp/tests/test_structs.py,sha256=zmNtAV1bojvJM6gatjDtvqrAeUpKzpfmLrzgFTZLo8U,12028
10
+ hatch_cpp/tests/test_vcpkg_ref.py,sha256=wcfbFp8Tc6E2hiQPlextdno0NYrA1yljRSRUIQEj0mM,9969
11
11
  hatch_cpp/tests/test_project_basic/pyproject.toml,sha256=eqM1UVpNmJWDfsuO18ZG_VOV9I4tAWgsM5Dhf49X8Nc,694
12
12
  hatch_cpp/tests/test_project_basic/cpp/project/basic.cpp,sha256=gQ2nmdLqIdgaqxSKvkN_vbp6Iv_pAoVIHETXPRnALb0,117
13
13
  hatch_cpp/tests/test_project_basic/cpp/project/basic.hpp,sha256=SO5GhPj8k3RzWrfH37lFSDc8w1Vf3yqTUhxmr1hnoko,422
@@ -57,11 +57,11 @@ hatch_cpp/tests/test_project_pybind_vcpkg/cpp/project/basic.cpp,sha256=MT3eCSQKr
57
57
  hatch_cpp/tests/test_project_pybind_vcpkg/cpp/project/basic.hpp,sha256=LZSfCfhLY_91MBOxtnvDk7DcceO-9GhCHKpMnAjGe18,146
58
58
  hatch_cpp/tests/test_project_pybind_vcpkg/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
59
  hatch_cpp/toolchains/__init__.py,sha256=anza4tXKxauobWMOUrxX3sEO0qs7sp6VdLXhLae3vkY,64
60
- hatch_cpp/toolchains/cmake.py,sha256=VQYYqiOu0ZEF0IBuep35Xad7jryWy04-dHYYf7c0XgY,3331
61
- hatch_cpp/toolchains/common.py,sha256=o6whV_7W9GGtBsxgxmg-dHQqdFnuMLffkLHzdbqKqME,19075
62
- hatch_cpp/toolchains/vcpkg.py,sha256=ku5NE4ihdzbxdReNUXsGx03fPG5FYdQC48mJhdpW-oo,3456
63
- hatch_cpp-0.3.5.dist-info/METADATA,sha256=BNI_Ezt3hDfW5xhETfORdexE3tb4Ul4dY4M__K0hj10,9896
64
- hatch_cpp-0.3.5.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
65
- hatch_cpp-0.3.5.dist-info/entry_points.txt,sha256=RgXfjpD4iwomJQK5n7FnPkXzb5fOnF5v3DI5hbkgVcw,30
66
- hatch_cpp-0.3.5.dist-info/licenses/LICENSE,sha256=FWyFTpd5xXEz50QpGDtsyIv6dgR2z_dHdoab_Nq_KMw,11452
67
- hatch_cpp-0.3.5.dist-info/RECORD,,
60
+ hatch_cpp/toolchains/cmake.py,sha256=yUMw6LzmasjRiJfxN98b-E9fDxUbM0-mz-RL0O7IVag,3748
61
+ hatch_cpp/toolchains/common.py,sha256=wOu1d9bOWW962NH-Bt-cXLlfAjgCbLgq-lIXxSupCvc,20265
62
+ hatch_cpp/toolchains/vcpkg.py,sha256=fBBModZOL49yJH8971zsOuuRPgTnmR2qvU_0AdT__DY,5233
63
+ hatch_cpp-0.3.7.dist-info/METADATA,sha256=qc9OfLqYolu8-toWPMfDhAY_9fyaq9OfbMK4XDU_eO0,9896
64
+ hatch_cpp-0.3.7.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
65
+ hatch_cpp-0.3.7.dist-info/entry_points.txt,sha256=RgXfjpD4iwomJQK5n7FnPkXzb5fOnF5v3DI5hbkgVcw,30
66
+ hatch_cpp-0.3.7.dist-info/licenses/LICENSE,sha256=FWyFTpd5xXEz50QpGDtsyIv6dgR2z_dHdoab_Nq_KMw,11452
67
+ hatch_cpp-0.3.7.dist-info/RECORD,,