diff --git a/cpp_linter_hooks/util.py b/cpp_linter_hooks/util.py index 3f97c13..13a7efc 100644 --- a/cpp_linter_hooks/util.py +++ b/cpp_linter_hooks/util.py @@ -20,6 +20,14 @@ 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: if dep.startswith(f"{tool}=="): @@ -176,6 +184,14 @@ 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) diff --git a/pyproject.toml b/pyproject.toml index 798646d..816601f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,9 +32,8 @@ classifiers = [ "Topic :: Software Development :: Build Tools", ] dependencies = [ - "clang-format==20.1.7", - "clang-tidy==20.1.0", "tomli>=1.1.0; python_version < '3.11'", + "setuptools>=45.0.0", # Required for pkg_resources in clang-tidy ] dynamic = ["version"] @@ -47,6 +46,12 @@ 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==20.1.7", + "clang-tidy==20.1.0", +] + dev = [ "coverage", "pre-commit", diff --git a/tests/test_util.py b/tests/test_util.py index ecf5258..48cc448 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -102,11 +102,13 @@ def test_get_version_from_dependency_success(): """Test get_version_from_dependency with valid pyproject.toml.""" mock_toml_content = { "project": { - "dependencies": [ - "clang-format==20.1.7", - "clang-tidy==20.1.0", - "other-package==1.0.0", - ] + "optional-dependencies": { + "tools": [ + "clang-format==20.1.7", + "clang-tidy==20.1.0", + "other-package==1.0.0", + ] + } } } @@ -132,7 +134,9 @@ 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 = {"project": {"dependencies": ["other-package==1.0.0"]}} + mock_toml_content = { + "project": {"optional-dependencies": {"tools": ["other-package==1.0.0"]}} + } with ( patch("pathlib.Path.exists", return_value=True), @@ -423,3 +427,22 @@ def test_version_lists_not_empty(): assert len(CLANG_TIDY_VERSIONS) > 0 assert all(isinstance(v, str) for v in CLANG_FORMAT_VERSIONS) assert all(isinstance(v, str) for v in CLANG_TIDY_VERSIONS) + + +@pytest.mark.benchmark +def test_resolve_install_with_none_default_version(): + """Test _resolve_install when DEFAULT versions are None.""" + with ( + patch("shutil.which", return_value=None), + patch("cpp_linter_hooks.util.DEFAULT_CLANG_FORMAT_VERSION", None), + patch("cpp_linter_hooks.util.DEFAULT_CLANG_TIDY_VERSION", None), + patch( + "cpp_linter_hooks.util._install_tool", + return_value=Path("/usr/bin/clang-format"), + ) as mock_install, + ): + result = _resolve_install("clang-format", None) + assert result == Path("/usr/bin/clang-format") + + # Should fallback to hardcoded version when DEFAULT is None + mock_install.assert_called_once_with("clang-format", None)