Skip to content

Commit

Permalink
Fix venv pruning.
Browse files Browse the repository at this point in the history
Now that PEP 517/618 build backend and Pip dogfood PEXes use proper
wheel name deps, all venvs, even dogfood venvs, can be pruned properly,
handling venv deps whether they use symlinked site packages or not.
  • Loading branch information
jsirois committed Oct 14, 2024
1 parent ff16957 commit 783efe8
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 34 deletions.
29 changes: 17 additions & 12 deletions pex/cache/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
UserCodeDir,
VenvDirs,
)
from pex.common import CopyMode
from pex.dist_metadata import ProjectNameAndVersion
from pex.typing import TYPE_CHECKING, overload

Expand Down Expand Up @@ -161,24 +162,28 @@ def record_zipapp_install(pex_info):


def record_venv_install(
copy_mode, # type: CopyMode.Value
pex_info, # type: PexInfo
venv_dirs, # type: VenvDirs
):
# type: (...) -> None

with _inserted_wheels(pex_info) as cursor:
cursor.executemany(
"""
INSERT OR IGNORE INTO venv_deps (
venv_hash,
wheel_install_hash
) VALUES (?, ?)
""",
tuple(
(venv_dirs.short_hash, wheel_install_hash)
for wheel_install_hash in pex_info.distributions.values()
),
).close()
if copy_mode is CopyMode.SYMLINK:
cursor.executemany(
"""
INSERT OR IGNORE INTO venv_deps (
venv_hash,
wheel_install_hash
) VALUES (?, ?)
""",
tuple(
(venv_dirs.short_hash, wheel_install_hash)
for wheel_install_hash in pex_info.distributions.values()
),
).close()
else:
cursor.close()


if TYPE_CHECKING:
Expand Down
28 changes: 21 additions & 7 deletions pex/cli/commands/cache/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,8 @@ def prune_pip_caches(wheels):
prunable_wheels.add(
(prunable_pnav.canonicalized_project_name, prunable_pnav.canonicalized_version)
)
if not prunable_wheels:
return

def spawn_list(pip):
# type: (Pip) -> SpawnedJob[Tuple[ProjectNameAndVersion, ...]]
Expand All @@ -545,10 +547,13 @@ def spawn_list(pip):
),
)

all_pips = tuple(iter_all_pips())
# N.B.:We just need 1 Pip per version (really per paired cache). Whether a Pip has extra
# requirements installed does not affect cache management.
all_pip_versions = tuple({pip.version: pip for pip in iter_all_pips()}.values())

pip_removes = [] # type: List[Tuple[Pip, str]]
for pip, project_name_and_versions in zip(
all_pips, execute_parallel(inputs=all_pips, spawn_func=spawn_list)
all_pip_versions, execute_parallel(inputs=all_pip_versions, spawn_func=spawn_list)
):
for pnav in project_name_and_versions:
if (
Expand All @@ -564,15 +569,24 @@ def spawn_list(pip):
)
)

def parse_remove(stdout):
# type: (bytes) -> int

# The output from `pip cache remove` is a line like:
# Files removed: 42
_, sep, count = stdout.decode("utf-8").partition(":")
if sep != ":" or not count:
return 0
try:
return int(count)
except ValueError:
return 0

def spawn_remove(args):
# type: (Tuple[Pip, str]) -> SpawnedJob[int]
pip, wheel_name_glob = args
# Files removed: 1
return SpawnedJob.stdout(
job=pip.spawn_cache_remove(wheel_name_glob),
result_func=lambda stdout: int(
stdout.decode("utf-8").rsplit(":", 1)[1].strip()
),
job=pip.spawn_cache_remove(wheel_name_glob), result_func=parse_remove
)

removes_by_pip = Counter() # type: typing.Counter[str]
Expand Down
15 changes: 8 additions & 7 deletions pex/pex_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,13 +599,14 @@ def ensure_venv(
hermetic_scripts=pex_info.venv_hermetic_scripts,
)

if copy_mode is CopyMode.SYMLINK:
with TRACER.timed(
"Recording venv install of {pex} {hash}".format(
pex=pex.path(), hash=pex_info.pex_hash
)
):
record_venv_install(pex_info=pex_info, venv_dirs=venv_dirs)
with TRACER.timed(
"Recording venv install of {pex} {hash}".format(
pex=pex.path(), hash=pex_info.pex_hash
)
):
record_venv_install(
copy_mode=copy_mode, pex_info=pex_info, venv_dirs=venv_dirs
)

# There are popular Linux distributions with shebang length limits
# (BINPRM_BUF_SIZE in /usr/include/linux/binfmts.h) set at 128 characters, so
Expand Down
1 change: 0 additions & 1 deletion pex/pip/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ def _pip_installation(

isolated_pip_builder = PEXBuilder(path=chroot.work_dir, copy_mode=CopyMode.SYMLINK)
isolated_pip_builder.info.venv = True
isolated_pip_builder.info.venv_site_packages_copies = True
# Allow REPRODUCIBLE_BUILDS_ENV PYTHONHASHSEED env var to take effect if needed.
isolated_pip_builder.info.venv_hermetic_scripts = False
for dist_location in iter_distribution_locations():
Expand Down
21 changes: 14 additions & 7 deletions pex/pip/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,34 +45,39 @@ def overridden(cls):
def __init__(
self,
version, # type: str
setuptools_version, # type: str
wheel_version, # type: str
requires_python, # type: str
name=None, # type: Optional[str]
requirement=None, # type: Optional[str]
setuptools_version=None, # type: Optional[str]
wheel_version=None, # type: Optional[str]
requires_python=None, # type: Optional[str]
setuptools_requirement=None, # type: Optional[str]
hidden=False, # type: bool
):
# type: (...) -> None
super(PipVersionValue, self).__init__(name or version)

def to_requirement(
project_name, # type: str
project_version=None, # type: Optional[str]
project_version, # type: str
):
# type: (...) -> Requirement
return Requirement.parse(
"{project_name}=={project_version}".format(
project_name=project_name, project_version=project_version
)
if project_version
else project_name
)

self.version = Version(version)
self.requirement = (
Requirement.parse(requirement) if requirement else to_requirement("pip", version)
)
self.setuptools_requirement = to_requirement("setuptools", setuptools_version)
self.setuptools_version = setuptools_version
self.setuptools_requirement = (
Requirement.parse(setuptools_requirement)
if setuptools_requirement
else to_requirement("setuptools", setuptools_version)
)
self.wheel_version = wheel_version
self.wheel_requirement = to_requirement("wheel", wheel_version)
self.requires_python = SpecifierSet(requires_python) if requires_python else None
self.hidden = hidden
Expand Down Expand Up @@ -174,6 +179,8 @@ def values(cls):
name="20.3.4-patched",
version="20.3.4+patched",
requirement=vendor.PIP_SPEC.requirement,
setuptools_version="44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863",
setuptools_requirement="setuptools",
wheel_version="0.37.1",
requires_python="<3.12",
)
Expand Down

0 comments on commit 783efe8

Please sign in to comment.