From 5bccc509c8971ec280849e8c202b47bec7c86a99 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 22:16:04 +0300 Subject: [PATCH 01/24] refactor: fix repeat install clang tools --- .pre-commit-hooks.yaml | 4 ++-- README.md | 3 +++ cpp_linter_hooks/clang_format.py | 4 ++-- cpp_linter_hooks/clang_tidy.py | 5 ++--- cpp_linter_hooks/util.py | 12 +----------- 5 files changed, 10 insertions(+), 18 deletions(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 4e30e38..1f40fdb 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -3,7 +3,7 @@ description: Automatically install any specific version of clang-format and format C/C++ code entry: clang-format-hook language: python - files: \.(h\+\+|h|hh|hxx|hpp|c|cc|cpp|c\+\+|cxx)$ + types_or: [c++, c] require_serial: false - id: clang-tidy @@ -11,5 +11,5 @@ description: Automatically install any specific version of clang-tidy and diagnose/fix typical programming errors entry: clang-tidy-hook language: python - files: \.(h\+\+|h|hh|hxx|hpp|c|cc|cpp|c\+\+|cxx)$ + types_or: [c++, c] require_serial: false diff --git a/README.md b/README.md index 193f308..cf00b3b 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,9 @@ repos: args: [--checks=.clang-tidy] # Loads checks from .clang-tidy file ``` +> [!TIP] +> Install the latest version of `clang-format` and `clang-tidy` if not specified. You can specify the version using the `--version` argument in the `args` list as shown below. + ### Custom Clang Tool Version To use specific versions of clang-format and clang-tidy (using Python wheel packages): diff --git a/cpp_linter_hooks/clang_format.py b/cpp_linter_hooks/clang_format.py index ae732f6..b288c05 100644 --- a/cpp_linter_hooks/clang_format.py +++ b/cpp_linter_hooks/clang_format.py @@ -15,8 +15,8 @@ def run_clang_format(args=None) -> Tuple[int, str]: hook_args, other_args = parser.parse_known_args(args) - tool_name = ensure_installed("clang-format", hook_args.version) - command = [tool_name, "-i"] + ensure_installed("clang-format", hook_args.version) + command = ["clang-format", "-i"] # Add verbose flag if requested if hook_args.verbose: diff --git a/cpp_linter_hooks/clang_tidy.py b/cpp_linter_hooks/clang_tidy.py index 7f5dd5c..db3377e 100644 --- a/cpp_linter_hooks/clang_tidy.py +++ b/cpp_linter_hooks/clang_tidy.py @@ -11,9 +11,8 @@ def run_clang_tidy(args=None) -> Tuple[int, str]: hook_args, other_args = parser.parse_known_args(args) - tool_name = ensure_installed("clang-tidy", hook_args.version) - command = [tool_name] - command.extend(other_args) + ensure_installed("clang-tidy", hook_args.version) + command = ["clang-tidy"] + other_args retval = 0 output = "" diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index 80f9b34..c17361c 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -187,14 +187,6 @@ def _resolve_install(tool: str, version: Optional[str]) -> Optional[Path]: else DEFAULT_CLANG_TIDY_VERSION ) - # Additional safety check in case DEFAULT versions are None - if user_version is None: - user_version = ( - DEFAULT_CLANG_FORMAT_VERSION - if tool == "clang-format" - else DEFAULT_CLANG_TIDY_VERSION - ) - path = shutil.which(tool) if path: runtime_version = _get_runtime_version(tool) @@ -219,12 +211,10 @@ def is_installed(tool: str) -> Optional[Path]: return None -def ensure_installed(tool: str, version: Optional[str] = None) -> str: +def ensure_installed(tool: str, version: Optional[str] = None) -> None: """Ensure a tool is installed, resolving its version if necessary.""" LOG.info("Ensuring %s is installed", tool) tool_path = _resolve_install(tool, version) if tool_path: LOG.info("%s available at %s", tool, tool_path) - return tool LOG.warning("%s not found and could not be installed", tool) - return tool From f41e42c1461426e5a080f195bd09f48459befb59 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 22:22:46 +0300 Subject: [PATCH 02/24] fix: add clang-tools to requires --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5665823..35b16fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=45", "setuptools-scm"] +requires = ["setuptools>=45", "setuptools-scm", "clang-format", "clang-tidy"] build-backend = "setuptools.build_meta" requires-python = ">=3.9" From 19538437a85bbfd79a5bfb6b27d39e88995fad03 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 22:30:46 +0300 Subject: [PATCH 03/24] fix: remove warning output --- cpp_linter_hooks/util.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index c17361c..b80ff39 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -214,7 +214,5 @@ def is_installed(tool: str) -> Optional[Path]: def ensure_installed(tool: str, version: Optional[str] = None) -> None: """Ensure a tool is installed, resolving its version if necessary.""" LOG.info("Ensuring %s is installed", tool) - tool_path = _resolve_install(tool, version) - if tool_path: - LOG.info("%s available at %s", tool, tool_path) - LOG.warning("%s not found and could not be installed", tool) + _resolve_install(tool, version) + From 2ba3176a017ddf9947c949abe4eac65fa8852da7 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 22:39:52 +0300 Subject: [PATCH 04/24] fix: just install clang-format --- cpp_linter_hooks/util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index b80ff39..820bd7e 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -214,5 +214,4 @@ def is_installed(tool: str) -> Optional[Path]: def ensure_installed(tool: str, version: Optional[str] = None) -> None: """Ensure a tool is installed, resolving its version if necessary.""" LOG.info("Ensuring %s is installed", tool) - _resolve_install(tool, version) - + _install_tool(tool, version) From 587380c2a6e1485f7443765acd77fdf37ec85fb2 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 22:44:54 +0300 Subject: [PATCH 05/24] fix: do not install clang-format when version is none --- cpp_linter_hooks/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index 820bd7e..6b69d3f 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -213,5 +213,5 @@ def is_installed(tool: str) -> Optional[Path]: def ensure_installed(tool: str, version: Optional[str] = None) -> None: """Ensure a tool is installed, resolving its version if necessary.""" - LOG.info("Ensuring %s is installed", tool) - _install_tool(tool, version) + if version is not None: + _install_tool(tool, version) From 9cac9d17306097cf18082ef406f1373e48f93e7b Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 23:06:46 +0300 Subject: [PATCH 06/24] test: add log for testing --- cpp_linter_hooks/clang_format.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cpp_linter_hooks/clang_format.py b/cpp_linter_hooks/clang_format.py index b288c05..0e95e85 100644 --- a/cpp_linter_hooks/clang_format.py +++ b/cpp_linter_hooks/clang_format.py @@ -2,9 +2,10 @@ import sys from argparse import ArgumentParser from typing import Tuple - +import logging from .util import ensure_installed, DEFAULT_CLANG_FORMAT_VERSION +LOG = logging.getLogger(__name__) parser = ArgumentParser() parser.add_argument("--version", default=DEFAULT_CLANG_FORMAT_VERSION) @@ -24,6 +25,8 @@ def run_clang_format(args=None) -> Tuple[int, str]: command.extend(other_args) + LOG.warning(command) + try: # Run the clang-format command with captured output sp = subprocess.run( From bc9075934e0f18c579ddd9466c4eb4e208d3ec10 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 23:11:58 +0300 Subject: [PATCH 07/24] test: remove log for testing --- cpp_linter_hooks/clang_format.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cpp_linter_hooks/clang_format.py b/cpp_linter_hooks/clang_format.py index 0e95e85..b288c05 100644 --- a/cpp_linter_hooks/clang_format.py +++ b/cpp_linter_hooks/clang_format.py @@ -2,10 +2,9 @@ import sys from argparse import ArgumentParser from typing import Tuple -import logging + from .util import ensure_installed, DEFAULT_CLANG_FORMAT_VERSION -LOG = logging.getLogger(__name__) parser = ArgumentParser() parser.add_argument("--version", default=DEFAULT_CLANG_FORMAT_VERSION) @@ -25,8 +24,6 @@ def run_clang_format(args=None) -> Tuple[int, str]: command.extend(other_args) - LOG.warning(command) - try: # Run the clang-format command with captured output sp = subprocess.run( From 20efee775f983d177b1bfe42086e956b70c69b14 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 23:13:58 +0300 Subject: [PATCH 08/24] refactor: code cleanup --- cpp_linter_hooks/clang_format.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cpp_linter_hooks/clang_format.py b/cpp_linter_hooks/clang_format.py index b288c05..450bb65 100644 --- a/cpp_linter_hooks/clang_format.py +++ b/cpp_linter_hooks/clang_format.py @@ -3,7 +3,7 @@ from argparse import ArgumentParser from typing import Tuple -from .util import ensure_installed, DEFAULT_CLANG_FORMAT_VERSION +from .util import _install_tool, DEFAULT_CLANG_FORMAT_VERSION parser = ArgumentParser() @@ -15,7 +15,8 @@ def run_clang_format(args=None) -> Tuple[int, str]: hook_args, other_args = parser.parse_known_args(args) - ensure_installed("clang-format", hook_args.version) + if hook_args.version is not None: + _install_tool("clang-format", hook_args.version) command = ["clang-format", "-i"] # Add verbose flag if requested From 0db3feaf68d050fcc16ade5d2ed0926b8ede7307 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 23:33:35 +0300 Subject: [PATCH 09/24] refactor: code cleanup --- cpp_linter_hooks/clang_format.py | 6 ++-- cpp_linter_hooks/clang_tidy.py | 5 +-- cpp_linter_hooks/util.py | 55 +++----------------------------- pyproject.toml | 8 +---- 4 files changed, 11 insertions(+), 63 deletions(-) diff --git a/cpp_linter_hooks/clang_format.py b/cpp_linter_hooks/clang_format.py index 450bb65..0cd0d34 100644 --- a/cpp_linter_hooks/clang_format.py +++ b/cpp_linter_hooks/clang_format.py @@ -3,7 +3,7 @@ from argparse import ArgumentParser from typing import Tuple -from .util import _install_tool, DEFAULT_CLANG_FORMAT_VERSION +from cpp_linter_hooks.util import _resolve_install, DEFAULT_CLANG_FORMAT_VERSION parser = ArgumentParser() @@ -15,8 +15,8 @@ def run_clang_format(args=None) -> Tuple[int, str]: hook_args, other_args = parser.parse_known_args(args) - if hook_args.version is not None: - _install_tool("clang-format", hook_args.version) + if hook_args.version: + _resolve_install("clang-format", hook_args.version) command = ["clang-format", "-i"] # Add verbose flag if requested diff --git a/cpp_linter_hooks/clang_tidy.py b/cpp_linter_hooks/clang_tidy.py index db3377e..f1cb163 100644 --- a/cpp_linter_hooks/clang_tidy.py +++ b/cpp_linter_hooks/clang_tidy.py @@ -2,7 +2,7 @@ from argparse import ArgumentParser from typing import Tuple -from .util import ensure_installed, DEFAULT_CLANG_TIDY_VERSION +from cpp_linter_hooks.util import _resolve_install, DEFAULT_CLANG_TIDY_VERSION parser = ArgumentParser() @@ -11,7 +11,8 @@ def run_clang_tidy(args=None) -> Tuple[int, str]: hook_args, other_args = parser.parse_known_args(args) - ensure_installed("clang-tidy", hook_args.version) + if hook_args.version: + _resolve_install("clang-tidy", hook_args.version) command = ["clang-tidy"] + other_args retval = 0 diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index 6b69d3f..5cb7432 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -20,16 +20,10 @@ def get_version_from_dependency(tool: str) -> Optional[str]: return None with open(pyproject_path, "rb") as f: data = tomllib.load(f) - # First try project.optional-dependencies.tools - optional_deps = data.get("project", {}).get("optional-dependencies", {}) - tools_deps = optional_deps.get("tools", []) - for dep in tools_deps: - if dep.startswith(f"{tool}=="): - return dep.split("==")[1] - - # Fallback to project.dependencies for backward compatibility - dependencies = data.get("project", {}).get("dependencies", []) - for dep in dependencies: + # Check build-system.requires + build_system = data.get("build-system", {}) + requires = build_system.get("requires", []) + for dep in requires: if dep.startswith(f"{tool}=="): return dep.split("==")[1] return None @@ -148,20 +142,6 @@ def parse_version(v: str): return None -def _get_runtime_version(tool: str) -> Optional[str]: - """Get the runtime version of a tool.""" - try: - output = subprocess.check_output([tool, "--version"], text=True) - if tool == "clang-tidy": - lines = output.strip().splitlines() - if len(lines) > 1: - return lines[1].split()[-1] - elif tool == "clang-format": - return output.strip().split()[-1] - except Exception: - return None - - def _install_tool(tool: str, version: str) -> Optional[Path]: """Install a tool using pip.""" try: @@ -187,31 +167,4 @@ def _resolve_install(tool: str, version: Optional[str]) -> Optional[Path]: else DEFAULT_CLANG_TIDY_VERSION ) - path = shutil.which(tool) - if path: - runtime_version = _get_runtime_version(tool) - if runtime_version and user_version not in runtime_version: - LOG.info( - "%s version mismatch (%s != %s), reinstalling...", - tool, - runtime_version, - user_version, - ) - return _install_tool(tool, user_version) - return Path(path) - return _install_tool(tool, user_version) - - -def is_installed(tool: str) -> Optional[Path]: - """Check if a tool is installed and return its path.""" - path = shutil.which(tool) - if path: - return Path(path) - return None - - -def ensure_installed(tool: str, version: Optional[str] = None) -> None: - """Ensure a tool is installed, resolving its version if necessary.""" - if version is not None: - _install_tool(tool, version) diff --git a/pyproject.toml b/pyproject.toml index 35b16fd..2e66bcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=45", "setuptools-scm", "clang-format", "clang-tidy"] +requires = ["setuptools>=45", "setuptools-scm", "clang-format==21.1.0", "clang-tidy==21.1.0"] build-backend = "setuptools.build_meta" requires-python = ">=3.9" @@ -46,12 +46,6 @@ source = "https://github.com/cpp-linter/cpp-linter-hooks" tracker = "https://github.com/cpp-linter/cpp-linter-hooks/issues" [project.optional-dependencies] -# only clang tools can added to this section to make hooks work -tools = [ - "clang-format==21.1.0", - "clang-tidy==21.1.0", -] - dev = [ "coverage", "pre-commit", From 6e7892307b0a769bf23230200a91c3a61412645b Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 23:43:55 +0300 Subject: [PATCH 10/24] fix: update test --- tests/test_util.py | 158 +-------------------------------------------- 1 file changed, 3 insertions(+), 155 deletions(-) diff --git a/tests/test_util.py b/tests/test_util.py index 6b3d806..0b6200d 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,17 +1,12 @@ -import logging import pytest -from itertools import product from unittest.mock import patch from pathlib import Path import subprocess import sys from cpp_linter_hooks.util import ( - ensure_installed, - is_installed, get_version_from_dependency, _resolve_version, - _get_runtime_version, _install_tool, _resolve_install, CLANG_FORMAT_VERSIONS, @@ -25,85 +20,14 @@ TOOLS = ["clang-format", "clang-tidy"] -@pytest.mark.benchmark -@pytest.mark.parametrize(("tool", "version"), list(product(TOOLS, VERSIONS))) -def test_ensure_installed(tool, version, tmp_path, monkeypatch, caplog): - """Test that ensure_installed returns the tool name for wheel packages.""" - with monkeypatch.context(): - # Mock shutil.which to simulate the tool being available - with patch("shutil.which", return_value=str(tmp_path / tool)): - # Mock _get_runtime_version to return a matching version - mock_version = "20.1.7" if tool == "clang-format" else "20.1.0" - with patch( - "cpp_linter_hooks.util._get_runtime_version", return_value=mock_version - ): - caplog.clear() - caplog.set_level(logging.INFO, logger="cpp_linter_hooks.util") - - if version is None: - result = ensure_installed(tool) - else: - result = ensure_installed(tool, version=version) - - # Should return the tool name for direct execution - assert result == tool - - # Check that we logged ensuring the tool is installed - assert any("Ensuring" in record.message for record in caplog.records) - - -@pytest.mark.benchmark -def test_is_installed_with_shutil_which(tmp_path): - """Test is_installed when tool is found via shutil.which.""" - tool_path = tmp_path / "clang-format" - tool_path.touch() - - with patch("shutil.which", return_value=str(tool_path)): - result = is_installed("clang-format") - assert result == tool_path - - -@pytest.mark.benchmark -def test_is_installed_not_found(): - """Test is_installed when tool is not found anywhere.""" - with ( - patch("shutil.which", return_value=None), - patch("sys.executable", "/nonexistent/python"), - ): - result = is_installed("clang-format") - assert result is None - - -@pytest.mark.benchmark -def test_ensure_installed_tool_not_found(caplog): - """Test ensure_installed when tool is not found.""" - with ( - patch("shutil.which", return_value=None), - patch("cpp_linter_hooks.util._install_tool", return_value=None), - ): - caplog.clear() - caplog.set_level(logging.WARNING, logger="cpp_linter_hooks.util") - - result = ensure_installed("clang-format") - - # Should still return the tool name - assert result == "clang-format" - - # Should log a warning - assert any( - "not found and could not be installed" in record.message - for record in caplog.records - ) - - # Tests for get_version_from_dependency @pytest.mark.benchmark def test_get_version_from_dependency_success(): """Test get_version_from_dependency with valid pyproject.toml.""" mock_toml_content = { "project": { - "optional-dependencies": { - "tools": [ + "build-system": { + "requires": [ "clang-format==20.1.7", "clang-tidy==20.1.0", "other-package==1.0.0", @@ -135,7 +59,7 @@ def test_get_version_from_dependency_missing_file(): def test_get_version_from_dependency_missing_dependency(): """Test get_version_from_dependency with missing dependency.""" mock_toml_content = { - "project": {"optional-dependencies": {"tools": ["other-package==1.0.0"]}} + "project": {"build-system": {"requires": ["other-package==1.0.0"]}} } with ( @@ -198,48 +122,6 @@ def test_resolve_version_clang_tidy(user_input, expected): assert result == expected -# Tests for _get_runtime_version -@pytest.mark.benchmark -def test_get_runtime_version_clang_format(): - """Test _get_runtime_version for clang-format.""" - mock_output = "Ubuntu clang-format version 20.1.7-1ubuntu1\n" - - with patch("subprocess.check_output", return_value=mock_output): - result = _get_runtime_version("clang-format") - assert result == "20.1.7-1ubuntu1" - - -@pytest.mark.benchmark -def test_get_runtime_version_clang_tidy(): - """Test _get_runtime_version for clang-tidy.""" - mock_output = "LLVM (http://llvm.org/):\n LLVM version 20.1.0\n" - - with patch("subprocess.check_output", return_value=mock_output): - result = _get_runtime_version("clang-tidy") - assert result == "20.1.0" - - -@pytest.mark.benchmark -def test_get_runtime_version_exception(): - """Test _get_runtime_version when subprocess fails.""" - with patch( - "subprocess.check_output", - side_effect=subprocess.CalledProcessError(1, ["clang-format"]), - ): - result = _get_runtime_version("clang-format") - assert result is None - - -@pytest.mark.benchmark -def test_get_runtime_version_clang_tidy_single_line(): - """Test _get_runtime_version for clang-tidy with single line output.""" - mock_output = "LLVM version 20.1.0\n" - - with patch("subprocess.check_output", return_value=mock_output): - result = _get_runtime_version("clang-tidy") - assert result is None # Should return None for single line - - # Tests for _install_tool @pytest.mark.benchmark def test_install_tool_success(): @@ -376,40 +258,6 @@ def test_resolve_install_invalid_version(): ) -# Tests for ensure_installed edge cases -@pytest.mark.benchmark -def test_ensure_installed_version_mismatch(caplog): - """Test ensure_installed with version mismatch scenario.""" - mock_path = "/usr/bin/clang-format" - - with ( - patch("shutil.which", return_value=mock_path), - patch("cpp_linter_hooks.util._get_runtime_version", return_value="18.1.8"), - patch("cpp_linter_hooks.util._install_tool", return_value=Path(mock_path)), - ): - caplog.clear() - caplog.set_level(logging.INFO, logger="cpp_linter_hooks.util") - - result = ensure_installed("clang-format", "20.1.7") - assert result == "clang-format" - - # Should log version mismatch - assert any("version mismatch" in record.message for record in caplog.records) - - -@pytest.mark.benchmark -def test_ensure_installed_no_runtime_version(): - """Test ensure_installed when runtime version cannot be determined.""" - mock_path = "/usr/bin/clang-format" - - with ( - patch("shutil.which", return_value=mock_path), - patch("cpp_linter_hooks.util._get_runtime_version", return_value=None), - ): - result = ensure_installed("clang-format", "20.1.7") - assert result == "clang-format" - - # Tests for constants and defaults @pytest.mark.benchmark def test_default_versions(): From 56db8c2be4a4bae3beae1f3cdd517fb3f2a95920 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 23:46:04 +0300 Subject: [PATCH 11/24] fix: update test --- tests/test_util.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_util.py b/tests/test_util.py index 0b6200d..14f0b1d 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -174,7 +174,6 @@ def test_resolve_install_tool_already_installed_correct_version(): with ( patch("shutil.which", return_value=mock_path), - patch("cpp_linter_hooks.util._get_runtime_version", return_value="20.1.7"), ): result = _resolve_install("clang-format", "20.1.7") assert result == Path(mock_path) @@ -187,7 +186,6 @@ def test_resolve_install_tool_version_mismatch(): with ( patch("shutil.which", return_value=mock_path), - patch("cpp_linter_hooks.util._get_runtime_version", return_value="18.1.8"), patch( "cpp_linter_hooks.util._install_tool", return_value=Path(mock_path) ) as mock_install, From 52d9d4cd1f17c152aa3e00dfaaf2c1cb173fa48c Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 23:50:01 +0300 Subject: [PATCH 12/24] fix: update test --- tests/test_util.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_util.py b/tests/test_util.py index 14f0b1d..566e5e8 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -195,12 +195,6 @@ def test_resolve_install_tool_version_mismatch(): assert result == Path(mock_path) mock_install.assert_called_once_with("clang-format", "20.1.7") - mock_log.info.assert_called_once_with( - "%s version mismatch (%s != %s), reinstalling...", - "clang-format", - "18.1.8", - "20.1.7", - ) @pytest.mark.benchmark From 0dc7ff7d05439f024528febc9f5dc5e8e0cb62c2 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 23:51:24 +0300 Subject: [PATCH 13/24] fix: update test --- tests/test_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_util.py b/tests/test_util.py index 566e5e8..afc0d69 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -189,7 +189,6 @@ def test_resolve_install_tool_version_mismatch(): patch( "cpp_linter_hooks.util._install_tool", return_value=Path(mock_path) ) as mock_install, - patch("cpp_linter_hooks.util.LOG") as mock_log, ): result = _resolve_install("clang-format", "20.1.7") assert result == Path(mock_path) From 25ccc0b55c58ed800872e4ae61c4995b198577d9 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Sun, 14 Sep 2025 23:58:22 +0300 Subject: [PATCH 14/24] fix: update tests --- cpp_linter_hooks/util.py | 14 ++++++++++++++ tests/test_util.py | 19 +++++++++---------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index 5cb7432..aba6c2c 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -26,6 +26,20 @@ def get_version_from_dependency(tool: str) -> Optional[str]: for dep in requires: if dep.startswith(f"{tool}=="): return dep.split("==")[1] + + # Check project.dependencies + dependencies = data.get("project", {}).get("dependencies", []) + for dep in dependencies: + if dep.startswith(f"{tool}=="): + return dep.split("==")[1] + + # Check project.optional-dependencies (all groups) + optional_deps = data.get("project", {}).get("optional-dependencies", {}) + for group in optional_deps.values(): + for dep in group: + if dep.startswith(f"{tool}=="): + return dep.split("==")[1] + return None diff --git a/tests/test_util.py b/tests/test_util.py index afc0d69..09aad38 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -25,15 +25,14 @@ def test_get_version_from_dependency_success(): """Test get_version_from_dependency with valid pyproject.toml.""" mock_toml_content = { - "project": { - "build-system": { - "requires": [ - "clang-format==20.1.7", - "clang-tidy==20.1.0", - "other-package==1.0.0", - ] - } - } + "build-system": { + "requires": [ + "clang-format==20.1.7", + "clang-tidy==20.1.0", + "other-package==1.0.0", + ] + }, + "project": {}, } with ( @@ -176,7 +175,7 @@ def test_resolve_install_tool_already_installed_correct_version(): patch("shutil.which", return_value=mock_path), ): result = _resolve_install("clang-format", "20.1.7") - assert result == Path(mock_path) + assert Path(result) == Path(mock_path) @pytest.mark.benchmark From ae3053a731fc93760a0f02b38ed23755a51f9545 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 15 Sep 2025 00:03:51 +0300 Subject: [PATCH 15/24] fix: update tests --- cpp_linter_hooks/util.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index aba6c2c..5cb7432 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -26,20 +26,6 @@ def get_version_from_dependency(tool: str) -> Optional[str]: for dep in requires: if dep.startswith(f"{tool}=="): return dep.split("==")[1] - - # Check project.dependencies - dependencies = data.get("project", {}).get("dependencies", []) - for dep in dependencies: - if dep.startswith(f"{tool}=="): - return dep.split("==")[1] - - # Check project.optional-dependencies (all groups) - optional_deps = data.get("project", {}).get("optional-dependencies", {}) - for group in optional_deps.values(): - for dep in group: - if dep.startswith(f"{tool}=="): - return dep.split("==")[1] - return None From bc250abdae8311cae108efc52008dc069db36820 Mon Sep 17 00:00:00 2001 From: Xianpeng Shen Date: Mon, 15 Sep 2025 00:08:42 +0300 Subject: [PATCH 16/24] Update tests/test_util.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/test_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_util.py b/tests/test_util.py index 09aad38..2c948a8 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -58,7 +58,7 @@ def test_get_version_from_dependency_missing_file(): def test_get_version_from_dependency_missing_dependency(): """Test get_version_from_dependency with missing dependency.""" mock_toml_content = { - "project": {"build-system": {"requires": ["other-package==1.0.0"]}} + "build-system": {"requires": ["other-package==1.0.0"]} } with ( From 6e12622d81b4e8f2bb85092ad90e0c83dcc234c1 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 15 Sep 2025 00:09:51 +0300 Subject: [PATCH 17/24] fix: update tests --- tests/test_util.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_util.py b/tests/test_util.py index 2c948a8..d16eee3 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -57,9 +57,7 @@ def test_get_version_from_dependency_missing_file(): @pytest.mark.benchmark def test_get_version_from_dependency_missing_dependency(): """Test get_version_from_dependency with missing dependency.""" - mock_toml_content = { - "build-system": {"requires": ["other-package==1.0.0"]} - } + mock_toml_content = {"build-system": {"requires": ["other-package==1.0.0"]}} with ( patch("pathlib.Path.exists", return_value=True), From e24aa3ade6c0326cecf2856c2fb6d8599b6c0d08 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 15 Sep 2025 00:23:51 +0300 Subject: [PATCH 18/24] fix: suppress output --- cpp_linter_hooks/util.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index 5cb7432..98c5bb6 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -143,10 +143,12 @@ def parse_version(v: str): def _install_tool(tool: str, version: str) -> Optional[Path]: - """Install a tool using pip.""" + """Install a tool using pip, suppressing output.""" try: subprocess.check_call( - [sys.executable, "-m", "pip", "install", f"{tool}=={version}"] + [sys.executable, "-m", "pip", "install", f"{tool}=={version}"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, ) return shutil.which(tool) except subprocess.CalledProcessError: From 1f1ebd1bdc82f657ae26d9bc113af41bd4d6c686 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 15 Sep 2025 00:28:26 +0300 Subject: [PATCH 19/24] fix: remove error --- cpp_linter_hooks/util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index 98c5bb6..7e652d0 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -152,7 +152,6 @@ def _install_tool(tool: str, version: str) -> Optional[Path]: ) return shutil.which(tool) except subprocess.CalledProcessError: - LOG.error("Failed to install %s==%s", tool, version) return None From 3f4051cd7943d27b72ded7c607594365bb05c6e6 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 15 Sep 2025 00:36:01 +0300 Subject: [PATCH 20/24] docs: update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf00b3b..6031038 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ repos: ``` > [!TIP] -> Install the latest version of `clang-format` and `clang-tidy` if not specified. You can specify the version using the `--version` argument in the `args` list as shown below. +> By default, the latest version of [`clang-format`](https://pypi.org/project/clang-format/#history) and [`clang-tidy`](https://pypi.org/project/clang-tidy/#history) will be installed if not specified. You can specify the version using the `--version` argument in the `args` list as shown below. ### Custom Clang Tool Version From cf4134899fb943f401683ed758cbf3a9c2794d60 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 15 Sep 2025 00:37:36 +0300 Subject: [PATCH 21/24] docs: update readme --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 6031038..2606549 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,6 @@ repos: args: [--checks=.clang-tidy, --version=21] # Specifies version ``` -> [!NOTE] -> Starting from version **v1.0.0**, this pre-commit hook now relies on Python wheel packages — [clang-format](https://pypi.org/project/clang-format/) and [clang-tidy](https://pypi.org/project/clang-tidy/) — instead of the [clang-tools binaries](https://github.com/cpp-linter/clang-tools-static-binaries). The wheel packages are lighter, easier to install, and offer better cross-platform compatibility. For more information, see the [detailed migration notes](docs/migration-notes.md). - ## Output ### clang-format Output From 64d70802d3d956c2ace85384352bfb7cbb05996b Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 15 Sep 2025 00:43:29 +0300 Subject: [PATCH 22/24] fix: update test --- tests/test_util.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/test_util.py b/tests/test_util.py index d16eee3..c014f7d 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -133,7 +133,9 @@ def test_install_tool_success(): assert result == mock_path mock_check_call.assert_called_once_with( - [sys.executable, "-m", "pip", "install", "clang-format==20.1.7"] + [sys.executable, "-m", "pip", "install", "clang-format==20.1.7"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, ) @@ -150,10 +152,6 @@ def test_install_tool_failure(): result = _install_tool("clang-format", "20.1.7") assert result is None - mock_log.error.assert_called_once_with( - "Failed to install %s==%s", "clang-format", "20.1.7" - ) - @pytest.mark.benchmark def test_install_tool_success_but_not_found(): From 77485ce92672c4fb15e720eea8bef5c0501618c4 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 15 Sep 2025 00:45:50 +0300 Subject: [PATCH 23/24] fix: update test --- tests/test_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_util.py b/tests/test_util.py index c014f7d..0c73714 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -147,7 +147,7 @@ def test_install_tool_failure(): "subprocess.check_call", side_effect=subprocess.CalledProcessError(1, ["pip"]), ), - patch("cpp_linter_hooks.util.LOG") as mock_log, + patch("cpp_linter_hooks.util.LOG"), ): result = _install_tool("clang-format", "20.1.7") assert result is None From f018c1f860f14ffb94c5a06eb20ae8ed8dabee5c Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Mon, 15 Sep 2025 00:49:39 +0300 Subject: [PATCH 24/24] docs: update version to v1.1.2 --- README.md | 10 +++++----- testing/benchmark_hook_1.yaml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2606549..628172c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Add this configuration to your `.pre-commit-config.yaml` file: ```yaml repos: - repo: https://github.com/cpp-linter/cpp-linter-hooks - rev: v1.1.0 # Use the tag or commit you want + rev: v1.1.2 # Use the tag or commit you want hooks: - id: clang-format args: [--style=Google] # Other coding style: LLVM, GNU, Chromium, Microsoft, Mozilla, WebKit. @@ -46,7 +46,7 @@ To use custom configurations like `.clang-format` and `.clang-tidy`: ```yaml repos: - repo: https://github.com/cpp-linter/cpp-linter-hooks - rev: v1.1.0 + rev: v1.1.2 hooks: - id: clang-format args: [--style=file] # Loads style from .clang-format file @@ -64,7 +64,7 @@ To use specific versions of clang-format and clang-tidy (using Python wheel pack ```yaml repos: - repo: https://github.com/cpp-linter/cpp-linter-hooks - rev: v1.1.0 + rev: v1.1.2 hooks: - id: clang-format args: [--style=file, --version=21] # Specifies version @@ -151,7 +151,7 @@ Use -header-filter=.* to display errors from all non-system headers. Use -system ```yaml - repo: https://github.com/cpp-linter/cpp-linter-hooks - rev: v1.1.0 + rev: v1.1.2 hooks: - id: clang-format args: [--style=file, --version=21] @@ -177,7 +177,7 @@ This approach ensures that only modified files are checked, further speeding up ```yaml repos: - repo: https://github.com/cpp-linter/cpp-linter-hooks - rev: v1.1.0 + rev: v1.1.2 hooks: - id: clang-format args: [--style=file, --version=21, --verbose] # Add -v or --verbose for detailed output diff --git a/testing/benchmark_hook_1.yaml b/testing/benchmark_hook_1.yaml index 7cac53f..b8ba6da 100644 --- a/testing/benchmark_hook_1.yaml +++ b/testing/benchmark_hook_1.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/cpp-linter/cpp-linter-hooks - rev: v1.1.0 + rev: v1.1.2 hooks: - id: clang-format args: [--style=file, --version=21]