2
0
mirror of https://github.com/checkpoint-restore/criu synced 2025-08-22 09:58:09 +00:00
criu/scripts/uninstall_module.py

77 lines
2.6 KiB
Python
Raw Normal View History

#!/usr/bin/python3
"""
`pip uninstall` doesn't support `--prefix`.
https://github.com/pypa/pip/issues/11213
"""
import argparse
import os
import shutil
import site
import subprocess
import sys
# With Python 3.13 the subprocess module now uses the `posix_spawn()`
# function which requires loading the `signal` module:
# https://docs.python.org/3/whatsnew/3.13.html#subprocess
#
# We need to load this module here, before PYTHONPATH and sys.path
# have been modified to use the path specified with `--prefix`.
#
# flake8: noqa: F401
import signal
import importlib_metadata
def add_site_dir(prefix: str):
"""
Add site directory with prefix to sys.path and update PYTHONPATH.
"""
# If prefix is used, we need to make sure that we
# do not uninstall other packages from the system paths.
sys.path = []
site.PREFIXES = [prefix]
pkgs = site.getsitepackages()
for path in pkgs:
site.addsitedir(path)
if 'dist-packages' in path:
# Ubuntu / Debian might use both dist- and site- packages.
site.addsitedir(path.replace('dist-packages', 'site-packages'))
os.environ['PYTHONPATH'] = os.pathsep.join(sys.path)
def uninstall_module(package_name: str, prefix=None):
"""
Enable support for '--prefix' with 'pip uninstall'.
"""
dist_info_path = None
if prefix:
add_site_dir(prefix)
try:
scripts/uninstall_module: fix package discovery The `uninstall_module.py` script is a wrapper for the `pip uninstall` command that enables support for specifying installation prefix (i.e., `--prefix`). When this functionality is used, we intentionally set `sys.path` to include only search paths for the specified prefix to avoid unintentional uninstallation of packages in system paths. Since `importlib_metadata` version 8.1.0, the `Distribution.from_name()` method has been modified [1] to perform additional pre-processing of Distribution objects [2] that requires loading distribution metadata and results in the following error: File "/usr/local/lib/python3.12/site-packages/importlib_metadata/__init__.py", line 422, in <lambda> buckets = bucket(dists, lambda dist: bool(dist.metadata)) ^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/importlib_metadata/__init__.py", line 454, in metadata from . import _adapters File "/usr/local/lib/python3.12/site-packages/importlib_metadata/_adapters.py", line 3, in <module> import email.message File "/usr/lib64/python3.12/email/message.py", line 11, in <module> import quopri ModuleNotFoundError: No module named 'quopri' This error occurs because we have excluded system paths from the list of search paths (`sys.path`). However, this pre-processing is not required for our use case, as we only use the discovery mechanism of importlib_metadata to resolve the metadata directory path of the module being uninstalled. To fix this problem, this patch updates `uninstall_module` to avoid the `from_name()` method and use `discover(name=package_name)` directly. [1] https://github.com/python/importlib_metadata/commit/a65c29adc027b3615154cab73aaedd58a6aa23da [2] https://github.com/python/importlib_metadata/blob/a65c29ad/importlib_metadata/__init__.py#L391 Fixes: #2468 Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
2024-08-17 14:30:21 +01:00
distribution = next(importlib_metadata.Distribution.discover(name=package_name))
dist_info_path = str(distribution._path)
except StopIteration:
print(f"Skipping {package_name} as it is not installed.")
sys.exit(0)
command = [sys.executable, '-m', 'pip', 'uninstall', '-y', package_name]
try:
subprocess.check_call(command, env=os.environ)
if dist_info_path and os.path.isdir(dist_info_path):
# .dist-info files are not cleaned up when the package
# has been installed with --prefix.
# https://github.com/pypa/pip/issues/5573
shutil.rmtree(dist_info_path)
if 'dist-packages' in dist_info_path:
shutil.rmtree(dist_info_path.replace('dist-packages', 'site-packages'))
except subprocess.CalledProcessError as err:
print(f'Error uninstalling package {package_name}: {err}')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('module_name', help='The name of the module to uninstall')
parser.add_argument('--prefix', help='The prefix where the module was installed')
args = parser.parse_args()
uninstall_module(args.module_name, args.prefix)