Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions hatch_cpp/tests/test_vcpkg_ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,66 @@ def test_generate_without_ref(self, tmp_path, monkeypatch):
def test_generate_skips_clone_when_vcpkg_root_exists(self, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
self._make_vcpkg_env(tmp_path)
(tmp_path / "vcpkg").mkdir()
vcpkg_root = tmp_path / "vcpkg"
vcpkg_root.mkdir()
# Existing bootstrap script and executable mean no clone/bootstrap is needed.
(vcpkg_root / "bootstrap-vcpkg.sh").write_text("#!/bin/sh\nexit 0\n")
(vcpkg_root / "vcpkg").write_text("#!/bin/sh\nexit 0\n")

cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
monkeypatch.setattr(cfg, "_is_vcpkg_working", lambda: True)
commands = cfg.generate(None)

# When vcpkg_root already exists, no clone or checkout happens
# When vcpkg_root already exists with a working executable, no clone or bootstrap happens.
assert not any("git clone" in cmd for cmd in commands)
assert not any("checkout" in cmd for cmd in commands)
assert not any("bootstrap-vcpkg" in cmd for cmd in commands)
assert any("vcpkg" in cmd and "install" in cmd for cmd in commands)

def test_generate_reclones_when_vcpkg_root_exists_but_empty(self, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
self._make_vcpkg_env(tmp_path)
(tmp_path / "vcpkg").mkdir()

cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
commands = cfg.generate(None)

assert any(cmd.startswith('rm -rf "vcpkg"') for cmd in commands)
assert any("git clone" in cmd for cmd in commands)
assert any("checkout 2024.01.12" in cmd for cmd in commands)
assert any("bootstrap-vcpkg" in cmd for cmd in commands)
assert any("vcpkg" in cmd and "install" in cmd for cmd in commands)

def test_generate_bootstraps_when_vcpkg_executable_missing(self, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
self._make_vcpkg_env(tmp_path)
vcpkg_root = tmp_path / "vcpkg"
vcpkg_root.mkdir()
(vcpkg_root / "bootstrap-vcpkg.sh").write_text("#!/bin/sh\nexit 0\n")

cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
commands = cfg.generate(None)

assert not any("git clone" in cmd for cmd in commands)
assert any("bootstrap-vcpkg" in cmd for cmd in commands)
assert any("vcpkg" in cmd and "install" in cmd for cmd in commands)

def test_generate_reclones_when_vcpkg_exists_but_not_working(self, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
self._make_vcpkg_env(tmp_path)
vcpkg_root = tmp_path / "vcpkg"
vcpkg_root.mkdir()
(vcpkg_root / "bootstrap-vcpkg.sh").write_text("#!/bin/sh\nexit 0\n")
(vcpkg_root / "vcpkg").write_text("#!/bin/sh\nexit 1\n")

cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
monkeypatch.setattr(cfg, "_is_vcpkg_working", lambda: False)
commands = cfg.generate(None)

assert any(cmd.startswith('rm -rf "vcpkg"') for cmd in commands)
assert any("git clone" in cmd for cmd in commands)
assert any("checkout 2024.01.12" in cmd for cmd in commands)
assert any("bootstrap-vcpkg" in cmd for cmd in commands)
assert any("vcpkg" in cmd and "install" in cmd for cmd in commands)

def test_generate_no_vcpkg_json(self, tmp_path, monkeypatch):
Expand Down
58 changes: 51 additions & 7 deletions hatch_cpp/toolchains/vcpkg.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import configparser
import subprocess
from pathlib import Path
from platform import machine as platform_machine
from sys import platform as sys_platform
Expand Down Expand Up @@ -78,6 +79,39 @@ def _resolve_vcpkg_ref(self) -> Optional[str]:
return self.vcpkg_ref
return _read_vcpkg_ref_from_gitmodules(self.vcpkg_root)

def _bootstrap_script_path(self) -> Path:
return self.vcpkg_root / ("bootstrap-vcpkg.bat" if sys_platform == "win32" else "bootstrap-vcpkg.sh")

def _vcpkg_executable_path(self) -> Path:
if sys_platform == "win32":
return self.vcpkg_root / "vcpkg.exe"
return self.vcpkg_root / "vcpkg"

def _delete_dir_command(self, path: Path) -> str:
if sys_platform == "win32":
return f'rmdir /s /q "{path}"'
return f'rm -rf "{path}"'

def _is_vcpkg_working(self) -> bool:
vcpkg_executable = self._vcpkg_executable_path()
if not vcpkg_executable.exists():
return False
try:
result = subprocess.run([str(vcpkg_executable), "version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False)
return result.returncode == 0
except OSError:
return False

def _clone_checkout_bootstrap_commands(self) -> list[str]:
commands = [f"git clone {self.vcpkg_repo} {self.vcpkg_root}"]

ref = self._resolve_vcpkg_ref()
if ref is not None:
commands.append(f"git -C {self.vcpkg_root} checkout {ref}")

commands.append(f"./{self._bootstrap_script_path()}")
return commands

def generate(self, config):
commands = []

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

if self.vcpkg and Path(self.vcpkg).exists():
if not Path(self.vcpkg_root).exists():
commands.append(f"git clone {self.vcpkg_repo} {self.vcpkg_root}")

ref = self._resolve_vcpkg_ref()
if ref is not None:
commands.append(f"git -C {self.vcpkg_root} checkout {ref}")
vcpkg_root = Path(self.vcpkg_root)
bootstrap_script = self._bootstrap_script_path()

if not vcpkg_root.exists():
commands.extend(self._clone_checkout_bootstrap_commands())
else:
is_empty_dir = vcpkg_root.is_dir() and not any(vcpkg_root.iterdir())
if is_empty_dir:
commands.append(self._delete_dir_command(vcpkg_root))
commands.extend(self._clone_checkout_bootstrap_commands())
else:
vcpkg_executable = self._vcpkg_executable_path()
if not vcpkg_executable.exists():
commands.append(f"./{bootstrap_script}")
elif not self._is_vcpkg_working():
commands.append(self._delete_dir_command(vcpkg_root))
commands.extend(self._clone_checkout_bootstrap_commands())

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

return commands
Loading