Skip to content

Commit 68de350

Browse files
authored
Merge branch 'python-project-templates:main' into tkp/integ
2 parents 3c66759 + 83524cc commit 68de350

File tree

10 files changed

+282
-41
lines changed

10 files changed

+282
-41
lines changed

.copier-answers.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Changes here will be overwritten by Copier
2-
_commit: b74d698
2+
_commit: 4d4d95a
33
_src_path: https://github.com/python-project-templates/base.git
44
add_docs: false
55
add_extension: python

.github/workflows/build.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
run: make coverage
5656

5757
- name: Upload test results (Python)
58-
uses: actions/upload-artifact@v6
58+
uses: actions/upload-artifact@v7
5959
with:
6060
name: test-results-${{ matrix.os }}-${{ matrix.python-version }}
6161
path: junit.xml
@@ -73,7 +73,7 @@ jobs:
7373
- name: Make dist
7474
run: make dist
7575

76-
- uses: actions/upload-artifact@v6
76+
- uses: actions/upload-artifact@v7
7777
with:
7878
name: dist-${{matrix.os}}
7979
path: dist

.gitignore

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,12 @@ js/node_modules
141141
js/test-results
142142
js/playwright-report
143143
js/*.tgz
144-
hatch_cpp/extension
145144

146145
# Jupyter
147146
.ipynb_checkpoints
148147
.autoversion
149148
Untitled*.ipynb
150-
!hatch_cpp/extension/hatch_cpp.json
151-
!hatch_cpp/extension/install.json
149+
hatch_cpp/extension
152150
hatch_cpp/nbextension
153151
hatch_cpp/labextension
154152

@@ -158,5 +156,9 @@ hatch_cpp/labextension
158156
# Rust
159157
target
160158

159+
# Hydra
160+
outputs/
161+
multirun/
162+
161163
vcpkg
162-
vcpkg_installed
164+
vcpkg_installed

Makefile

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,15 @@ format: fix
4646
################
4747
# Other Checks #
4848
################
49-
.PHONY: check-manifest checks check
49+
.PHONY: check-dist check-types checks check
5050

51-
check-manifest: ## check python sdist manifest with check-manifest
52-
check-manifest -v
51+
check-dist: ## check python sdist and wheel with check-dist
52+
check-dist -v
5353

54-
checks: check-manifest
54+
check-types: ## check python types with ty
55+
ty check --python $$(which python)
56+
57+
checks: check-dist
5558

5659
# Alias
5760
check: checks

hatch_cpp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.3.2"
1+
__version__ = "0.3.5"
22

33
from .config import *
44
from .hooks import *

hatch_cpp/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ def generate(self):
108108

109109
def execute(self):
110110
for command in self.commands:
111-
system_call(command)
111+
ret = system_call(command)
112+
if ret != 0:
113+
raise RuntimeError(f"hatch-cpp build command failed with exit code {ret}: {command}")
112114
return self.commands
113115

114116
def cleanup(self):

hatch_cpp/tests/test_vcpkg_ref.py

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
"""Tests for vcpkg ref/branch checkout support."""
2+
3+
from __future__ import annotations
4+
5+
from pathlib import Path
6+
7+
from hatch_cpp.toolchains.vcpkg import (
8+
HatchCppVcpkgConfiguration,
9+
_read_vcpkg_ref_from_gitmodules,
10+
)
11+
12+
13+
class TestReadVcpkgRefFromGitmodules:
14+
"""Tests for the _read_vcpkg_ref_from_gitmodules helper."""
15+
16+
def test_no_gitmodules_file(self, tmp_path, monkeypatch):
17+
monkeypatch.chdir(tmp_path)
18+
assert _read_vcpkg_ref_from_gitmodules(Path("vcpkg")) is None
19+
20+
def test_gitmodules_without_vcpkg_submodule(self, tmp_path, monkeypatch):
21+
monkeypatch.chdir(tmp_path)
22+
(tmp_path / ".gitmodules").write_text('[submodule "other"]\n\tpath = other\n\turl = https://github.com/example/other.git\n')
23+
assert _read_vcpkg_ref_from_gitmodules(Path("vcpkg")) is None
24+
25+
def test_gitmodules_vcpkg_without_branch(self, tmp_path, monkeypatch):
26+
monkeypatch.chdir(tmp_path)
27+
(tmp_path / ".gitmodules").write_text('[submodule "vcpkg"]\n\tpath = vcpkg\n\turl = https://github.com/microsoft/vcpkg.git\n')
28+
assert _read_vcpkg_ref_from_gitmodules(Path("vcpkg")) is None
29+
30+
def test_gitmodules_vcpkg_with_branch(self, tmp_path, monkeypatch):
31+
monkeypatch.chdir(tmp_path)
32+
(tmp_path / ".gitmodules").write_text(
33+
'[submodule "vcpkg"]\n\tpath = vcpkg\n\turl = https://github.com/microsoft/vcpkg.git\n\tbranch = 2024.01.12\n'
34+
)
35+
assert _read_vcpkg_ref_from_gitmodules(Path("vcpkg")) == "2024.01.12"
36+
37+
def test_gitmodules_vcpkg_with_commit_sha_branch(self, tmp_path, monkeypatch):
38+
monkeypatch.chdir(tmp_path)
39+
sha = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
40+
(tmp_path / ".gitmodules").write_text(
41+
f'[submodule "vcpkg"]\n\tpath = vcpkg\n\turl = https://github.com/microsoft/vcpkg.git\n\tbranch = {sha}\n'
42+
)
43+
assert _read_vcpkg_ref_from_gitmodules(Path("vcpkg")) == sha
44+
45+
def test_gitmodules_custom_vcpkg_root(self, tmp_path, monkeypatch):
46+
monkeypatch.chdir(tmp_path)
47+
(tmp_path / ".gitmodules").write_text(
48+
'[submodule "deps/vcpkg"]\n\tpath = deps/vcpkg\n\turl = https://github.com/microsoft/vcpkg.git\n\tbranch = 2024.06.15\n'
49+
)
50+
# Default vcpkg root won't match
51+
assert _read_vcpkg_ref_from_gitmodules(Path("vcpkg")) is None
52+
# Custom root matches
53+
assert _read_vcpkg_ref_from_gitmodules(Path("deps/vcpkg")) == "2024.06.15"
54+
55+
def test_gitmodules_multiple_submodules(self, tmp_path, monkeypatch):
56+
monkeypatch.chdir(tmp_path)
57+
(tmp_path / ".gitmodules").write_text(
58+
'[submodule "other"]\n'
59+
"\tpath = other\n"
60+
"\turl = https://github.com/example/other.git\n"
61+
"\tbranch = main\n"
62+
'[submodule "vcpkg"]\n'
63+
"\tpath = vcpkg\n"
64+
"\turl = https://github.com/microsoft/vcpkg.git\n"
65+
"\tbranch = 2024.01.12\n"
66+
)
67+
assert _read_vcpkg_ref_from_gitmodules(Path("vcpkg")) == "2024.01.12"
68+
69+
70+
class TestVcpkgRefConfig:
71+
"""Tests for vcpkg_ref configuration field."""
72+
73+
def test_default_vcpkg_ref_is_none(self):
74+
cfg = HatchCppVcpkgConfiguration()
75+
assert cfg.vcpkg_ref is None
76+
77+
def test_explicit_vcpkg_ref(self):
78+
cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
79+
assert cfg.vcpkg_ref == "2024.01.12"
80+
81+
def test_explicit_vcpkg_ref_commit_sha(self):
82+
sha = "a1b2c3d4e5f6"
83+
cfg = HatchCppVcpkgConfiguration(vcpkg_ref=sha)
84+
assert cfg.vcpkg_ref == sha
85+
86+
87+
class TestResolveVcpkgRef:
88+
"""Tests for _resolve_vcpkg_ref priority logic."""
89+
90+
def test_explicit_ref_takes_priority_over_gitmodules(self, tmp_path, monkeypatch):
91+
monkeypatch.chdir(tmp_path)
92+
(tmp_path / ".gitmodules").write_text(
93+
'[submodule "vcpkg"]\n\tpath = vcpkg\n\turl = https://github.com/microsoft/vcpkg.git\n\tbranch = 2024.01.12\n'
94+
)
95+
cfg = HatchCppVcpkgConfiguration(vcpkg_ref="my-custom-tag")
96+
assert cfg._resolve_vcpkg_ref() == "my-custom-tag"
97+
98+
def test_falls_back_to_gitmodules(self, tmp_path, monkeypatch):
99+
monkeypatch.chdir(tmp_path)
100+
(tmp_path / ".gitmodules").write_text(
101+
'[submodule "vcpkg"]\n\tpath = vcpkg\n\turl = https://github.com/microsoft/vcpkg.git\n\tbranch = 2024.01.12\n'
102+
)
103+
cfg = HatchCppVcpkgConfiguration()
104+
assert cfg._resolve_vcpkg_ref() == "2024.01.12"
105+
106+
def test_returns_none_when_no_ref(self, tmp_path, monkeypatch):
107+
monkeypatch.chdir(tmp_path)
108+
cfg = HatchCppVcpkgConfiguration()
109+
assert cfg._resolve_vcpkg_ref() is None
110+
111+
112+
class TestVcpkgGenerate:
113+
"""Tests that generate() includes the checkout command when a ref is set."""
114+
115+
def _make_vcpkg_env(self, tmp_path):
116+
"""Create a minimal vcpkg.json so generate() produces commands."""
117+
(tmp_path / "vcpkg.json").write_text("{}")
118+
119+
def test_generate_with_explicit_ref(self, tmp_path, monkeypatch):
120+
monkeypatch.chdir(tmp_path)
121+
self._make_vcpkg_env(tmp_path)
122+
123+
cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
124+
commands = cfg.generate(None)
125+
126+
assert any("git clone" in cmd for cmd in commands)
127+
assert any("git -C vcpkg checkout 2024.01.12" in cmd for cmd in commands)
128+
# checkout must come after clone but before bootstrap
129+
clone_idx = next(i for i, c in enumerate(commands) if "git clone" in c)
130+
checkout_idx = next(i for i, c in enumerate(commands) if "checkout" in c)
131+
bootstrap_idx = next(i for i, c in enumerate(commands) if "bootstrap" in c)
132+
assert clone_idx < checkout_idx < bootstrap_idx
133+
134+
def test_generate_with_gitmodules_ref(self, tmp_path, monkeypatch):
135+
monkeypatch.chdir(tmp_path)
136+
self._make_vcpkg_env(tmp_path)
137+
(tmp_path / ".gitmodules").write_text(
138+
'[submodule "vcpkg"]\n\tpath = vcpkg\n\turl = https://github.com/microsoft/vcpkg.git\n\tbranch = 2024.06.15\n'
139+
)
140+
141+
cfg = HatchCppVcpkgConfiguration()
142+
commands = cfg.generate(None)
143+
144+
assert any("git -C vcpkg checkout 2024.06.15" in cmd for cmd in commands)
145+
146+
def test_generate_without_ref(self, tmp_path, monkeypatch):
147+
monkeypatch.chdir(tmp_path)
148+
self._make_vcpkg_env(tmp_path)
149+
150+
cfg = HatchCppVcpkgConfiguration()
151+
commands = cfg.generate(None)
152+
153+
assert not any("checkout" in cmd for cmd in commands)
154+
assert any("git clone" in cmd for cmd in commands)
155+
156+
def test_generate_skips_clone_when_vcpkg_root_exists(self, tmp_path, monkeypatch):
157+
monkeypatch.chdir(tmp_path)
158+
self._make_vcpkg_env(tmp_path)
159+
(tmp_path / "vcpkg").mkdir()
160+
161+
cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
162+
commands = cfg.generate(None)
163+
164+
# When vcpkg_root already exists, no clone or checkout happens
165+
assert not any("git clone" in cmd for cmd in commands)
166+
assert not any("checkout" in cmd for cmd in commands)
167+
assert any("vcpkg" in cmd and "install" in cmd for cmd in commands)
168+
169+
def test_generate_no_vcpkg_json(self, tmp_path, monkeypatch):
170+
monkeypatch.chdir(tmp_path)
171+
# No vcpkg.json => no commands at all
172+
cfg = HatchCppVcpkgConfiguration(vcpkg_ref="2024.01.12")
173+
commands = cfg.generate(None)
174+
assert commands == []

hatch_cpp/toolchains/common.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import Path
55
from re import match
66
from shutil import which
7-
from sys import executable, platform as sys_platform
7+
from sys import base_exec_prefix, exec_prefix, executable, platform as sys_platform
88
from sysconfig import get_config_var, get_path
99
from typing import Any, List, Literal, Optional
1010

@@ -346,7 +346,9 @@ def get_compile_flags(self, library: HatchCppLibrary, build_type: BuildType = "r
346346
flags += " " + " ".join(f"/U{macro}" for macro in effective_undef_macros)
347347
flags += " /EHsc /DWIN32"
348348
if library.std:
349-
flags += f" /std:{library.std}"
349+
# MSVC minimum is c++14; clamp older standards
350+
std = library.std if library.std not in ("c++11", "c++0x") else "c++14"
351+
flags += f" /std:{std}"
350352
# clean
351353
while flags.count(" "):
352354
flags = flags.replace(" ", " ")
@@ -395,10 +397,14 @@ def get_link_flags(self, library: HatchCppLibrary, build_type: BuildType = "rele
395397
flags += f" /Fe:{library.get_qualified_name(self.platform)}"
396398
flags += " /link /DLL"
397399
# Add Python libs directory - check multiple possible locations
400+
# In virtual environments, sys.executable is in the venv, but pythonXX.lib
401+
# lives under the base Python installation's 'libs' directory.
398402
python_libs_paths = [
399403
Path(executable).parent / "libs", # Standard Python install
400404
Path(executable).parent.parent / "libs", # Some virtualenv layouts
401405
Path(get_config_var("installed_base") or "") / "libs", # sysconfig approach
406+
Path(exec_prefix) / "libs", # exec_prefix approach
407+
Path(base_exec_prefix) / "libs", # base_exec_prefix approach
402408
]
403409
for libs_path in python_libs_paths:
404410
if libs_path.exists():

hatch_cpp/toolchains/vcpkg.py

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

3+
import configparser
34
from pathlib import Path
45
from platform import machine as platform_machine
56
from sys import platform as sys_platform
@@ -38,14 +39,45 @@
3839
}
3940

4041

42+
def _read_vcpkg_ref_from_gitmodules(vcpkg_root: Path) -> Optional[str]:
43+
"""Read the branch/ref for vcpkg from .gitmodules if it exists.
44+
45+
Looks for a submodule whose path matches ``vcpkg_root`` and returns
46+
its ``branch`` value when present.
47+
"""
48+
gitmodules_path = Path(".gitmodules")
49+
if not gitmodules_path.exists():
50+
return None
51+
52+
parser = configparser.ConfigParser()
53+
parser.read(str(gitmodules_path))
54+
55+
for section in parser.sections():
56+
if parser.get(section, "path", fallback=None) == str(vcpkg_root):
57+
return parser.get(section, "branch", fallback=None)
58+
59+
return None
60+
61+
4162
class HatchCppVcpkgConfiguration(BaseModel):
4263
vcpkg: Optional[str] = Field(default="vcpkg.json")
4364
vcpkg_root: Optional[Path] = Field(default=Path("vcpkg"))
4465
vcpkg_repo: Optional[str] = Field(default="https://github.com/microsoft/vcpkg.git")
4566
vcpkg_triplet: Optional[VcpkgTriplet] = Field(default=None)
67+
vcpkg_ref: Optional[str] = Field(
68+
default=None,
69+
description="Branch, tag, or commit SHA to checkout after cloning vcpkg. "
70+
"If not set, falls back to the branch specified in .gitmodules for the vcpkg submodule.",
71+
)
4672

4773
# TODO: overlay
4874

75+
def _resolve_vcpkg_ref(self) -> Optional[str]:
76+
"""Return the ref to checkout: explicit config takes priority, then .gitmodules."""
77+
if self.vcpkg_ref is not None:
78+
return self.vcpkg_ref
79+
return _read_vcpkg_ref_from_gitmodules(self.vcpkg_root)
80+
4981
def generate(self, config):
5082
commands = []
5183

@@ -57,9 +89,12 @@ def generate(self, config):
5789
if self.vcpkg and Path(self.vcpkg).exists():
5890
if not Path(self.vcpkg_root).exists():
5991
commands.append(f"git clone {self.vcpkg_repo} {self.vcpkg_root}")
60-
commands.append(
61-
f"./{self.vcpkg_root / 'bootstrap-vcpkg.sh' if sys_platform != 'win32' else self.vcpkg_root / 'sbootstrap-vcpkg.bat'}"
62-
)
92+
93+
ref = self._resolve_vcpkg_ref()
94+
if ref is not None:
95+
commands.append(f"git -C {self.vcpkg_root} checkout {ref}")
96+
97+
commands.append(f"./{self.vcpkg_root / 'bootstrap-vcpkg.sh' if sys_platform != 'win32' else self.vcpkg_root / 'bootstrap-vcpkg.bat'}")
6398
commands.append(f"./{self.vcpkg_root / 'vcpkg'} install --triplet {self.vcpkg_triplet}")
6499

65100
return commands

0 commit comments

Comments
 (0)