@samitouri / QOSamiQemu / commits / b42ad8ef32

python/mkvenv: add mechanism to install local package(s)

Currently, we "implicitly" install the local 'qemu' python package for 'make check-venv' with some logic inside tests/Makefile.include. I would like to make this installation explicit in pythondeps.toml instead. This patch adds a path constraint that can be used in lieu of version constraints to specify that a package should be installed from the source tree instead of from PyPI or vendored packages. This is done to allow us to install the python packages hosted inside of the tree while also processing dependencies; i.e. so that our "qemu" package can specify that it needs "qemu.qmp", which soon will not be included in qemu.git. This also has the benefit of being able to specify in a declarative configuration file that our pyvenv environment *will* have our local python packages installed and available without any PYTHONPATH hacks, which should simplify iotests, device-crash-test and functional tests without needing to manage local inclusion paths in environment variables. On the downsides, installing packages through mkvenv/ensuregroup means that there are extra steps we need to take in order to install a local package *offline*; namely we must disable build isolation (so we have access to setuptools) and we must also include python3-wheel in QEMU's build dependencies in order for "make check" to run successfully when in an offline, isolated environment. These extra dependencies are handled in a forthcoming commit; for now, nothing is utilizing this new pathway. Reviewed-by: Thomas Huth <thuth@redhat.com> Message-ID: <20260218213416.674483-6-jsnow@redhat.com> Signed-off-by: John Snow <jsnow@redhat.com>

John Snow committed Feb 18, 2026 at 16:34 UTC b42ad8ef321da4e0e9bb3c0837ca6bd289d60feb
1 file changed +31 -6
python/scripts/mkvenv.py
+31 -6
@@ -662,6 +662,7 @@ def pip_install(
662 args: Sequence[str],
663 online: bool = False,
664 wheels_dir: Optional[Union[str, Path]] = None,
665 + env: Optional[Dict[str, str]] = None,
666 ) -> None:
667 """
668 Use pip to install a package or package(s) as specified in @args.
@@ -687,6 +688,7 @@ def pip_install(
688 full_args += list(args)
689 subprocess.run(
690 full_args,
691 + env=env,
692 check=True,
693 )
694
@@ -733,9 +735,14 @@ def _do_ensure(
735 :param wheels_dir: If specified, search this path for packages.
736 """
737 absent = []
738 + local_packages = []
739 present = []
740 canary = None
741 for name, info in group.items():
742 + if "path" in info:
743 + pkgpath = Path(__file__).parents[2].joinpath(info["path"])
744 + local_packages.append(str(pkgpath))
745 + continue
746 constraint = _make_version_constraint(info, False)
747 matcher = Matcher(name + constraint)
748 print(f"mkvenv: checking for {matcher}", file=sys.stderr)
@@ -770,15 +777,33 @@ def _do_ensure(
777 print(f"mkvenv: installing {', '.join(absent)}", file=sys.stderr)
778 try:
779 pip_install(args=absent, online=online, wheels_dir=wheels_dir)
773 - return None
780 + absent = []
781 except subprocess.CalledProcessError:
782 pass
783
777 - return diagnose(
778 - absent[0],
779 - online,
780 - wheels_dir,
781 - canary,
784 + if absent:
785 + return diagnose(
786 + absent[0],
787 + online,
788 + wheels_dir,
789 + canary,
790 + )
791 +
792 + # Handle local packages separately and last so we can use different
793 + # installation arguments (-e), and so that any dependencies that may
794 + # be covered above will be handled according to the depfile
795 + # specifications.
796 + if local_packages:
797 + print(f"mkvenv: installing {', '.join(local_packages)}",
798 + file=sys.stderr)
799 + env = dict(os.environ)
800 + env['PIP_CONFIG_SETTINGS'] = "editable_mode=compat"
801 + pip_install(
802 + args=["--no-build-isolation",
803 + "-e"] + local_packages,
804 + online=online,
805 + wheels_dir=wheels_dir,
806 + env=env,
807 )
808
809 return None