diff --git a/src/skillspector/nodes/analyzers/static_patterns_supply_chain.py b/src/skillspector/nodes/analyzers/static_patterns_supply_chain.py index 7d3ba27..3d9f838 100644 --- a/src/skillspector/nodes/analyzers/static_patterns_supply_chain.py +++ b/src/skillspector/nodes/analyzers/static_patterns_supply_chain.py @@ -441,10 +441,10 @@ def _extract_packages_from_package_json(content: str) -> list[tuple[str, str | N def _extract_packages_from_pyproject(content: str) -> list[tuple[str, str | None, int]]: """Extract (package_name, version_or_None, line_number) from pyproject.toml. - Only PEP 621 ``[project]`` ``dependencies`` / ``optional-dependencies`` and - PEP 735 ``[dependency-groups]`` hold real packages. Standard metadata keys - (``requires-python``, ``name``, ``version``, ...) are not dependencies and - must not be looked up as packages. + Reads PEP 621 ``[project]`` ``dependencies`` / ``optional-dependencies``, + PEP 735 ``[dependency-groups]``, and ``[build-system].requires``. Standard + metadata keys (``requires-python``, ``name``, ``version``, ...) are not + dependencies and must not be looked up as packages. """ try: data = tomllib.loads(content) @@ -467,6 +467,11 @@ def _extract_packages_from_pyproject(content: str) -> list[tuple[str, str | None for group in groups.values(): if isinstance(group, list): specs.extend(d for d in group if isinstance(d, str)) + build_system = data.get("build-system") + if isinstance(build_system, dict): + requires = build_system.get("requires") + if isinstance(requires, list): + specs.extend(d for d in requires if isinstance(d, str)) results: list[tuple[str, str | None, int]] = [] for spec in specs: diff --git a/tests/unit/test_patterns_new.py b/tests/unit/test_patterns_new.py index 917a8d0..575e6bd 100644 --- a/tests/unit/test_patterns_new.py +++ b/tests/unit/test_patterns_new.py @@ -938,6 +938,17 @@ def test_pyproject_skips_non_pep508_and_include_group_entries(self) -> None: names = sorted(p[0] for p in sc_mod._extract_packages_from_pyproject(content)) assert names == ["pytest", "ruff"] + def test_pyproject_build_system_requires_extracted(self) -> None: + """[build-system].requires packages are scanned (e.g. setuptools, hatchling).""" + content = ( + '[project]\nname = "mypkg"\n' + "[build-system]\n" + 'requires = ["setuptools>=68", "wheel"]\n' + 'build-backend = "setuptools.build_meta"\n' + ) + names = sorted(p[0] for p in sc_mod._extract_packages_from_pyproject(content)) + assert names == ["setuptools", "wheel"] + # ── Supply Chain Safe Patterns (SC2) ───────────────────────────────────