aboutsummaryrefslogtreecommitdiff
path: root/python/scripts/mkvenv.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/scripts/mkvenv.py')
-rw-r--r--python/scripts/mkvenv.py272
1 files changed, 46 insertions, 226 deletions
diff --git a/python/scripts/mkvenv.py b/python/scripts/mkvenv.py
index 4f2349fbb6..d0b9c215ca 100644
--- a/python/scripts/mkvenv.py
+++ b/python/scripts/mkvenv.py
@@ -61,9 +61,6 @@ options:
"""
-# The duplication between importlib and pkg_resources does not help
-# pylint: disable=too-many-lines
-
# Copyright (C) 2022-2023 Red Hat, Inc.
#
# Authors:
@@ -74,6 +71,13 @@ options:
# later. See the COPYING file in the top-level directory.
import argparse
+from importlib.metadata import (
+ Distribution,
+ EntryPoint,
+ PackageNotFoundError,
+ distribution,
+ version,
+)
from importlib.util import find_spec
import logging
import os
@@ -189,7 +193,7 @@ class QemuEnvBuilder(venv.EnvBuilder):
):
kwargs["with_pip"] = False
else:
- check_ensurepip(suggest_remedy=True)
+ check_ensurepip()
super().__init__(*args, **kwargs)
@@ -294,7 +298,7 @@ def need_ensurepip() -> bool:
return True
-def check_ensurepip(prefix: str = "", suggest_remedy: bool = False) -> None:
+def check_ensurepip() -> None:
"""
Check that we have ensurepip.
@@ -305,15 +309,12 @@ def check_ensurepip(prefix: str = "", suggest_remedy: bool = False) -> None:
"Python's ensurepip module is not found.\n"
"It's normally part of the Python standard library, "
"maybe your distribution packages it separately?\n"
- "(Debian puts ensurepip in its python3-venv package.)\n"
+ "Either install ensurepip, or alleviate the need for it in the "
+ "first place by installing pip and setuptools for "
+ f"'{sys.executable}'.\n"
+ "(Hint: Debian puts ensurepip in its python3-venv package.)"
)
- if suggest_remedy:
- msg += (
- "Either install ensurepip, or alleviate the need for it in the"
- " first place by installing pip and setuptools for "
- f"'{sys.executable}'.\n"
- )
- raise Ouch(prefix + msg)
+ raise Ouch(msg)
# ensurepip uses pyexpat, which can also go missing on us:
if not find_spec("pyexpat"):
@@ -321,15 +322,12 @@ def check_ensurepip(prefix: str = "", suggest_remedy: bool = False) -> None:
"Python's pyexpat module is not found.\n"
"It's normally part of the Python standard library, "
"maybe your distribution packages it separately?\n"
- "(NetBSD's pkgsrc debundles this to e.g. 'py310-expat'.)\n"
+ "Either install pyexpat, or alleviate the need for it in the "
+ "first place by installing pip and setuptools for "
+ f"'{sys.executable}'.\n\n"
+ "(Hint: NetBSD's pkgsrc debundles this to e.g. 'py310-expat'.)"
)
- if suggest_remedy:
- msg += (
- "Either install pyexpat, or alleviate the need for it in the "
- "first place by installing pip and setuptools for "
- f"'{sys.executable}'.\n"
- )
- raise Ouch(prefix + msg)
+ raise Ouch(msg)
def make_venv( # pylint: disable=too-many-arguments
@@ -428,28 +426,13 @@ def make_venv( # pylint: disable=too-many-arguments
print(builder.get_value("env_exe"))
-def _gen_importlib(packages: Sequence[str]) -> Iterator[str]:
- # pylint: disable=import-outside-toplevel
- # pylint: disable=no-name-in-module
- # pylint: disable=import-error
- try:
- # First preference: Python 3.8+ stdlib
- from importlib.metadata import ( # type: ignore
- PackageNotFoundError,
- distribution,
- )
- except ImportError as exc:
- logger.debug("%s", str(exc))
- # Second preference: Commonly available PyPI backport
- from importlib_metadata import ( # type: ignore
- PackageNotFoundError,
- distribution,
- )
+def _get_entry_points(packages: Sequence[str]) -> Iterator[str]:
def _generator() -> Iterator[str]:
for package in packages:
try:
- entry_points = distribution(package).entry_points
+ entry_points: Iterator[EntryPoint] = \
+ iter(distribution(package).entry_points)
except PackageNotFoundError:
continue
@@ -465,24 +448,6 @@ def _gen_importlib(packages: Sequence[str]) -> Iterator[str]:
return _generator()
-def _gen_pkg_resources(packages: Sequence[str]) -> Iterator[str]:
- # pylint: disable=import-outside-toplevel
- # Bundled with setuptools; has a good chance of being available.
- import pkg_resources
-
- def _generator() -> Iterator[str]:
- for package in packages:
- try:
- eps = pkg_resources.get_entry_map(package, "console_scripts")
- except pkg_resources.DistributionNotFound:
- continue
-
- for entry_point in eps.values():
- yield str(entry_point)
-
- return _generator()
-
-
def generate_console_scripts(
packages: Sequence[str],
python_path: Optional[str] = None,
@@ -507,66 +472,15 @@ def generate_console_scripts(
if not packages:
return
- def _get_entry_points() -> Iterator[str]:
- """Python 3.7 compatibility shim for iterating entry points."""
- # Python 3.8+, or Python 3.7 with importlib_metadata installed.
- try:
- return _gen_importlib(packages)
- except ImportError as exc:
- logger.debug("%s", str(exc))
-
- # Python 3.7 with setuptools installed.
- try:
- return _gen_pkg_resources(packages)
- except ImportError as exc:
- logger.debug("%s", str(exc))
- raise Ouch(
- "Neither importlib.metadata nor pkg_resources found, "
- "can't generate console script shims.\n"
- "Use Python 3.8+, or install importlib-metadata or setuptools."
- ) from exc
-
maker = distlib.scripts.ScriptMaker(None, bin_path)
maker.variants = {""}
maker.clobber = False
- for entry_point in _get_entry_points():
+ for entry_point in _get_entry_points(packages):
for filename in maker.make(entry_point):
logger.debug("wrote console_script '%s'", filename)
-def checkpip() -> bool:
- """
- Debian10 has a pip that's broken when used inside of a virtual environment.
-
- We try to detect and correct that case here.
- """
- try:
- # pylint: disable=import-outside-toplevel,unused-import,import-error
- # pylint: disable=redefined-outer-name
- import pip._internal # type: ignore # noqa: F401
-
- logger.debug("pip appears to be working correctly.")
- return False
- except ModuleNotFoundError as exc:
- if exc.name == "pip._internal":
- # Uh, fair enough. They did say "internal".
- # Let's just assume it's fine.
- return False
- logger.warning("pip appears to be malfunctioning: %s", str(exc))
-
- check_ensurepip("pip appears to be non-functional, and ")
-
- logger.debug("Attempting to repair pip ...")
- subprocess.run(
- (sys.executable, "-m", "ensurepip"),
- stdout=subprocess.DEVNULL,
- check=True,
- )
- logger.debug("Pip is now (hopefully) repaired!")
- return True
-
-
def pkgname_from_depspec(dep_spec: str) -> str:
"""
Parse package name out of a PEP-508 depspec.
@@ -584,57 +498,6 @@ def pkgname_from_depspec(dep_spec: str) -> str:
return match.group(0)
-def _get_path_importlib(package: str) -> Optional[str]:
- # pylint: disable=import-outside-toplevel
- # pylint: disable=no-name-in-module
- # pylint: disable=import-error
- try:
- # First preference: Python 3.8+ stdlib
- from importlib.metadata import ( # type: ignore
- PackageNotFoundError,
- distribution,
- )
- except ImportError as exc:
- logger.debug("%s", str(exc))
- # Second preference: Commonly available PyPI backport
- from importlib_metadata import ( # type: ignore
- PackageNotFoundError,
- distribution,
- )
-
- try:
- return str(distribution(package).locate_file("."))
- except PackageNotFoundError:
- return None
-
-
-def _get_path_pkg_resources(package: str) -> Optional[str]:
- # pylint: disable=import-outside-toplevel
- # Bundled with setuptools; has a good chance of being available.
- import pkg_resources
-
- try:
- return str(pkg_resources.get_distribution(package).location)
- except pkg_resources.DistributionNotFound:
- return None
-
-
-def _get_path(package: str) -> Optional[str]:
- try:
- return _get_path_importlib(package)
- except ImportError as exc:
- logger.debug("%s", str(exc))
-
- try:
- return _get_path_pkg_resources(package)
- except ImportError as exc:
- logger.debug("%s", str(exc))
- raise Ouch(
- "Neither importlib.metadata nor pkg_resources found. "
- "Use Python 3.8+, or install importlib-metadata or setuptools."
- ) from exc
-
-
def _path_is_prefix(prefix: Optional[str], path: str) -> bool:
try:
return (
@@ -644,65 +507,14 @@ def _path_is_prefix(prefix: Optional[str], path: str) -> bool:
return False
-def _is_system_package(package: str) -> bool:
- path = _get_path(package)
- return path is not None and not (
+def _is_system_package(dist: Distribution) -> bool:
+ path = str(dist.locate_file("."))
+ return not (
_path_is_prefix(sysconfig.get_path("purelib"), path)
or _path_is_prefix(sysconfig.get_path("platlib"), path)
)
-def _get_version_importlib(package: str) -> Optional[str]:
- # pylint: disable=import-outside-toplevel
- # pylint: disable=no-name-in-module
- # pylint: disable=import-error
- try:
- # First preference: Python 3.8+ stdlib
- from importlib.metadata import ( # type: ignore
- PackageNotFoundError,
- distribution,
- )
- except ImportError as exc:
- logger.debug("%s", str(exc))
- # Second preference: Commonly available PyPI backport
- from importlib_metadata import ( # type: ignore
- PackageNotFoundError,
- distribution,
- )
-
- try:
- return str(distribution(package).version)
- except PackageNotFoundError:
- return None
-
-
-def _get_version_pkg_resources(package: str) -> Optional[str]:
- # pylint: disable=import-outside-toplevel
- # Bundled with setuptools; has a good chance of being available.
- import pkg_resources
-
- try:
- return str(pkg_resources.get_distribution(package).version)
- except pkg_resources.DistributionNotFound:
- return None
-
-
-def _get_version(package: str) -> Optional[str]:
- try:
- return _get_version_importlib(package)
- except ImportError as exc:
- logger.debug("%s", str(exc))
-
- try:
- return _get_version_pkg_resources(package)
- except ImportError as exc:
- logger.debug("%s", str(exc))
- raise Ouch(
- "Neither importlib.metadata nor pkg_resources found. "
- "Use Python 3.8+, or install importlib-metadata or setuptools."
- ) from exc
-
-
def diagnose(
dep_spec: str,
online: bool,
@@ -728,7 +540,11 @@ def diagnose(
bad = False
pkg_name = pkgname_from_depspec(dep_spec)
- pkg_version = _get_version(pkg_name)
+ pkg_version: Optional[str] = None
+ try:
+ pkg_version = version(pkg_name)
+ except PackageNotFoundError:
+ pass
lines = []
@@ -865,19 +681,25 @@ def _do_ensure(
constraint = _make_version_constraint(info, False)
matcher = distlib.version.LegacyMatcher(name + constraint)
print(f"mkvenv: checking for {matcher}", file=sys.stderr)
- ver = _get_version(name)
+
+ dist: Optional[Distribution] = None
+ try:
+ dist = distribution(matcher.name)
+ except PackageNotFoundError:
+ pass
+
if (
- ver is None
+ dist is None
# Always pass installed package to pip, so that they can be
# updated if the requested version changes
- or not _is_system_package(name)
- or not matcher.match(distlib.version.LegacyVersion(ver))
+ or not _is_system_package(dist)
+ or not matcher.match(distlib.version.LegacyVersion(dist.version))
):
absent.append(name + _make_version_constraint(info, True))
if len(absent) == 1:
canary = info.get("canary", None)
else:
- logger.info("found %s %s", name, ver)
+ logger.info("found %s %s", name, dist.version)
present.append(name)
if present:
@@ -1015,12 +837,10 @@ def post_venv_setup() -> None:
This is intended to be run *inside the venv* after it is created.
"""
logger.debug("post_venv_setup()")
- # Test for a broken pip (Debian 10 or derivative?) and fix it if needed
- if not checkpip():
- # Finally, generate a 'pip' script so the venv is usable in a normal
- # way from the CLI. This only happens when we inherited pip from a
- # parent/system-site and haven't run ensurepip in some way.
- generate_console_scripts(["pip"])
+ # Generate a 'pip' script so the venv is usable in a normal
+ # way from the CLI. This only happens when we inherited pip from a
+ # parent/system-site and haven't run ensurepip in some way.
+ generate_console_scripts(["pip"])
def _add_create_subcommand(subparsers: Any) -> None: