From 33aeacc7ff71b8f123a22cacb899f0196fd6a67d Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Tue, 28 Oct 2025 23:32:26 +0200 Subject: [PATCH 1/3] feat: support CLI tool to install clang tool wheels --- README.md | 30 ++++++++++++++++++++++++++++++ cpp_linter_hooks/util.py | 19 +++++++++++++++++++ pyproject.toml | 1 + 3 files changed, 50 insertions(+) diff --git a/README.md b/README.md index 6ec0f81..6b4c6c6 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ A pre-commit hook that automatically formats and lints your C/C++ code using `cl - [Quick Start](#quick-start) - [Custom Configuration Files](#custom-configuration-files) - [Custom Clang Tool Version](#custom-clang-tool-version) + - [Clang Tool Wheel CLI](#clang-tool-wheel-cli) - [Output](#output) - [clang-format Output](#clang-format-output) - [clang-tidy Output](#clang-tidy-output) @@ -72,6 +73,35 @@ repos: args: [--checks=.clang-tidy, --version=21] # Specifies version ``` +### Clang Tool Wheel CLI + +This package also provides a CLI tool `clang-tools-wheel` to install specific versions of clang-format and clang-tidy wheels directly. + +It can automatically resolve and install compatible versions even if no explicit version number is provided. + +```bash +# Install the package +pip install cpp-linter-hooks + +# Install specific version of clang-format +clang-tools-wheel --tool clang-format --version 21 +clang-format installed at: /home/sxp/.local/bin/clang-format + +# Check clang-format version +/home/sxp/.local/bin/clang-format --version +clang-format version 21.1.2 + +# Install specific version of clang-tidy +clang-tools-wheel --tool clang-tidy --version 21 +clang-tidy installed at: /home/sxp/.local/bin/clang-tidy + +# Check clang-tidy version +/home/sxp/.local/bin/clang-tidy --version +LLVM (http://llvm.org/): + LLVM version 21.1.1 + Optimized build. +``` + ## Output ### clang-format Output diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index 37d431a..c6fb806 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -1,6 +1,7 @@ import sys import shutil import subprocess +from argparse import ArgumentParser from pathlib import Path import logging from typing import Optional, List @@ -86,3 +87,21 @@ def _resolve_install(tool: str, version: Optional[str]) -> Optional[Path]: ) return _install_tool(tool, user_version) + + +def main(): + parser = ArgumentParser("Install specified clang tool wheel") + parser.add_argument("--tool", default="clang-format") + parser.add_argument("--version", default=None) + args = parser.parse_args() + path = _resolve_install(args.tool, args.version) + if path: + print(f"{args.tool} installed at: {path}") + return 0 + else: + print(f"Failed to install {args.tool} version {args.version}") + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/pyproject.toml b/pyproject.toml index e27dfa0..e9db66c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dependencies = [ dynamic = ["version"] [project.scripts] +clang-tools-wheel = "cpp_linter_hooks.util:main" clang-format-hook = "cpp_linter_hooks.clang_format:main" clang-tidy-hook = "cpp_linter_hooks.clang_tidy:main" From 997feb4146c5bb24b966e1bd797e6b7f3a23f28c Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Tue, 28 Oct 2025 23:58:18 +0200 Subject: [PATCH 2/3] feat: add more test and fix pytest warnings --- pyproject.toml | 5 +++++ tests/test_util.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e9db66c..65ad7bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,3 +80,8 @@ exclude_also = [ "__name__", "FileNotFoundError" ] + +[tool.pytest.ini_options] +markers = [ + "benchmark: mark test as a benchmark test" +] diff --git a/tests/test_util.py b/tests/test_util.py index 38d5e6d..6487cb8 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -4,6 +4,7 @@ import subprocess import sys +from cpp_linter_hooks import util from cpp_linter_hooks.util import ( get_version_from_dependency, _resolve_version, @@ -278,3 +279,42 @@ def test_resolve_install_with_none_default_version(): # Should fallback to hardcoded version when DEFAULT is None mock_install.assert_called_once_with("clang-format", None) + + +@pytest.mark.benchmark +def test_main_success(monkeypatch): + # Patch _resolve_install to simulate success + monkeypatch.setattr( + "cpp_linter_hooks.util._resolve_install", + lambda tool, version: "/usr/bin/clang-format", + ) + monkeypatch.setattr( + sys, "argv", ["util.py", "--tool", "clang-format", "--version", "15.0.7"] + ) + exit_code = util.main() + assert exit_code == 0 + + +@pytest.mark.benchmark +def test_main_failure(monkeypatch): + # Patch _resolve_install to simulate failure + monkeypatch.setattr( + "cpp_linter_hooks.util._resolve_install", lambda tool, version: None + ) + monkeypatch.setattr( + sys, "argv", ["util.py", "--tool", "clang-format", "--version", "99.99.99"] + ) + exit_code = util.main() + assert exit_code == 1 + + +@pytest.mark.benchmark +def test_main_default_tool(monkeypatch): + # Patch _resolve_install to simulate success for default tool + monkeypatch.setattr( + "cpp_linter_hooks.util._resolve_install", + lambda tool, version: "/usr/bin/clang-format", + ) + monkeypatch.setattr(sys, "argv", ["util.py"]) + exit_code = util.main() + assert exit_code == 0 From 7feda5b7b036ea2e1e3bec2a70ce30f11ff080bf Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Wed, 29 Oct 2025 00:02:46 +0200 Subject: [PATCH 3/3] feat: update per code review --- cpp_linter_hooks/util.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index c6fb806..4765e62 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -89,10 +89,19 @@ def _resolve_install(tool: str, version: Optional[str]) -> Optional[Path]: return _install_tool(tool, user_version) -def main(): - parser = ArgumentParser("Install specified clang tool wheel") - parser.add_argument("--tool", default="clang-format") - parser.add_argument("--version", default=None) +def main() -> int: + parser = ArgumentParser(description="Install specified clang tool wheel") + parser.add_argument( + "--tool", + default="clang-format", + choices=["clang-format", "clang-tidy"], + help="Tool to install (clang-format or clang-tidy)", + ) + parser.add_argument( + "--version", + default=None, + help="Version to install (e.g., 21 or 21.1.2). Defaults to latest compatible version.", + ) args = parser.parse_args() path = _resolve_install(args.tool, args.version) if path: