Skip to content

Commit 99a6db4

Browse files
committed
Be more durable to partial vcpkg checkouts or vcpkg submodules
1 parent f3c00be commit 99a6db4

File tree

2 files changed

+105
-9
lines changed

2 files changed

+105
-9
lines changed

hatch_cpp/tests/test_vcpkg_ref.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,66 @@ def test_generate_without_ref(self, tmp_path, monkeypatch):
156156
def test_generate_skips_clone_when_vcpkg_root_exists(self, tmp_path, monkeypatch):
157157
monkeypatch.chdir(tmp_path)
158158
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")
160164

161165
cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
166+
monkeypatch.setattr(cfg, "_is_vcpkg_working", lambda: True)
162167
commands = cfg.generate(None)
163168

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.
165170
assert not any("git clone" in cmd for cmd in commands)
166171
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)
167219
assert any("vcpkg" in cmd and "install" in cmd for cmd in commands)
168220

169221
def test_generate_no_vcpkg_json(self, tmp_path, monkeypatch):

hatch_cpp/toolchains/vcpkg.py

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import configparser
4+
import subprocess
45
from pathlib import Path
56
from platform import machine as platform_machine
67
from sys import platform as sys_platform
@@ -78,6 +79,39 @@ def _resolve_vcpkg_ref(self) -> Optional[str]:
7879
return self.vcpkg_ref
7980
return _read_vcpkg_ref_from_gitmodules(self.vcpkg_root)
8081

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+
81115
def generate(self, config):
82116
commands = []
83117

@@ -87,14 +121,24 @@ def generate(self, config):
87121
raise ValueError(f"Could not determine vcpkg triplet for platform {sys_platform} and architecture {platform_machine()}")
88122

89123
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())
96141

97-
commands.append(f"./{self.vcpkg_root / 'bootstrap-vcpkg.sh' if sys_platform != 'win32' else self.vcpkg_root / 'bootstrap-vcpkg.bat'}")
98142
commands.append(f"./{self.vcpkg_root / 'vcpkg'} install --triplet {self.vcpkg_triplet}")
99143

100144
return commands

0 commit comments

Comments
 (0)