@@ -11,6 +11,7 @@
Commands:
command Description
create create a venv
+ ensure Ensure that the specified package is installed.
--------------------------------------------------
@@ -22,6 +23,18 @@
options:
-h, --help show this help message and exit
+--------------------------------------------------
+
+usage: mkvenv ensure [-h] [--online] [--dir DIR] dep_spec...
+
+positional arguments:
+ dep_spec PEP 508 Dependency specification, e.g. 'meson>=0.61.5'
+
+options:
+ -h, --help show this help message and exit
+ --online Install packages from PyPI, if necessary.
+ --dir DIR Path to vendored packages where we may install from.
+
"""
# Copyright (C) 2022-2023 Red Hat, Inc.
@@ -43,7 +56,12 @@
import sys
import sysconfig
from types import SimpleNamespace
-from typing import Any, Optional, Union
+from typing import (
+ Any,
+ Optional,
+ Sequence,
+ Union,
+)
import venv
@@ -333,6 +351,85 @@ def _stringify(data: Union[str, bytes]) -> str:
print(builder.get_value("env_exe"))
+def pip_install(
+ args: Sequence[str],
+ online: bool = False,
+ wheels_dir: Optional[Union[str, Path]] = None,
+ devnull: bool = False,
+) -> None:
+ """
+ Use pip to install a package or package(s) as specified in @args.
+ """
+ loud = bool(
+ os.environ.get("DEBUG")
+ or os.environ.get("GITLAB_CI")
+ or os.environ.get("V")
+ )
+
+ full_args = [
+ sys.executable,
+ "-m",
+ "pip",
+ "install",
+ "--disable-pip-version-check",
+ "-v" if loud else "-q",
+ ]
+ if not online:
+ full_args += ["--no-index"]
+ if wheels_dir:
+ full_args += ["--find-links", f"file://{str(wheels_dir)}"]
+ full_args += list(args)
+ subprocess.run(
+ full_args,
+ check=True,
+ stdout=subprocess.DEVNULL if devnull else None,
+ stderr=subprocess.DEVNULL if devnull else None,
+ )
+
+
+def ensure(
+ dep_specs: Sequence[str],
+ online: bool = False,
+ wheels_dir: Optional[Union[str, Path]] = 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.
+ """
+ # This first install command will:
+ # (A) Do nothing, if we already have a suitable package.
+ # (B) Install the package from vendored source, if possible.
+ # (C) Fail if neither A nor B.
+ try:
+ pip_install(
+ dep_specs,
+ online=False,
+ wheels_dir=wheels_dir,
+ devnull=online and not wheels_dir,
+ )
+ # (A) or (B) happened. Success.
+ return
+ except subprocess.CalledProcessError:
+ # (C) Happened.
+ # The package is missing or isn't a suitable version,
+ # and we weren't able to install a suitable vendored package.
+ if online:
+ print(
+ f"mkvenv: installing {', '.join(dep_specs)}", file=sys.stderr
+ )
+ pip_install(dep_specs, online=True)
+ else:
+ raise
+
+
def _add_create_subcommand(subparsers: Any) -> None:
subparser = subparsers.add_parser("create", help="create a venv")
subparser.add_argument(
@@ -343,6 +440,30 @@ def _add_create_subcommand(subparsers: Any) -> None:
)
+def _add_ensure_subcommand(subparsers: Any) -> None:
+ subparser = subparsers.add_parser(
+ "ensure", help="Ensure that the specified package is installed."
+ )
+ subparser.add_argument(
+ "--online",
+ action="store_true",
+ help="Install packages from PyPI, if necessary.",
+ )
+ subparser.add_argument(
+ "--dir",
+ type=str,
+ action="store",
+ help="Path to vendored packages where we may install from.",
+ )
+ subparser.add_argument(
+ "dep_specs",
+ type=str,
+ action="store",
+ help="PEP 508 Dependency specification, e.g. 'meson>=0.61.5'",
+ nargs="+",
+ )
+
+
def main() -> int:
"""CLI interface to make_qemu_venv. See module docstring."""
if os.environ.get("DEBUG") or os.environ.get("GITLAB_CI"):
@@ -363,6 +484,7 @@ def main() -> int:
)
_add_create_subcommand(subparsers)
+ _add_ensure_subcommand(subparsers)
args = parser.parse_args()
try:
@@ -372,6 +494,12 @@ def main() -> int:
system_site_packages=True,
clear=True,
)
+ if args.command == "ensure":
+ ensure(
+ dep_specs=args.dep_specs,
+ online=args.online,
+ wheels_dir=args.dir,
+ )
logger.debug("mkvenv.py %s: exiting", args.command)
except Ouch as exc:
print("\n*** Ouch! ***\n", file=sys.stderr)