1dd84028fSJohn Snow""" 2dd84028fSJohn Snowmkvenv - QEMU pyvenv bootstrapping utility 3dd84028fSJohn Snow 4dd84028fSJohn Snowusage: mkvenv [-h] command ... 5dd84028fSJohn Snow 6dd84028fSJohn SnowQEMU pyvenv bootstrapping utility 7dd84028fSJohn Snow 8dd84028fSJohn Snowoptions: 9dd84028fSJohn Snow -h, --help show this help message and exit 10dd84028fSJohn Snow 11dd84028fSJohn SnowCommands: 12dd84028fSJohn Snow command Description 13dd84028fSJohn Snow create create a venv 14*c5538eedSJohn Snow ensure Ensure that the specified package is installed. 15dd84028fSJohn Snow 16dd84028fSJohn Snow-------------------------------------------------- 17dd84028fSJohn Snow 18dd84028fSJohn Snowusage: mkvenv create [-h] target 19dd84028fSJohn Snow 20dd84028fSJohn Snowpositional arguments: 21dd84028fSJohn Snow target Target directory to install virtual environment into. 22dd84028fSJohn Snow 23dd84028fSJohn Snowoptions: 24dd84028fSJohn Snow -h, --help show this help message and exit 25dd84028fSJohn Snow 26*c5538eedSJohn Snow-------------------------------------------------- 27*c5538eedSJohn Snow 28*c5538eedSJohn Snowusage: mkvenv ensure [-h] [--online] [--dir DIR] dep_spec... 29*c5538eedSJohn Snow 30*c5538eedSJohn Snowpositional arguments: 31*c5538eedSJohn Snow dep_spec PEP 508 Dependency specification, e.g. 'meson>=0.61.5' 32*c5538eedSJohn Snow 33*c5538eedSJohn Snowoptions: 34*c5538eedSJohn Snow -h, --help show this help message and exit 35*c5538eedSJohn Snow --online Install packages from PyPI, if necessary. 36*c5538eedSJohn Snow --dir DIR Path to vendored packages where we may install from. 37*c5538eedSJohn Snow 38dd84028fSJohn Snow""" 39dd84028fSJohn Snow 40dd84028fSJohn Snow# Copyright (C) 2022-2023 Red Hat, Inc. 41dd84028fSJohn Snow# 42dd84028fSJohn Snow# Authors: 43dd84028fSJohn Snow# John Snow <jsnow@redhat.com> 44dd84028fSJohn Snow# Paolo Bonzini <pbonzini@redhat.com> 45dd84028fSJohn Snow# 46dd84028fSJohn Snow# This work is licensed under the terms of the GNU GPL, version 2 or 47dd84028fSJohn Snow# later. See the COPYING file in the top-level directory. 48dd84028fSJohn Snow 49dd84028fSJohn Snowimport argparse 50a9dbde71SJohn Snowfrom importlib.util import find_spec 51dd84028fSJohn Snowimport logging 52dd84028fSJohn Snowimport os 53dd84028fSJohn Snowfrom pathlib import Path 54dee01b82SJohn Snowimport site 55dd84028fSJohn Snowimport subprocess 56dd84028fSJohn Snowimport sys 57dee01b82SJohn Snowimport sysconfig 58dd84028fSJohn Snowfrom types import SimpleNamespace 59*c5538eedSJohn Snowfrom typing import ( 60*c5538eedSJohn Snow Any, 61*c5538eedSJohn Snow Optional, 62*c5538eedSJohn Snow Sequence, 63*c5538eedSJohn Snow Union, 64*c5538eedSJohn Snow) 65dd84028fSJohn Snowimport venv 66*c5538eedSJohn Snowimport warnings 67*c5538eedSJohn Snow 68*c5538eedSJohn Snowimport distlib.database 69*c5538eedSJohn Snowimport distlib.version 70dd84028fSJohn Snow 71dd84028fSJohn Snow 72dd84028fSJohn Snow# Do not add any mandatory dependencies from outside the stdlib: 73dd84028fSJohn Snow# This script *must* be usable standalone! 74dd84028fSJohn Snow 75dd84028fSJohn SnowDirType = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"] 76dd84028fSJohn Snowlogger = logging.getLogger("mkvenv") 77dd84028fSJohn Snow 78dd84028fSJohn Snow 79dee01b82SJohn Snowdef inside_a_venv() -> bool: 80dee01b82SJohn Snow """Returns True if it is executed inside of a virtual environment.""" 81dee01b82SJohn Snow return sys.prefix != sys.base_prefix 82dee01b82SJohn Snow 83dee01b82SJohn Snow 84dd84028fSJohn Snowclass Ouch(RuntimeError): 85dd84028fSJohn Snow """An Exception class we can't confuse with a builtin.""" 86dd84028fSJohn Snow 87dd84028fSJohn Snow 88dd84028fSJohn Snowclass QemuEnvBuilder(venv.EnvBuilder): 89dd84028fSJohn Snow """ 90dd84028fSJohn Snow An extension of venv.EnvBuilder for building QEMU's configure-time venv. 91dd84028fSJohn Snow 92dee01b82SJohn Snow The primary difference is that it emulates a "nested" virtual 93dee01b82SJohn Snow environment when invoked from inside of an existing virtual 94dee01b82SJohn Snow environment by including packages from the parent. 95dd84028fSJohn Snow 96dd84028fSJohn Snow Parameters for base class init: 97dd84028fSJohn Snow - system_site_packages: bool = False 98dd84028fSJohn Snow - clear: bool = False 99dd84028fSJohn Snow - symlinks: bool = False 100dd84028fSJohn Snow - upgrade: bool = False 101dd84028fSJohn Snow - with_pip: bool = False 102dd84028fSJohn Snow - prompt: Optional[str] = None 103dd84028fSJohn Snow - upgrade_deps: bool = False (Since 3.9) 104dd84028fSJohn Snow """ 105dd84028fSJohn Snow 106dd84028fSJohn Snow def __init__(self, *args: Any, **kwargs: Any) -> None: 107dd84028fSJohn Snow logger.debug("QemuEnvBuilder.__init__(...)") 108a9dbde71SJohn Snow 109dee01b82SJohn Snow # For nested venv emulation: 110dee01b82SJohn Snow self.use_parent_packages = False 111dee01b82SJohn Snow if inside_a_venv(): 112dee01b82SJohn Snow # Include parent packages only if we're in a venv and 113dee01b82SJohn Snow # system_site_packages was True. 114dee01b82SJohn Snow self.use_parent_packages = kwargs.pop( 115dee01b82SJohn Snow "system_site_packages", False 116dee01b82SJohn Snow ) 117dee01b82SJohn Snow # Include system_site_packages only when the parent, 118dee01b82SJohn Snow # The venv we are currently in, also does so. 119dee01b82SJohn Snow kwargs["system_site_packages"] = sys.base_prefix in site.PREFIXES 120dee01b82SJohn Snow 121a9dbde71SJohn Snow if kwargs.get("with_pip", False): 122a9dbde71SJohn Snow check_ensurepip() 123a9dbde71SJohn Snow 124dd84028fSJohn Snow super().__init__(*args, **kwargs) 125dd84028fSJohn Snow 126dd84028fSJohn Snow # Make the context available post-creation: 127dd84028fSJohn Snow self._context: Optional[SimpleNamespace] = None 128dd84028fSJohn Snow 129dee01b82SJohn Snow def get_parent_libpath(self) -> Optional[str]: 130dee01b82SJohn Snow """Return the libpath of the parent venv, if applicable.""" 131dee01b82SJohn Snow if self.use_parent_packages: 132dee01b82SJohn Snow return sysconfig.get_path("purelib") 133dee01b82SJohn Snow return None 134dee01b82SJohn Snow 135dee01b82SJohn Snow @staticmethod 136dee01b82SJohn Snow def compute_venv_libpath(context: SimpleNamespace) -> str: 137dee01b82SJohn Snow """ 138dee01b82SJohn Snow Compatibility wrapper for context.lib_path for Python < 3.12 139dee01b82SJohn Snow """ 140dee01b82SJohn Snow # Python 3.12+, not strictly necessary because it's documented 141dee01b82SJohn Snow # to be the same as 3.10 code below: 142dee01b82SJohn Snow if sys.version_info >= (3, 12): 143dee01b82SJohn Snow return context.lib_path 144dee01b82SJohn Snow 145dee01b82SJohn Snow # Python 3.10+ 146dee01b82SJohn Snow if "venv" in sysconfig.get_scheme_names(): 147dee01b82SJohn Snow lib_path = sysconfig.get_path( 148dee01b82SJohn Snow "purelib", scheme="venv", vars={"base": context.env_dir} 149dee01b82SJohn Snow ) 150dee01b82SJohn Snow assert lib_path is not None 151dee01b82SJohn Snow return lib_path 152dee01b82SJohn Snow 153dee01b82SJohn Snow # For Python <= 3.9 we need to hardcode this. Fortunately the 154dee01b82SJohn Snow # code below was the same in Python 3.6-3.10, so there is only 155dee01b82SJohn Snow # one case. 156dee01b82SJohn Snow if sys.platform == "win32": 157dee01b82SJohn Snow return os.path.join(context.env_dir, "Lib", "site-packages") 158dee01b82SJohn Snow return os.path.join( 159dee01b82SJohn Snow context.env_dir, 160dee01b82SJohn Snow "lib", 161dee01b82SJohn Snow "python%d.%d" % sys.version_info[:2], 162dee01b82SJohn Snow "site-packages", 163dee01b82SJohn Snow ) 164dee01b82SJohn Snow 165dd84028fSJohn Snow def ensure_directories(self, env_dir: DirType) -> SimpleNamespace: 166dd84028fSJohn Snow logger.debug("ensure_directories(env_dir=%s)", env_dir) 167dd84028fSJohn Snow self._context = super().ensure_directories(env_dir) 168dd84028fSJohn Snow return self._context 169dd84028fSJohn Snow 170dee01b82SJohn Snow def create(self, env_dir: DirType) -> None: 171dee01b82SJohn Snow logger.debug("create(env_dir=%s)", env_dir) 172dee01b82SJohn Snow super().create(env_dir) 173dee01b82SJohn Snow assert self._context is not None 174dee01b82SJohn Snow self.post_post_setup(self._context) 175dee01b82SJohn Snow 176dee01b82SJohn Snow def post_post_setup(self, context: SimpleNamespace) -> None: 177dee01b82SJohn Snow """ 178dee01b82SJohn Snow The final, final hook. Enter the venv and run commands inside of it. 179dee01b82SJohn Snow """ 180dee01b82SJohn Snow if self.use_parent_packages: 181dee01b82SJohn Snow # We're inside of a venv and we want to include the parent 182dee01b82SJohn Snow # venv's packages. 183dee01b82SJohn Snow parent_libpath = self.get_parent_libpath() 184dee01b82SJohn Snow assert parent_libpath is not None 185dee01b82SJohn Snow logger.debug("parent_libpath: %s", parent_libpath) 186dee01b82SJohn Snow 187dee01b82SJohn Snow our_libpath = self.compute_venv_libpath(context) 188dee01b82SJohn Snow logger.debug("our_libpath: %s", our_libpath) 189dee01b82SJohn Snow 190dee01b82SJohn Snow pth_file = os.path.join(our_libpath, "nested.pth") 191dee01b82SJohn Snow with open(pth_file, "w", encoding="UTF-8") as file: 192dee01b82SJohn Snow file.write(parent_libpath + os.linesep) 193dee01b82SJohn Snow 194dd84028fSJohn Snow def get_value(self, field: str) -> str: 195dd84028fSJohn Snow """ 196dd84028fSJohn Snow Get a string value from the context namespace after a call to build. 197dd84028fSJohn Snow 198dd84028fSJohn Snow For valid field names, see: 199dd84028fSJohn Snow https://docs.python.org/3/library/venv.html#venv.EnvBuilder.ensure_directories 200dd84028fSJohn Snow """ 201dd84028fSJohn Snow ret = getattr(self._context, field) 202dd84028fSJohn Snow assert isinstance(ret, str) 203dd84028fSJohn Snow return ret 204dd84028fSJohn Snow 205dd84028fSJohn Snow 206a9dbde71SJohn Snowdef check_ensurepip() -> None: 207a9dbde71SJohn Snow """ 208a9dbde71SJohn Snow Check that we have ensurepip. 209a9dbde71SJohn Snow 210a9dbde71SJohn Snow Raise a fatal exception with a helpful hint if it isn't available. 211a9dbde71SJohn Snow """ 212a9dbde71SJohn Snow if not find_spec("ensurepip"): 213a9dbde71SJohn Snow msg = ( 214a9dbde71SJohn Snow "Python's ensurepip module is not found.\n" 215a9dbde71SJohn Snow "It's normally part of the Python standard library, " 216a9dbde71SJohn Snow "maybe your distribution packages it separately?\n" 217a9dbde71SJohn Snow "Either install ensurepip, or alleviate the need for it in the " 218a9dbde71SJohn Snow "first place by installing pip and setuptools for " 219a9dbde71SJohn Snow f"'{sys.executable}'.\n" 220a9dbde71SJohn Snow "(Hint: Debian puts ensurepip in its python3-venv package.)" 221a9dbde71SJohn Snow ) 222a9dbde71SJohn Snow raise Ouch(msg) 223a9dbde71SJohn Snow 224a9dbde71SJohn Snow # ensurepip uses pyexpat, which can also go missing on us: 225a9dbde71SJohn Snow if not find_spec("pyexpat"): 226a9dbde71SJohn Snow msg = ( 227a9dbde71SJohn Snow "Python's pyexpat module is not found.\n" 228a9dbde71SJohn Snow "It's normally part of the Python standard library, " 229a9dbde71SJohn Snow "maybe your distribution packages it separately?\n" 230a9dbde71SJohn Snow "Either install pyexpat, or alleviate the need for it in the " 231a9dbde71SJohn Snow "first place by installing pip and setuptools for " 232a9dbde71SJohn Snow f"'{sys.executable}'.\n\n" 233a9dbde71SJohn Snow "(Hint: NetBSD's pkgsrc debundles this to e.g. 'py310-expat'.)" 234a9dbde71SJohn Snow ) 235a9dbde71SJohn Snow raise Ouch(msg) 236a9dbde71SJohn Snow 237a9dbde71SJohn Snow 238dd84028fSJohn Snowdef make_venv( # pylint: disable=too-many-arguments 239dd84028fSJohn Snow env_dir: Union[str, Path], 240dd84028fSJohn Snow system_site_packages: bool = False, 241dd84028fSJohn Snow clear: bool = True, 242dd84028fSJohn Snow symlinks: Optional[bool] = None, 243dd84028fSJohn Snow with_pip: bool = True, 244dd84028fSJohn Snow) -> None: 245dd84028fSJohn Snow """ 246dd84028fSJohn Snow Create a venv using `QemuEnvBuilder`. 247dd84028fSJohn Snow 248dd84028fSJohn Snow This is analogous to the `venv.create` module-level convenience 249dd84028fSJohn Snow function that is part of the Python stdblib, except it uses 250dd84028fSJohn Snow `QemuEnvBuilder` instead. 251dd84028fSJohn Snow 252dd84028fSJohn Snow :param env_dir: The directory to create/install to. 253dd84028fSJohn Snow :param system_site_packages: 254dd84028fSJohn Snow Allow inheriting packages from the system installation. 255dd84028fSJohn Snow :param clear: When True, fully remove any prior venv and files. 256dd84028fSJohn Snow :param symlinks: 257dd84028fSJohn Snow Whether to use symlinks to the target interpreter or not. If 258dd84028fSJohn Snow left unspecified, it will use symlinks except on Windows to 259dd84028fSJohn Snow match behavior with the "venv" CLI tool. 260dd84028fSJohn Snow :param with_pip: 261dd84028fSJohn Snow Whether to install "pip" binaries or not. 262dd84028fSJohn Snow """ 263dd84028fSJohn Snow logger.debug( 264dd84028fSJohn Snow "%s: make_venv(env_dir=%s, system_site_packages=%s, " 265dd84028fSJohn Snow "clear=%s, symlinks=%s, with_pip=%s)", 266dd84028fSJohn Snow __file__, 267dd84028fSJohn Snow str(env_dir), 268dd84028fSJohn Snow system_site_packages, 269dd84028fSJohn Snow clear, 270dd84028fSJohn Snow symlinks, 271dd84028fSJohn Snow with_pip, 272dd84028fSJohn Snow ) 273dd84028fSJohn Snow 274dd84028fSJohn Snow if symlinks is None: 275dd84028fSJohn Snow # Default behavior of standard venv CLI 276dd84028fSJohn Snow symlinks = os.name != "nt" 277dd84028fSJohn Snow 278dd84028fSJohn Snow builder = QemuEnvBuilder( 279dd84028fSJohn Snow system_site_packages=system_site_packages, 280dd84028fSJohn Snow clear=clear, 281dd84028fSJohn Snow symlinks=symlinks, 282dd84028fSJohn Snow with_pip=with_pip, 283dd84028fSJohn Snow ) 284dd84028fSJohn Snow 285dd84028fSJohn Snow style = "non-isolated" if builder.system_site_packages else "isolated" 286dee01b82SJohn Snow nested = "" 287dee01b82SJohn Snow if builder.use_parent_packages: 288dee01b82SJohn Snow nested = f"(with packages from '{builder.get_parent_libpath()}') " 289dd84028fSJohn Snow print( 290dd84028fSJohn Snow f"mkvenv: Creating {style} virtual environment" 291dee01b82SJohn Snow f" {nested}at '{str(env_dir)}'", 292dd84028fSJohn Snow file=sys.stderr, 293dd84028fSJohn Snow ) 294dd84028fSJohn Snow 295dd84028fSJohn Snow try: 296dd84028fSJohn Snow logger.debug("Invoking builder.create()") 297dd84028fSJohn Snow try: 298dd84028fSJohn Snow builder.create(str(env_dir)) 299dd84028fSJohn Snow except SystemExit as exc: 300dd84028fSJohn Snow # Some versions of the venv module raise SystemExit; *nasty*! 301dd84028fSJohn Snow # We want the exception that prompted it. It might be a subprocess 302dd84028fSJohn Snow # error that has output we *really* want to see. 303dd84028fSJohn Snow logger.debug("Intercepted SystemExit from EnvBuilder.create()") 304dd84028fSJohn Snow raise exc.__cause__ or exc.__context__ or exc 305dd84028fSJohn Snow logger.debug("builder.create() finished") 306dd84028fSJohn Snow except subprocess.CalledProcessError as exc: 307dd84028fSJohn Snow logger.error("mkvenv subprocess failed:") 308dd84028fSJohn Snow logger.error("cmd: %s", exc.cmd) 309dd84028fSJohn Snow logger.error("returncode: %d", exc.returncode) 310dd84028fSJohn Snow 311dd84028fSJohn Snow def _stringify(data: Union[str, bytes]) -> str: 312dd84028fSJohn Snow if isinstance(data, bytes): 313dd84028fSJohn Snow return data.decode() 314dd84028fSJohn Snow return data 315dd84028fSJohn Snow 316dd84028fSJohn Snow lines = [] 317dd84028fSJohn Snow if exc.stdout: 318dd84028fSJohn Snow lines.append("========== stdout ==========") 319dd84028fSJohn Snow lines.append(_stringify(exc.stdout)) 320dd84028fSJohn Snow lines.append("============================") 321dd84028fSJohn Snow if exc.stderr: 322dd84028fSJohn Snow lines.append("========== stderr ==========") 323dd84028fSJohn Snow lines.append(_stringify(exc.stderr)) 324dd84028fSJohn Snow lines.append("============================") 325dd84028fSJohn Snow if lines: 326dd84028fSJohn Snow logger.error(os.linesep.join(lines)) 327dd84028fSJohn Snow 328dd84028fSJohn Snow raise Ouch("VENV creation subprocess failed.") from exc 329dd84028fSJohn Snow 330dd84028fSJohn Snow # print the python executable to stdout for configure. 331dd84028fSJohn Snow print(builder.get_value("env_exe")) 332dd84028fSJohn Snow 333dd84028fSJohn Snow 334*c5538eedSJohn Snowdef pip_install( 335*c5538eedSJohn Snow args: Sequence[str], 336*c5538eedSJohn Snow online: bool = False, 337*c5538eedSJohn Snow wheels_dir: Optional[Union[str, Path]] = None, 338*c5538eedSJohn Snow) -> None: 339*c5538eedSJohn Snow """ 340*c5538eedSJohn Snow Use pip to install a package or package(s) as specified in @args. 341*c5538eedSJohn Snow """ 342*c5538eedSJohn Snow loud = bool( 343*c5538eedSJohn Snow os.environ.get("DEBUG") 344*c5538eedSJohn Snow or os.environ.get("GITLAB_CI") 345*c5538eedSJohn Snow or os.environ.get("V") 346*c5538eedSJohn Snow ) 347*c5538eedSJohn Snow 348*c5538eedSJohn Snow full_args = [ 349*c5538eedSJohn Snow sys.executable, 350*c5538eedSJohn Snow "-m", 351*c5538eedSJohn Snow "pip", 352*c5538eedSJohn Snow "install", 353*c5538eedSJohn Snow "--disable-pip-version-check", 354*c5538eedSJohn Snow "-v" if loud else "-q", 355*c5538eedSJohn Snow ] 356*c5538eedSJohn Snow if not online: 357*c5538eedSJohn Snow full_args += ["--no-index"] 358*c5538eedSJohn Snow if wheels_dir: 359*c5538eedSJohn Snow full_args += ["--find-links", f"file://{str(wheels_dir)}"] 360*c5538eedSJohn Snow full_args += list(args) 361*c5538eedSJohn Snow subprocess.run( 362*c5538eedSJohn Snow full_args, 363*c5538eedSJohn Snow check=True, 364*c5538eedSJohn Snow ) 365*c5538eedSJohn Snow 366*c5538eedSJohn Snow 367*c5538eedSJohn Snowdef ensure( 368*c5538eedSJohn Snow dep_specs: Sequence[str], 369*c5538eedSJohn Snow online: bool = False, 370*c5538eedSJohn Snow wheels_dir: Optional[Union[str, Path]] = None, 371*c5538eedSJohn Snow) -> None: 372*c5538eedSJohn Snow """ 373*c5538eedSJohn Snow Use pip to ensure we have the package specified by @dep_specs. 374*c5538eedSJohn Snow 375*c5538eedSJohn Snow If the package is already installed, do nothing. If online and 376*c5538eedSJohn Snow wheels_dir are both provided, prefer packages found in wheels_dir 377*c5538eedSJohn Snow first before connecting to PyPI. 378*c5538eedSJohn Snow 379*c5538eedSJohn Snow :param dep_specs: 380*c5538eedSJohn Snow PEP 508 dependency specifications. e.g. ['meson>=0.61.5']. 381*c5538eedSJohn Snow :param online: If True, fall back to PyPI. 382*c5538eedSJohn Snow :param wheels_dir: If specified, search this path for packages. 383*c5538eedSJohn Snow """ 384*c5538eedSJohn Snow with warnings.catch_warnings(): 385*c5538eedSJohn Snow warnings.filterwarnings( 386*c5538eedSJohn Snow "ignore", category=UserWarning, module="distlib" 387*c5538eedSJohn Snow ) 388*c5538eedSJohn Snow dist_path = distlib.database.DistributionPath(include_egg=True) 389*c5538eedSJohn Snow absent = [] 390*c5538eedSJohn Snow for spec in dep_specs: 391*c5538eedSJohn Snow matcher = distlib.version.LegacyMatcher(spec) 392*c5538eedSJohn Snow dist = dist_path.get_distribution(matcher.name) 393*c5538eedSJohn Snow if dist is None or not matcher.match(dist.version): 394*c5538eedSJohn Snow absent.append(spec) 395*c5538eedSJohn Snow else: 396*c5538eedSJohn Snow logger.info("found %s", dist) 397*c5538eedSJohn Snow 398*c5538eedSJohn Snow if absent: 399*c5538eedSJohn Snow # Some packages are missing or aren't a suitable version, 400*c5538eedSJohn Snow # install a suitable (possibly vendored) package. 401*c5538eedSJohn Snow print(f"mkvenv: installing {', '.join(absent)}", file=sys.stderr) 402*c5538eedSJohn Snow pip_install(args=absent, online=online, wheels_dir=wheels_dir) 403*c5538eedSJohn Snow 404*c5538eedSJohn Snow 405dd84028fSJohn Snowdef _add_create_subcommand(subparsers: Any) -> None: 406dd84028fSJohn Snow subparser = subparsers.add_parser("create", help="create a venv") 407dd84028fSJohn Snow subparser.add_argument( 408dd84028fSJohn Snow "target", 409dd84028fSJohn Snow type=str, 410dd84028fSJohn Snow action="store", 411dd84028fSJohn Snow help="Target directory to install virtual environment into.", 412dd84028fSJohn Snow ) 413dd84028fSJohn Snow 414dd84028fSJohn Snow 415*c5538eedSJohn Snowdef _add_ensure_subcommand(subparsers: Any) -> None: 416*c5538eedSJohn Snow subparser = subparsers.add_parser( 417*c5538eedSJohn Snow "ensure", help="Ensure that the specified package is installed." 418*c5538eedSJohn Snow ) 419*c5538eedSJohn Snow subparser.add_argument( 420*c5538eedSJohn Snow "--online", 421*c5538eedSJohn Snow action="store_true", 422*c5538eedSJohn Snow help="Install packages from PyPI, if necessary.", 423*c5538eedSJohn Snow ) 424*c5538eedSJohn Snow subparser.add_argument( 425*c5538eedSJohn Snow "--dir", 426*c5538eedSJohn Snow type=str, 427*c5538eedSJohn Snow action="store", 428*c5538eedSJohn Snow help="Path to vendored packages where we may install from.", 429*c5538eedSJohn Snow ) 430*c5538eedSJohn Snow subparser.add_argument( 431*c5538eedSJohn Snow "dep_specs", 432*c5538eedSJohn Snow type=str, 433*c5538eedSJohn Snow action="store", 434*c5538eedSJohn Snow help="PEP 508 Dependency specification, e.g. 'meson>=0.61.5'", 435*c5538eedSJohn Snow nargs="+", 436*c5538eedSJohn Snow ) 437*c5538eedSJohn Snow 438*c5538eedSJohn Snow 439dd84028fSJohn Snowdef main() -> int: 440dd84028fSJohn Snow """CLI interface to make_qemu_venv. See module docstring.""" 441dd84028fSJohn Snow if os.environ.get("DEBUG") or os.environ.get("GITLAB_CI"): 442dd84028fSJohn Snow # You're welcome. 443dd84028fSJohn Snow logging.basicConfig(level=logging.DEBUG) 444*c5538eedSJohn Snow else: 445*c5538eedSJohn Snow if os.environ.get("V"): 446dd84028fSJohn Snow logging.basicConfig(level=logging.INFO) 447dd84028fSJohn Snow 448*c5538eedSJohn Snow # These are incredibly noisy even for V=1 449*c5538eedSJohn Snow logging.getLogger("distlib.metadata").addFilter(lambda record: False) 450*c5538eedSJohn Snow logging.getLogger("distlib.database").addFilter(lambda record: False) 451*c5538eedSJohn Snow 452dd84028fSJohn Snow parser = argparse.ArgumentParser( 453dd84028fSJohn Snow prog="mkvenv", 454dd84028fSJohn Snow description="QEMU pyvenv bootstrapping utility", 455dd84028fSJohn Snow ) 456dd84028fSJohn Snow subparsers = parser.add_subparsers( 457dd84028fSJohn Snow title="Commands", 458dd84028fSJohn Snow dest="command", 459dd84028fSJohn Snow metavar="command", 460dd84028fSJohn Snow help="Description", 461dd84028fSJohn Snow ) 462dd84028fSJohn Snow 463dd84028fSJohn Snow _add_create_subcommand(subparsers) 464*c5538eedSJohn Snow _add_ensure_subcommand(subparsers) 465dd84028fSJohn Snow 466dd84028fSJohn Snow args = parser.parse_args() 467dd84028fSJohn Snow try: 468dd84028fSJohn Snow if args.command == "create": 469dd84028fSJohn Snow make_venv( 470dd84028fSJohn Snow args.target, 471dd84028fSJohn Snow system_site_packages=True, 472dd84028fSJohn Snow clear=True, 473dd84028fSJohn Snow ) 474*c5538eedSJohn Snow if args.command == "ensure": 475*c5538eedSJohn Snow ensure( 476*c5538eedSJohn Snow dep_specs=args.dep_specs, 477*c5538eedSJohn Snow online=args.online, 478*c5538eedSJohn Snow wheels_dir=args.dir, 479*c5538eedSJohn Snow ) 480dd84028fSJohn Snow logger.debug("mkvenv.py %s: exiting", args.command) 481dd84028fSJohn Snow except Ouch as exc: 482dd84028fSJohn Snow print("\n*** Ouch! ***\n", file=sys.stderr) 483dd84028fSJohn Snow print(str(exc), "\n\n", file=sys.stderr) 484dd84028fSJohn Snow return 1 485dd84028fSJohn Snow except SystemExit: 486dd84028fSJohn Snow raise 487dd84028fSJohn Snow except: # pylint: disable=bare-except 488dd84028fSJohn Snow logger.exception("mkvenv did not complete successfully:") 489dd84028fSJohn Snow return 2 490dd84028fSJohn Snow return 0 491dd84028fSJohn Snow 492dd84028fSJohn Snow 493dd84028fSJohn Snowif __name__ == "__main__": 494dd84028fSJohn Snow sys.exit(main()) 495