diff --git a/fawltydeps/packages.py b/fawltydeps/packages.py index 88aebb91..2a86cf3a 100644 --- a/fawltydeps/packages.py +++ b/fawltydeps/packages.py @@ -311,7 +311,7 @@ def __init__(self, srcs: AbstractSet[PyEnvSource] = frozenset()) -> None: self.package_dirs: Set[Path] = {src.path for src in srcs} @classmethod - def find_package_dirs(cls, path: Path) -> Iterator[Path]: # noqa: C901, PLR0912 + def find_package_dirs(cls, path: Path, *, dprint: bool = False) -> Iterator[Path]: # noqa: C901, PLR0912 """Return the packages directories corresponding to the given path. The given 'path' is a user-provided directory path meant to point to @@ -330,18 +330,31 @@ def find_package_dirs(cls, path: Path) -> Iterator[Path]: # noqa: C901, PLR0912 # versions X.Y that are different from the current Python version. found = False + def debug_print(msg: str) -> None: + if dprint: + print(msg) # noqa: T201 + + bin_python = path / "bin/python" if sys.platform.startswith("win"): # Check for packages on Windows + debug_print(" - on win") if (path / "Scripts" / "python.exe").is_file(): + debug_print(" - found python.exe") _site_packages = site_packages(path) if _site_packages.is_dir(): + debug_print(f" - found site-packages at {_site_packages}!") yield _site_packages found = True if found: return - elif (path / "bin/python").is_file(): # Assume POSIX + elif bin_python.is_file(): # Assume POSIX + debug_print(" - on posix, found bin/python") + if bin_python.is_symlink() and hasattr(bin_python, "readlink"): + debug_print(f" and points to {bin_python.readlink()!r}") + debug_print(f" which exists? {bin_python.readlink().exists()!r}") for _site_packages in path.glob("lib/python?.*/site-packages"): if _site_packages.is_dir(): + debug_print(f" - found site-packages at {_site_packages}!") yield _site_packages found = True if found: @@ -349,8 +362,10 @@ def find_package_dirs(cls, path: Path) -> Iterator[Path]: # noqa: C901, PLR0912 # Workaround for projects using PEP582: if path.name == "__pypackages__": + debug_print(" - found __pypackages__") for _site_packages in path.glob("?.*/lib"): if _site_packages.is_dir(): + debug_print(f" - found site-packages at {_site_packages}!") yield _site_packages found = True if found: @@ -359,9 +374,12 @@ def find_package_dirs(cls, path: Path) -> Iterator[Path]: # noqa: C901, PLR0912 # Given path is not a python environment, but it might be _inside_ one. # Try again with parent directory if path.parent != path: + debug_print(" - recurse up") for package_dir in cls.find_package_dirs(path.parent): + debug_print(f" - from parent: {package_dir}") with suppress(ValueError): package_dir.relative_to(path) # ValueError if not relative + debug_print(" - yield it!") yield package_dir @property @@ -381,7 +399,7 @@ def _pyenvs() -> Iterator[Tuple[CustomMapping, str]]: return accumulate_mappings(self.__class__, _pyenvs()) -def pyenv_sources(*pyenv_paths: Path) -> Set[PyEnvSource]: +def pyenv_sources(*pyenv_paths: Path, dprint: bool = False) -> Set[PyEnvSource]: """Convert Python environment paths into PyEnvSources. Convenience helper when you want to construct a LocalPackageResolver from @@ -389,7 +407,11 @@ def pyenv_sources(*pyenv_paths: Path) -> Set[PyEnvSource]: """ ret: Set[PyEnvSource] = set() for path in pyenv_paths: - package_dirs = set(LocalPackageResolver.find_package_dirs(path)) + if dprint: + print(f"Looking for package dirs under {path}") # noqa: T201 + package_dirs = set(LocalPackageResolver.find_package_dirs(path, dprint=dprint)) + if dprint: + print(f" Found {package_dirs!r}") # noqa: T201 if not package_dirs: logger.debug(f"Could not find a Python env at {path}!") ret.update(PyEnvSource(d) for d in package_dirs) diff --git a/tests/test_real_projects.py b/tests/test_real_projects.py index 7347e24b..50142bba 100644 --- a/tests/test_real_projects.py +++ b/tests/test_real_projects.py @@ -10,6 +10,7 @@ import json import logging +import os import subprocess import sys import tarfile @@ -48,7 +49,20 @@ def verify_requirements(venv_path: Path, requirements: List[str]) -> None: for req in requirements if "python_version" not in req # we don't know how to parse these (yet) } - resolved = LocalPackageResolver(pyenv_sources(venv_path)).lookup_packages(deps) + print(f"Walking virtualenv at {venv_path}:") + for dirpath, dirnames, filenames in os.walk(venv_path): + print(f" {dirpath}:") + print(f" DIRS: {dirnames!r}") + print(f" FILES: {filenames!r}") + if ( + dirpath.endswith("/bin") + and "python" in filenames + and hasattr(Path, "readlink") + ): + print(f" READLINK: {Path(dirpath, "python").readlink()}") + resolved = LocalPackageResolver( + pyenv_sources(venv_path, dprint=True) + ).lookup_packages(deps) assert all(dep in resolved for dep in deps)