diff --git a/scripts/build_wheel.py b/scripts/build_wheel.py index e546b2e12ca8..edde50d81a1f 100755 --- a/scripts/build_wheel.py +++ b/scripts/build_wheel.py @@ -768,6 +768,17 @@ def stage_python_package(project_dir: Path, staging_dir: Path) -> None: copy(src, staging_dir / name) +def install_editable_package(venv_python: Path) -> None: + """Editable-install the built package into the venv `setup_venv` created. + + Not `sys.executable`: a fresh checkout has to start this script with the + system interpreter, so installing with it puts the package in the system + site-packages and leaves the new venv without it. The wheel build above + already uses `venv_python` for the same reason. + """ + build_run(f"\"{venv_python}\" -m pip install -e .[devel]") + + def main(*, build_type: str = "Release", generator: str = "", @@ -1523,7 +1534,7 @@ def get_binding_lib(subdirectory, name): env=env) if install: - build_run(f"\"{sys.executable}\" -m pip install -e .[devel]") + install_editable_package(venv_python) def add_arguments(parser: ArgumentParser): diff --git a/tests/unittest/others/test_build_wheel_install_interpreter.py b/tests/unittest/others/test_build_wheel_install_interpreter.py new file mode 100644 index 000000000000..249d1bf2fa4e --- /dev/null +++ b/tests/unittest/others/test_build_wheel_install_interpreter.py @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +"""``build_wheel.py --install`` must use the venv interpreter. + +A fresh checkout has to start ``build_wheel.py`` with the system interpreter, +so installing with ``sys.executable`` puts the package into the system +site-packages and leaves the venv that ``setup_venv()`` just created without +it. The install has to run with ``venv_python``. +""" + +import importlib.util +import sys +from pathlib import Path + +import pytest + +_BUILD_WHEEL = Path(__file__).resolve().parents[3] / "scripts" / "build_wheel.py" + + +@pytest.fixture(scope="module") +def build_wheel(): + """``scripts/build_wheel.py`` loaded by path. + + It is a script rather than a package member. Importing it runs only its + imports and constants; ``main()`` is behind the usual ``__main__`` guard. + """ + assert _BUILD_WHEEL.is_file(), _BUILD_WHEEL + spec = importlib.util.spec_from_file_location("_build_wheel_under_test", _BUILD_WHEEL) + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + yield module + sys.modules.pop(spec.name, None) + + +def test_install_runs_with_the_venv_interpreter(build_wheel, monkeypatch, tmp_path): + commands = [] + monkeypatch.setattr(build_wheel, "build_run", lambda cmd, **kwargs: commands.append(cmd)) + + venv_python = tmp_path / "venv" / "bin" / "python" + build_wheel.install_editable_package(venv_python) + + assert len(commands) == 1 + command = commands[0] + assert command.startswith(f'"{venv_python}" -m pip install -e ') + assert str(venv_python) in command + assert sys.executable not in command