From: John Snow <jsnow@redhat.com>
To: Paolo Bonzini <pbonzini@redhat.com>
Cc: qemu-devel@nongnu.org, philmd@linaro.org, berrange@redhat.com
Subject: Re: [PATCH v2 07/27] mkvenv: add --diagnose option to explain "ensure" failures
Date: Tue, 16 May 2023 14:27:01 -0400 [thread overview]
Message-ID: <CAFn=p-be3oyTDFszUH24dOGw2EqbfRyTPxxvq+v3h=nGZVEMKA@mail.gmail.com> (raw)
In-Reply-To: <20230516105908.527838-7-pbonzini@redhat.com>
On Tue, May 16, 2023 at 6:59 AM Paolo Bonzini <pbonzini@redhat.com> wrote:
>
> From: John Snow <jsnow@redhat.com>
>
> This is a routine that is designed to print some usable info for human
> beings back out to the terminal if/when "mkvenv ensure" fails to locate
> or install a package during configure time, such as meson or sphinx.
>
> Since we are requiring that "meson" and "sphinx" are installed to the
> same Python environment as QEMU is configured to build with, this can
> produce some surprising failures when things are mismatched. This method
> is here to try and ease that sting by offering some actionable
> diagnosis.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> Message-Id: <20230511035435.734312-8-jsnow@redhat.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> python/scripts/mkvenv.py | 170 ++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 169 insertions(+), 1 deletion(-)
>
> diff --git a/python/scripts/mkvenv.py b/python/scripts/mkvenv.py
> index fd4b62c70ffa..5ac22f584fab 100644
> --- a/python/scripts/mkvenv.py
> +++ b/python/scripts/mkvenv.py
> @@ -51,6 +51,8 @@
> import logging
> import os
> from pathlib import Path
> +import re
> +import shutil
> import site
> import subprocess
> import sys
> @@ -60,6 +62,7 @@
> Any,
> Optional,
> Sequence,
> + Tuple,
> Union,
> )
> import venv
> @@ -331,6 +334,128 @@ def _stringify(data: Union[str, bytes]) -> str:
> print(builder.get_value("env_exe"))
>
>
> +def pkgname_from_depspec(dep_spec: str) -> str:
> + """
> + Parse package name out of a PEP-508 depspec.
> +
> + See https://peps.python.org/pep-0508/#names
> + """
> + match = re.match(
> + r"^([A-Z0-9]([A-Z0-9._-]*[A-Z0-9])?)", dep_spec, re.IGNORECASE
> + )
> + if not match:
> + raise ValueError(
> + f"dep_spec '{dep_spec}'"
> + " does not appear to contain a valid package name"
> + )
> + return match.group(0)
> +
> +
> +def diagnose(
> + dep_spec: str,
> + online: bool,
> + wheels_dir: Optional[Union[str, Path]],
> + prog: Optional[str],
> +) -> Tuple[str, bool]:
> + """
> + Offer a summary to the user as to why a package failed to be installed.
> +
> + :param dep_spec: The package we tried to ensure, e.g. 'meson>=0.61.5'
> + :param online: Did we allow PyPI access?
> + :param prog:
> + Optionally, a shell program name that can be used as a
> + bellwether to detect if this program is installed elsewhere on
> + the system. This is used to offer advice when a program is
> + detected for a different python version.
> + :param wheels_dir:
> + Optionally, a directory that was searched for vendored packages.
> + """
> + # pylint: disable=too-many-branches
> +
> + # Some errors are not particularly serious
> + bad = False
> +
> + pkg_name = pkgname_from_depspec(dep_spec)
> + pkg_version = None
> +
> + has_importlib = False
> + try:
> + # Python 3.8+ stdlib
> + # pylint: disable=import-outside-toplevel
> + # pylint: disable=no-name-in-module
> + # pylint: disable=import-error
> + from importlib.metadata import ( # type: ignore
> + PackageNotFoundError,
> + version,
> + )
> +
> + has_importlib = True
> + try:
> + pkg_version = version(pkg_name)
> + except PackageNotFoundError:
> + pass
> + except ModuleNotFoundError:
> + pass
> +
> + lines = []
> +
> + if pkg_version:
> + lines.append(
> + f"Python package '{pkg_name}' version '{pkg_version}' was found,"
> + " but isn't suitable."
> + )
> + elif has_importlib:
> + lines.append(
> + f"Python package '{pkg_name}' was not found nor installed."
> + )
> + else:
> + lines.append(
> + f"Python package '{pkg_name}' is either not found or"
> + " not a suitable version."
> + )
> +
> + if wheels_dir:
> + lines.append(
> + "No suitable version found in, or failed to install from"
> + f" '{wheels_dir}'."
> + )
> + bad = True
> +
> + if online:
> + lines.append("A suitable version could not be obtained from PyPI.")
> + bad = True
> + else:
> + lines.append(
> + "mkvenv was configured to operate offline and did not check PyPI."
> + )
> +
> + if prog and not pkg_version:
> + which = shutil.which(prog)
> + if which:
> + if sys.base_prefix in site.PREFIXES:
> + pypath = Path(sys.executable).resolve()
> + lines.append(
> + f"'{prog}' was detected on your system at '{which}', "
> + f"but the Python package '{pkg_name}' was not found by "
> + f"this Python interpreter ('{pypath}'). "
> + f"Typically this means that '{prog}' has been installed "
> + "against a different Python interpreter on your system."
> + )
> + else:
> + lines.append(
> + f"'{prog}' was detected on your system at '{which}', "
> + "but the build is using an isolated virtual environment."
> + )
> + bad = True
> +
> + lines = [f" • {line}" for line in lines]
> + if bad:
> + lines.insert(0, f"Could not provide build dependency '{dep_spec}':")
> + else:
> + lines.insert(0, f"'{dep_spec}' not found:")
> + return os.linesep.join(lines), bad
> +
> +
> def pip_install(
> args: Sequence[str],
> online: bool = False,
> @@ -364,7 +489,7 @@ def pip_install(
> )
>
>
> -def ensure(
> +def _do_ensure(
> dep_specs: Sequence[str],
> online: bool = False,
> wheels_dir: Optional[Union[str, Path]] = None,
> @@ -402,6 +527,39 @@ def ensure(
> pip_install(args=absent, online=online, wheels_dir=wheels_dir)
>
>
> +def ensure(
> + dep_specs: Sequence[str],
> + online: bool = False,
> + wheels_dir: Optional[Union[str, Path]] = None,
> + prog: Optional[str] = None,
> +) -> None:
> + """
> + Use pip to ensure we have the package specified by @dep_specs.
> +
> + If the package is already installed, do nothing. If online and
> + wheels_dir are both provided, prefer packages found in wheels_dir
> + first before connecting to PyPI.
> +
> + :param dep_specs:
> + PEP 508 dependency specifications. e.g. ['meson>=0.61.5'].
> + :param online: If True, fall back to PyPI.
> + :param wheels_dir: If specified, search this path for packages.
> + :param prog:
> + If specified, use this program name for error diagnostics that will
> + be presented to the user. e.g., 'sphinx-build' can be used as a
> + bellwether for the presence of 'sphinx'.
> + """
> + print(f"mkvenv: checking for {', '.join(dep_specs)}", file=sys.stderr)
> + try:
> + _do_ensure(dep_specs, online, wheels_dir)
> + except subprocess.CalledProcessError as exc:
> + # Well, that's not good.
> + msg, bad = diagnose(dep_specs[0], online, wheels_dir, prog)
> + if bad:
> + raise Ouch(msg) from exc
> + raise SystemExit(f"\n{msg}\n\n") from exc
> +
> +
> def _add_create_subcommand(subparsers: Any) -> None:
> subparser = subparsers.add_parser("create", help="create a venv")
> subparser.add_argument(
> @@ -427,6 +585,15 @@ def _add_ensure_subcommand(subparsers: Any) -> None:
> action="store",
> help="Path to vendored packages where we may install from.",
> )
> + subparser.add_argument(
> + "--diagnose",
> + type=str,
> + action="store",
> + help=(
> + "Name of a shell utility to use for "
> + "diagnostics if this command fails."
> + ),
> + )
> subparser.add_argument(
> "dep_specs",
> type=str,
> @@ -476,6 +643,7 @@ def main() -> int:
> dep_specs=args.dep_specs,
> online=args.online,
> wheels_dir=args.dir,
> + prog=args.diagnose,
> )
> logger.debug("mkvenv.py %s: exiting", args.command)
> except Ouch as exc:
> --
> 2.40.1
>
>
ACK, seems good.
(What a lot of work for an error diagnose function that I hope nobody
ever sees, haha!)
--js
next prev parent reply other threads:[~2023-05-16 18:28 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-05-16 10:58 [PATCH v2 01/27] python: shut up "pip install" during "make check-minreqs" Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 02/27] python: update pylint configuration Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 03/27] python: add mkvenv.py Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 04/27] mkvenv: add better error message for broken or missing ensurepip Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 05/27] mkvenv: add nested venv workaround Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 06/27] mkvenv: add ensure subcommand Paolo Bonzini
2023-05-25 20:42 ` John Snow
2023-05-16 10:58 ` [PATCH v2 07/27] mkvenv: add --diagnose option to explain "ensure" failures Paolo Bonzini
2023-05-16 18:27 ` John Snow [this message]
2023-05-16 10:58 ` [PATCH v2 08/27] mkvenv: add console script entry point generation Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 09/27] mkvenv: use pip's vendored distlib as a fallback Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 10/27] mkvenv: avoid ensurepip if pip is installed Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 11/27] mkvenv: work around broken pip installations on Debian 10 Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 12/27] tests/docker: add python3-venv dependency Paolo Bonzini
2023-05-26 9:17 ` Philippe Mathieu-Daudé
2023-05-16 10:58 ` [PATCH v2 13/27] tests/vm: Configure netbsd to use Python 3.10 Paolo Bonzini
2023-05-26 9:15 ` Philippe Mathieu-Daudé
2023-05-16 10:58 ` [PATCH v2 14/27] tests/vm: add py310-expat to NetBSD Paolo Bonzini
2023-05-26 9:15 ` Philippe Mathieu-Daudé
2023-05-16 10:58 ` [PATCH v2 15/27] python: add vendor.py utility Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 16/27] configure: create a python venv unconditionally Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 17/27] python/wheels: add vendored meson package Paolo Bonzini
2023-05-16 10:58 ` [PATCH v2 18/27] configure: use 'mkvenv ensure meson' to bootstrap meson Paolo Bonzini
2023-05-16 10:59 ` [PATCH v2 19/27] qemu.git: drop meson git submodule Paolo Bonzini
2023-05-16 10:59 ` [PATCH v2 20/27] tests: Use configure-provided pyvenv for tests Paolo Bonzini
2023-05-16 10:59 ` [PATCH v2 21/27] configure: move --enable-docs and --disable-docs back to configure Paolo Bonzini
2023-05-16 10:59 ` [PATCH v2 22/27] configure: bootstrap sphinx with mkvenv Paolo Bonzini
2023-05-16 10:59 ` [PATCH v2 23/27] configure: add --enable-pypi and --disable-pypi Paolo Bonzini
2023-05-16 10:59 ` [PATCH v2 24/27] Python: Drop support for Python 3.6 Paolo Bonzini
2023-05-16 10:59 ` [PATCH v2 25/27] configure: Add courtesy hint to Python version failure message Paolo Bonzini
2023-05-16 10:59 ` [PATCH v2 26/27] mkvenv: mark command as required Paolo Bonzini
2023-05-16 10:59 ` [PATCH v2 27/27] python: bump some of the dependencies Paolo Bonzini
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CAFn=p-be3oyTDFszUH24dOGw2EqbfRyTPxxvq+v3h=nGZVEMKA@mail.gmail.com' \
--to=jsnow@redhat.com \
--cc=berrange@redhat.com \
--cc=pbonzini@redhat.com \
--cc=philmd@linaro.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).