From cbf8399748d7390da294a31112b3486c536a1208 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Fri, 13 Nov 2026 18:25:49 -0500 Subject: [PATCH 1/3] Run tests on macOS and Windows The build job ran on Ubuntu only, so the MSVC code and the Windows import library handling for depends had no CI coverage. Add one macOS and one Windows job on Python 3.11, set up the MSVC environment, and keep lint, checks, and the test result comment on Linux. Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- .github/workflows/build.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 227f029..e55077b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -31,6 +31,11 @@ jobs: matrix: os: [ubuntu-24.04] python-version: ["3.11", "3.12", "3.13"] + include: + - os: macos-26 + python-version: "3.11" + - os: windows-2025 + python-version: "3.11" steps: - name: Checkout @@ -43,14 +48,22 @@ jobs: with: version: ${{ matrix.python-version }} + - name: Setup C++ + uses: actions-ext/cpp/setup@7de57593fbcba72d513072bfb4b30b5a344aa547 + with: + cache: 'false' + clang_format: 'false' + - name: Install dependencies run: make develop - name: Lint run: make lint + if: runner.os == 'Linux' - name: Checks run: make checks + if: runner.os == 'Linux' - name: Build run: make build @@ -69,6 +82,7 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@7622c5bf3d112a72ab4fa3951c2aa5723d8ab92d # v2.25.0 with: files: '**/junit.xml' + if: runner.os == 'Linux' - name: Upload coverage uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 From eef148188cbf29ac82abcc111e0e0a5bf02ec7a6 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Fri, 13 Nov 2026 18:33:15 -0500 Subject: [PATCH 2/3] Let CMake pick the generator on Windows The CMake toolchain passed -G "Visual Studio 17 2022" on Windows unless CMAKE_GENERATOR was set. The windows-2025 runner image has Visual Studio 2026 only, so configure failed with "could not find any instance of Visual Studio". Pass -G only when CMAKE_GENERATOR is set and use CMake's default otherwise, which is the newest installed Visual Studio. Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- hatch_cpp/toolchains/cmake.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/hatch_cpp/toolchains/cmake.py b/hatch_cpp/toolchains/cmake.py index 685a5ef..82c3fe7 100644 --- a/hatch_cpp/toolchains/cmake.py +++ b/hatch_cpp/toolchains/cmake.py @@ -54,13 +54,10 @@ def generate(self, config) -> list[str]: commands[-1] += f" -DCMAKE_INSTALL_PREFIX={Path(self.root).parent}" # TODO: CMAKE_CXX_COMPILER - # Respect CMAKE_GENERATOR environment variable + # Respect CMAKE_GENERATOR environment variable. Without it CMake picks its default, + # which on Windows is the newest installed Visual Studio. cmake_generator = environ.get("CMAKE_GENERATOR", "") - if config.platform.platform == "win32": - if not cmake_generator: - cmake_generator = "Visual Studio 17 2022" - commands[-1] += f' -G "{cmake_generator}"' - elif cmake_generator: + if cmake_generator: commands[-1] += f' -G "{cmake_generator}"' # Put in CMake flags From 47cf71ccd7ee352daa46e3238cb7d0a264ccbd31 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Fri, 13 Nov 2026 18:33:15 -0500 Subject: [PATCH 3/3] Fix path handling and tests on Windows hosts A vcpkg root in a subdirectory was compared with its .gitmodules path using backslashes on Windows, so the configured branch was ignored. POSIX vcpkg commands and Emscripten object paths were also built with backslashes when the host was Windows. The vcpkg tests that assert POSIX commands pin a Linux host, the gcc toolchain fixture is skipped on Windows, and the build matrix no longer cancels the other jobs when one fails. Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- .github/workflows/build.yaml | 1 + hatch_cpp/config.py | 4 ++-- hatch_cpp/tests/test_projects.py | 7 ++++++- hatch_cpp/tests/test_vcpkg_ref.py | 10 ++++++++++ hatch_cpp/toolchains/vcpkg.py | 4 ++-- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e55077b..c539e41 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,6 +28,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-24.04] python-version: ["3.11", "3.12", "3.13"] diff --git a/hatch_cpp/config.py b/hatch_cpp/config.py index 3bd7478..b5e276e 100644 --- a/hatch_cpp/config.py +++ b/hatch_cpp/config.py @@ -197,8 +197,8 @@ def generate(self): if self.platform.platform == "emscripten": objects = [] for source_index, source in enumerate(library.sources): - obj = Path("build/hatch-cpp") / f"{library_index}-{source_index}-{Path(source).stem}.o" - objects.append(str(obj)) + obj = f"build/hatch-cpp/{library_index}-{source_index}-{Path(source).stem}.o" + objects.append(obj) self.commands.append(f"{compiler} -c {source} {compile_flags} -o {obj}") self.commands.append(f"{compiler} {' '.join(objects)} {link_flags}") else: diff --git a/hatch_cpp/tests/test_projects.py b/hatch_cpp/tests/test_projects.py index 27f467a..a94edac 100644 --- a/hatch_cpp/tests/test_projects.py +++ b/hatch_cpp/tests/test_projects.py @@ -15,7 +15,12 @@ class TestProject: [ "test_project_basic", "test_project_override_classes", - "test_project_override_toolchain", + pytest.param( + "test_project_override_toolchain", + marks=pytest.mark.skipif( + platform == "win32", reason="the fixture selects the gcc toolchain, whose flags clang rejects for MSVC targets" + ), + ), "test_project_pybind", "test_project_pybind_vcpkg", "test_project_nanobind", diff --git a/hatch_cpp/tests/test_vcpkg_ref.py b/hatch_cpp/tests/test_vcpkg_ref.py index 2cfecdc..2c0779b 100644 --- a/hatch_cpp/tests/test_vcpkg_ref.py +++ b/hatch_cpp/tests/test_vcpkg_ref.py @@ -125,6 +125,11 @@ def _make_vcpkg_env(self, tmp_path): """Create a minimal vcpkg.json so generate() produces commands.""" (tmp_path / "vcpkg.json").write_text("{}") + def _pin_linux_host(self, monkeypatch): + """Make generate() behave as on an x86_64 Linux host, whatever host runs the test.""" + monkeypatch.setattr("hatch_cpp.toolchains.vcpkg.sys_platform", "linux") + monkeypatch.setattr("hatch_cpp.toolchains.vcpkg.platform_machine", lambda: "x86_64") + def test_generate_with_explicit_ref(self, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) self._make_vcpkg_env(tmp_path) @@ -164,6 +169,7 @@ def test_generate_without_ref(self, tmp_path, monkeypatch): def test_generate_uses_posix_command_paths(self, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) + self._pin_linux_host(monkeypatch) self._make_vcpkg_env(tmp_path) cfg = HatchCppVcpkgConfiguration(vcpkg_triplet="x64-linux") @@ -185,6 +191,7 @@ def test_generate_detects_linux_arm64_triplet(self, tmp_path, monkeypatch): def test_generate_detects_emscripten_triplet(self, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) + self._pin_linux_host(monkeypatch) self._make_vcpkg_env(tmp_path) build_plan = SimpleNamespace(platform=SimpleNamespace(platform="emscripten")) @@ -206,6 +213,7 @@ def test_generate_uses_windows_command_paths(self, tmp_path, monkeypatch): def test_generate_skips_clone_when_vcpkg_root_exists(self, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) + self._pin_linux_host(monkeypatch) self._make_vcpkg_env(tmp_path) vcpkg_root = tmp_path / "vcpkg" vcpkg_root.mkdir() @@ -225,6 +233,7 @@ def test_generate_skips_clone_when_vcpkg_root_exists(self, tmp_path, monkeypatch def test_generate_reclones_when_vcpkg_root_exists_but_empty(self, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) + self._pin_linux_host(monkeypatch) self._make_vcpkg_env(tmp_path) (tmp_path / "vcpkg").mkdir() @@ -253,6 +262,7 @@ def test_generate_bootstraps_when_vcpkg_executable_missing(self, tmp_path, monke def test_generate_reclones_when_vcpkg_exists_but_not_working(self, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) + self._pin_linux_host(monkeypatch) self._make_vcpkg_env(tmp_path) vcpkg_root = tmp_path / "vcpkg" vcpkg_root.mkdir() diff --git a/hatch_cpp/toolchains/vcpkg.py b/hatch_cpp/toolchains/vcpkg.py index 5856e40..216007e 100644 --- a/hatch_cpp/toolchains/vcpkg.py +++ b/hatch_cpp/toolchains/vcpkg.py @@ -57,7 +57,7 @@ def _read_vcpkg_ref_from_gitmodules(vcpkg_root: Path) -> str | None: parser.read(str(gitmodules_path)) for section in parser.sections(): - if parser.get(section, "path", fallback=None) == str(vcpkg_root): + if parser.get(section, "path", fallback=None) == vcpkg_root.as_posix(): return parser.get(section, "branch", fallback=None) return None @@ -93,7 +93,7 @@ def _vcpkg_executable_path(self) -> Path: def _command_path(self, path: Path) -> str: if sys_platform == "win32": return str(path).replace("/", "\\") - return f"./{path}" + return f"./{path.as_posix()}" def _delete_dir_command(self, path: Path) -> str: if sys_platform == "win32":