Skip to content

back to Reference (Gold) summary

Reference (Gold): virtualenv

Pytest Summary for test tests

status count
passed 296
skipped 24
failed 12
total 332
collected 332

Failed pytests:

test_csh.py::test_csh[no_prompt]

test_csh.py::test_csh[no_prompt]
activation_tester_class = 
activation_tester = ._tester at 0x7f064db8b370>

    def test_csh(activation_tester_class, activation_tester):
        class Csh(activation_tester_class):
            def __init__(self, session) -> None:
                super().__init__(CShellActivator, session, "csh", "activate.csh", "csh")

            def print_prompt(self):
                # Original csh doesn't print the last newline,
                # breaking the test; hence the trailing echo.
                return "echo 'source \"$VIRTUAL_ENV/bin/activate.csh\"; echo $prompt' | csh -i ; echo"

>       activation_tester(Csh)

Csh        = .Csh'>
activation_tester = ._tester at 0x7f064db8b370>
activation_tester_class = 

/testbed/tests/unit/activation/test_csh.py:16: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f52d7b0>
        tester     = Csh(
version='# exit\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Csh'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_csh_no_prompt_0')
        version    = '# exit\n'
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate.csh')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/root', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['csh', '/tmp/pytest-of-root/pytest-0/test_csh_no_prompt_0/script.csh']
        line       = b'rehash'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f52d7b0>
        out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
        process    = 
        raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...s shell.\n# (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) #\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) # exit\nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        raw_       = b'/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ \xc3\xa8\xd1\x80\...x9a\x92\xe2\x99\x9e\xe4\xb8\xad\xe7\x89\x87-j) # exit\nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        script_content = b'# This file must be used with "source bin/activate.csh" *from csh*.\n# You cannot run it directly.\n# Created by Dav...ompt:q"\n        endif\n    endif\nendif\n\nunset env_name\nunset do_prompt\n\nalias pydoc python -m pydoc\n\nrehash\n'
        self       = Csh(
version='# exit\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_csh_no_prompt_0/script.csh')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_csh_no_prompt_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Csh(
version='# exit\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...s shell.\n# (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) #\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) # exit\nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_csh_no_prompt_0')

    def assert_output(self, out, raw, tmp_path):
        # pre-activation
        assert out[0], raw
        assert out[1] == "None", raw
>       assert out[2] == "None", raw
E       AssertionError: /testbed/.venv/bin/python3
E         None
E         testbed
E         /tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3
E         /tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         Warning: no access to tty (Bad file descriptor).
E         Thus no job control in this shell.
E         # (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) #
E         (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) # exit
E         wrote pydoc_test.html
E         /testbed/.venv/bin/python3
E         None
E         None
E         
E       assert 'testbed' == 'None'
E         
E         - None
E         + testbed

out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...s shell.\n# (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) #\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) # exit\nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
self       = Csh(
version='# exit\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_csh_no_prompt_0')

/testbed/tests/unit/activation/conftest.py:137: AssertionError

test_nushell.py::test_nushell[no_prompt]

test_nushell.py::test_nushell[no_prompt]
activation_tester_class = 
activation_tester = ._tester at 0x7f064db8be20>

    def test_nushell(activation_tester_class, activation_tester):
        class Nushell(activation_tester_class):
            def __init__(self, session) -> None:
                cmd = which("nu")
                if cmd is None and IS_WIN:
                    cmd = "c:\\program files\\nu\\bin\\nu.exe"

                super().__init__(NushellActivator, session, cmd, "activate.nu", "nu")

                self.activate_cmd = "overlay use"
                self.unix_line_ending = not IS_WIN

            def print_prompt(self):
                return r"print $env.VIRTUAL_PREFIX"

            def activate_call(self, script):
                # Commands are called without quotes in Nushell
                cmd = self.activate_cmd
                scr = self.quote(str(script))
                return f"{cmd} {scr}".strip()

>       activation_tester(Nushell)

Nushell    = .Nushell'>
activation_tester = ._tester at 0x7f064db8be20>
activation_tester_class = 

/testbed/tests/unit/activation/test_nushell.py:30: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f4e1840>
        tester     = Nushell(
version='0.98.0\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Nushell'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_nushell_no_prompt_0')
        version    = '0.98.0\n'
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate.nu')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/root', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['/root/.cargo/bin/nu', '/tmp/pytest-of-root/pytest-0/test_nushell_no_prompt_0/script.nu']
        line       = b'export alias deactivate = overlay hide activate'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f4e1840>
        out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
        process    = 
        raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...nv0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\ntestbed\n'
        raw_       = b'/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ \xc3\xa8\xd1\x80\...9f\x9a\x92\xe2\x99\x9e\xe4\xb8\xad\xe7\x89\x87-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\ntestbed\n'
        script_content = b'# virtualenv activation module\n# Activate with `overlay use activate.nu`\n# Deactivate with `deactivate`, as usual\...v\n    load-env $new_env\n}\n\nexport alias pydoc = python -m pydoc\nexport alias deactivate = overlay hide activate\n'
        self       = Nushell(
version='0.98.0\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_nushell_no_prompt_0/script.nu')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_nushell_no_prompt_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Nushell(
version='0.98.0\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...nv0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\ntestbed\n'
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_nushell_no_prompt_0')

    def assert_output(self, out, raw, tmp_path):
        # pre-activation
        assert out[0], raw
        assert out[1] == "None", raw
>       assert out[2] == "None", raw
E       AssertionError: /testbed/.venv/bin/python3
E         None
E         testbed
E         /tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3
E         /tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) 
E         wrote pydoc_test.html
E         /testbed/.venv/bin/python3
E         None
E         testbed
E         
E       assert 'testbed' == 'None'
E         
E         - None
E         + testbed

out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...nv0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\ntestbed\n'
self       = Nushell(
version='0.98.0\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_nushell_no_prompt_0')

/testbed/tests/unit/activation/conftest.py:137: AssertionError

test_fish.py::test_fish[no_prompt]

test_fish.py::test_fish[no_prompt]
activation_tester_class = 
activation_tester = ._tester at 0x7f064db89870>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f768880>
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_no_prompt_0')

    @pytest.mark.skipif(IS_WIN, reason="we have not setup fish in CI yet")
    def test_fish(activation_tester_class, activation_tester, monkeypatch, tmp_path):
        monkeypatch.setenv("HOME", str(tmp_path))
        fish_conf_dir = tmp_path / ".config" / "fish"
        fish_conf_dir.mkdir(parents=True)
        (fish_conf_dir / "config.fish").write_text("", encoding="utf-8")

        class Fish(activation_tester_class):
            def __init__(self, session) -> None:
                super().__init__(FishActivator, session, "fish", "activate.fish", "fish")

            def print_prompt(self):
                return "fish_prompt"

>       activation_tester(Fish)

Fish       = .Fish'>
activation_tester = ._tester at 0x7f064db89870>
activation_tester_class = 
fish_conf_dir = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_no_prompt_0/.config/fish')
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f768880>
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_no_prompt_0')

/testbed/tests/unit/activation/test_fish.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f768880>
        tester     = Fish(
version='fish, version 3.3.1\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Fish'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_no_prompt_0')
        version    = 'fish, version 3.3.1\n'
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate.fish')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/tmp/pytest-of-root/pytest-0/test_fish_no_prompt_0', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['fish', '/tmp/pytest-of-root/pytest-0/test_fish_no_prompt_0/script.fish']
        line       = b'end'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f768880>
        out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
        process    = 
        raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...B\x1b[m@modal\x1b(B\x1b[m ~\x1b(B\x1b[m\x1b(B\x1b[m# \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        raw_       = b'/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ \xc3\xa8\xd1\x80\...B\x1b[m@modal\x1b(B\x1b[m ~\x1b(B\x1b[m\x1b(B\x1b[m# \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        script_content = b'# This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*.\...oin -- \\n $prompt # handle multi-line prompts\n    end\n\n    set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"\nend\n'
        self       = Fish(
version='fish, version 3.3.1\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_no_prompt_0/script.fish')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_no_prompt_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Fish(
version='fish, version 3.3.1\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...B\x1b[m@modal\x1b(B\x1b[m ~\x1b(B\x1b[m\x1b(B\x1b[m# \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_no_prompt_0')

    def assert_output(self, out, raw, tmp_path):
        # pre-activation
        assert out[0], raw
        assert out[1] == "None", raw
>       assert out[2] == "None", raw
E       AssertionError: /testbed/.venv/bin/python3
E         None
E         testbed
E         /tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3
E         /tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) root(B@modal(B ~(B(B# 
E         wrote pydoc_test.html
E         /testbed/.venv/bin/python3
E         None
E         None
E         
E       assert 'testbed' == 'None'
E         
E         - None
E         + testbed

out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...B\x1b[m@modal\x1b(B\x1b[m ~\x1b(B\x1b[m\x1b(B\x1b[m# \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
self       = Fish(
version='fish, version 3.3.1\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_no_prompt_0')

/testbed/tests/unit/activation/conftest.py:137: AssertionError

test_python_activator.py::test_python[no_prompt]

test_python_activator.py::test_python[no_prompt]
raise_on_non_source_class = 
activation_tester = ._tester at 0x7f064dbf8a60>

    def test_python(raise_on_non_source_class, activation_tester):
        class Python(raise_on_non_source_class):
            def __init__(self, session) -> None:
                super().__init__(
                    PythonActivator,
                    session,
                    sys.executable,
                    activate_script="activate_this.py",
                    extension="py",
                    non_source_fail_message="You must use import runpy; runpy.run_path(this_file)",
                )
                self.unix_line_ending = not IS_WIN

            def env(self, tmp_path):
                env = os.environ.copy()
                env["PYTHONIOENCODING"] = "utf-8"
                for key in ("VIRTUAL_ENV", "PYTHONPATH"):
                    env.pop(str(key), None)
                env["PATH"] = os.pathsep.join([str(tmp_path), str(tmp_path / "other")])
                return env

            @staticmethod
            def _get_test_lines(activate_script):
                raw = f"""
                import os
                import sys
                import platform
                import runpy

                def print_r(value):
                    print(repr(value))

                print_r(os.environ.get("VIRTUAL_ENV"))
                print_r(os.environ.get("VIRTUAL_ENV_PROMPT"))
                print_r(os.environ.get("PATH").split(os.pathsep))
                print_r(sys.path)

                file_at = {str(activate_script)!r}
                # CPython 2 requires non-ascii path open to be unicode
                runpy.run_path(file_at)
                print_r(os.environ.get("VIRTUAL_ENV"))
                print_r(os.environ.get("VIRTUAL_ENV_PROMPT"))
                print_r(os.environ.get("PATH").split(os.pathsep))
                print_r(sys.path)

                import pydoc_test
                print_r(pydoc_test.__file__)
                """
                return dedent(raw).splitlines()

            def assert_output(self, out, raw, tmp_path):  # noqa: ARG002
                out = [literal_eval(i) for i in out]
                assert out[0] is None  # start with VIRTUAL_ENV None
                assert out[1] is None  # likewise for VIRTUAL_ENV_PROMPT

                prev_path = out[2]
                prev_sys_path = out[3]
                assert out[4] == str(self._creator.dest)  # VIRTUAL_ENV now points to the virtual env folder

                assert out[5] == str(self._creator.env_name)  # VIRTUAL_ENV_PROMPT now has the env name

                new_path = out[6]  # PATH now starts with bin path of current
                assert ([str(self._creator.bin_dir), *prev_path]) == new_path

                # sys path contains the site package at its start
                new_sys_path = out[7]

                new_lib_paths = {str(i) for i in self._creator.libs}
                assert prev_sys_path == new_sys_path[len(new_lib_paths) :]
                assert new_lib_paths == set(new_sys_path[: len(new_lib_paths)])

                # manage to import from activate site package
                dest = self.norm_path(self._creator.purelib / "pydoc_test.py")
                found = self.norm_path(out[8])
                assert found.startswith(dest)

            def non_source_activate(self, activate_script):
                act = str(activate_script)
                return [*self._invoke_script, "-c", f"exec(open({act!r}).read())"]

>       activation_tester(Python)

Python     = .Python'>
activation_tester = ._tester at 0x7f064dbf8a60>
raise_on_non_source_class = 

/testbed/tests/unit/activation/test_python_activator.py:92: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f4e1270>
        tester     = Python(
version='Python 3.10.12\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Python'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0')
        version    = 'Python 3.10.12\n'
/testbed/tests/unit/activation/conftest.py:209: in __call__
    env, activate_script = super().__call__(monkeypatch, tmp_path)
        __class__  = 
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f4e1270>
        self       = Python(
version='Python 3.10.12\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0')
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate_this.py')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/root', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['/testbed/.venv/bin/python3', '/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0/script.py']
        line       = b'sys.prefix = base'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f4e1270>
        out        = ['None', "'testbed'", "['/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0', '/tmp/pytest-of-root/pytest-0/test_pyt...ages', '/testbed/src']", "'/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j'", "'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j'", ...]
        process    = 
        raw        = "None\n'testbed'\n['/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0', '/tmp/pytest-of-root/pytest-0/test_python_n...rc']\n'/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/lib/python3.10/site-packages/pydoc_test.py'\n"
        raw_       = b"None\n'testbed'\n['/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0', '/tmp/pytest-of-root/pytest-0/test_python_...a8\xd1\x80\xd1\x82\xf0\x9f\x9a\x92\xe2\x99\x9e\xe4\xb8\xad\xe7\x89\x87-j/lib/python3.10/site-packages/pydoc_test.py'\n"
        script_content = b'"""\nActivate virtualenv for current interpreter:\n\nimport runpy\nrunpy.run_path(this_file)\n\nThis can be used whe...)\nsys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]\n\nsys.real_prefix = sys.prefix\nsys.prefix = base\n'
        self       = Python(
version='Python 3.10.12\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0/script.py')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Python(
version='Python 3.10.12\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = [None, 'testbed', ['/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0', '/tmp/pytest-of-root/pytest-0/test_python_n...-packages', '/testbed/src'], '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = "None\n'testbed'\n['/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0', '/tmp/pytest-of-root/pytest-0/test_python_n...rc']\n'/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/lib/python3.10/site-packages/pydoc_test.py'\n"
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0')

    def assert_output(self, out, raw, tmp_path):  # noqa: ARG002
        out = [literal_eval(i) for i in out]
        assert out[0] is None  # start with VIRTUAL_ENV None
>       assert out[1] is None  # likewise for VIRTUAL_ENV_PROMPT
E       AssertionError: assert 'testbed' is None

out        = [None, 'testbed', ['/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0', '/tmp/pytest-of-root/pytest-0/test_python_n...-packages', '/testbed/src'], '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = "None\n'testbed'\n['/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0', '/tmp/pytest-of-root/pytest-0/test_python_n...rc']\n'/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/lib/python3.10/site-packages/pydoc_test.py'\n"
self       = Python(
version='Python 3.10.12\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_no_prompt_0')

/testbed/tests/unit/activation/test_python_activator.py:65: AssertionError

test_bash.py::test_bash[no_prompt-False]

test_bash.py::test_bash[no_prompt-False]
raise_on_non_source_class = 
hashing_enabled = False
activation_tester = ._tester at 0x7f064dbf8700>

    @pytest.mark.skipif(IS_WIN, reason="Github Actions ships with WSL bash")
    @pytest.mark.parametrize("hashing_enabled", [True, False])
    def test_bash(raise_on_non_source_class, hashing_enabled, activation_tester):
        class Bash(raise_on_non_source_class):
            def __init__(self, session) -> None:
                super().__init__(
                    BashActivator,
                    session,
                    "bash",
                    "activate",
                    "sh",
                    "You must source this script: $ source ",
                )
                self.deactivate += " || exit 1"
                self._invoke_script.append("-h" if hashing_enabled else "+h")

            def activate_call(self, script):
                return super().activate_call(script) + " || exit 1"

            def print_prompt(self):
                return self.print_os_env_var("PS1")

>       activation_tester(Bash)

Bash       = .Bash'>
activation_tester = ._tester at 0x7f064dbf8700>
hashing_enabled = False
raise_on_non_source_class = 

/testbed/tests/unit/activation/test_bash.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f768e20>
        tester     = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Bash'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_False_0')
        version    = 'GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation, Inc.\nLicense...is free software; you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\n'
/testbed/tests/unit/activation/conftest.py:209: in __call__
    env, activate_script = super().__call__(monkeypatch, tmp_path)
        __class__  = 
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f768e20>
        self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_False_0')
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/root', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['bash', '+h', '/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_False_0/script.sh']
        line       = b'hash -r 2>/dev/null || true'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f768e20>
        out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
        process    = 
        raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        raw_       = b'/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ \xc3\xa8\xd1\x80\...0\x9f\x9a\x92\xe2\x99\x9e\xe4\xb8\xad\xe7\x89\x87-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        script_content = b'# This file must be used with "source bin/activate" *from bash*\n# you cannot run it directly\n\n\nif [ "${BASH_SOUR...nds. Without forgetting past commands the $PATH changes\n# we made may not be respected\nhash -r 2>/dev/null || true\n'
        self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_False_0/script.sh')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_False_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_False_0')

    def assert_output(self, out, raw, tmp_path):
        # pre-activation
        assert out[0], raw
        assert out[1] == "None", raw
>       assert out[2] == "None", raw
E       AssertionError: /testbed/.venv/bin/python3
E         None
E         testbed
E         /tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3
E         /tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) 
E         wrote pydoc_test.html
E         /testbed/.venv/bin/python3
E         None
E         None
E         
E       assert 'testbed' == 'None'
E         
E         - None
E         + testbed

out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_False_0')

/testbed/tests/unit/activation/conftest.py:137: AssertionError

test_bash.py::test_bash[no_prompt-True]

test_bash.py::test_bash[no_prompt-True]
raise_on_non_source_class = 
hashing_enabled = True
activation_tester = ._tester at 0x7f064dbf9990>

    @pytest.mark.skipif(IS_WIN, reason="Github Actions ships with WSL bash")
    @pytest.mark.parametrize("hashing_enabled", [True, False])
    def test_bash(raise_on_non_source_class, hashing_enabled, activation_tester):
        class Bash(raise_on_non_source_class):
            def __init__(self, session) -> None:
                super().__init__(
                    BashActivator,
                    session,
                    "bash",
                    "activate",
                    "sh",
                    "You must source this script: $ source ",
                )
                self.deactivate += " || exit 1"
                self._invoke_script.append("-h" if hashing_enabled else "+h")

            def activate_call(self, script):
                return super().activate_call(script) + " || exit 1"

            def print_prompt(self):
                return self.print_os_env_var("PS1")

>       activation_tester(Bash)

Bash       = .Bash'>
activation_tester = ._tester at 0x7f064dbf9990>
hashing_enabled = True
raise_on_non_source_class = 

/testbed/tests/unit/activation/test_bash.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f73efe0>
        tester     = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Bash'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_True_0')
        version    = 'GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation, Inc.\nLicense...is free software; you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\n'
/testbed/tests/unit/activation/conftest.py:209: in __call__
    env, activate_script = super().__call__(monkeypatch, tmp_path)
        __class__  = 
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f73efe0>
        self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_True_0')
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/root', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['bash', '-h', '/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_True_0/script.sh']
        line       = b'hash -r 2>/dev/null || true'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f73efe0>
        out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
        process    = 
        raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        raw_       = b'/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ \xc3\xa8\xd1\x80\...0\x9f\x9a\x92\xe2\x99\x9e\xe4\xb8\xad\xe7\x89\x87-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        script_content = b'# This file must be used with "source bin/activate" *from bash*\n# you cannot run it directly\n\n\nif [ "${BASH_SOUR...nds. Without forgetting past commands the $PATH changes\n# we made may not be respected\nhash -r 2>/dev/null || true\n'
        self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_True_0/script.sh')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_True_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_True_0')

    def assert_output(self, out, raw, tmp_path):
        # pre-activation
        assert out[0], raw
        assert out[1] == "None", raw
>       assert out[2] == "None", raw
E       AssertionError: /testbed/.venv/bin/python3
E         None
E         testbed
E         /tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3
E         /tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) 
E         wrote pydoc_test.html
E         /testbed/.venv/bin/python3
E         None
E         None
E         
E       assert 'testbed' == 'None'
E         
E         - None
E         + testbed

out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env0/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_no_prompt_True_0')

/testbed/tests/unit/activation/conftest.py:137: AssertionError

test_csh.py::test_csh[with_prompt]

test_csh.py::test_csh[with_prompt]
activation_tester_class = 
activation_tester = ._tester at 0x7f064dbfa950>

    def test_csh(activation_tester_class, activation_tester):
        class Csh(activation_tester_class):
            def __init__(self, session) -> None:
                super().__init__(CShellActivator, session, "csh", "activate.csh", "csh")

            def print_prompt(self):
                # Original csh doesn't print the last newline,
                # breaking the test; hence the trailing echo.
                return "echo 'source \"$VIRTUAL_ENV/bin/activate.csh\"; echo $prompt' | csh -i ; echo"

>       activation_tester(Csh)

Csh        = .Csh'>
activation_tester = ._tester at 0x7f064dbfa950>
activation_tester_class = 

/testbed/tests/unit/activation/test_csh.py:16: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f5b7a60>
        tester     = Csh(
version='# exit\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Csh'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_csh_with_prompt_0')
        version    = '# exit\n'
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate.csh')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/root', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['csh', '/tmp/pytest-of-root/pytest-0/test_csh_with_prompt_0/script.csh']
        line       = b'rehash'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f5b7a60>
        out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
        process    = 
        raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...s shell.\n# (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) #\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) # exit\nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        raw_       = b'/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ \xc3\xa8\xd1\x80\...x9a\x92\xe2\x99\x9e\xe4\xb8\xad\xe7\x89\x87-j) # exit\nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        script_content = b'# This file must be used with "source bin/activate.csh" *from csh*.\n# You cannot run it directly.\n# Created by Dav...ompt:q"\n        endif\n    endif\nendif\n\nunset env_name\nunset do_prompt\n\nalias pydoc python -m pydoc\n\nrehash\n'
        self       = Csh(
version='# exit\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_csh_with_prompt_0/script.csh')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_csh_with_prompt_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Csh(
version='# exit\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...s shell.\n# (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) #\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) # exit\nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_csh_with_prompt_0')

    def assert_output(self, out, raw, tmp_path):
        # pre-activation
        assert out[0], raw
        assert out[1] == "None", raw
>       assert out[2] == "None", raw
E       AssertionError: /testbed/.venv/bin/python3
E         None
E         testbed
E         /tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3
E         /tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         Warning: no access to tty (Bad file descriptor).
E         Thus no job control in this shell.
E         # (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) #
E         (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) # exit
E         wrote pydoc_test.html
E         /testbed/.venv/bin/python3
E         None
E         None
E         
E       assert 'testbed' == 'None'
E         
E         - None
E         + testbed

out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...s shell.\n# (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) #\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) # exit\nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
self       = Csh(
version='# exit\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_csh_with_prompt_0')

/testbed/tests/unit/activation/conftest.py:137: AssertionError

test_nushell.py::test_nushell[with_prompt]

test_nushell.py::test_nushell[with_prompt]
activation_tester_class = 
activation_tester = ._tester at 0x7f064dbfb0a0>

    def test_nushell(activation_tester_class, activation_tester):
        class Nushell(activation_tester_class):
            def __init__(self, session) -> None:
                cmd = which("nu")
                if cmd is None and IS_WIN:
                    cmd = "c:\\program files\\nu\\bin\\nu.exe"

                super().__init__(NushellActivator, session, cmd, "activate.nu", "nu")

                self.activate_cmd = "overlay use"
                self.unix_line_ending = not IS_WIN

            def print_prompt(self):
                return r"print $env.VIRTUAL_PREFIX"

            def activate_call(self, script):
                # Commands are called without quotes in Nushell
                cmd = self.activate_cmd
                scr = self.quote(str(script))
                return f"{cmd} {scr}".strip()

>       activation_tester(Nushell)

Nushell    = .Nushell'>
activation_tester = ._tester at 0x7f064dbfb0a0>
activation_tester_class = 

/testbed/tests/unit/activation/test_nushell.py:30: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f5b5120>
        tester     = Nushell(
version='0.98.0\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Nushell'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_nushell_with_prompt_0')
        version    = '0.98.0\n'
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate.nu')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/root', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['/root/.cargo/bin/nu', '/tmp/pytest-of-root/pytest-0/test_nushell_with_prompt_0/script.nu']
        line       = b'export alias deactivate = overlay hide activate'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f5b5120>
        out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
        process    = 
        raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...nv1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\ntestbed\n'
        raw_       = b'/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ \xc3\xa8\xd1\x80\...9f\x9a\x92\xe2\x99\x9e\xe4\xb8\xad\xe7\x89\x87-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\ntestbed\n'
        script_content = b'# virtualenv activation module\n# Activate with `overlay use activate.nu`\n# Deactivate with `deactivate`, as usual\...v\n    load-env $new_env\n}\n\nexport alias pydoc = python -m pydoc\nexport alias deactivate = overlay hide activate\n'
        self       = Nushell(
version='0.98.0\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_nushell_with_prompt_0/script.nu')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_nushell_with_prompt_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Nushell(
version='0.98.0\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...nv1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\ntestbed\n'
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_nushell_with_prompt_0')

    def assert_output(self, out, raw, tmp_path):
        # pre-activation
        assert out[0], raw
        assert out[1] == "None", raw
>       assert out[2] == "None", raw
E       AssertionError: /testbed/.venv/bin/python3
E         None
E         testbed
E         /tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3
E         /tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) 
E         wrote pydoc_test.html
E         /testbed/.venv/bin/python3
E         None
E         testbed
E         
E       assert 'testbed' == 'None'
E         
E         - None
E         + testbed

out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...nv1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\ntestbed\n'
self       = Nushell(
version='0.98.0\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_nushell_with_prompt_0')

/testbed/tests/unit/activation/conftest.py:137: AssertionError

test_fish.py::test_fish[with_prompt]

test_fish.py::test_fish[with_prompt]
activation_tester_class = 
activation_tester = ._tester at 0x7f064dbfb250>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f4e0a60>
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_with_prompt_0')

    @pytest.mark.skipif(IS_WIN, reason="we have not setup fish in CI yet")
    def test_fish(activation_tester_class, activation_tester, monkeypatch, tmp_path):
        monkeypatch.setenv("HOME", str(tmp_path))
        fish_conf_dir = tmp_path / ".config" / "fish"
        fish_conf_dir.mkdir(parents=True)
        (fish_conf_dir / "config.fish").write_text("", encoding="utf-8")

        class Fish(activation_tester_class):
            def __init__(self, session) -> None:
                super().__init__(FishActivator, session, "fish", "activate.fish", "fish")

            def print_prompt(self):
                return "fish_prompt"

>       activation_tester(Fish)

Fish       = .Fish'>
activation_tester = ._tester at 0x7f064dbfb250>
activation_tester_class = 
fish_conf_dir = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_with_prompt_0/.config/fish')
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f4e0a60>
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_with_prompt_0')

/testbed/tests/unit/activation/test_fish.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f4e0a60>
        tester     = Fish(
version='fish, version 3.3.1\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Fish'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_with_prompt_0')
        version    = 'fish, version 3.3.1\n'
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate.fish')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/tmp/pytest-of-root/pytest-0/test_fish_with_prompt_0', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['fish', '/tmp/pytest-of-root/pytest-0/test_fish_with_prompt_0/script.fish']
        line       = b'end'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f4e0a60>
        out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
        process    = 
        raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...B\x1b[m@modal\x1b(B\x1b[m ~\x1b(B\x1b[m\x1b(B\x1b[m# \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        raw_       = b'/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ \xc3\xa8\xd1\x80\...B\x1b[m@modal\x1b(B\x1b[m ~\x1b(B\x1b[m\x1b(B\x1b[m# \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        script_content = b'# This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*.\...oin -- \\n $prompt # handle multi-line prompts\n    end\n\n    set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"\nend\n'
        self       = Fish(
version='fish, version 3.3.1\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_with_prompt_0/script.fish')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_with_prompt_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Fish(
version='fish, version 3.3.1\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...B\x1b[m@modal\x1b(B\x1b[m ~\x1b(B\x1b[m\x1b(B\x1b[m# \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_with_prompt_0')

    def assert_output(self, out, raw, tmp_path):
        # pre-activation
        assert out[0], raw
        assert out[1] == "None", raw
>       assert out[2] == "None", raw
E       AssertionError: /testbed/.venv/bin/python3
E         None
E         testbed
E         /tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3
E         /tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) root(B@modal(B ~(B(B# 
E         wrote pydoc_test.html
E         /testbed/.venv/bin/python3
E         None
E         None
E         
E       assert 'testbed' == 'None'
E         
E         - None
E         + testbed

out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...B\x1b[m@modal\x1b(B\x1b[m ~\x1b(B\x1b[m\x1b(B\x1b[m# \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
self       = Fish(
version='fish, version 3.3.1\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_fish_with_prompt_0')

/testbed/tests/unit/activation/conftest.py:137: AssertionError

test_python_activator.py::test_python[with_prompt]

test_python_activator.py::test_python[with_prompt]
raise_on_non_source_class = 
activation_tester = ._tester at 0x7f064dc38040>

    def test_python(raise_on_non_source_class, activation_tester):
        class Python(raise_on_non_source_class):
            def __init__(self, session) -> None:
                super().__init__(
                    PythonActivator,
                    session,
                    sys.executable,
                    activate_script="activate_this.py",
                    extension="py",
                    non_source_fail_message="You must use import runpy; runpy.run_path(this_file)",
                )
                self.unix_line_ending = not IS_WIN

            def env(self, tmp_path):
                env = os.environ.copy()
                env["PYTHONIOENCODING"] = "utf-8"
                for key in ("VIRTUAL_ENV", "PYTHONPATH"):
                    env.pop(str(key), None)
                env["PATH"] = os.pathsep.join([str(tmp_path), str(tmp_path / "other")])
                return env

            @staticmethod
            def _get_test_lines(activate_script):
                raw = f"""
                import os
                import sys
                import platform
                import runpy

                def print_r(value):
                    print(repr(value))

                print_r(os.environ.get("VIRTUAL_ENV"))
                print_r(os.environ.get("VIRTUAL_ENV_PROMPT"))
                print_r(os.environ.get("PATH").split(os.pathsep))
                print_r(sys.path)

                file_at = {str(activate_script)!r}
                # CPython 2 requires non-ascii path open to be unicode
                runpy.run_path(file_at)
                print_r(os.environ.get("VIRTUAL_ENV"))
                print_r(os.environ.get("VIRTUAL_ENV_PROMPT"))
                print_r(os.environ.get("PATH").split(os.pathsep))
                print_r(sys.path)

                import pydoc_test
                print_r(pydoc_test.__file__)
                """
                return dedent(raw).splitlines()

            def assert_output(self, out, raw, tmp_path):  # noqa: ARG002
                out = [literal_eval(i) for i in out]
                assert out[0] is None  # start with VIRTUAL_ENV None
                assert out[1] is None  # likewise for VIRTUAL_ENV_PROMPT

                prev_path = out[2]
                prev_sys_path = out[3]
                assert out[4] == str(self._creator.dest)  # VIRTUAL_ENV now points to the virtual env folder

                assert out[5] == str(self._creator.env_name)  # VIRTUAL_ENV_PROMPT now has the env name

                new_path = out[6]  # PATH now starts with bin path of current
                assert ([str(self._creator.bin_dir), *prev_path]) == new_path

                # sys path contains the site package at its start
                new_sys_path = out[7]

                new_lib_paths = {str(i) for i in self._creator.libs}
                assert prev_sys_path == new_sys_path[len(new_lib_paths) :]
                assert new_lib_paths == set(new_sys_path[: len(new_lib_paths)])

                # manage to import from activate site package
                dest = self.norm_path(self._creator.purelib / "pydoc_test.py")
                found = self.norm_path(out[8])
                assert found.startswith(dest)

            def non_source_activate(self, activate_script):
                act = str(activate_script)
                return [*self._invoke_script, "-c", f"exec(open({act!r}).read())"]

>       activation_tester(Python)

Python     = .Python'>
activation_tester = ._tester at 0x7f064dc38040>
raise_on_non_source_class = 

/testbed/tests/unit/activation/test_python_activator.py:92: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f5b4340>
        tester     = Python(
version='Python 3.10.12\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Python'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0')
        version    = 'Python 3.10.12\n'
/testbed/tests/unit/activation/conftest.py:209: in __call__
    env, activate_script = super().__call__(monkeypatch, tmp_path)
        __class__  = 
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f5b4340>
        self       = Python(
version='Python 3.10.12\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0')
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate_this.py')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/root', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['/testbed/.venv/bin/python3', '/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0/script.py']
        line       = b'sys.prefix = base'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f5b4340>
        out        = ['None', "'testbed'", "['/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0', '/tmp/pytest-of-root/pytest-0/test_p...ages', '/testbed/src']", "'/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j'", "'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j'", ...]
        process    = 
        raw        = "None\n'testbed'\n['/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0', '/tmp/pytest-of-root/pytest-0/test_python...rc']\n'/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/lib/python3.10/site-packages/pydoc_test.py'\n"
        raw_       = b"None\n'testbed'\n['/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0', '/tmp/pytest-of-root/pytest-0/test_pytho...a8\xd1\x80\xd1\x82\xf0\x9f\x9a\x92\xe2\x99\x9e\xe4\xb8\xad\xe7\x89\x87-j/lib/python3.10/site-packages/pydoc_test.py'\n"
        script_content = b'"""\nActivate virtualenv for current interpreter:\n\nimport runpy\nrunpy.run_path(this_file)\n\nThis can be used whe...)\nsys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]\n\nsys.real_prefix = sys.prefix\nsys.prefix = base\n'
        self       = Python(
version='Python 3.10.12\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0/script.py')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Python(
version='Python 3.10.12\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = [None, 'testbed', ['/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0', '/tmp/pytest-of-root/pytest-0/test_python...-packages', '/testbed/src'], '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = "None\n'testbed'\n['/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0', '/tmp/pytest-of-root/pytest-0/test_python...rc']\n'/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/lib/python3.10/site-packages/pydoc_test.py'\n"
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0')

    def assert_output(self, out, raw, tmp_path):  # noqa: ARG002
        out = [literal_eval(i) for i in out]
        assert out[0] is None  # start with VIRTUAL_ENV None
>       assert out[1] is None  # likewise for VIRTUAL_ENV_PROMPT
E       AssertionError: assert 'testbed' is None

out        = [None, 'testbed', ['/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0', '/tmp/pytest-of-root/pytest-0/test_python...-packages', '/testbed/src'], '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = "None\n'testbed'\n['/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0', '/tmp/pytest-of-root/pytest-0/test_python...rc']\n'/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/lib/python3.10/site-packages/pydoc_test.py'\n"
self       = Python(
version='Python 3.10.12\n',
creator=CPython3Posix(dest=/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_python_with_prompt_0')

/testbed/tests/unit/activation/test_python_activator.py:65: AssertionError

test_bash.py::test_bash[with_prompt-True]

test_bash.py::test_bash[with_prompt-True]
raise_on_non_source_class = 
hashing_enabled = True
activation_tester = ._tester at 0x7f064dc395a0>

    @pytest.mark.skipif(IS_WIN, reason="Github Actions ships with WSL bash")
    @pytest.mark.parametrize("hashing_enabled", [True, False])
    def test_bash(raise_on_non_source_class, hashing_enabled, activation_tester):
        class Bash(raise_on_non_source_class):
            def __init__(self, session) -> None:
                super().__init__(
                    BashActivator,
                    session,
                    "bash",
                    "activate",
                    "sh",
                    "You must source this script: $ source ",
                )
                self.deactivate += " || exit 1"
                self._invoke_script.append("-h" if hashing_enabled else "+h")

            def activate_call(self, script):
                return super().activate_call(script) + " || exit 1"

            def print_prompt(self):
                return self.print_os_env_var("PS1")

>       activation_tester(Bash)

Bash       = .Bash'>
activation_tester = ._tester at 0x7f064dc395a0>
hashing_enabled = True
raise_on_non_source_class = 

/testbed/tests/unit/activation/test_bash.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064da7fac0>
        tester     = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Bash'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_True_0')
        version    = 'GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation, Inc.\nLicense...is free software; you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\n'
/testbed/tests/unit/activation/conftest.py:209: in __call__
    env, activate_script = super().__call__(monkeypatch, tmp_path)
        __class__  = 
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064da7fac0>
        self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_True_0')
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/root', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['bash', '-h', '/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_True_0/script.sh']
        line       = b'hash -r 2>/dev/null || true'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064da7fac0>
        out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
        process    = 
        raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        raw_       = b'/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ \xc3\xa8\xd1\x80\...0\x9f\x9a\x92\xe2\x99\x9e\xe4\xb8\xad\xe7\x89\x87-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        script_content = b'# This file must be used with "source bin/activate" *from bash*\n# you cannot run it directly\n\n\nif [ "${BASH_SOUR...nds. Without forgetting past commands the $PATH changes\n# we made may not be respected\nhash -r 2>/dev/null || true\n'
        self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_True_0/script.sh')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_True_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_True_0')

    def assert_output(self, out, raw, tmp_path):
        # pre-activation
        assert out[0], raw
        assert out[1] == "None", raw
>       assert out[2] == "None", raw
E       AssertionError: /testbed/.venv/bin/python3
E         None
E         testbed
E         /tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3
E         /tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) 
E         wrote pydoc_test.html
E         /testbed/.venv/bin/python3
E         None
E         None
E         
E       assert 'testbed' == 'None'
E         
E         - None
E         + testbed

out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_True_0')

/testbed/tests/unit/activation/conftest.py:137: AssertionError

test_bash.py::test_bash[with_prompt-False]

test_bash.py::test_bash[with_prompt-False]
raise_on_non_source_class = 
hashing_enabled = False
activation_tester = ._tester at 0x7f064dc38af0>

    @pytest.mark.skipif(IS_WIN, reason="Github Actions ships with WSL bash")
    @pytest.mark.parametrize("hashing_enabled", [True, False])
    def test_bash(raise_on_non_source_class, hashing_enabled, activation_tester):
        class Bash(raise_on_non_source_class):
            def __init__(self, session) -> None:
                super().__init__(
                    BashActivator,
                    session,
                    "bash",
                    "activate",
                    "sh",
                    "You must source this script: $ source ",
                )
                self.deactivate += " || exit 1"
                self._invoke_script.append("-h" if hashing_enabled else "+h")

            def activate_call(self, script):
                return super().activate_call(script) + " || exit 1"

            def print_prompt(self):
                return self.print_os_env_var("PS1")

>       activation_tester(Bash)

Bash       = .Bash'>
activation_tester = ._tester at 0x7f064dc38af0>
hashing_enabled = False
raise_on_non_source_class = 

/testbed/tests/unit/activation/test_bash.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/testbed/tests/unit/activation/conftest.py:253: in _tester
    return tester(monkeypatch, tmp_path)
        activation_python = 
        is_inside_ci = False
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f851090>
        tester     = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tester_class = .Bash'>
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_False_0')
        version    = 'GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation, Inc.\nLicense...is free software; you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\n'
/testbed/tests/unit/activation/conftest.py:209: in __call__
    env, activate_script = super().__call__(monkeypatch, tmp_path)
        __class__  = 
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f851090>
        self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_False_0')
/testbed/tests/unit/activation/conftest.py:89: in __call__
    self.assert_output(out, raw, tmp_path)
        _          = None
        activate_script = PosixPath('/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/activate')
        env        = {'BLIS_NUM_THREADS': '1', 'CFLAGS': '-g0', 'HOME': '/root', 'LC_CTYPE': 'C.UTF-8', ...}
        invoke     = ['bash', '+h', '/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_False_0/script.sh']
        line       = b'hash -r 2>/dev/null || true'
        monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f064f851090>
        out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
        process    = 
        raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        raw_       = b'/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ \xc3\xa8\xd1\x80\...0\x9f\x9a\x92\xe2\x99\x9e\xe4\xb8\xad\xe7\x89\x87-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
        script_content = b'# This file must be used with "source bin/activate" *from bash*\n# you cannot run it directly\n\n\nif [ "${BASH_SOUR...nds. Without forgetting past commands the $PATH changes\n# we made may not be respected\nhash -r 2>/dev/null || true\n'
        self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
        test_script = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_False_0/script.sh')
        tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_False_0')
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
out = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
tmp_path = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_False_0')

    def assert_output(self, out, raw, tmp_path):
        # pre-activation
        assert out[0], raw
        assert out[1] == "None", raw
>       assert out[2] == "None", raw
E       AssertionError: /testbed/.venv/bin/python3
E         None
E         testbed
E         /tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3
E         /tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j
E         (e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) 
E         wrote pydoc_test.html
E         /testbed/.venv/bin/python3
E         None
E         None
E         
E       assert 'testbed' == 'None'
E         
E         - None
E         + testbed

out        = ['/testbed/.venv/bin/python3', 'None', 'testbed', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/python3', '/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', 'e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j', ...]
raw        = '/testbed/.venv/bin/python3\nNone\ntestbed\n/tmp/pytest-of-root/pytest-0/activation-tester-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j/bin/pyth...r-env1/e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\ne-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j\n(e-$ Γ¨Ρ€Ρ‚πŸš’β™žδΈ­η‰‡-j) \nwrote pydoc_test.html\n/testbed/.venv/bin/python3\nNone\nNone\n'
self       = Bash(
version='GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)\nCopyright (C) 2020 Free Software Foundation,...bin/python3, platform=linux, version='3.10.12 (main, Sep 11 2024, 15:47:36) [GCC 11.4.0]', encoding_fs_io=utf-8-utf-8))
tmp_path   = PosixPath('/tmp/pytest-of-root/pytest-0/test_bash_with_prompt_False_0')

/testbed/tests/unit/activation/conftest.py:137: AssertionError

Patch diff

diff --git a/src/virtualenv/activation/activator.py b/src/virtualenv/activation/activator.py
index 328589c..dd404b4 100644
--- a/src/virtualenv/activation/activator.py
+++ b/src/virtualenv/activation/activator.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+
 import os
 from abc import ABC, abstractmethod

@@ -6,26 +7,25 @@ from abc import ABC, abstractmethod
 class Activator(ABC):
     """Generates activate script for the virtual environment."""

-    def __init__(self, options) ->None:
+    def __init__(self, options) -> None:
         """
         Create a new activator generator.

         :param options: the parsed options as defined within :meth:`add_parser_arguments`
         """
-        self.flag_prompt = os.path.basename(os.getcwd()
-            ) if options.prompt == '.' else options.prompt
+        self.flag_prompt = os.path.basename(os.getcwd()) if options.prompt == "." else options.prompt

     @classmethod
-    def supports(cls, interpreter):
+    def supports(cls, interpreter):  # noqa: ARG003
         """
         Check if the activation script is supported in the given interpreter.

         :param interpreter: the interpreter we need to support
         :return: ``True`` if supported, ``False`` otherwise
         """
-        pass
+        return True

-    @classmethod
+    @classmethod  # noqa: B027
     def add_parser_arguments(cls, parser, interpreter):
         """
         Add CLI arguments for this activation script.
@@ -33,16 +33,18 @@ class Activator(ABC):
         :param parser: the CLI parser
         :param interpreter: the interpreter this virtual environment is based of
         """
-        pass

     @abstractmethod
     def generate(self, creator):
         """
         Generate activate script for the given creator.

-        :param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this         virtual environment
+        :param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this \
+        virtual environment
         """
-        pass
+        raise NotImplementedError


-__all__ = ['Activator']
+__all__ = [
+    "Activator",
+]
diff --git a/src/virtualenv/activation/python/activate_this.py b/src/virtualenv/activation/python/activate_this.py
index cde4ce6..388e001 100644
--- a/src/virtualenv/activation/python/activate_this.py
+++ b/src/virtualenv/activation/python/activate_this.py
@@ -5,27 +5,34 @@ import runpy
 runpy.run_path(this_file)

 This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
-"""
+"""  # noqa: D415
+
 from __future__ import annotations
+
 import os
 import site
 import sys
+
 try:
     abs_file = os.path.abspath(__file__)
 except NameError as exc:
-    msg = 'You must use import runpy; runpy.run_path(this_file)'
+    msg = "You must use import runpy; runpy.run_path(this_file)"
     raise AssertionError(msg) from exc
+
 bin_dir = os.path.dirname(abs_file)
-base = bin_dir[:-len('__BIN_NAME__') - 1]
-os.environ['PATH'] = os.pathsep.join([bin_dir, *os.environ.get('PATH', '').
-    split(os.pathsep)])
-os.environ['VIRTUAL_ENV'] = base
-os.environ['VIRTUAL_ENV_PROMPT'] = '__VIRTUAL_PROMPT__' or os.path.basename(
-    base)
+base = bin_dir[: -len("__BIN_NAME__") - 1]  # strip away the bin part from the __file__, plus the path separator
+
+# prepend bin to PATH (this file is inside the bin directory)
+os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)])
+os.environ["VIRTUAL_ENV"] = base  # virtual env is right above bin directory
+os.environ["VIRTUAL_ENV_PROMPT"] = "__VIRTUAL_PROMPT__" or os.path.basename(base)  # noqa: SIM222
+
+# add the virtual environments libraries to the host python import mechanism
 prev_length = len(sys.path)
-for lib in '__LIB_FOLDERS__'.split(os.pathsep):
+for lib in "__LIB_FOLDERS__".split(os.pathsep):
     path = os.path.realpath(os.path.join(bin_dir, lib))
-    site.addsitedir(path.decode('utf-8') if '__DECODE_PATH__' else path)
+    site.addsitedir(path.decode("utf-8") if "__DECODE_PATH__" else path)
 sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
+
 sys.real_prefix = sys.prefix
 sys.prefix = base
diff --git a/src/virtualenv/activation/via_template.py b/src/virtualenv/activation/via_template.py
index 6b70a4d..ab9b901 100644
--- a/src/virtualenv/activation/via_template.py
+++ b/src/virtualenv/activation/via_template.py
@@ -1,16 +1,76 @@
 from __future__ import annotations
+
 import os
 import sys
 from abc import ABC, abstractmethod
+
 from .activator import Activator
+
 if sys.version_info >= (3, 10):
     from importlib.resources import files
+
+    def read_binary(module_name: str, filename: str) -> bytes:
+        return (files(module_name) / filename).read_bytes()
+
 else:
     from importlib.resources import read_binary


 class ViaTemplateActivator(Activator, ABC):
-    pass
+    @abstractmethod
+    def templates(self):
+        raise NotImplementedError
+
+    def generate(self, creator):
+        dest_folder = creator.bin_dir
+        replacements = self.replacements(creator, dest_folder)
+        generated = self._generate(replacements, self.templates(), dest_folder, creator)
+        if self.flag_prompt is not None:
+            creator.pyenv_cfg["prompt"] = self.flag_prompt
+        return generated
+
+    def replacements(self, creator, dest_folder):  # noqa: ARG002
+        return {
+            "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,
+            "__VIRTUAL_ENV__": str(creator.dest),
+            "__VIRTUAL_NAME__": creator.env_name,
+            "__BIN_NAME__": str(creator.bin_dir.relative_to(creator.dest)),
+            "__PATH_SEP__": os.pathsep,
+        }
+
+    def _generate(self, replacements, templates, to_folder, creator):
+        generated = []
+        for template in templates:
+            text = self.instantiate_template(replacements, template, creator)
+            dest = to_folder / self.as_name(template)
+            # remove the file if it already exists - this prevents permission
+            # errors when the dest is not writable
+            if dest.exists():
+                dest.unlink()
+            # Powershell assumes Windows 1252 encoding when reading files without BOM
+            encoding = "utf-8-sig" if str(template).endswith(".ps1") else "utf-8"
+            # use write_bytes to avoid platform specific line normalization (\n -> \r\n)
+            dest.write_bytes(text.encode(encoding))
+            generated.append(dest)
+        return generated
+
+    def as_name(self, template):
+        return template
+
+    def instantiate_template(self, replacements, template, creator):
+        # read content as binary to avoid platform specific line normalization (\n -> \r\n)
+        binary = read_binary(self.__module__, template)
+        text = binary.decode("utf-8", errors="strict")
+        for key, value in replacements.items():
+            value_uni = self._repr_unicode(creator, value)
+            text = text.replace(key, value_uni)
+        return text
+
+    @staticmethod
+    def _repr_unicode(creator, value):  # noqa: ARG004
+        return value  # by default, we just let it be unicode


-__all__ = ['ViaTemplateActivator']
+__all__ = [
+    "ViaTemplateActivator",
+]
diff --git a/src/virtualenv/app_data/base.py b/src/virtualenv/app_data/base.py
index c8e5b7b..2077dee 100644
--- a/src/virtualenv/app_data/base.py
+++ b/src/virtualenv/app_data/base.py
@@ -1,7 +1,10 @@
 """Application data stored by virtualenv."""
+
 from __future__ import annotations
+
 from abc import ABC, abstractmethod
 from contextlib import contextmanager
+
 from virtualenv.info import IS_ZIPAPP


@@ -11,21 +14,83 @@ class AppData(ABC):
     @abstractmethod
     def close(self):
         """Called before virtualenv exits."""
-        pass

     @abstractmethod
     def reset(self):
         """Called when the user passes in the reset app data."""
-        pass
+
+    @abstractmethod
+    def py_info(self, path):
+        raise NotImplementedError
+
+    @abstractmethod
+    def py_info_clear(self):
+        raise NotImplementedError
+
+    @property
+    def can_update(self):
+        raise NotImplementedError
+
+    @abstractmethod
+    def embed_update_log(self, distribution, for_py_version):
+        raise NotImplementedError
+
+    @property
+    def house(self):
+        raise NotImplementedError
+
+    @property
+    def transient(self):
+        raise NotImplementedError
+
+    @abstractmethod
+    def wheel_image(self, for_py_version, name):
+        raise NotImplementedError

     @contextmanager
     def ensure_extracted(self, path, to_folder=None):
         """Some paths might be within the zipapp, unzip these to a path on the disk."""
-        pass
+        if IS_ZIPAPP:
+            with self.extract(path, to_folder) as result:
+                yield result
+        else:
+            yield path
+
+    @abstractmethod
+    @contextmanager
+    def extract(self, path, to_folder):
+        raise NotImplementedError
+
+    @abstractmethod
+    @contextmanager
+    def locked(self, path):
+        raise NotImplementedError


 class ContentStore(ABC):
-    pass
+    @abstractmethod
+    def exists(self):
+        raise NotImplementedError
+
+    @abstractmethod
+    def read(self):
+        raise NotImplementedError
+
+    @abstractmethod
+    def write(self, content):
+        raise NotImplementedError
+
+    @abstractmethod
+    def remove(self):
+        raise NotImplementedError
+
+    @abstractmethod
+    @contextmanager
+    def locked(self):
+        pass


-__all__ = ['AppData', 'ContentStore']
+__all__ = [
+    "AppData",
+    "ContentStore",
+]
diff --git a/src/virtualenv/app_data/na.py b/src/virtualenv/app_data/na.py
index 76639f8..921e83a 100644
--- a/src/virtualenv/app_data/na.py
+++ b/src/virtualenv/app_data/na.py
@@ -1,49 +1,72 @@
 from __future__ import annotations
+
 from contextlib import contextmanager
+
 from .base import AppData, ContentStore


 class AppDataDisabled(AppData):
     """No application cache available (most likely as we don't have write permissions)."""
+
     transient = True
     can_update = False

-    def __init__(self) ->None:
+    def __init__(self) -> None:
         pass
-    error = RuntimeError(
-        'no app data folder available, probably no write access to the folder')
+
+    error = RuntimeError("no app data folder available, probably no write access to the folder")

     def close(self):
         """Do nothing."""
-        pass

     def reset(self):
         """Do nothing."""
-        pass
+
+    def py_info(self, path):  # noqa: ARG002
+        return ContentStoreNA()
+
+    def embed_update_log(self, distribution, for_py_version):  # noqa: ARG002
+        return ContentStoreNA()
+
+    def extract(self, path, to_folder):  # noqa: ARG002
+        raise self.error

     @contextmanager
-    def locked(self, path):
+    def locked(self, path):  # noqa: ARG002
         """Do nothing."""
-        pass
+        yield
+
+    @property
+    def house(self):
+        raise self.error
+
+    def wheel_image(self, for_py_version, name):  # noqa: ARG002
+        raise self.error

     def py_info_clear(self):
         """Nothing to clear."""
-        pass


 class ContentStoreNA(ContentStore):
+    def exists(self):
+        return False

     def read(self):
         """Nothing to read."""
-        pass
+        return

     def write(self, content):
         """Nothing to write."""
-        pass

     def remove(self):
         """Nothing to remove."""
-        pass
+
+    @contextmanager
+    def locked(self):
+        yield


-__all__ = ['AppDataDisabled', 'ContentStoreNA']
+__all__ = [
+    "AppDataDisabled",
+    "ContentStoreNA",
+]
diff --git a/src/virtualenv/app_data/read_only.py b/src/virtualenv/app_data/read_only.py
index a2e161c..952dbad 100644
--- a/src/virtualenv/app_data/read_only.py
+++ b/src/virtualenv/app_data/read_only.py
@@ -1,22 +1,42 @@
 from __future__ import annotations
+
 import os.path
+
 from virtualenv.util.lock import NoOpFileLock
+
 from .via_disk_folder import AppDataDiskFolder, PyInfoStoreDisk


 class ReadOnlyAppData(AppDataDiskFolder):
     can_update = False

-    def __init__(self, folder: str) ->None:
+    def __init__(self, folder: str) -> None:
         if not os.path.isdir(folder):
-            msg = f'read-only app data directory {folder} does not exist'
+            msg = f"read-only app data directory {folder} does not exist"
             raise RuntimeError(msg)
         super().__init__(folder)
         self.lock = NoOpFileLock(folder)

+    def reset(self) -> None:
+        msg = "read-only app data does not support reset"
+        raise RuntimeError(msg)
+
+    def py_info_clear(self) -> None:
+        raise NotImplementedError
+
+    def py_info(self, path):
+        return _PyInfoStoreDiskReadOnly(self.py_info_at, path)
+
+    def embed_update_log(self, distribution, for_py_version):
+        raise NotImplementedError
+

 class _PyInfoStoreDiskReadOnly(PyInfoStoreDisk):
-    pass
+    def write(self, content):  # noqa: ARG002
+        msg = "read-only app data python info cannot be updated"
+        raise RuntimeError(msg)


-__all__ = ['ReadOnlyAppData']
+__all__ = [
+    "ReadOnlyAppData",
+]
diff --git a/src/virtualenv/app_data/via_disk_folder.py b/src/virtualenv/app_data/via_disk_folder.py
index fe0a164..5228e49 100644
--- a/src/virtualenv/app_data/via_disk_folder.py
+++ b/src/virtualenv/app_data/via_disk_folder.py
@@ -20,64 +20,155 @@ virtualenv-app-data
          β”œβ”€β”€ py_info.py
          β”œβ”€β”€ debug.py
          └── _virtualenv.py
-"""
+"""  # noqa: D415
+
 from __future__ import annotations
+
 import json
 import logging
 from abc import ABC
 from contextlib import contextmanager, suppress
 from hashlib import sha256
+
 from virtualenv.util.lock import ReentrantFileLock
 from virtualenv.util.path import safe_delete
 from virtualenv.util.zipapp import extract
 from virtualenv.version import __version__
+
 from .base import AppData, ContentStore


 class AppDataDiskFolder(AppData):
     """Store the application data on the disk within a folder layout."""
+
     transient = False
     can_update = True

-    def __init__(self, folder) ->None:
+    def __init__(self, folder) -> None:
         self.lock = ReentrantFileLock(folder)

-    def __repr__(self) ->str:
-        return f'{type(self).__name__}({self.lock.path})'
+    def __repr__(self) -> str:
+        return f"{type(self).__name__}({self.lock.path})"

-    def __str__(self) ->str:
+    def __str__(self) -> str:
         return str(self.lock.path)

+    def reset(self):
+        logging.debug("reset app data folder %s", self.lock.path)
+        safe_delete(self.lock.path)
+
     def close(self):
         """Do nothing."""
-        pass
+
+    @contextmanager
+    def locked(self, path):
+        path_lock = self.lock / path
+        with path_lock:
+            yield path_lock.path
+
+    @contextmanager
+    def extract(self, path, to_folder):
+        root = ReentrantFileLock(to_folder()) if to_folder is not None else self.lock / "unzip" / __version__
+        with root.lock_for_key(path.name):
+            dest = root.path / path.name
+            if not dest.exists():
+                extract(path, dest)
+            yield dest
+
+    @property
+    def py_info_at(self):
+        return self.lock / "py_info" / "1"
+
+    def py_info(self, path):
+        return PyInfoStoreDisk(self.py_info_at, path)

     def py_info_clear(self):
         """clear py info."""
-        pass
+        py_info_folder = self.py_info_at
+        with py_info_folder:
+            for filename in py_info_folder.path.iterdir():
+                if filename.suffix == ".json":
+                    with py_info_folder.lock_for_key(filename.stem):
+                        if filename.exists():
+                            filename.unlink()

+    def embed_update_log(self, distribution, for_py_version):
+        return EmbedDistributionUpdateStoreDisk(self.lock / "wheel" / for_py_version / "embed" / "3", distribution)

-class JSONStoreDisk(ContentStore, ABC):
+    @property
+    def house(self):
+        path = self.lock.path / "wheel" / "house"
+        path.mkdir(parents=True, exist_ok=True)
+        return path
+
+    def wheel_image(self, for_py_version, name):
+        return self.lock.path / "wheel" / for_py_version / "image" / "1" / name

-    def __init__(self, in_folder, key, msg, msg_args) ->None:
+
+class JSONStoreDisk(ContentStore, ABC):
+    def __init__(self, in_folder, key, msg, msg_args) -> None:
         self.in_folder = in_folder
         self.key = key
         self.msg = msg
-        self.msg_args = *msg_args, self.file
+        self.msg_args = (*msg_args, self.file)
+
+    @property
+    def file(self):
+        return self.in_folder.path / f"{self.key}.json"
+
+    def exists(self):
+        return self.file.exists()
+
+    def read(self):
+        data, bad_format = None, False
+        try:
+            data = json.loads(self.file.read_text(encoding="utf-8"))
+        except ValueError:
+            bad_format = True
+        except Exception:  # noqa: BLE001, S110
+            pass
+        else:
+            logging.debug("got %s from %s", self.msg, self.msg_args)
+            return data
+        if bad_format:
+            with suppress(OSError):  # reading and writing on the same file may cause race on multiple processes
+                self.remove()
+        return None
+
+    def remove(self):
+        self.file.unlink()
+        logging.debug("removed %s at %s", self.msg, self.msg_args)
+
+    @contextmanager
+    def locked(self):
+        with self.in_folder.lock_for_key(self.key):
+            yield
+
+    def write(self, content):
+        folder = self.file.parent
+        folder.mkdir(parents=True, exist_ok=True)
+        self.file.write_text(json.dumps(content, sort_keys=True, indent=2), encoding="utf-8")
+        logging.debug("wrote %s at %s", self.msg, self.msg_args)


 class PyInfoStoreDisk(JSONStoreDisk):
-
-    def __init__(self, in_folder, path) ->None:
-        key = sha256(str(path).encode('utf-8')).hexdigest()
-        super().__init__(in_folder, key, 'python info of %s', (path,))
+    def __init__(self, in_folder, path) -> None:
+        key = sha256(str(path).encode("utf-8")).hexdigest()
+        super().__init__(in_folder, key, "python info of %s", (path,))


 class EmbedDistributionUpdateStoreDisk(JSONStoreDisk):
-
-    def __init__(self, in_folder, distribution) ->None:
-        super().__init__(in_folder, distribution,
-            'embed update of distribution %s', (distribution,))
-
-
-__all__ = ['AppDataDiskFolder', 'JSONStoreDisk', 'PyInfoStoreDisk']
+    def __init__(self, in_folder, distribution) -> None:
+        super().__init__(
+            in_folder,
+            distribution,
+            "embed update of distribution %s",
+            (distribution,),
+        )
+
+
+__all__ = [
+    "AppDataDiskFolder",
+    "JSONStoreDisk",
+    "PyInfoStoreDisk",
+]
diff --git a/src/virtualenv/app_data/via_tempdir.py b/src/virtualenv/app_data/via_tempdir.py
index c3cae69..0a30dfe 100644
--- a/src/virtualenv/app_data/via_tempdir.py
+++ b/src/virtualenv/app_data/via_tempdir.py
@@ -1,7 +1,10 @@
 from __future__ import annotations
+
 import logging
 from tempfile import mkdtemp
+
 from virtualenv.util.path import safe_delete
+
 from .via_disk_folder import AppDataDiskFolder


@@ -9,13 +12,21 @@ class TempAppData(AppDataDiskFolder):
     transient = True
     can_update = False

-    def __init__(self) ->None:
+    def __init__(self) -> None:
         super().__init__(folder=mkdtemp())
-        logging.debug('created temporary app data folder %s', self.lock.path)
+        logging.debug("created temporary app data folder %s", self.lock.path)

     def reset(self):
         """This is a temporary folder, is already empty to start with."""
-        pass
+
+    def close(self):
+        logging.debug("remove temporary app data folder %s", self.lock.path)
+        safe_delete(self.lock.path)
+
+    def embed_update_log(self, distribution, for_py_version):
+        raise NotImplementedError


-__all__ = ['TempAppData']
+__all__ = [
+    "TempAppData",
+]
diff --git a/src/virtualenv/config/cli/parser.py b/src/virtualenv/config/cli/parser.py
index 6deeebf..9323d4e 100644
--- a/src/virtualenv/config/cli/parser.py
+++ b/src/virtualenv/config/cli/parser.py
@@ -1,56 +1,126 @@
 from __future__ import annotations
+
 import os
 from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
 from collections import OrderedDict
+
 from virtualenv.config.convert import get_type
 from virtualenv.config.env_var import get_env_var
 from virtualenv.config.ini import IniConfig


 class VirtualEnvOptions(Namespace):
-
-    def __init__(self, **kwargs) ->None:
+    def __init__(self, **kwargs) -> None:
         super().__init__(**kwargs)
         self._src = None
         self._sources = {}

-    def __setattr__(self, key, value) ->None:
-        if getattr(self, '_src', None) is not None:
+    def set_src(self, key, value, src):
+        setattr(self, key, value)
+        if src.startswith("env var"):
+            src = "env var"
+        self._sources[key] = src
+
+    def __setattr__(self, key, value) -> None:
+        if getattr(self, "_src", None) is not None:
             self._sources[key] = self._src
         super().__setattr__(key, value)

-    def __repr__(self) ->str:
-        return (
-            f"{type(self).__name__}({', '.join(f'{k}={v}' for k, v in vars(self).items() if not k.startswith('_'))})"
-            )
+    def get_source(self, key):
+        return self._sources.get(key)
+
+    @property
+    def verbosity(self):
+        if not hasattr(self, "verbose") and not hasattr(self, "quiet"):
+            return None
+        return max(self.verbose - self.quiet, 0)
+
+    def __repr__(self) -> str:
+        return f"{type(self).__name__}({', '.join(f'{k}={v}' for k, v in vars(self).items() if not k.startswith('_'))})"


 class VirtualEnvConfigParser(ArgumentParser):
     """Custom option parser which updates its defaults by checking the configuration files and environmental vars."""

-    def __init__(self, options=None, env=None, *args, **kwargs) ->None:
+    def __init__(self, options=None, env=None, *args, **kwargs) -> None:
         env = os.environ if env is None else env
         self.file_config = IniConfig(env)
         self.epilog_list = []
         self.env = env
-        kwargs['epilog'] = self.file_config.epilog
-        kwargs['add_help'] = False
-        kwargs['formatter_class'] = HelpFormatter
-        kwargs['prog'] = 'virtualenv'
+        kwargs["epilog"] = self.file_config.epilog
+        kwargs["add_help"] = False
+        kwargs["formatter_class"] = HelpFormatter
+        kwargs["prog"] = "virtualenv"
         super().__init__(*args, **kwargs)
         self._fixed = set()
         if options is not None and not isinstance(options, VirtualEnvOptions):
-            msg = 'options must be of type VirtualEnvOptions'
+            msg = "options must be of type VirtualEnvOptions"
             raise TypeError(msg)
         self.options = VirtualEnvOptions() if options is None else options
         self._interpreter = None
         self._app_data = None

+    def _fix_defaults(self):
+        for action in self._actions:
+            action_id = id(action)
+            if action_id not in self._fixed:
+                self._fix_default(action)
+                self._fixed.add(action_id)

-class HelpFormatter(ArgumentDefaultsHelpFormatter):
+    def _fix_default(self, action):
+        if hasattr(action, "default") and hasattr(action, "dest") and action.default != SUPPRESS:
+            as_type = get_type(action)
+            names = OrderedDict((i.lstrip("-").replace("-", "_"), None) for i in action.option_strings)
+            outcome = None
+            for name in names:
+                outcome = get_env_var(name, as_type, self.env)
+                if outcome is not None:
+                    break
+            if outcome is None and self.file_config:
+                for name in names:
+                    outcome = self.file_config.get(name, as_type)
+                    if outcome is not None:
+                        break
+            if outcome is not None:
+                action.default, action.default_source = outcome
+            else:
+                outcome = action.default, "default"
+            self.options.set_src(action.dest, *outcome)
+
+    def enable_help(self):
+        self._fix_defaults()
+        self.add_argument("-h", "--help", action="help", default=SUPPRESS, help="show this help message and exit")
+
+    def parse_known_args(self, args=None, namespace=None):
+        if namespace is None:
+            namespace = self.options
+        elif namespace is not self.options:
+            msg = "can only pass in parser.options"
+            raise ValueError(msg)
+        self._fix_defaults()
+        self.options._src = "cli"  # noqa: SLF001
+        try:
+            namespace.env = self.env
+            return super().parse_known_args(args, namespace=namespace)
+        finally:
+            self.options._src = None  # noqa: SLF001

-    def __init__(self, prog) ->None:
+
+class HelpFormatter(ArgumentDefaultsHelpFormatter):
+    def __init__(self, prog) -> None:
         super().__init__(prog, max_help_position=32, width=240)

+    def _get_help_string(self, action):
+        text = super()._get_help_string(action)
+        if hasattr(action, "default_source"):
+            default = " (default: %(default)s)"
+            if text.endswith(default):
+                text = f"{text[: -len(default)]} (default: %(default)s -> from %(default_source)s)"
+        return text
+

-__all__ = ['HelpFormatter', 'VirtualEnvConfigParser', 'VirtualEnvOptions']
+__all__ = [
+    "HelpFormatter",
+    "VirtualEnvConfigParser",
+    "VirtualEnvOptions",
+]
diff --git a/src/virtualenv/config/convert.py b/src/virtualenv/config/convert.py
index d0fdefb..ecd9d2b 100644
--- a/src/virtualenv/config/convert.py
+++ b/src/virtualenv/config/convert.py
@@ -1,36 +1,59 @@
 from __future__ import annotations
+
 import logging
 import os
 from typing import ClassVar


 class TypeData:
-
-    def __init__(self, default_type, as_type) ->None:
+    def __init__(self, default_type, as_type) -> None:
         self.default_type = default_type
         self.as_type = as_type

-    def __repr__(self) ->str:
-        return (
-            f'{self.__class__.__name__}(base={self.default_type}, as={self.as_type})'
-            )
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}(base={self.default_type}, as={self.as_type})"
+
+    def convert(self, value):
+        return self.default_type(value)


 class BoolType(TypeData):
-    BOOLEAN_STATES: ClassVar[dict[str, bool]] = {'1': True, 'yes': True,
-        'true': True, 'on': True, '0': False, 'no': False, 'false': False,
-        'off': False}
+    BOOLEAN_STATES: ClassVar[dict[str, bool]] = {
+        "1": True,
+        "yes": True,
+        "true": True,
+        "on": True,
+        "0": False,
+        "no": False,
+        "false": False,
+        "off": False,
+    }
+
+    def convert(self, value):
+        if value.lower() not in self.BOOLEAN_STATES:
+            msg = f"Not a boolean: {value}"
+            raise ValueError(msg)
+        return self.BOOLEAN_STATES[value.lower()]


 class NoneType(TypeData):
-    pass
+    def convert(self, value):
+        if not value:
+            return None
+        return str(value)


 class ListType(TypeData):
-
     def _validate(self):
         """no op."""
-        pass
+
+    def convert(self, value, flatten=True):  # noqa: ARG002, FBT002
+        values = self.split_values(value)
+        result = []
+        for a_value in values:
+            sub_values = a_value.split(os.pathsep)
+            result.extend(sub_values)
+        return [self.as_type(i) for i in result]

     def split_values(self, value):
         """
@@ -39,13 +62,39 @@ class ListType(TypeData):
         First this is done by newlines. If there were no newlines in the text,
         then we next try to split by comma.
         """
-        pass
+        if isinstance(value, (str, bytes)):
+            # Use `splitlines` rather than a custom check for whether there is
+            # more than one line. This ensures that the full `splitlines()`
+            # logic is supported here.
+            values = value.splitlines()
+            if len(values) <= 1:
+                values = value.split(",")
+            values = filter(None, [x.strip() for x in values])
+        else:
+            values = list(value)
+
+        return values


 def convert(value, as_type, source):
     """Convert the value as a given type where the value comes from the given source."""
-    pass
+    try:
+        return as_type.convert(value)
+    except Exception as exception:
+        logging.warning("%s failed to convert %r as %r because %r", source, value, as_type, exception)
+        raise


 _CONVERT = {bool: BoolType, type(None): NoneType, list: ListType}
-__all__ = ['convert', 'get_type']
+
+
+def get_type(action):
+    default_type = type(action.default)
+    as_type = default_type if action.type is None else action.type
+    return _CONVERT.get(default_type, TypeData)(default_type, as_type)
+
+
+__all__ = [
+    "convert",
+    "get_type",
+]
diff --git a/src/virtualenv/config/env_var.py b/src/virtualenv/config/env_var.py
index 1c12a68..e127234 100644
--- a/src/virtualenv/config/env_var.py
+++ b/src/virtualenv/config/env_var.py
@@ -1,5 +1,7 @@
 from __future__ import annotations
+
 from contextlib import suppress
+
 from .convert import convert


@@ -12,7 +14,17 @@ def get_env_var(key, as_type, env):
     :param env: environment variables to use
     :return:
     """
-    pass
+    environ_key = f"VIRTUALENV_{key.upper()}"
+    if env.get(environ_key):
+        value = env[environ_key]
+
+        with suppress(Exception):  # note the converter already logs a warning when failures happen
+            source = f"env var {environ_key}"
+            as_type = convert(value, as_type, source)
+            return as_type, source
+    return None


-__all__ = ['get_env_var']
+__all__ = [
+    "get_env_var",
+]
diff --git a/src/virtualenv/config/ini.py b/src/virtualenv/config/ini.py
index 572f07e..cd6ecf5 100644
--- a/src/virtualenv/config/ini.py
+++ b/src/virtualenv/config/ini.py
@@ -1,30 +1,33 @@
 from __future__ import annotations
+
 import logging
 import os
 from configparser import ConfigParser
 from pathlib import Path
 from typing import ClassVar
+
 from platformdirs import user_config_dir
+
 from .convert import convert


 class IniConfig:
-    VIRTUALENV_CONFIG_FILE_ENV_VAR: ClassVar[str] = 'VIRTUALENV_CONFIG_FILE'
-    STATE: ClassVar[dict[bool | None, str]] = {None: 'failed to parse', (
-        True): 'active', (False): 'missing'}
-    section = 'virtualenv'
+    VIRTUALENV_CONFIG_FILE_ENV_VAR: ClassVar[str] = "VIRTUALENV_CONFIG_FILE"
+    STATE: ClassVar[dict[bool | None, str]] = {None: "failed to parse", True: "active", False: "missing"}

-    def __init__(self, env=None) ->None:
+    section = "virtualenv"
+
+    def __init__(self, env=None) -> None:
         env = os.environ if env is None else env
         config_file = env.get(self.VIRTUALENV_CONFIG_FILE_ENV_VAR, None)
         self.is_env_var = config_file is not None
         if config_file is None:
-            config_file = Path(user_config_dir(appname='virtualenv',
-                appauthor='pypa')) / 'virtualenv.ini'
+            config_file = Path(user_config_dir(appname="virtualenv", appauthor="pypa")) / "virtualenv.ini"
         else:
             config_file = Path(config_file)
         self.config_file = config_file
         self._cache = {}
+
         exception = None
         self.has_config_file = None
         try:
@@ -37,13 +40,36 @@ class IniConfig:
                 self.config_parser = ConfigParser()
                 try:
                     self._load()
-                    self.has_virtualenv_section = (self.config_parser.
-                        has_section(self.section))
-                except Exception as exc:
+                    self.has_virtualenv_section = self.config_parser.has_section(self.section)
+                except Exception as exc:  # noqa: BLE001
                     exception = exc
         if exception is not None:
-            logging.error('failed to read config file %s because %r',
-                config_file, exception)
+            logging.error("failed to read config file %s because %r", config_file, exception)

-    def __bool__(self) ->bool:
+    def _load(self):
+        with self.config_file.open("rt", encoding="utf-8") as file_handler:
+            return self.config_parser.read_file(file_handler)
+
+    def get(self, key, as_type):
+        cache_key = key, as_type
+        if cache_key in self._cache:
+            return self._cache[cache_key]
+        try:
+            source = "file"
+            raw_value = self.config_parser.get(self.section, key.lower())
+            value = convert(raw_value, as_type, source)
+            result = value, source
+        except Exception:  # noqa: BLE001
+            result = None
+        self._cache[cache_key] = result
+        return result
+
+    def __bool__(self) -> bool:
         return bool(self.has_config_file) and bool(self.has_virtualenv_section)
+
+    @property
+    def epilog(self):
+        return (
+            f"\nconfig file {self.config_file} {self.STATE[self.has_config_file]} "
+            f"(change{'d' if self.is_env_var else ''} via env var {self.VIRTUALENV_CONFIG_FILE_ENV_VAR})"
+        )
diff --git a/src/virtualenv/create/creator.py b/src/virtualenv/create/creator.py
index 96df65b..7a98a51 100644
--- a/src/virtualenv/create/creator.py
+++ b/src/virtualenv/create/creator.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+
 import json
 import logging
 import os
@@ -8,25 +9,27 @@ from argparse import ArgumentTypeError
 from ast import literal_eval
 from collections import OrderedDict
 from pathlib import Path
+
 from virtualenv.discovery.cached_py_info import LogCmd
 from virtualenv.util.path import safe_delete
 from virtualenv.util.subprocess import run_cmd
 from virtualenv.version import __version__
+
 from .pyenv_cfg import PyEnvCfg
+
 HERE = Path(os.path.abspath(__file__)).parent
-DEBUG_SCRIPT = HERE / 'debug.py'
+DEBUG_SCRIPT = HERE / "debug.py"


 class CreatorMeta:
-
-    def __init__(self) ->None:
+    def __init__(self) -> None:
         self.error = None


 class Creator(ABC):
     """A class that given a python Interpreter creates a virtual environment."""

-    def __init__(self, options, interpreter) ->None:
+    def __init__(self, options, interpreter) -> None:
         """
         Construct a new virtual environment creator.

@@ -42,23 +45,29 @@ class Creator(ABC):
         self.app_data = options.app_data
         self.env = options.env

-    def __repr__(self) ->str:
-        return (
-            f"{self.__class__.__name__}({', '.join(f'{k}={v}' for k, v in self._args())})"
-            )
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}({', '.join(f'{k}={v}' for k, v in self._args())})"
+
+    def _args(self):
+        return [
+            ("dest", str(self.dest)),
+            ("clear", self.clear),
+            ("no_vcs_ignore", self.no_vcs_ignore),
+        ]

     @classmethod
-    def can_create(cls, interpreter):
+    def can_create(cls, interpreter):  # noqa: ARG003
         """
         Determine if we can create a virtual environment.

         :param interpreter: the interpreter in question
-        :return: ``None`` if we can't create, any other object otherwise that will be forwarded to                   :meth:`add_parser_arguments`
+        :return: ``None`` if we can't create, any other object otherwise that will be forwarded to \
+                  :meth:`add_parser_arguments`
         """
-        pass
+        return True

     @classmethod
-    def add_parser_arguments(cls, parser, interpreter, meta, app_data):
+    def add_parser_arguments(cls, parser, interpreter, meta, app_data):  # noqa: ARG003
         """
         Add CLI arguments for the creator.

@@ -67,26 +76,151 @@ class Creator(ABC):
         :param interpreter: the interpreter we're asked to create virtual environment for
         :param meta: value as returned by :meth:`can_create`
         """
-        pass
+        parser.add_argument(
+            "dest",
+            help="directory to create virtualenv at",
+            type=cls.validate_dest,
+        )
+        parser.add_argument(
+            "--clear",
+            dest="clear",
+            action="store_true",
+            help="remove the destination directory if exist before starting (will overwrite files otherwise)",
+            default=False,
+        )
+        parser.add_argument(
+            "--no-vcs-ignore",
+            dest="no_vcs_ignore",
+            action="store_true",
+            help="don't create VCS ignore directive in the destination directory",
+            default=False,
+        )

     @abstractmethod
     def create(self):
         """Perform the virtual environment creation."""
-        pass
+        raise NotImplementedError

     @classmethod
-    def validate_dest(cls, raw_value):
+    def validate_dest(cls, raw_value):  # noqa: C901
         """No path separator in the path, valid chars and must be write-able."""
-        pass
+
+        def non_write_able(dest, value):
+            common = Path(*os.path.commonprefix([value.parts, dest.parts]))
+            msg = f"the destination {dest.relative_to(common)} is not write-able at {common}"
+            raise ArgumentTypeError(msg)
+
+        # the file system must be able to encode
+        # note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/
+        encoding = sys.getfilesystemencoding()
+        refused = OrderedDict()
+        kwargs = {"errors": "ignore"} if encoding != "mbcs" else {}
+        for char in str(raw_value):
+            try:
+                trip = char.encode(encoding, **kwargs).decode(encoding)
+                if trip == char:
+                    continue
+                raise ValueError(trip)  # noqa: TRY301
+            except ValueError:
+                refused[char] = None
+        if refused:
+            bad = "".join(refused.keys())
+            msg = f"the file system codec ({encoding}) cannot handle characters {bad!r} within {raw_value!r}"
+            raise ArgumentTypeError(msg)
+        if os.pathsep in raw_value:
+            msg = (
+                f"destination {raw_value!r} must not contain the path separator ({os.pathsep})"
+                f" as this would break the activation scripts"
+            )
+            raise ArgumentTypeError(msg)
+
+        value = Path(raw_value)
+        if value.exists() and value.is_file():
+            msg = f"the destination {value} already exists and is a file"
+            raise ArgumentTypeError(msg)
+        dest = Path(os.path.abspath(str(value))).resolve()  # on Windows absolute does not imply resolve so use both
+        value = dest
+        while dest:
+            if dest.exists():
+                if os.access(str(dest), os.W_OK):
+                    break
+                non_write_able(dest, value)
+            base, _ = dest.parent, dest.name
+            if base == dest:
+                non_write_able(dest, value)  # pragma: no cover
+            dest = base
+        return str(value)
+
+    def run(self):
+        if self.dest.exists() and self.clear:
+            logging.debug("delete %s", self.dest)
+            safe_delete(self.dest)
+        self.create()
+        self.set_pyenv_cfg()
+        if not self.no_vcs_ignore:
+            self.setup_ignore_vcs()
+
+    def set_pyenv_cfg(self):
+        self.pyenv_cfg.content = OrderedDict()
+        self.pyenv_cfg["home"] = os.path.dirname(os.path.abspath(self.interpreter.system_executable))
+        self.pyenv_cfg["implementation"] = self.interpreter.implementation
+        self.pyenv_cfg["version_info"] = ".".join(str(i) for i in self.interpreter.version_info)
+        self.pyenv_cfg["virtualenv"] = __version__

     def setup_ignore_vcs(self):
         """Generate ignore instructions for version control systems."""
-        pass
+        # mark this folder to be ignored by VCS, handle https://www.python.org/dev/peps/pep-0610/#registered-vcs
+        git_ignore = self.dest / ".gitignore"
+        if not git_ignore.exists():
+            git_ignore.write_text("# created by virtualenv automatically\n*\n", encoding="utf-8")
+        # Mercurial - does not support the .hgignore file inside a subdirectory directly, but only if included via the
+        # subinclude directive from root, at which point on might as well ignore the directory itself, see
+        # https://www.selenic.com/mercurial/hgignore.5.html for more details
+        # Bazaar - does not support ignore files in sub-directories, only at root level via .bzrignore
+        # Subversion - does not support ignore files, requires direct manipulation with the svn tool

     @property
     def debug(self):
         """:return: debug information about the virtual environment (only valid after :meth:`create` has run)"""
-        pass
-
-
-__all__ = ['Creator', 'CreatorMeta']
+        if self._debug is None and self.exe is not None:
+            self._debug = get_env_debug_info(self.exe, self.debug_script(), self.app_data, self.env)
+        return self._debug
+
+    @staticmethod
+    def debug_script():
+        return DEBUG_SCRIPT
+
+
+def get_env_debug_info(env_exe, debug_script, app_data, env):
+    env = env.copy()
+    env.pop("PYTHONPATH", None)
+
+    with app_data.ensure_extracted(debug_script) as debug_script_extracted:
+        cmd = [str(env_exe), str(debug_script_extracted)]
+        logging.debug("debug via %r", LogCmd(cmd))
+        code, out, err = run_cmd(cmd)
+
+    try:
+        if code != 0:
+            if out:
+                result = literal_eval(out)
+            else:
+                if code == 2 and "file" in err:  # noqa: PLR2004
+                    # Re-raise FileNotFoundError from `run_cmd()`
+                    raise OSError(err)  # noqa: TRY301
+                raise Exception(err)  # noqa: TRY002, TRY301
+        else:
+            result = json.loads(out)
+        if err:
+            result["err"] = err
+    except Exception as exception:  # noqa: BLE001
+        return {"out": out, "err": err, "returncode": code, "exception": repr(exception)}
+    if "sys" in result and "path" in result["sys"]:
+        del result["sys"]["path"][0]
+    return result
+
+
+__all__ = [
+    "Creator",
+    "CreatorMeta",
+]
diff --git a/src/virtualenv/create/debug.py b/src/virtualenv/create/debug.py
index e728f1f..8a4845e 100644
--- a/src/virtualenv/create/debug.py
+++ b/src/virtualenv/create/debug.py
@@ -1,12 +1,102 @@
 """Inspect a target Python interpreter virtual environment wise."""
+
 from __future__ import annotations
-import sys
+
+import sys  # built-in
+
+
+def encode_path(value):
+    if value is None:
+        return None
+    if not isinstance(value, (str, bytes)):
+        value = repr(value) if isinstance(value, type) else repr(type(value))
+    if isinstance(value, bytes):
+        value = value.decode(sys.getfilesystemencoding())
+    return value
+
+
+def encode_list_path(value):
+    return [encode_path(i) for i in value]


 def run():
     """Print debug data about the virtual environment."""
-    pass
+    try:
+        from collections import OrderedDict  # noqa: PLC0415
+    except ImportError:  # pragma: no cover
+        # this is possible if the standard library cannot be accessed
+
+        OrderedDict = dict  # pragma: no cover  # noqa: N806
+    result = OrderedDict([("sys", OrderedDict())])
+    path_keys = (
+        "executable",
+        "_base_executable",
+        "prefix",
+        "base_prefix",
+        "real_prefix",
+        "exec_prefix",
+        "base_exec_prefix",
+        "path",
+        "meta_path",
+    )
+    for key in path_keys:
+        value = getattr(sys, key, None)
+        value = encode_list_path(value) if isinstance(value, list) else encode_path(value)
+        result["sys"][key] = value
+    result["sys"]["fs_encoding"] = sys.getfilesystemencoding()
+    result["sys"]["io_encoding"] = getattr(sys.stdout, "encoding", None)
+    result["version"] = sys.version
+
+    try:
+        import sysconfig  # noqa: PLC0415
+
+        # https://bugs.python.org/issue22199
+        makefile = getattr(sysconfig, "get_makefile_filename", getattr(sysconfig, "_get_makefile_filename", None))
+        result["makefile_filename"] = encode_path(makefile())
+    except ImportError:
+        pass
+
+    import os  # landmark  # noqa: PLC0415
+
+    result["os"] = repr(os)
+
+    try:
+        import site  # site  # noqa: PLC0415
+
+        result["site"] = repr(site)
+    except ImportError as exception:  # pragma: no cover
+        result["site"] = repr(exception)  # pragma: no cover
+
+    try:
+        import datetime  # site  # noqa: PLC0415
+
+        result["datetime"] = repr(datetime)
+    except ImportError as exception:  # pragma: no cover
+        result["datetime"] = repr(exception)  # pragma: no cover
+
+    try:
+        import math  # site  # noqa: PLC0415
+
+        result["math"] = repr(math)
+    except ImportError as exception:  # pragma: no cover
+        result["math"] = repr(exception)  # pragma: no cover
+
+    # try to print out, this will validate if other core modules are available (json in this case)
+    try:
+        import json  # noqa: PLC0415
+
+        result["json"] = repr(json)
+    except ImportError as exception:
+        result["json"] = repr(exception)
+    else:
+        try:
+            content = json.dumps(result, indent=2)
+            sys.stdout.write(content)
+        except (ValueError, TypeError) as exception:  # pragma: no cover
+            sys.stderr.write(repr(exception))
+            sys.stdout.write(repr(result))  # pragma: no cover
+            raise SystemExit(1)  # noqa: B904  # pragma: no cover


-if __name__ == '__main__':
+if __name__ == "__main__":
     run()
diff --git a/src/virtualenv/create/describe.py b/src/virtualenv/create/describe.py
index 5d7690c..1ee250c 100644
--- a/src/virtualenv/create/describe.py
+++ b/src/virtualenv/create/describe.py
@@ -1,15 +1,18 @@
 from __future__ import annotations
+
 from abc import ABC
 from collections import OrderedDict
 from pathlib import Path
+
 from virtualenv.info import IS_WIN


 class Describe:
     """Given a host interpreter tell us information about what the created interpreter might look like."""
-    suffix = '.exe' if IS_WIN else ''

-    def __init__(self, dest, interpreter) ->None:
+    suffix = ".exe" if IS_WIN else ""
+
+    def __init__(self, dest, interpreter) -> None:
         self.interpreter = interpreter
         self.dest = dest
         self._stdlib = None
@@ -17,27 +20,91 @@ class Describe:
         self._system_stdlib = None
         self._conf_vars = None

+    @property
+    def bin_dir(self):
+        return self.script_dir
+
+    @property
+    def script_dir(self):
+        return self.dest / self.interpreter.install_path("scripts")
+
+    @property
+    def purelib(self):
+        return self.dest / self.interpreter.install_path("purelib")
+
+    @property
+    def platlib(self):
+        return self.dest / self.interpreter.install_path("platlib")
+
+    @property
+    def libs(self):
+        return list(OrderedDict(((self.platlib, None), (self.purelib, None))).keys())
+
+    @property
+    def stdlib(self):
+        if self._stdlib is None:
+            self._stdlib = Path(self.interpreter.sysconfig_path("stdlib", config_var=self._config_vars))
+        return self._stdlib
+
+    @property
+    def stdlib_platform(self):
+        if self._stdlib_platform is None:
+            self._stdlib_platform = Path(self.interpreter.sysconfig_path("platstdlib", config_var=self._config_vars))
+        return self._stdlib_platform
+
+    @property
+    def _config_vars(self):
+        if self._conf_vars is None:
+            self._conf_vars = self._calc_config_vars(self.dest)
+        return self._conf_vars
+
+    def _calc_config_vars(self, to):
+        sys_vars = self.interpreter.sysconfig_vars
+        return {k: (to if v is not None and v.startswith(self.interpreter.prefix) else v) for k, v in sys_vars.items()}
+
     @classmethod
-    def can_describe(cls, interpreter):
+    def can_describe(cls, interpreter):  # noqa: ARG003
         """Knows means it knows how the output will look."""
-        pass
+        return True
+
+    @property
+    def env_name(self):
+        return self.dest.parts[-1]
+
+    @property
+    def exe(self):
+        return self.bin_dir / f"{self.exe_stem()}{self.suffix}"

     @classmethod
     def exe_stem(cls):
         """Executable name without suffix - there seems to be no standard way to get this without creating it."""
-        pass
+        raise NotImplementedError
+
+    def script(self, name):
+        return self.script_dir / f"{name}{self.suffix}"


 class Python3Supports(Describe, ABC):
-    pass
+    @classmethod
+    def can_describe(cls, interpreter):
+        return interpreter.version_info.major == 3 and super().can_describe(interpreter)  # noqa: PLR2004


 class PosixSupports(Describe, ABC):
-    pass
+    @classmethod
+    def can_describe(cls, interpreter):
+        return interpreter.os == "posix" and super().can_describe(interpreter)


 class WindowsSupports(Describe, ABC):
-    pass
+    @classmethod
+    def can_describe(cls, interpreter):
+        return interpreter.os == "nt" and super().can_describe(interpreter)


-__all__ = ['Describe', 'PosixSupports', 'Python3Supports', 'WindowsSupports']
+__all__ = [
+    "Describe",
+    "PosixSupports",
+    "Python3Supports",
+    "WindowsSupports",
+]
diff --git a/src/virtualenv/create/pyenv_cfg.py b/src/virtualenv/create/pyenv_cfg.py
index a47c2ac..04883de 100644
--- a/src/virtualenv/create/pyenv_cfg.py
+++ b/src/virtualenv/create/pyenv_cfg.py
@@ -1,26 +1,66 @@
 from __future__ import annotations
+
 import logging
 import os
 from collections import OrderedDict


 class PyEnvCfg:
-
-    def __init__(self, content, path) ->None:
+    def __init__(self, content, path) -> None:
         self.content = content
         self.path = path

-    def __setitem__(self, key, value) ->None:
+    @classmethod
+    def from_folder(cls, folder):
+        return cls.from_file(folder / "pyvenv.cfg")
+
+    @classmethod
+    def from_file(cls, path):
+        content = cls._read_values(path) if path.exists() else OrderedDict()
+        return PyEnvCfg(content, path)
+
+    @staticmethod
+    def _read_values(path):
+        content = OrderedDict()
+        for line in path.read_text(encoding="utf-8").splitlines():
+            equals_at = line.index("=")
+            key = line[:equals_at].strip()
+            value = line[equals_at + 1 :].strip()
+            content[key] = value
+        return content
+
+    def write(self):
+        logging.debug("write %s", self.path)
+        text = ""
+        for key, value in self.content.items():
+            normalized_value = os.path.realpath(value) if value and os.path.exists(value) else value
+            line = f"{key} = {normalized_value}"
+            logging.debug("\t%s", line)
+            text += line
+            text += "\n"
+        self.path.write_text(text, encoding="utf-8")
+
+    def refresh(self):
+        self.content = self._read_values(self.path)
+        return self.content
+
+    def __setitem__(self, key, value) -> None:
         self.content[key] = value

     def __getitem__(self, key):
         return self.content[key]

-    def __contains__(self, item) ->bool:
+    def __contains__(self, item) -> bool:
         return item in self.content

-    def __repr__(self) ->str:
-        return f'{self.__class__.__name__}(path={self.path})'
+    def update(self, other):
+        self.content.update(other)
+        return self
+
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}(path={self.path})"


-__all__ = ['PyEnvCfg']
+__all__ = [
+    "PyEnvCfg",
+]
diff --git a/src/virtualenv/create/via_global_ref/_virtualenv.py b/src/virtualenv/create/via_global_ref/_virtualenv.py
index e27c1e4..b61db30 100644
--- a/src/virtualenv/create/via_global_ref/_virtualenv.py
+++ b/src/virtualenv/create/via_global_ref/_virtualenv.py
@@ -1,7 +1,10 @@
 """Patches that are applied at runtime to the virtual environment."""
+
 from __future__ import annotations
+
 import os
 import sys
+
 VIRTUALENV_PATCH_FILE = os.path.join(__file__)


@@ -11,17 +14,90 @@ def patch_dist(dist):
     https://docs.python.org/3/install/index.html#distutils-configuration-files.

     Some of this arguments though don't make sense in context of the virtual environment files, let's fix them up.
-    """
-    pass
+    """  # noqa: D205
+    # we cannot allow some install config as that would get packages installed outside of the virtual environment
+    old_parse_config_files = dist.Distribution.parse_config_files

+    def parse_config_files(self, *args, **kwargs):
+        result = old_parse_config_files(self, *args, **kwargs)
+        install = self.get_option_dict("install")

-_DISTUTILS_PATCH = 'distutils.dist', 'setuptools.dist'
+        if "prefix" in install:  # the prefix governs where to install the libraries
+            install["prefix"] = VIRTUALENV_PATCH_FILE, os.path.abspath(sys.prefix)
+        for base in ("purelib", "platlib", "headers", "scripts", "data"):
+            key = f"install_{base}"
+            if key in install:  # do not allow global configs to hijack venv paths
+                install.pop(key, None)
+        return result
+
+    dist.Distribution.parse_config_files = parse_config_files
+
+
+# Import hook that patches some modules to ignore configuration values that break package installation in case
+# of virtual environments.
+_DISTUTILS_PATCH = "distutils.dist", "setuptools.dist"
+# https://docs.python.org/3/library/importlib.html#setting-up-an-importer


 class _Finder:
     """A meta path finder that allows patching the imported distutils modules."""
+
     fullname = None
-    lock = []
+
+    # lock[0] is threading.Lock(), but initialized lazily to avoid importing threading very early at startup,
+    # because there are gevent-based applications that need to be first to import threading by themselves.
+    # See https://github.com/pypa/virtualenv/issues/1895 for details.
+    lock = []  # noqa: RUF012
+
+    def find_spec(self, fullname, path, target=None):  # noqa: ARG002
+        if fullname in _DISTUTILS_PATCH and self.fullname is None:  # noqa: PLR1702
+            # initialize lock[0] lazily
+            if len(self.lock) == 0:
+                import threading  # noqa: PLC0415
+
+                lock = threading.Lock()
+                # there is possibility that two threads T1 and T2 are simultaneously running into find_spec,
+                # observing .lock as empty, and further going into hereby initialization. However due to the GIL,
+                # list.append() operation is atomic and this way only one of the threads will "win" to put the lock
+                # - that every thread will use - into .lock[0].
+                # https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
+                self.lock.append(lock)
+
+            from functools import partial  # noqa: PLC0415
+            from importlib.util import find_spec  # noqa: PLC0415
+
+            with self.lock[0]:
+                self.fullname = fullname
+                try:
+                    spec = find_spec(fullname, path)
+                    if spec is not None:
+                        # https://www.python.org/dev/peps/pep-0451/#how-loading-will-work
+                        is_new_api = hasattr(spec.loader, "exec_module")
+                        func_name = "exec_module" if is_new_api else "load_module"
+                        old = getattr(spec.loader, func_name)
+                        func = self.exec_module if is_new_api else self.load_module
+                        if old is not func:
+                            try:  # noqa: SIM105
+                                setattr(spec.loader, func_name, partial(func, old))
+                            except AttributeError:
+                                pass  # C-Extension loaders are r/o such as zipimporter with <3.7
+                        return spec
+                finally:
+                    self.fullname = None
+        return None
+
+    @staticmethod
+    def exec_module(old, module):
+        old(module)
+        if module.__name__ in _DISTUTILS_PATCH:
+            patch_dist(module)
+
+    @staticmethod
+    def load_module(old, name):
+        module = old(name)
+        if module.__name__ in _DISTUTILS_PATCH:
+            patch_dist(module)
+        return module


 sys.meta_path.insert(0, _Finder())
diff --git a/src/virtualenv/create/via_global_ref/api.py b/src/virtualenv/create/via_global_ref/api.py
index 942e93c..d29067c 100644
--- a/src/virtualenv/create/via_global_ref/api.py
+++ b/src/virtualenv/create/via_global_ref/api.py
@@ -1,32 +1,114 @@
 from __future__ import annotations
+
 import logging
 import os
 from abc import ABC
 from pathlib import Path
+
 from virtualenv.create.creator import Creator, CreatorMeta
 from virtualenv.info import fs_supports_symlink


 class ViaGlobalRefMeta(CreatorMeta):
-
-    def __init__(self) ->None:
+    def __init__(self) -> None:
         super().__init__()
         self.copy_error = None
         self.symlink_error = None
         if not fs_supports_symlink():
-            self.symlink_error = 'the filesystem does not supports symlink'
+            self.symlink_error = "the filesystem does not supports symlink"

+    @property
+    def can_copy(self):
+        return not self.copy_error
+
+    @property
+    def can_symlink(self):
+        return not self.symlink_error

-class ViaGlobalRefApi(Creator, ABC):

-    def __init__(self, options, interpreter) ->None:
+class ViaGlobalRefApi(Creator, ABC):
+    def __init__(self, options, interpreter) -> None:
         super().__init__(options, interpreter)
         self.symlinks = self._should_symlink(options)
         self.enable_system_site_package = options.system_site

+    @staticmethod
+    def _should_symlink(options):
+        # Priority of where the option is set to follow the order: CLI, env var, file, hardcoded.
+        # If both set at same level prefers copy over symlink.
+        copies, symlinks = getattr(options, "copies", False), getattr(options, "symlinks", False)
+        copy_src, sym_src = options.get_source("copies"), options.get_source("symlinks")
+        for level in ["cli", "env var", "file", "default"]:
+            s_opt = symlinks if sym_src == level else None
+            c_opt = copies if copy_src == level else None
+            if s_opt is True and c_opt is True:
+                return False
+            if s_opt is True:
+                return True
+            if c_opt is True:
+                return False
+        return False  # fallback to copy
+
+    @classmethod
+    def add_parser_arguments(cls, parser, interpreter, meta, app_data):
+        super().add_parser_arguments(parser, interpreter, meta, app_data)
+        parser.add_argument(
+            "--system-site-packages",
+            default=False,
+            action="store_true",
+            dest="system_site",
+            help="give the virtual environment access to the system site-packages dir",
+        )
+        if not meta.can_symlink and not meta.can_copy:
+            msg = "neither symlink or copy method supported"
+            raise RuntimeError(msg)
+        group = parser.add_mutually_exclusive_group()
+        if meta.can_symlink:
+            group.add_argument(
+                "--symlinks",
+                default=True,
+                action="store_true",
+                dest="symlinks",
+                help="try to use symlinks rather than copies, when symlinks are not the default for the platform",
+            )
+        if meta.can_copy:
+            group.add_argument(
+                "--copies",
+                "--always-copy",
+                default=not meta.can_symlink,
+                action="store_true",
+                dest="copies",
+                help="try to use copies rather than symlinks, even when symlinks are the default for the platform",
+            )
+
+    def create(self):
+        self.install_patch()
+
+    def install_patch(self):
+        text = self.env_patch_text()
+        if text:
+            pth = self.purelib / "_virtualenv.pth"
+            logging.debug("create virtualenv import hook file %s", pth)
+            pth.write_text("import _virtualenv", encoding="utf-8")
+            dest_path = self.purelib / "_virtualenv.py"
+            logging.debug("create %s", dest_path)
+            dest_path.write_text(text, encoding="utf-8")
+
     def env_patch_text(self):
         """Patch the distutils package to not be derailed by its configuration files."""
-        pass
+        with self.app_data.ensure_extracted(Path(__file__).parent / "_virtualenv.py") as resolved_path:
+            text = resolved_path.read_text(encoding="utf-8")
+            return text.replace('"__SCRIPT_DIR__"', repr(os.path.relpath(str(self.script_dir), str(self.purelib))))
+
+    def _args(self):
+        return [*super()._args(), ("global", self.enable_system_site_package)]
+
+    def set_pyenv_cfg(self):
+        super().set_pyenv_cfg()
+        self.pyenv_cfg["include-system-site-packages"] = "true" if self.enable_system_site_package else "false"


-__all__ = ['ViaGlobalRefApi', 'ViaGlobalRefMeta']
+__all__ = [
+    "ViaGlobalRefApi",
+    "ViaGlobalRefMeta",
+]
diff --git a/src/virtualenv/create/via_global_ref/builtin/builtin_way.py b/src/virtualenv/create/via_global_ref/builtin/builtin_way.py
index 69e3068..791b1d9 100644
--- a/src/virtualenv/create/via_global_ref/builtin/builtin_way.py
+++ b/src/virtualenv/create/via_global_ref/builtin/builtin_way.py
@@ -1,5 +1,7 @@
 from __future__ import annotations
+
 from abc import ABC
+
 from virtualenv.create.creator import Creator
 from virtualenv.create.describe import Describe

@@ -7,9 +9,11 @@ from virtualenv.create.describe import Describe
 class VirtualenvBuiltin(Creator, Describe, ABC):
     """A creator that does operations itself without delegation, if we can create it we can also describe it."""

-    def __init__(self, options, interpreter) ->None:
+    def __init__(self, options, interpreter) -> None:
         Creator.__init__(self, options, interpreter)
         Describe.__init__(self, self.dest, interpreter)


-__all__ = ['VirtualenvBuiltin']
+__all__ = [
+    "VirtualenvBuiltin",
+]
diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/common.py b/src/virtualenv/create/via_global_ref/builtin/cpython/common.py
index 4560e8d..2c28f37 100644
--- a/src/virtualenv/create/via_global_ref/builtin/cpython/common.py
+++ b/src/virtualenv/create/via_global_ref/builtin/cpython/common.py
@@ -1,27 +1,73 @@
 from __future__ import annotations
+
 import re
 from abc import ABC
 from collections import OrderedDict
 from pathlib import Path
+
 from virtualenv.create.describe import PosixSupports, WindowsSupports
 from virtualenv.create.via_global_ref.builtin.ref import RefMust, RefWhen
 from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin


 class CPython(ViaGlobalRefVirtualenvBuiltin, ABC):
-    pass
+    @classmethod
+    def can_describe(cls, interpreter):
+        return interpreter.implementation == "CPython" and super().can_describe(interpreter)
+
+    @classmethod
+    def exe_stem(cls):
+        return "python"


 class CPythonPosix(CPython, PosixSupports, ABC):
     """Create a CPython virtual environment on POSIX platforms."""

+    @classmethod
+    def _executables(cls, interpreter):
+        host_exe = Path(interpreter.system_executable)
+        major, minor = interpreter.version_info.major, interpreter.version_info.minor
+        targets = OrderedDict((i, None) for i in ["python", f"python{major}", f"python{major}.{minor}", host_exe.name])
+        yield host_exe, list(targets.keys()), RefMust.NA, RefWhen.ANY
+

 class CPythonWindows(CPython, WindowsSupports, ABC):
-    pass
+    @classmethod
+    def _executables(cls, interpreter):
+        # symlink of the python executables does not work reliably, copy always instead
+        # - https://bugs.python.org/issue42013
+        # - venv
+        host = cls.host_python(interpreter)
+        for path in (host.parent / n for n in {"python.exe", host.name}):  # noqa: PLC0208
+            yield host, [path.name], RefMust.COPY, RefWhen.ANY
+        # for more info on pythonw.exe see https://stackoverflow.com/a/30313091
+        python_w = host.parent / "pythonw.exe"
+        yield python_w, [python_w.name], RefMust.COPY, RefWhen.ANY
+
+    @classmethod
+    def host_python(cls, interpreter):
+        return Path(interpreter.system_executable)
+
+
+def is_mac_os_framework(interpreter):
+    if interpreter.platform == "darwin":
+        return interpreter.sysconfig_vars.get("PYTHONFRAMEWORK") == "Python3"
+    return False
+
+
+def is_macos_brew(interpreter):
+    return interpreter.platform == "darwin" and _BREW.fullmatch(interpreter.system_prefix) is not None


 _BREW = re.compile(
-    '/(usr/local|opt/homebrew)/(opt/python@3\\.\\d{1,2}|Cellar/python@3\\.\\d{1,2}/3\\.\\d{1,2}\\.\\d{1,2})/Frameworks/Python\\.framework/Versions/3\\.\\d{1,2}'
-    )
-__all__ = ['CPython', 'CPythonPosix', 'CPythonWindows',
-    'is_mac_os_framework', 'is_macos_brew']
+    r"/(usr/local|opt/homebrew)/(opt/python@3\.\d{1,2}|Cellar/python@3\.\d{1,2}/3\.\d{1,2}\.\d{1,2})/Frameworks/"
+    r"Python\.framework/Versions/3\.\d{1,2}",
+)
+
+__all__ = [
+    "CPython",
+    "CPythonPosix",
+    "CPythonWindows",
+    "is_mac_os_framework",
+    "is_macos_brew",
+]
diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py
index fd977a0..daa4741 100644
--- a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py
+++ b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py
@@ -1,13 +1,16 @@
 from __future__ import annotations
+
 import abc
 import fnmatch
 from itertools import chain
 from operator import methodcaller as method
 from pathlib import Path
 from textwrap import dedent
+
 from virtualenv.create.describe import Python3Supports
 from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest
 from virtualenv.create.via_global_ref.store import is_store_python
+
 from .common import CPython, CPythonPosix, CPythonWindows, is_mac_os_framework, is_macos_brew


@@ -16,12 +19,92 @@ class CPython3(CPython, Python3Supports, abc.ABC):


 class CPython3Posix(CPythonPosix, CPython3):
-    pass
+    @classmethod
+    def can_describe(cls, interpreter):
+        return (
+            is_mac_os_framework(interpreter) is False
+            and is_macos_brew(interpreter) is False
+            and super().can_describe(interpreter)
+        )
+
+    def env_patch_text(self):
+        text = super().env_patch_text()
+        if self.pyvenv_launch_patch_active(self.interpreter):
+            text += dedent(
+                """
+                # for https://github.com/python/cpython/pull/9516, see https://github.com/pypa/virtualenv/issues/1704
+                import os
+                if "__PYVENV_LAUNCHER__" in os.environ:
+                    del os.environ["__PYVENV_LAUNCHER__"]
+                """,
+            )
+        return text
+
+    @classmethod
+    def pyvenv_launch_patch_active(cls, interpreter):
+        ver = interpreter.version_info
+        return interpreter.platform == "darwin" and ((3, 7, 8) > ver >= (3, 7) or (3, 8, 3) > ver >= (3, 8))


 class CPython3Windows(CPythonWindows, CPython3):
     """CPython 3 on Windows."""

+    @classmethod
+    def setup_meta(cls, interpreter):
+        if is_store_python(interpreter):  # store python is not supported here
+            return None
+        return super().setup_meta(interpreter)
+
+    @classmethod
+    def sources(cls, interpreter):
+        if cls.has_shim(interpreter):
+            refs = cls.executables(interpreter)
+        else:
+            refs = chain(
+                cls.executables(interpreter),
+                cls.dll_and_pyd(interpreter),
+                cls.python_zip(interpreter),
+            )
+        yield from refs
+
+    @classmethod
+    def executables(cls, interpreter):
+        return super().sources(interpreter)
+
+    @classmethod
+    def has_shim(cls, interpreter):
+        return interpreter.version_info.minor >= 7 and cls.shim(interpreter) is not None  # noqa: PLR2004
+
+    @classmethod
+    def shim(cls, interpreter):
+        shim = Path(interpreter.system_stdlib) / "venv" / "scripts" / "nt" / "python.exe"
+        if shim.exists():
+            return shim
+        return None
+
+    @classmethod
+    def host_python(cls, interpreter):
+        if cls.has_shim(interpreter):
+            # starting with CPython 3.7 Windows ships with a venvlauncher.exe that avoids the need for dll/pyd copies
+            # it also means the wrapper must be copied to avoid bugs such as https://bugs.python.org/issue42013
+            return cls.shim(interpreter)
+        return super().host_python(interpreter)
+
+    @classmethod
+    def dll_and_pyd(cls, interpreter):
+        folders = [Path(interpreter.system_executable).parent]
+
+        # May be missing on some Python hosts.
+        # See https://github.com/pypa/virtualenv/issues/2368
+        dll_folder = Path(interpreter.system_prefix) / "DLLs"
+        if dll_folder.is_dir():
+            folders.append(dll_folder)
+
+        for folder in folders:
+            for file in folder.iterdir():
+                if file.suffix in {".pyd", ".dll"}:
+                    yield PathRefToDest(file, cls.to_bin)
+
     @classmethod
     def python_zip(cls, interpreter):
         """
@@ -35,8 +118,18 @@ class CPython3Windows(CPythonWindows, CPython3):
         "python{VERSION}.zip" and "python{VERSION}._pth" files. User can
         move/rename *zip* file and edit `sys.path` by editing *_pth* file.
         Here the `pattern` is used only for the default *zip* file name!
-        """
-        pass
+        """  # noqa: D205
+        pattern = f"*python{interpreter.version_nodot}.zip"
+        matches = fnmatch.filter(interpreter.path, pattern)
+        matched_paths = map(Path, matches)
+        existing_paths = filter(method("exists"), matched_paths)
+        path = next(existing_paths, None)
+        if path is not None:
+            yield PathRefToDest(path, cls.to_bin)


-__all__ = ['CPython3', 'CPython3Posix', 'CPython3Windows']
+__all__ = [
+    "CPython3",
+    "CPython3Posix",
+    "CPython3Windows",
+]
diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py b/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py
index ed427f7..b5e9313 100644
--- a/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py
+++ b/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py
@@ -1,5 +1,7 @@
 """The Apple Framework builds require their own customization."""
+
 from __future__ import annotations
+
 import logging
 import os
 import struct
@@ -7,18 +9,85 @@ import subprocess
 from abc import ABC, abstractmethod
 from pathlib import Path
 from textwrap import dedent
-from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, PathRefToDest, RefMust
+
+from virtualenv.create.via_global_ref.builtin.ref import (
+    ExePathRefToDest,
+    PathRefToDest,
+    RefMust,
+)
 from virtualenv.create.via_global_ref.builtin.via_global_self_do import BuiltinViaGlobalRefMeta
+
 from .common import CPython, CPythonPosix, is_mac_os_framework, is_macos_brew
 from .cpython3 import CPython3


 class CPythonmacOsFramework(CPython, ABC):
-    pass
+    @classmethod
+    def can_describe(cls, interpreter):
+        return is_mac_os_framework(interpreter) and super().can_describe(interpreter)
+
+    def create(self):
+        super().create()
+
+        # change the install_name of the copied python executables
+        target = self.desired_mach_o_image_path()
+        current = self.current_mach_o_image_path()
+        for src in self._sources:
+            if isinstance(src, ExePathRefToDest) and (src.must == RefMust.COPY or not self.symlinks):
+                exes = [self.bin_dir / src.base]
+                if not self.symlinks:
+                    exes.extend(self.bin_dir / a for a in src.aliases)
+                for exe in exes:
+                    fix_mach_o(str(exe), current, target, self.interpreter.max_size)
+
+    @classmethod
+    def _executables(cls, interpreter):
+        for _, targets, must, when in super()._executables(interpreter):
+            # Make sure we use the embedded interpreter inside the framework, even if sys.executable points to the
+            # stub executable in ${sys.prefix}/bin.
+            # See http://groups.google.com/group/python-virtualenv/browse_thread/thread/17cab2f85da75951
+            fixed_host_exe = Path(interpreter.prefix) / "Resources" / "Python.app" / "Contents" / "MacOS" / "Python"
+            yield fixed_host_exe, targets, must, when
+
+    @abstractmethod
+    def current_mach_o_image_path(self):
+        raise NotImplementedError
+
+    @abstractmethod
+    def desired_mach_o_image_path(self):
+        raise NotImplementedError


 class CPython3macOsFramework(CPythonmacOsFramework, CPython3, CPythonPosix):
-    pass
+    def current_mach_o_image_path(self):
+        return "@executable_path/../../../../Python3"
+
+    def desired_mach_o_image_path(self):
+        return "@executable_path/../.Python"
+
+    @classmethod
+    def sources(cls, interpreter):
+        yield from super().sources(interpreter)
+
+        # add a symlink to the host python image
+        exe = Path(interpreter.prefix) / "Python3"
+        yield PathRefToDest(exe, dest=lambda self, _: self.dest / ".Python", must=RefMust.SYMLINK)
+
+    @property
+    def reload_code(self):
+        result = super().reload_code
+        return dedent(
+            f"""
+        # the bundled site.py always adds the global site package if we're on python framework build, escape this
+        import sys
+        before = sys._framework
+        try:
+            sys._framework = None
+            {result}
+        finally:
+            sys._framework = before
+        """,
+        )


 def fix_mach_o(exe, current, new, max_size):
@@ -45,12 +114,166 @@ def fix_mach_o(exe, current, new, max_size):
     (found in the __LINKEDIT section) function. In 10.6 these new Link Edit tables are compressed by removing unused and
     unneeded bits of information, however Mac OS X 10.5 and earlier cannot read this new Link Edit table format.
     """
-    pass
+    try:
+        logging.debug("change Mach-O for %s from %s to %s", exe, current, new)
+        _builtin_change_mach_o(max_size)(exe, current, new)
+    except Exception as e:  # noqa: BLE001
+        logging.warning("Could not call _builtin_change_mac_o: %s. Trying to call install_name_tool instead.", e)
+        try:
+            cmd = ["install_name_tool", "-change", current, new, exe]
+            subprocess.check_call(cmd)  # noqa: S603
+        except Exception:
+            logging.fatal("Could not call install_name_tool -- you must have Apple's development tools installed")
+            raise
+
+
+def _builtin_change_mach_o(maxint):  # noqa: C901
+    MH_MAGIC = 0xFEEDFACE  # noqa: N806
+    MH_CIGAM = 0xCEFAEDFE  # noqa: N806
+    MH_MAGIC_64 = 0xFEEDFACF  # noqa: N806
+    MH_CIGAM_64 = 0xCFFAEDFE  # noqa: N806
+    FAT_MAGIC = 0xCAFEBABE  # noqa: N806
+    BIG_ENDIAN = ">"  # noqa: N806
+    LITTLE_ENDIAN = "<"  # noqa: N806
+    LC_LOAD_DYLIB = 0xC  # noqa: N806
+
+    class FileView:
+        """A proxy for file-like objects that exposes a given view of a file. Modified from macholib."""
+
+        def __init__(self, file_obj, start=0, size=maxint) -> None:
+            if isinstance(file_obj, FileView):
+                self._file_obj = file_obj._file_obj  # noqa: SLF001
+            else:
+                self._file_obj = file_obj
+            self._start = start
+            self._end = start + size
+            self._pos = 0
+
+        def __repr__(self) -> str:
+            return f"<fileview [{self._start:d}, {self._end:d}] {self._file_obj!r}>"
+
+        def tell(self):
+            return self._pos
+
+        def _checkwindow(self, seek_to, op):
+            if not (self._start <= seek_to <= self._end):
+                msg = f"{op} to offset {seek_to:d} is outside window [{self._start:d}, {self._end:d}]"
+                raise OSError(msg)
+
+        def seek(self, offset, whence=0):
+            seek_to = offset
+            if whence == os.SEEK_SET:
+                seek_to += self._start
+            elif whence == os.SEEK_CUR:
+                seek_to += self._start + self._pos
+            elif whence == os.SEEK_END:
+                seek_to += self._end
+            else:
+                msg = f"Invalid whence argument to seek: {whence!r}"
+                raise OSError(msg)
+            self._checkwindow(seek_to, "seek")
+            self._file_obj.seek(seek_to)
+            self._pos = seek_to - self._start
+
+        def write(self, content):
+            here = self._start + self._pos
+            self._checkwindow(here, "write")
+            self._checkwindow(here + len(content), "write")
+            self._file_obj.seek(here, os.SEEK_SET)
+            self._file_obj.write(content)
+            self._pos += len(content)
+
+        def read(self, size=maxint):
+            assert size >= 0  # noqa: S101
+            here = self._start + self._pos
+            self._checkwindow(here, "read")
+            size = min(size, self._end - here)
+            self._file_obj.seek(here, os.SEEK_SET)
+            read_bytes = self._file_obj.read(size)
+            self._pos += len(read_bytes)
+            return read_bytes
+
+    def read_data(file, endian, num=1):
+        """Read a given number of 32-bits unsigned integers from the given file with the given endianness."""
+        res = struct.unpack(endian + "L" * num, file.read(num * 4))
+        if len(res) == 1:
+            return res[0]
+        return res
+
+    def mach_o_change(at_path, what, value):  # noqa: C901
+        """
+        Replace a given name (what) in any LC_LOAD_DYLIB command found in the given binary with a new name (value),
+        provided it's shorter.
+        """  # noqa: D205
+
+        def do_macho(file, bits, endian):
+            # Read Mach-O header (the magic number is assumed read by the caller)
+            _cpu_type, _cpu_sub_type, _file_type, n_commands, _size_of_commands, _flags = read_data(file, endian, 6)
+            # 64-bits header has one more field.
+            if bits == 64:  # noqa: PLR2004
+                read_data(file, endian)
+            # The header is followed by n commands
+            for _ in range(n_commands):
+                where = file.tell()
+                # Read command header
+                cmd, cmd_size = read_data(file, endian, 2)
+                if cmd == LC_LOAD_DYLIB:
+                    # The first data field in LC_LOAD_DYLIB commands is the offset of the name, starting from the
+                    # beginning of the  command.
+                    name_offset = read_data(file, endian)
+                    file.seek(where + name_offset, os.SEEK_SET)
+                    # Read the NUL terminated string
+                    load = file.read(cmd_size - name_offset).decode()
+                    load = load[: load.index("\0")]
+                    # If the string is what is being replaced, overwrite it.
+                    if load == what:
+                        file.seek(where + name_offset, os.SEEK_SET)
+                        file.write(value.encode() + b"\0")
+                # Seek to the next command
+                file.seek(where + cmd_size, os.SEEK_SET)
+
+        def do_file(file, offset=0, size=maxint):
+            file = FileView(file, offset, size)
+            # Read magic number
+            magic = read_data(file, BIG_ENDIAN)
+            if magic == FAT_MAGIC:
+                # Fat binaries contain nfat_arch Mach-O binaries
+                n_fat_arch = read_data(file, BIG_ENDIAN)
+                for _ in range(n_fat_arch):
+                    # Read arch header
+                    _cpu_type, _cpu_sub_type, offset, size, _align = read_data(file, BIG_ENDIAN, 5)
+                    do_file(file, offset, size)
+            elif magic == MH_MAGIC:
+                do_macho(file, 32, BIG_ENDIAN)
+            elif magic == MH_CIGAM:
+                do_macho(file, 32, LITTLE_ENDIAN)
+            elif magic == MH_MAGIC_64:
+                do_macho(file, 64, BIG_ENDIAN)
+            elif magic == MH_CIGAM_64:
+                do_macho(file, 64, LITTLE_ENDIAN)
+
+        assert len(what) >= len(value)  # noqa: S101
+
+        with open(at_path, "r+b") as f:
+            do_file(f)
+
+    return mach_o_change


 class CPython3macOsBrew(CPython3, CPythonPosix):
-    pass
+    @classmethod
+    def can_describe(cls, interpreter):
+        return is_macos_brew(interpreter) and super().can_describe(interpreter)
+
+    @classmethod
+    def setup_meta(cls, interpreter):  # noqa: ARG003
+        meta = BuiltinViaGlobalRefMeta()
+        meta.copy_error = "Brew disables copy creation: https://github.com/Homebrew/homebrew-core/issues/138159"
+        return meta


-__all__ = ['CPython3macOsBrew', 'CPython3macOsFramework',
-    'CPythonmacOsFramework']
+__all__ = [
+    "CPython3macOsBrew",
+    "CPython3macOsFramework",
+    "CPythonmacOsFramework",
+]
diff --git a/src/virtualenv/create/via_global_ref/builtin/pypy/common.py b/src/virtualenv/create/via_global_ref/builtin/pypy/common.py
index 1056bf3..ca4b45f 100644
--- a/src/virtualenv/create/via_global_ref/builtin/pypy/common.py
+++ b/src/virtualenv/create/via_global_ref/builtin/pypy/common.py
@@ -1,12 +1,53 @@
 from __future__ import annotations
+
 import abc
 from pathlib import Path
+
 from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen
 from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin


 class PyPy(ViaGlobalRefVirtualenvBuiltin, abc.ABC):
-    pass
+    @classmethod
+    def can_describe(cls, interpreter):
+        return interpreter.implementation == "PyPy" and super().can_describe(interpreter)
+
+    @classmethod
+    def _executables(cls, interpreter):
+        host = Path(interpreter.system_executable)
+        targets = sorted(f"{name}{PyPy.suffix}" for name in cls.exe_names(interpreter))
+        yield host, targets, RefMust.NA, RefWhen.ANY
+
+    @classmethod
+    def executables(cls, interpreter):
+        yield from super().sources(interpreter)
+
+    @classmethod
+    def exe_names(cls, interpreter):
+        return {
+            cls.exe_stem(),
+            "python",
+            f"python{interpreter.version_info.major}",
+            f"python{interpreter.version_info.major}.{interpreter.version_info.minor}",
+        }
+
+    @classmethod
+    def sources(cls, interpreter):
+        yield from cls.executables(interpreter)
+        for host in cls._add_shared_libs(interpreter):
+            yield PathRefToDest(host, dest=lambda self, s: self.bin_dir / s.name)
+
+    @classmethod
+    def _add_shared_libs(cls, interpreter):
+        # https://bitbucket.org/pypy/pypy/issue/1922/future-proofing-virtualenv
+        python_dir = Path(interpreter.system_executable).resolve().parent
+        yield from cls._shared_libs(python_dir)
+
+    @classmethod
+    def _shared_libs(cls, python_dir):
+        raise NotImplementedError


-__all__ = ['PyPy']
+__all__ = [
+    "PyPy",
+]
diff --git a/src/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py b/src/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py
index 023840f..fa61ebc 100644
--- a/src/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py
+++ b/src/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py
@@ -1,21 +1,76 @@
 from __future__ import annotations
+
 import abc
 from pathlib import Path
+
 from virtualenv.create.describe import PosixSupports, Python3Supports, WindowsSupports
 from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest
+
 from .common import PyPy


 class PyPy3(PyPy, Python3Supports, abc.ABC):
-    pass
+    @classmethod
+    def exe_stem(cls):
+        return "pypy3"
+
+    @classmethod
+    def exe_names(cls, interpreter):
+        return super().exe_names(interpreter) | {"pypy"}


 class PyPy3Posix(PyPy3, PosixSupports):
     """PyPy 3 on POSIX."""

+    @classmethod
+    def _shared_libs(cls, python_dir):
+        # glob for libpypy3-c.so, libpypy3-c.dylib, libpypy3.9-c.so ...
+        return python_dir.glob("libpypy3*.*")
+
+    def to_lib(self, src):
+        return self.dest / "lib" / src.name
+
+    @classmethod
+    def sources(cls, interpreter):
+        yield from super().sources(interpreter)
+        # PyPy >= 3.8 supports a standard prefix installation, where older
+        # versions always used a portable/development style installation.
+        # If this is a standard prefix installation, skip the below:
+        if interpreter.system_prefix == "/usr":
+            return
+        # Also copy/symlink anything under prefix/lib, which, for "portable"
+        # PyPy builds, includes the tk,tcl runtime and a number of shared
+        # objects. In distro-specific builds or on conda this should be empty
+        # (on PyPy3.8+ it will, like on CPython, hold the stdlib).
+        host_lib = Path(interpreter.system_prefix) / "lib"
+        stdlib = Path(interpreter.system_stdlib)
+        if host_lib.exists() and host_lib.is_dir():
+            for path in host_lib.iterdir():
+                if stdlib == path:
+                    # For PyPy3.8+ the stdlib lives in lib/pypy3.8
+                    # We need to avoid creating a symlink to it since that
+                    # will defeat the purpose of a virtualenv
+                    continue
+                yield PathRefToDest(path, dest=cls.to_lib)
+

 class Pypy3Windows(PyPy3, WindowsSupports):
     """PyPy 3 on Windows."""

+    @property
+    def less_v37(self):
+        return self.interpreter.version_info.minor < 7  # noqa: PLR2004
+
+    @classmethod
+    def _shared_libs(cls, python_dir):
+        # glob for libpypy*.dll and libffi*.dll
+        for pattern in ["libpypy*.dll", "libffi*.dll"]:
+            srcs = python_dir.glob(pattern)
+            yield from srcs
+

-__all__ = ['PyPy3', 'PyPy3Posix', 'Pypy3Windows']
+__all__ = [
+    "PyPy3",
+    "PyPy3Posix",
+    "Pypy3Windows",
+]
diff --git a/src/virtualenv/create/via_global_ref/builtin/ref.py b/src/virtualenv/create/via_global_ref/builtin/ref.py
index 0892a96..2598c67 100644
--- a/src/virtualenv/create/via_global_ref/builtin/ref.py
+++ b/src/virtualenv/create/via_global_ref/builtin/ref.py
@@ -2,34 +2,38 @@
 Virtual environments in the traditional sense are built as reference to the host python. This file allows declarative
 references to elements on the file system, allowing our system to automatically detect what modes it can support given
 the constraints: e.g. can the file system symlink, can the files be read, executed, etc.
-"""
+"""  # noqa: D205
+
 from __future__ import annotations
+
 import os
 from abc import ABC, abstractmethod
 from collections import OrderedDict
 from stat import S_IXGRP, S_IXOTH, S_IXUSR
+
 from virtualenv.info import fs_is_case_sensitive, fs_supports_symlink
 from virtualenv.util.path import copy, make_exe, symlink


 class RefMust:
-    NA = 'NA'
-    COPY = 'copy'
-    SYMLINK = 'symlink'
+    NA = "NA"
+    COPY = "copy"
+    SYMLINK = "symlink"


 class RefWhen:
-    ANY = 'ANY'
-    COPY = 'copy'
-    SYMLINK = 'symlink'
+    ANY = "ANY"
+    COPY = "copy"
+    SYMLINK = "symlink"


 class PathRef(ABC):
     """Base class that checks if a file reference can be symlink/copied."""
+
     FS_SUPPORTS_SYMLINK = fs_supports_symlink()
     FS_CASE_SENSITIVE = fs_is_case_sensitive()

-    def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY) ->None:
+    def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY) -> None:
         self.must = must
         self.when = when
         self.src = src
@@ -41,44 +45,134 @@ class PathRef(ABC):
         self._can_copy = None if self.exists else False
         self._can_symlink = None if self.exists else False

-    def __repr__(self) ->str:
-        return f'{self.__class__.__name__}(src={self.src})'
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}(src={self.src})"
+
+    @property
+    def can_read(self):
+        if self._can_read is None:
+            if self.src.is_file():
+                try:
+                    with self.src.open("rb"):
+                        self._can_read = True
+                except OSError:
+                    self._can_read = False
+            else:
+                self._can_read = os.access(str(self.src), os.R_OK)
+        return self._can_read
+
+    @property
+    def can_copy(self):
+        if self._can_copy is None:
+            if self.must == RefMust.SYMLINK:
+                self._can_copy = self.can_symlink
+            else:
+                self._can_copy = self.can_read
+        return self._can_copy
+
+    @property
+    def can_symlink(self):
+        if self._can_symlink is None:
+            if self.must == RefMust.COPY:
+                self._can_symlink = self.can_copy
+            else:
+                self._can_symlink = self.FS_SUPPORTS_SYMLINK and self.can_read
+        return self._can_symlink
+
+    @abstractmethod
+    def run(self, creator, symlinks):
+        raise NotImplementedError
+
+    def method(self, symlinks):
+        if self.must == RefMust.SYMLINK:
+            return symlink
+        if self.must == RefMust.COPY:
+            return copy
+        return symlink if symlinks else copy


 class ExePathRef(PathRef, ABC):
     """Base class that checks if a executable can be references via symlink/copy."""

-    def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY) ->None:
+    def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY) -> None:
         super().__init__(src, must, when)
         self._can_run = None

+    @property
+    def can_symlink(self):
+        if self.FS_SUPPORTS_SYMLINK:
+            return self.can_run
+        return False
+
+    @property
+    def can_run(self):
+        if self._can_run is None:
+            mode = self.src.stat().st_mode
+            for key in [S_IXUSR, S_IXGRP, S_IXOTH]:
+                if mode & key:
+                    self._can_run = True
+                break
+            else:
+                self._can_run = False
+        return self._can_run
+

 class PathRefToDest(PathRef):
     """Link a path on the file system."""

-    def __init__(self, src, dest, must=RefMust.NA, when=RefWhen.ANY) ->None:
+    def __init__(self, src, dest, must=RefMust.NA, when=RefWhen.ANY) -> None:
         super().__init__(src, must, when)
         self.dest = dest

+    def run(self, creator, symlinks):
+        dest = self.dest(creator, self.src)
+        method = self.method(symlinks)
+        dest_iterable = dest if isinstance(dest, list) else (dest,)
+        if not dest.parent.exists():
+            dest.parent.mkdir(parents=True, exist_ok=True)
+        for dst in dest_iterable:
+            method(self.src, dst)
+

 class ExePathRefToDest(PathRefToDest, ExePathRef):
     """Link a exe path on the file system."""

-    def __init__(self, src, targets, dest, must=RefMust.NA, when=RefWhen.ANY
-        ) ->None:
+    def __init__(self, src, targets, dest, must=RefMust.NA, when=RefWhen.ANY) -> None:  # noqa: PLR0913
         ExePathRef.__init__(self, src, must, when)
         PathRefToDest.__init__(self, src, dest, must, when)
         if not self.FS_CASE_SENSITIVE:
-            targets = list(OrderedDict((i.lower(), None) for i in targets).
-                keys())
+            targets = list(OrderedDict((i.lower(), None) for i in targets).keys())
         self.base = targets[0]
         self.aliases = targets[1:]
         self.dest = dest

-    def __repr__(self) ->str:
-        return (
-            f'{self.__class__.__name__}(src={self.src}, alias={self.aliases})')
-
-
-__all__ = ['ExePathRef', 'ExePathRefToDest', 'PathRef', 'PathRefToDest',
-    'RefMust', 'RefWhen']
+    def run(self, creator, symlinks):
+        bin_dir = self.dest(creator, self.src).parent
+        dest = bin_dir / self.base
+        method = self.method(symlinks)
+        method(self.src, dest)
+        if not symlinks:
+            make_exe(dest)
+        for extra in self.aliases:
+            link_file = bin_dir / extra
+            if link_file.exists():
+                link_file.unlink()
+            if symlinks:
+                link_file.symlink_to(self.base)
+            else:
+                copy(self.src, link_file)
+            if not symlinks:
+                make_exe(link_file)
+
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}(src={self.src}, alias={self.aliases})"
+
+
+__all__ = [
+    "ExePathRef",
+    "ExePathRefToDest",
+    "PathRef",
+    "PathRefToDest",
+    "RefMust",
+    "RefWhen",
+]
diff --git a/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py b/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py
index 5be5210..2f7f2f1 100644
--- a/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py
+++ b/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py
@@ -1,35 +1,118 @@
 from __future__ import annotations
+
 from abc import ABC
+
 from virtualenv.create.via_global_ref.api import ViaGlobalRefApi, ViaGlobalRefMeta
-from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest, RefMust, RefWhen
+from virtualenv.create.via_global_ref.builtin.ref import (
+    ExePathRefToDest,
+    RefMust,
+    RefWhen,
+)
 from virtualenv.util.path import ensure_dir
+
 from .builtin_way import VirtualenvBuiltin


 class BuiltinViaGlobalRefMeta(ViaGlobalRefMeta):
-
-    def __init__(self) ->None:
+    def __init__(self) -> None:
         super().__init__()
         self.sources = []


 class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin, ABC):
-
-    def __init__(self, options, interpreter) ->None:
+    def __init__(self, options, interpreter) -> None:
         super().__init__(options, interpreter)
-        self._sources = getattr(options.meta, 'sources', None)
+        self._sources = getattr(options.meta, "sources", None)  # if we're created as a describer this might be missing

     @classmethod
     def can_create(cls, interpreter):
         """By default, all built-in methods assume that if we can describe it we can create it."""
-        pass
+        # first we must be able to describe it
+        if not cls.can_describe(interpreter):
+            return None
+        meta = cls.setup_meta(interpreter)
+        if meta is not None and meta:
+            cls._sources_can_be_applied(interpreter, meta)
+        return meta
+
+    @classmethod
+    def _sources_can_be_applied(cls, interpreter, meta):
+        for src in cls.sources(interpreter):
+            if src.exists:
+                if meta.can_copy and not src.can_copy:
+                    meta.copy_error = f"cannot copy {src}"
+                if meta.can_symlink and not src.can_symlink:
+                    meta.symlink_error = f"cannot symlink {src}"
+            else:
+                msg = f"missing required file {src}"
+                if src.when == RefMust.NA:
+                    meta.error = msg
+                elif src.when == RefMust.COPY:
+                    meta.copy_error = msg
+                elif src.when == RefMust.SYMLINK:
+                    meta.symlink_error = msg
+            if not meta.can_copy and not meta.can_symlink:
+                meta.error = f"neither copy or symlink supported, copy: {meta.copy_error} symlink: {meta.symlink_error}"
+            if meta.error:
+                break
+            meta.sources.append(src)
+
+    @classmethod
+    def setup_meta(cls, interpreter):  # noqa: ARG003
+        return BuiltinViaGlobalRefMeta()
+
+    @classmethod
+    def sources(cls, interpreter):
+        for host_exe, targets, must, when in cls._executables(interpreter):
+            yield ExePathRefToDest(host_exe, dest=cls.to_bin, targets=targets, must=must, when=when)
+
+    def to_bin(self, src):
+        return self.bin_dir / src.name
+
+    @classmethod
+    def _executables(cls, interpreter):
+        raise NotImplementedError
+
+    def create(self):
+        dirs = self.ensure_directories()
+        for directory in list(dirs):
+            if any(i for i in dirs if i is not directory and directory.parts == i.parts[: len(directory.parts)]):
+                dirs.remove(directory)
+        for directory in sorted(dirs):
+            ensure_dir(directory)
+
+        self.set_pyenv_cfg()
+        self.pyenv_cfg.write()
+        true_system_site = self.enable_system_site_package
+        try:
+            self.enable_system_site_package = False
+            for src in self._sources:
+                if (
+                    src.when == RefWhen.ANY
+                    or (src.when == RefWhen.SYMLINK and self.symlinks is True)
+                    or (src.when == RefWhen.COPY and self.symlinks is False)
+                ):
+                    src.run(self, self.symlinks)
+        finally:
+            if true_system_site != self.enable_system_site_package:
+                self.enable_system_site_package = true_system_site
+        super().create()
+
+    def ensure_directories(self):
+        return {self.dest, self.bin_dir, self.script_dir, self.stdlib} | set(self.libs)

     def set_pyenv_cfg(self):
         """
         We directly inject the base prefix and base exec prefix to avoid site.py needing to discover these
         from home (which usually is done within the interpreter itself).
-        """
-        pass
+        """  # noqa: D205
+        super().set_pyenv_cfg()
+        self.pyenv_cfg["base-prefix"] = self.interpreter.system_prefix
+        self.pyenv_cfg["base-exec-prefix"] = self.interpreter.system_exec_prefix
+        self.pyenv_cfg["base-executable"] = self.interpreter.system_executable


-__all__ = ['BuiltinViaGlobalRefMeta', 'ViaGlobalRefVirtualenvBuiltin']
+__all__ = [
+    "BuiltinViaGlobalRefMeta",
+    "ViaGlobalRefVirtualenvBuiltin",
+]
diff --git a/src/virtualenv/create/via_global_ref/store.py b/src/virtualenv/create/via_global_ref/store.py
index 9bcde55..4be6689 100644
--- a/src/virtualenv/create/via_global_ref/store.py
+++ b/src/virtualenv/create/via_global_ref/store.py
@@ -1,3 +1,26 @@
 from __future__ import annotations
+
 from pathlib import Path
-__all__ = ['handle_store_python', 'is_store_python']
+
+
+def handle_store_python(meta, interpreter):
+    if is_store_python(interpreter):
+        meta.symlink_error = "Windows Store Python does not support virtual environments via symlink"
+    return meta
+
+
+def is_store_python(interpreter):
+    parts = Path(interpreter.system_executable).parts
+    return (
+        len(parts) > 4  # noqa: PLR2004
+        and parts[-4] == "Microsoft"
+        and parts[-3] == "WindowsApps"
+        and parts[-2].startswith("PythonSoftwareFoundation.Python.3.")
+        and parts[-1].startswith("python")
+    )
+
+
+__all__ = [
+    "handle_store_python",
+    "is_store_python",
+]
diff --git a/src/virtualenv/create/via_global_ref/venv.py b/src/virtualenv/create/via_global_ref/venv.py
index dab115b..7220af1 100644
--- a/src/virtualenv/create/via_global_ref/venv.py
+++ b/src/virtualenv/create/via_global_ref/venv.py
@@ -1,41 +1,102 @@
 from __future__ import annotations
+
 import logging
 from copy import copy
+
 from virtualenv.create.via_global_ref.store import handle_store_python
 from virtualenv.discovery.py_info import PythonInfo
 from virtualenv.util.error import ProcessCallFailedError
 from virtualenv.util.path import ensure_dir
 from virtualenv.util.subprocess import run_cmd
+
 from .api import ViaGlobalRefApi, ViaGlobalRefMeta
 from .builtin.cpython.mac_os import CPython3macOsBrew
 from .builtin.pypy.pypy3 import Pypy3Windows


 class Venv(ViaGlobalRefApi):
-
-    def __init__(self, options, interpreter) ->None:
+    def __init__(self, options, interpreter) -> None:
         self.describe = options.describe
         super().__init__(options, interpreter)
         current = PythonInfo.current()
-        self.can_be_inline = (interpreter is current and interpreter.
-            executable == interpreter.system_executable)
+        self.can_be_inline = interpreter is current and interpreter.executable == interpreter.system_executable
         self._context = None

+    def _args(self):
+        return super()._args() + ([("describe", self.describe.__class__.__name__)] if self.describe else [])
+
+    @classmethod
+    def can_create(cls, interpreter):
+        if interpreter.has_venv:
+            if CPython3macOsBrew.can_describe(interpreter):
+                return CPython3macOsBrew.setup_meta(interpreter)
+            meta = ViaGlobalRefMeta()
+            if interpreter.platform == "win32":
+                meta = handle_store_python(meta, interpreter)
+            return meta
+        return None
+
+    def create(self):
+        if self.can_be_inline:
+            self.create_inline()
+        else:
+            self.create_via_sub_process()
+        for lib in self.libs:
+            ensure_dir(lib)
+        super().create()
+        self.executables_for_win_pypy_less_v37()
+
     def executables_for_win_pypy_less_v37(self):
         """
         PyPy <= 3.6 (v7.3.3) for Windows contains only pypy3.exe and pypy3w.exe
         Venv does not handle non-existing exe sources, e.g. python.exe, so this
         patch does it.
-        """
-        pass
+        """  # noqa: D205
+        creator = self.describe
+        if isinstance(creator, Pypy3Windows) and creator.less_v37:
+            for exe in creator.executables(self.interpreter):
+                exe.run(creator, self.symlinks)
+
+    def create_inline(self):
+        from venv import EnvBuilder  # noqa: PLC0415
+
+        builder = EnvBuilder(
+            system_site_packages=self.enable_system_site_package,
+            clear=False,
+            symlinks=self.symlinks,
+            with_pip=False,
+        )
+        builder.create(str(self.dest))
+
+    def create_via_sub_process(self):
+        cmd = self.get_host_create_cmd()
+        logging.info("using host built-in venv to create via %s", " ".join(cmd))
+        code, out, err = run_cmd(cmd)
+        if code != 0:
+            raise ProcessCallFailedError(code, out, err, cmd)
+
+    def get_host_create_cmd(self):
+        cmd = [self.interpreter.system_executable, "-m", "venv", "--without-pip"]
+        if self.enable_system_site_package:
+            cmd.append("--system-site-packages")
+        cmd.extend(("--symlinks" if self.symlinks else "--copies", str(self.dest)))
+        return cmd
+
+    def set_pyenv_cfg(self):
+        # prefer venv options over ours, but keep our extra
+        venv_content = copy(self.pyenv_cfg.refresh())
+        super().set_pyenv_cfg()
+        self.pyenv_cfg.update(venv_content)

     def __getattribute__(self, item):
-        describe = object.__getattribute__(self, 'describe')
+        describe = object.__getattribute__(self, "describe")
         if describe is not None and hasattr(describe, item):
             element = getattr(describe, item)
-            if not callable(element) or item == 'script':
+            if not callable(element) or item == "script":
                 return element
         return object.__getattribute__(self, item)


-__all__ = ['Venv']
+__all__ = [
+    "Venv",
+]
diff --git a/src/virtualenv/discovery/builtin.py b/src/virtualenv/discovery/builtin.py
index c685c6c..ae0612b 100644
--- a/src/virtualenv/discovery/builtin.py
+++ b/src/virtualenv/discovery/builtin.py
@@ -1,16 +1,21 @@
 from __future__ import annotations
+
 import logging
 import os
 import sys
 from pathlib import Path
 from typing import TYPE_CHECKING, Callable
+
 from virtualenv.info import IS_WIN, fs_path_id
+
 from .discover import Discover
 from .py_info import PythonInfo
 from .py_spec import PythonSpec
+
 if TYPE_CHECKING:
     from argparse import ArgumentParser
     from collections.abc import Generator, Iterable, Mapping, Sequence
+
     from virtualenv.app_data.base import AppData


@@ -19,49 +24,202 @@ class Builtin(Discover):
     app_data: AppData
     try_first_with: Sequence[str]

-    def __init__(self, options) ->None:
+    def __init__(self, options) -> None:
         super().__init__(options)
         self.python_spec = options.python or [sys.executable]
         self.app_data = options.app_data
         self.try_first_with = options.try_first_with

-    def __repr__(self) ->str:
-        spec = self.python_spec[0] if len(self.python_spec
-            ) == 1 else self.python_spec
-        return f'{self.__class__.__name__} discover of python_spec={spec!r}'
+    @classmethod
+    def add_parser_arguments(cls, parser: ArgumentParser) -> None:
+        parser.add_argument(
+            "-p",
+            "--python",
+            dest="python",
+            metavar="py",
+            type=str,
+            action="append",
+            default=[],
+            help="interpreter based on what to create environment (path/identifier) "
+            "- by default use the interpreter where the tool is installed - first found wins",
+        )
+        parser.add_argument(
+            "--try-first-with",
+            dest="try_first_with",
+            metavar="py_exe",
+            type=str,
+            action="append",
+            default=[],
+            help="try first these interpreters before starting the discovery",
+        )

+    def run(self) -> PythonInfo | None:
+        for python_spec in self.python_spec:
+            result = get_interpreter(python_spec, self.try_first_with, self.app_data, self._env)
+            if result is not None:
+                return result
+        return None

-class LazyPathDump:
+    def __repr__(self) -> str:
+        spec = self.python_spec[0] if len(self.python_spec) == 1 else self.python_spec
+        return f"{self.__class__.__name__} discover of python_spec={spec!r}"
+
+
+def get_interpreter(
+    key, try_first_with: Iterable[str], app_data: AppData | None = None, env: Mapping[str, str] | None = None
+) -> PythonInfo | None:
+    spec = PythonSpec.from_string_spec(key)
+    logging.info("find interpreter for spec %r", spec)
+    proposed_paths = set()
+    env = os.environ if env is None else env
+    for interpreter, impl_must_match in propose_interpreters(spec, try_first_with, app_data, env):
+        key = interpreter.system_executable, impl_must_match
+        if key in proposed_paths:
+            continue
+        logging.info("proposed %s", interpreter)
+        if interpreter.satisfies(spec, impl_must_match):
+            logging.debug("accepted %s", interpreter)
+            return interpreter
+        proposed_paths.add(key)
+    return None
+
+
+def propose_interpreters(  # noqa: C901, PLR0912, PLR0915
+    spec: PythonSpec,
+    try_first_with: Iterable[str],
+    app_data: AppData | None = None,
+    env: Mapping[str, str] | None = None,
+) -> Generator[tuple[PythonInfo, bool], None, None]:
+    # 0. try with first
+    env = os.environ if env is None else env
+    tested_exes: set[str] = set()
+    for py_exe in try_first_with:
+        path = os.path.abspath(py_exe)
+        try:
+            os.lstat(path)  # Windows Store Python does not work with os.path.exists, but does for os.lstat
+        except OSError:
+            pass
+        else:
+            exe_raw = os.path.abspath(path)
+            exe_id = fs_path_id(exe_raw)
+            if exe_id in tested_exes:
+                continue
+            tested_exes.add(exe_id)
+            yield PythonInfo.from_exe(exe_raw, app_data, env=env), True

-    def __init__(self, pos: int, path: Path, env: Mapping[str, str]) ->None:
+    # 1. if it's a path and exists
+    if spec.path is not None:
+        try:
+            os.lstat(spec.path)  # Windows Store Python does not work with os.path.exists, but does for os.lstat
+        except OSError:
+            if spec.is_abs:
+                raise
+        else:
+            exe_raw = os.path.abspath(spec.path)
+            exe_id = fs_path_id(exe_raw)
+            if exe_id not in tested_exes:
+                tested_exes.add(exe_id)
+                yield PythonInfo.from_exe(exe_raw, app_data, env=env), True
+        if spec.is_abs:
+            return
+    else:
+        # 2. otherwise try with the current
+        current_python = PythonInfo.current_system(app_data)
+        exe_raw = str(current_python.executable)
+        exe_id = fs_path_id(exe_raw)
+        if exe_id not in tested_exes:
+            tested_exes.add(exe_id)
+            yield current_python, True
+
+        # 3. otherwise fallback to platform default logic
+        if IS_WIN:
+            from .windows import propose_interpreters  # noqa: PLC0415
+
+            for interpreter in propose_interpreters(spec, app_data, env):
+                exe_raw = str(interpreter.executable)
+                exe_id = fs_path_id(exe_raw)
+                if exe_id in tested_exes:
+                    continue
+                tested_exes.add(exe_id)
+                yield interpreter, True
+    # finally just find on path, the path order matters (as the candidates are less easy to control by end user)
+    find_candidates = path_exe_finder(spec)
+    for pos, path in enumerate(get_paths(env)):
+        logging.debug(LazyPathDump(pos, path, env))
+        for exe, impl_must_match in find_candidates(path):
+            exe_raw = str(exe)
+            exe_id = fs_path_id(exe_raw)
+            if exe_id in tested_exes:
+                continue
+            tested_exes.add(exe_id)
+            interpreter = PathPythonInfo.from_exe(exe_raw, app_data, raise_on_error=False, env=env)
+            if interpreter is not None:
+                yield interpreter, impl_must_match
+
+
+def get_paths(env: Mapping[str, str]) -> Generator[Path, None, None]:
+    path = env.get("PATH", None)
+    if path is None:
+        try:
+            path = os.confstr("CS_PATH")
+        except (AttributeError, ValueError):
+            path = os.defpath
+    if path:
+        for p in map(Path, path.split(os.pathsep)):
+            if p.exists():
+                yield p
+
+
+class LazyPathDump:
+    def __init__(self, pos: int, path: Path, env: Mapping[str, str]) -> None:
         self.pos = pos
         self.path = path
         self.env = env

-    def __repr__(self) ->str:
-        content = f'discover PATH[{self.pos}]={self.path}'
-        if self.env.get('_VIRTUALENV_DEBUG'):
-            content += ' with =>'
+    def __repr__(self) -> str:
+        content = f"discover PATH[{self.pos}]={self.path}"
+        if self.env.get("_VIRTUALENV_DEBUG"):  # this is the over the board debug
+            content += " with =>"
             for file_path in self.path.iterdir():
                 try:
-                    if file_path.is_dir() or not file_path.stat(
-                        ).st_mode & os.X_OK:
+                    if file_path.is_dir() or not (file_path.stat().st_mode & os.X_OK):
                         continue
                 except OSError:
                     pass
-                content += ' '
+                content += " "
                 content += file_path.name
         return content


-def path_exe_finder(spec: PythonSpec) ->Callable[[Path], Generator[tuple[
-    Path, bool], None, None]]:
+def path_exe_finder(spec: PythonSpec) -> Callable[[Path], Generator[tuple[Path, bool], None, None]]:
     """Given a spec, return a function that can be called on a path to find all matching files in it."""
-    pass
+    pat = spec.generate_re(windows=sys.platform == "win32")
+    direct = spec.str_spec
+    if sys.platform == "win32":
+        direct = f"{direct}.exe"
+
+    def path_exes(path: Path) -> Generator[tuple[Path, bool], None, None]:
+        # 4. then maybe it's something exact on PATH - if it was direct lookup implementation no longer counts
+        direct_path = path / direct
+        if direct_path.exists():
+            yield direct_path, False
+
+        # 5. or from the spec we can deduce if a name on path matches
+        for exe in path.iterdir():
+            match = pat.fullmatch(exe.name)
+            if match:
+                # the implementation must match when we find β€œpython[ver]”
+                yield exe.absolute(), match["impl"] == "python"
+
+    return path_exes


 class PathPythonInfo(PythonInfo):
     """python info from path."""


-__all__ = ['Builtin', 'PathPythonInfo', 'get_interpreter']
+__all__ = [
+    "Builtin",
+    "PathPythonInfo",
+    "get_interpreter",
+]
diff --git a/src/virtualenv/discovery/cached_py_info.py b/src/virtualenv/discovery/cached_py_info.py
index ba59bf6..d2cf607 100644
--- a/src/virtualenv/discovery/cached_py_info.py
+++ b/src/virtualenv/discovery/cached_py_info.py
@@ -3,8 +3,10 @@
 We acquire the python information by running an interrogation script via subprocess trigger. This operation is not
 cheap, especially not on Windows. To not have to pay this hefty cost every time we apply multiple levels of
 caching.
-"""
+"""  # noqa: D205
+
 from __future__ import annotations
+
 import logging
 import os
 import random
@@ -14,25 +16,163 @@ from pathlib import Path
 from shlex import quote
 from string import ascii_lowercase, ascii_uppercase, digits
 from subprocess import Popen
+
 from virtualenv.app_data import AppDataDisabled
 from virtualenv.discovery.py_info import PythonInfo
 from virtualenv.util.subprocess import subprocess
+
 _CACHE = OrderedDict()
 _CACHE[Path(sys.executable)] = PythonInfo()
+
+
+def from_exe(cls, app_data, exe, env=None, raise_on_error=True, ignore_cache=False):  # noqa: FBT002, PLR0913
+    env = os.environ if env is None else env
+    result = _get_from_cache(cls, app_data, exe, env, ignore_cache=ignore_cache)
+    if isinstance(result, Exception):
+        if raise_on_error:
+            raise result
+        logging.info("%s", result)
+        result = None
+    return result
+
+
+def _get_from_cache(cls, app_data, exe, env, ignore_cache=True):  # noqa: FBT002
+    # note here we cannot resolve symlinks, as the symlink may trigger different prefix information if there's a
+    # pyenv.cfg somewhere alongside on python3.5+
+    exe_path = Path(exe)
+    if not ignore_cache and exe_path in _CACHE:  # check in the in-memory cache
+        result = _CACHE[exe_path]
+    else:  # otherwise go through the app data cache
+        py_info = _get_via_file_cache(cls, app_data, exe_path, exe, env)
+        result = _CACHE[exe_path] = py_info
+    # independent if it was from the file or in-memory cache fix the original executable location
+    if isinstance(result, PythonInfo):
+        result.executable = exe
+    return result
+
+
+def _get_via_file_cache(cls, app_data, path, exe, env):
+    path_text = str(path)
+    try:
+        path_modified = path.stat().st_mtime
+    except OSError:
+        path_modified = -1
+    if app_data is None:
+        app_data = AppDataDisabled()
+    py_info, py_info_store = None, app_data.py_info(path)
+    with py_info_store.locked():
+        if py_info_store.exists():  # if exists and matches load
+            data = py_info_store.read()
+            of_path, of_st_mtime, of_content = data["path"], data["st_mtime"], data["content"]
+            if of_path == path_text and of_st_mtime == path_modified:
+                py_info = cls._from_dict(of_content.copy())
+                sys_exe = py_info.system_executable
+                if sys_exe is not None and not os.path.exists(sys_exe):
+                    py_info_store.remove()
+                    py_info = None
+            else:
+                py_info_store.remove()
+        if py_info is None:  # if not loaded run and save
+            failure, py_info = _run_subprocess(cls, exe, app_data, env)
+            if failure is None:
+                data = {"st_mtime": path_modified, "path": path_text, "content": py_info._to_dict()}  # noqa: SLF001
+                py_info_store.write(data)
+            else:
+                py_info = failure
+    return py_info
+
+
 COOKIE_LENGTH: int = 32


-class LogCmd:
+def gen_cookie():
+    return "".join(
+        random.choice(f"{ascii_lowercase}{ascii_uppercase}{digits}")  # noqa: S311
+        for _ in range(COOKIE_LENGTH)
+    )
+

-    def __init__(self, cmd, env=None) ->None:
+def _run_subprocess(cls, exe, app_data, env):
+    py_info_script = Path(os.path.abspath(__file__)).parent / "py_info.py"
+    # Cookies allow to split the serialized stdout output generated by the script collecting the info from the output
+    # generated by something else. The right way to deal with it is to create an anonymous pipe and pass its descriptor
+    # to the child and output to it. But AFAIK all of them are either not cross-platform or too big to implement and are
+    # not in the stdlib. So the easiest and the shortest way I could mind is just using the cookies.
+    # We generate pseudorandom cookies because it easy to implement and avoids breakage from outputting modules source
+    # code, i.e. by debug output libraries. We reverse the cookies to avoid breakages resulting from variable values
+    # appearing in debug output.
+
+    start_cookie = gen_cookie()
+    end_cookie = gen_cookie()
+    with app_data.ensure_extracted(py_info_script) as py_info_script:
+        cmd = [exe, str(py_info_script), start_cookie, end_cookie]
+        # prevent sys.prefix from leaking into the child process - see https://bugs.python.org/issue22490
+        env = env.copy()
+        env.pop("__PYVENV_LAUNCHER__", None)
+        logging.debug("get interpreter info via cmd: %s", LogCmd(cmd))
+        try:
+            process = Popen(
+                cmd,  # noqa: S603
+                universal_newlines=True,
+                stdin=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+                stdout=subprocess.PIPE,
+                env=env,
+                encoding="utf-8",
+            )
+            out, err = process.communicate()
+            code = process.returncode
+        except OSError as os_error:
+            out, err, code = "", os_error.strerror, os_error.errno
+    result, failure = None, None
+    if code == 0:
+        out_starts = out.find(start_cookie[::-1])
+
+        if out_starts > -1:
+            pre_cookie = out[:out_starts]
+
+            if pre_cookie:
+                sys.stdout.write(pre_cookie)
+
+            out = out[out_starts + COOKIE_LENGTH :]
+
+        out_ends = out.find(end_cookie[::-1])
+
+        if out_ends > -1:
+            post_cookie = out[out_ends + COOKIE_LENGTH :]
+
+            if post_cookie:
+                sys.stdout.write(post_cookie)
+
+            out = out[:out_ends]
+
+        result = cls._from_json(out)
+        result.executable = exe  # keep original executable as this may contain initialization code
+    else:
+        msg = f"{exe} with code {code}{f' out: {out!r}' if out else ''}{f' err: {err!r}' if err else ''}"
+        failure = RuntimeError(f"failed to query {msg}")
+    return failure, result
+
+
+class LogCmd:
+    def __init__(self, cmd, env=None) -> None:
         self.cmd = cmd
         self.env = env

-    def __repr__(self) ->str:
-        cmd_repr = ' '.join(quote(str(c)) for c in self.cmd)
+    def __repr__(self) -> str:
+        cmd_repr = " ".join(quote(str(c)) for c in self.cmd)
         if self.env is not None:
-            cmd_repr = f'{cmd_repr} env of {self.env!r}'
+            cmd_repr = f"{cmd_repr} env of {self.env!r}"
         return cmd_repr


-___all___ = ['from_exe', 'clear', 'LogCmd']
+def clear(app_data):
+    app_data.py_info_clear()
+    _CACHE.clear()
+
+
+___all___ = [
+    "from_exe",
+    "clear",
+    "LogCmd",
+]
diff --git a/src/virtualenv/discovery/discover.py b/src/virtualenv/discovery/discover.py
index c6a9976..0aaa17c 100644
--- a/src/virtualenv/discovery/discover.py
+++ b/src/virtualenv/discovery/discover.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+
 from abc import ABC, abstractmethod


@@ -12,9 +13,9 @@ class Discover(ABC):

         :param parser: the CLI parser
         """
-        pass
+        raise NotImplementedError

-    def __init__(self, options) ->None:
+    def __init__(self, options) -> None:
         """
         Create a new discovery mechanism.

@@ -31,12 +32,17 @@ class Discover(ABC):

         :return: the interpreter ready to use for virtual environment creation
         """
-        pass
+        raise NotImplementedError

     @property
     def interpreter(self):
         """:return: the interpreter as returned by :meth:`run`, cached"""
-        pass
+        if self._has_run is False:
+            self._interpreter = self.run()
+            self._has_run = True
+        return self._interpreter


-__all__ = ['Discover']
+__all__ = [
+    "Discover",
+]
diff --git a/src/virtualenv/discovery/py_info.py b/src/virtualenv/discovery/py_info.py
index 423ca50..882daa3 100644
--- a/src/virtualenv/discovery/py_info.py
+++ b/src/virtualenv/discovery/py_info.py
@@ -3,7 +3,9 @@ The PythonInfo contains information about a concrete instance of a Python interp

 Note: this file is also used to query target interpreters, so can only use standard library methods
 """
+
 from __future__ import annotations
+
 import json
 import logging
 import os
@@ -14,105 +16,320 @@ import sysconfig
 import warnings
 from collections import OrderedDict, namedtuple
 from string import digits
-VersionInfo = namedtuple('VersionInfo', ['major', 'minor', 'micro',
-    'releaselevel', 'serial'])
+
+VersionInfo = namedtuple("VersionInfo", ["major", "minor", "micro", "releaselevel", "serial"])  # noqa: PYI024
+
+
+def _get_path_extensions():
+    return list(OrderedDict.fromkeys(["", *os.environ.get("PATHEXT", "").lower().split(os.pathsep)]))
+
+
 EXTENSIONS = _get_path_extensions()
-_CONF_VAR_RE = re.compile('\\{\\w+\\}')
+_CONF_VAR_RE = re.compile(r"\{\w+\}")


-class PythonInfo:
+class PythonInfo:  # noqa: PLR0904
     """Contains information for a Python interpreter."""

-    def __init__(self) ->None:
-
+    def __init__(self) -> None:  # noqa: PLR0915
         def abs_path(v):
-            return None if v is None else os.path.abspath(v)
+            return None if v is None else os.path.abspath(v)  # unroll relative elements from path (e.g. ..)
+
+        # qualifies the python
         self.platform = sys.platform
         self.implementation = platform.python_implementation()
-        if self.implementation == 'PyPy':
+        if self.implementation == "PyPy":
             self.pypy_version_info = tuple(sys.pypy_version_info)
+
+        # this is a tuple in earlier, struct later, unify to our own named tuple
         self.version_info = VersionInfo(*sys.version_info)
-        self.architecture = 64 if sys.maxsize > 2 ** 32 else 32
-        self.version_nodot = sysconfig.get_config_var('py_version_nodot')
+        self.architecture = 64 if sys.maxsize > 2**32 else 32
+
+        # Used to determine some file names.
+        # See `CPython3Windows.python_zip()`.
+        self.version_nodot = sysconfig.get_config_var("py_version_nodot")
+
         self.version = sys.version
         self.os = os.name
-        self.prefix = abs_path(getattr(sys, 'prefix', None))
-        self.base_prefix = abs_path(getattr(sys, 'base_prefix', None))
-        self.real_prefix = abs_path(getattr(sys, 'real_prefix', None))
-        self.base_exec_prefix = abs_path(getattr(sys, 'base_exec_prefix', None)
-            )
-        self.exec_prefix = abs_path(getattr(sys, 'exec_prefix', None))
-        self.executable = abs_path(sys.executable)
-        self.original_executable = abs_path(self.executable)
-        self.system_executable = self._fast_get_system_executable()
+
+        # information about the prefix - determines python home
+        self.prefix = abs_path(getattr(sys, "prefix", None))  # prefix we think
+        self.base_prefix = abs_path(getattr(sys, "base_prefix", None))  # venv
+        self.real_prefix = abs_path(getattr(sys, "real_prefix", None))  # old virtualenv
+
+        # information about the exec prefix - dynamic stdlib modules
+        self.base_exec_prefix = abs_path(getattr(sys, "base_exec_prefix", None))
+        self.exec_prefix = abs_path(getattr(sys, "exec_prefix", None))
+
+        self.executable = abs_path(sys.executable)  # the executable we were invoked via
+        self.original_executable = abs_path(self.executable)  # the executable as known by the interpreter
+        self.system_executable = self._fast_get_system_executable()  # the executable we are based of (if available)
+
         try:
-            __import__('venv')
+            __import__("venv")
             has = True
         except ImportError:
             has = False
         self.has_venv = has
         self.path = sys.path
         self.file_system_encoding = sys.getfilesystemencoding()
-        self.stdout_encoding = getattr(sys.stdout, 'encoding', None)
+        self.stdout_encoding = getattr(sys.stdout, "encoding", None)
+
         scheme_names = sysconfig.get_scheme_names()
-        if 'venv' in scheme_names:
-            self.sysconfig_scheme = 'venv'
-            self.sysconfig_paths = {i: sysconfig.get_path(i, expand=False,
-                scheme=self.sysconfig_scheme) for i in sysconfig.
-                get_path_names()}
+
+        if "venv" in scheme_names:
+            self.sysconfig_scheme = "venv"
+            self.sysconfig_paths = {
+                i: sysconfig.get_path(i, expand=False, scheme=self.sysconfig_scheme) for i in sysconfig.get_path_names()
+            }
+            # we cannot use distutils at all if "venv" exists, distutils don't know it
             self.distutils_install = {}
-        elif sys.version_info[:2] == (3, 10) and 'deb_system' in scheme_names:
-            self.sysconfig_scheme = 'posix_prefix'
-            self.sysconfig_paths = {i: sysconfig.get_path(i, expand=False,
-                scheme=self.sysconfig_scheme) for i in sysconfig.
-                get_path_names()}
+        # debian / ubuntu python 3.10 without `python3-distutils` will report
+        # mangled `local/bin` / etc. names for the default prefix
+        # intentionally select `posix_prefix` which is the unaltered posix-like paths
+        elif sys.version_info[:2] == (3, 10) and "deb_system" in scheme_names:
+            self.sysconfig_scheme = "posix_prefix"
+            self.sysconfig_paths = {
+                i: sysconfig.get_path(i, expand=False, scheme=self.sysconfig_scheme) for i in sysconfig.get_path_names()
+            }
+            # we cannot use distutils at all if "venv" exists, distutils don't know it
             self.distutils_install = {}
         else:
             self.sysconfig_scheme = None
-            self.sysconfig_paths = {i: sysconfig.get_path(i, expand=False) for
-                i in sysconfig.get_path_names()}
+            self.sysconfig_paths = {i: sysconfig.get_path(i, expand=False) for i in sysconfig.get_path_names()}
             self.distutils_install = self._distutils_install().copy()
-        makefile = getattr(sysconfig, 'get_makefile_filename', getattr(
-            sysconfig, '_get_makefile_filename', None))
-        self.sysconfig = {k: v for k, v in [('makefile_filename', makefile(
-            ))] if k is not None}
+
+        # https://bugs.python.org/issue22199
+        makefile = getattr(sysconfig, "get_makefile_filename", getattr(sysconfig, "_get_makefile_filename", None))
+        self.sysconfig = {
+            k: v
+            for k, v in [
+                # a list of content to store from sysconfig
+                ("makefile_filename", makefile()),
+            ]
+            if k is not None
+        }
+
         config_var_keys = set()
         for element in self.sysconfig_paths.values():
-            config_var_keys.update(k[1:-1] for k in _CONF_VAR_RE.findall(
-                element))
-        config_var_keys.add('PYTHONFRAMEWORK')
-        self.sysconfig_vars = {i: sysconfig.get_config_var(i or '') for i in
-            config_var_keys}
-        confs = {k: (self.system_prefix if v is not None and v.startswith(
-            self.prefix) else v) for k, v in self.sysconfig_vars.items()}
-        self.system_stdlib = self.sysconfig_path('stdlib', confs)
-        self.system_stdlib_platform = self.sysconfig_path('platstdlib', confs)
-        self.max_size = getattr(sys, 'maxsize', getattr(sys, 'maxint', None))
+            config_var_keys.update(k[1:-1] for k in _CONF_VAR_RE.findall(element))
+        config_var_keys.add("PYTHONFRAMEWORK")
+
+        self.sysconfig_vars = {i: sysconfig.get_config_var(i or "") for i in config_var_keys}
+
+        confs = {
+            k: (self.system_prefix if v is not None and v.startswith(self.prefix) else v)
+            for k, v in self.sysconfig_vars.items()
+        }
+        self.system_stdlib = self.sysconfig_path("stdlib", confs)
+        self.system_stdlib_platform = self.sysconfig_path("platstdlib", confs)
+        self.max_size = getattr(sys, "maxsize", getattr(sys, "maxint", None))
         self._creators = None

     def _fast_get_system_executable(self):
         """Try to get the system executable by just looking at properties."""
-        pass
-
-    def __repr__(self) ->str:
-        return '{}({!r})'.format(self.__class__.__name__, {k: v for k, v in
-            self.__dict__.items() if not k.startswith('_')})
-
-    def __str__(self) ->str:
-        return '{}({})'.format(self.__class__.__name__, ', '.join(
-            f'{k}={v}' for k, v in (('spec', self.spec), ('system' if self.
-            system_executable is not None and self.system_executable !=
-            self.executable else None, self.system_executable), ('original' if
-            self.original_executable not in {self.system_executable, self.
-            executable} else None, self.original_executable), ('exe', self.
-            executable), ('platform', self.platform), ('version', repr(self
-            .version)), ('encoding_fs_io',
-            f'{self.file_system_encoding}-{self.stdout_encoding}')) if k is not
-            None))
-
-    def satisfies(self, spec, impl_must_match):
+        if self.real_prefix or (  # noqa: PLR1702
+            self.base_prefix is not None and self.base_prefix != self.prefix
+        ):  # if this is a virtual environment
+            if self.real_prefix is None:
+                base_executable = getattr(sys, "_base_executable", None)  # some platforms may set this to help us
+                if base_executable is not None:  # noqa: SIM102 # use the saved system executable if present
+                    if sys.executable != base_executable:  # we know we're in a virtual environment, cannot be us
+                        if os.path.exists(base_executable):
+                            return base_executable
+                        # Python may return "python" because it was invoked from the POSIX virtual environment
+                        # however some installs/distributions do not provide a version-less "python" binary in
+                        # the system install location (see PEP 394) so try to fallback to a versioned binary.
+                        #
+                        # Gate this to Python 3.11 as `sys._base_executable` path resolution is now relative to
+                        # the 'home' key from pyvenv.cfg which often points to the system install location.
+                        major, minor = self.version_info.major, self.version_info.minor
+                        if self.os == "posix" and (major, minor) >= (3, 11):
+                            # search relative to the directory of sys._base_executable
+                            base_dir = os.path.dirname(base_executable)
+                            for base_executable in [
+                                os.path.join(base_dir, exe) for exe in (f"python{major}", f"python{major}.{minor}")
+                            ]:
+                                if os.path.exists(base_executable):
+                                    return base_executable
+            return None  # in this case we just can't tell easily without poking around FS and calling them, bail
+        # if we're not in a virtual environment, this is already a system python, so return the original executable
+        # note we must choose the original and not the pure executable as shim scripts might throw us off
+        return self.original_executable
+
+    def install_path(self, key):
+        result = self.distutils_install.get(key)
+        if result is None:  # use sysconfig if sysconfig_scheme is set or distutils is unavailable
+            # set prefixes to empty => result is relative from cwd
+            prefixes = self.prefix, self.exec_prefix, self.base_prefix, self.base_exec_prefix
+            config_var = {k: "" if v in prefixes else v for k, v in self.sysconfig_vars.items()}
+            result = self.sysconfig_path(key, config_var=config_var).lstrip(os.sep)
+        return result
+
+    @staticmethod
+    def _distutils_install():
+        # use distutils primarily because that's what pip does
+        # https://github.com/pypa/pip/blob/main/src/pip/_internal/locations.py#L95
+        # note here we don't import Distribution directly to allow setuptools to patch it
+        with warnings.catch_warnings():  # disable warning for PEP-632
+            warnings.simplefilter("ignore")
+            try:
+                from distutils import dist  # noqa: PLC0415
+                from distutils.command.install import SCHEME_KEYS  # noqa: PLC0415
+            except ImportError:  # if removed or not installed ignore
+                return {}
+
+        d = dist.Distribution({"script_args": "--no-user-cfg"})  # conf files not parsed so they do not hijack paths
+        if hasattr(sys, "_framework"):
+            sys._framework = None  # disable macOS static paths for framework  # noqa: SLF001
+
+        with warnings.catch_warnings():  # disable warning for PEP-632
+            warnings.simplefilter("ignore")
+            i = d.get_command_obj("install", create=True)
+
+        i.prefix = os.sep  # paths generated are relative to prefix that contains the path sep, this makes it relative
+        i.finalize_options()
+        return {key: (getattr(i, f"install_{key}")[1:]).lstrip(os.sep) for key in SCHEME_KEYS}
+
+    @property
+    def version_str(self):
+        return ".".join(str(i) for i in self.version_info[0:3])
+
+    @property
+    def version_release_str(self):
+        return ".".join(str(i) for i in self.version_info[0:2])
+
+    @property
+    def python_name(self):
+        version_info = self.version_info
+        return f"python{version_info.major}.{version_info.minor}"
+
+    @property
+    def is_old_virtualenv(self):
+        return self.real_prefix is not None
+
+    @property
+    def is_venv(self):
+        return self.base_prefix is not None
+
+    def sysconfig_path(self, key, config_var=None, sep=os.sep):
+        pattern = self.sysconfig_paths[key]
+        if config_var is None:
+            config_var = self.sysconfig_vars
+        else:
+            base = self.sysconfig_vars.copy()
+            base.update(config_var)
+            config_var = base
+        return pattern.format(**config_var).replace("/", sep)
+
+    def creators(self, refresh=False):  # noqa: FBT002
+        if self._creators is None or refresh is True:
+            from virtualenv.run.plugin.creators import CreatorSelector  # noqa: PLC0415
+
+            self._creators = CreatorSelector.for_interpreter(self)
+        return self._creators
+
+    @property
+    def system_include(self):
+        path = self.sysconfig_path(
+            "include",
+            {
+                k: (self.system_prefix if v is not None and v.startswith(self.prefix) else v)
+                for k, v in self.sysconfig_vars.items()
+            },
+        )
+        if not os.path.exists(path):  # some broken packaging don't respect the sysconfig, fallback to distutils path
+            # the pattern include the distribution name too at the end, remove that via the parent call
+            fallback = os.path.join(self.prefix, os.path.dirname(self.install_path("headers")))
+            if os.path.exists(fallback):
+                path = fallback
+        return path
+
+    @property
+    def system_prefix(self):
+        return self.real_prefix or self.base_prefix or self.prefix
+
+    @property
+    def system_exec_prefix(self):
+        return self.real_prefix or self.base_exec_prefix or self.exec_prefix
+
+    def __repr__(self) -> str:
+        return "{}({!r})".format(
+            self.__class__.__name__,
+            {k: v for k, v in self.__dict__.items() if not k.startswith("_")},
+        )
+
+    def __str__(self) -> str:
+        return "{}({})".format(
+            self.__class__.__name__,
+            ", ".join(
+                f"{k}={v}"
+                for k, v in (
+                    ("spec", self.spec),
+                    (
+                        "system"
+                        if self.system_executable is not None and self.system_executable != self.executable
+                        else None,
+                        self.system_executable,
+                    ),
+                    (
+                        "original"
+                        if self.original_executable not in {self.system_executable, self.executable}
+                        else None,
+                        self.original_executable,
+                    ),
+                    ("exe", self.executable),
+                    ("platform", self.platform),
+                    ("version", repr(self.version)),
+                    ("encoding_fs_io", f"{self.file_system_encoding}-{self.stdout_encoding}"),
+                )
+                if k is not None
+            ),
+        )
+
+    @property
+    def spec(self):
+        return "{}{}-{}".format(self.implementation, ".".join(str(i) for i in self.version_info), self.architecture)
+
+    @classmethod
+    def clear_cache(cls, app_data):
+        # this method is not used by itself, so here and called functions can import stuff locally
+        from virtualenv.discovery.cached_py_info import clear  # noqa: PLC0415
+
+        clear(app_data)
+        cls._cache_exe_discovery.clear()
+
+    def satisfies(self, spec, impl_must_match):  # noqa: C901
         """Check if a given specification can be satisfied by the this python interpreter instance."""
-        pass
+        if spec.path:
+            if self.executable == os.path.abspath(spec.path):
+                return True  # if the path is a our own executable path we're done
+            if not spec.is_abs:
+                # if path set, and is not our original executable name, this does not match
+                basename = os.path.basename(self.original_executable)
+                spec_path = spec.path
+                if sys.platform == "win32":
+                    basename, suffix = os.path.splitext(basename)
+                    if spec_path.endswith(suffix):
+                        spec_path = spec_path[: -len(suffix)]
+                if basename != spec_path:
+                    return False
+
+        if (
+            impl_must_match
+            and spec.implementation is not None
+            and spec.implementation.lower() != self.implementation.lower()
+        ):
+            return False
+
+        if spec.architecture is not None and spec.architecture != self.architecture:
+            return False
+
+        for our, req in zip(self.version_info[0:3], (spec.major, spec.minor, spec.micro)):
+            if req is not None and our is not None and our != req:
+                return False
+        return True
+
     _current_system = None
     _current = None

@@ -121,37 +338,235 @@ class PythonInfo:
         """
         This locates the current host interpreter information. This might be different than what we run into in case
         the host python has been upgraded from underneath us.
-        """
-        pass
+        """  # noqa: D205
+        if cls._current is None:
+            cls._current = cls.from_exe(sys.executable, app_data, raise_on_error=True, resolve_to_host=False)
+        return cls._current

     @classmethod
-    def current_system(cls, app_data=None) ->PythonInfo:
+    def current_system(cls, app_data=None) -> PythonInfo:
         """
         This locates the current host interpreter information. This might be different than what we run into in case
         the host python has been upgraded from underneath us.
-        """
-        pass
+        """  # noqa: D205
+        if cls._current_system is None:
+            cls._current_system = cls.from_exe(sys.executable, app_data, raise_on_error=True, resolve_to_host=True)
+        return cls._current_system
+
+    def _to_json(self):
+        # don't save calculated paths, as these are non primitive types
+        return json.dumps(self._to_dict(), indent=2)
+
+    def _to_dict(self):
+        data = {var: (getattr(self, var) if var != "_creators" else None) for var in vars(self)}
+
+        data["version_info"] = data["version_info"]._asdict()  # namedtuple to dictionary
+        return data

     @classmethod
-    def from_exe(cls, exe, app_data=None, raise_on_error=True, ignore_cache
-        =False, resolve_to_host=True, env=None):
+    def from_exe(  # noqa: PLR0913
+        cls,
+        exe,
+        app_data=None,
+        raise_on_error=True,  # noqa: FBT002
+        ignore_cache=False,  # noqa: FBT002
+        resolve_to_host=True,  # noqa: FBT002
+        env=None,
+    ):
         """Given a path to an executable get the python information."""
-        pass
-    _cache_exe_discovery = {}
+        # this method is not used by itself, so here and called functions can import stuff locally
+        from virtualenv.discovery.cached_py_info import from_exe  # noqa: PLC0415

+        env = os.environ if env is None else env
+        proposed = from_exe(cls, app_data, exe, env=env, raise_on_error=raise_on_error, ignore_cache=ignore_cache)
+
+        if isinstance(proposed, PythonInfo) and resolve_to_host:
+            try:
+                proposed = proposed._resolve_to_system(app_data, proposed)  # noqa: SLF001
+            except Exception as exception:
+                if raise_on_error:
+                    raise
+                logging.info("ignore %s due cannot resolve system due to %r", proposed.original_executable, exception)
+                proposed = None
+        return proposed
+
+    @classmethod
+    def _from_json(cls, payload):
+        # the dictionary unroll here is to protect against pypy bug of interpreter crashing
+        raw = json.loads(payload)
+        return cls._from_dict(raw.copy())
+
+    @classmethod
+    def _from_dict(cls, data):
+        data["version_info"] = VersionInfo(**data["version_info"])  # restore this to a named tuple structure
+        result = cls()
+        result.__dict__ = data.copy()
+        return result
+
+    @classmethod
+    def _resolve_to_system(cls, app_data, target):
+        start_executable = target.executable
+        prefixes = OrderedDict()
+        while target.system_executable is None:
+            prefix = target.real_prefix or target.base_prefix or target.prefix
+            if prefix in prefixes:
+                if len(prefixes) == 1:
+                    # if we're linking back to ourselves accept ourselves with a WARNING
+                    logging.info("%r links back to itself via prefixes", target)
+                    target.system_executable = target.executable
+                    break
+                for at, (p, t) in enumerate(prefixes.items(), start=1):
+                    logging.error("%d: prefix=%s, info=%r", at, p, t)
+                logging.error("%d: prefix=%s, info=%r", len(prefixes) + 1, prefix, target)
+                msg = "prefixes are causing a circle {}".format("|".join(prefixes.keys()))
+                raise RuntimeError(msg)
+            prefixes[prefix] = target
+            target = target.discover_exe(app_data, prefix=prefix, exact=False)
+        if target.executable != target.system_executable:
+            target = cls.from_exe(target.system_executable, app_data)
+        target.executable = start_executable
+        return target
+
+    _cache_exe_discovery = {}  # noqa: RUF012
+
+    def discover_exe(self, app_data, prefix, exact=True, env=None):  # noqa: FBT002
+        key = prefix, exact
+        if key in self._cache_exe_discovery and prefix:
+            logging.debug("discover exe from cache %s - exact %s: %r", prefix, exact, self._cache_exe_discovery[key])
+            return self._cache_exe_discovery[key]
+        logging.debug("discover exe for %s in %s", self, prefix)
+        # we don't know explicitly here, do some guess work - our executable name should tell
+        possible_names = self._find_possible_exe_names()
+        possible_folders = self._find_possible_folders(prefix)
+        discovered = []
+        env = os.environ if env is None else env
+        for folder in possible_folders:
+            for name in possible_names:
+                info = self._check_exe(app_data, folder, name, exact, discovered, env)
+                if info is not None:
+                    self._cache_exe_discovery[key] = info
+                    return info
+        if exact is False and discovered:
+            info = self._select_most_likely(discovered, self)
+            folders = os.pathsep.join(possible_folders)
+            self._cache_exe_discovery[key] = info
+            logging.debug("no exact match found, chosen most similar of %s within base folders %s", info, folders)
+            return info
+        msg = "failed to detect {} in {}".format("|".join(possible_names), os.pathsep.join(possible_folders))
+        raise RuntimeError(msg)
+
+    def _check_exe(self, app_data, folder, name, exact, discovered, env):  # noqa: PLR0913
+        exe_path = os.path.join(folder, name)
+        if not os.path.exists(exe_path):
+            return None
+        info = self.from_exe(exe_path, app_data, resolve_to_host=False, raise_on_error=False, env=env)
+        if info is None:  # ignore if for some reason we can't query
+            return None
+        for item in ["implementation", "architecture", "version_info"]:
+            found = getattr(info, item)
+            searched = getattr(self, item)
+            if found != searched:
+                if item == "version_info":
+                    found, searched = ".".join(str(i) for i in found), ".".join(str(i) for i in searched)
+                executable = info.executable
+                logging.debug("refused interpreter %s because %s differs %s != %s", executable, item, found, searched)
+                if exact is False:
+                    discovered.append(info)
+                break
+        else:
+            return info
+        return None
+
+    @staticmethod
+    def _select_most_likely(discovered, target):
+        # no exact match found, start relaxing our requirements then to facilitate system package upgrades that
+        # could cause this (when using copy strategy of the host python)
+        def sort_by(info):
+            # we need to setup some priority of traits, this is as follows:
+            # implementation, major, minor, micro, architecture, tag, serial
+            matches = [
+                info.implementation == target.implementation,
+                info.version_info.major == target.version_info.major,
+                info.version_info.minor == target.version_info.minor,
+                info.architecture == target.architecture,
+                info.version_info.micro == target.version_info.micro,
+                info.version_info.releaselevel == target.version_info.releaselevel,
+                info.version_info.serial == target.version_info.serial,
+            ]
+            return sum((1 << pos if match else 0) for pos, match in enumerate(reversed(matches)))
+
+        sorted_discovered = sorted(discovered, key=sort_by, reverse=True)  # sort by priority in decreasing order
+        return sorted_discovered[0]
+
+    def _find_possible_folders(self, inside_folder):
+        candidate_folder = OrderedDict()
+        executables = OrderedDict()
+        executables[os.path.realpath(self.executable)] = None
+        executables[self.executable] = None
+        executables[os.path.realpath(self.original_executable)] = None
+        executables[self.original_executable] = None
+        for exe in executables:
+            base = os.path.dirname(exe)
+            # following path pattern of the current
+            if base.startswith(self.prefix):
+                relative = base[len(self.prefix) :]
+                candidate_folder[f"{inside_folder}{relative}"] = None
+
+        # or at root level
+        candidate_folder[inside_folder] = None
+        return [i for i in candidate_folder if os.path.exists(i)]
+
+    def _find_possible_exe_names(self):
+        name_candidate = OrderedDict()
+        for name in self._possible_base():
+            for at in (3, 2, 1, 0):
+                version = ".".join(str(i) for i in self.version_info[:at])
+                for arch in [f"-{self.architecture}", ""]:
+                    for ext in EXTENSIONS:
+                        candidate = f"{name}{version}{arch}{ext}"
+                        name_candidate[candidate] = None
+        return list(name_candidate.keys())
+
+    def _possible_base(self):
+        possible_base = OrderedDict()
+        basename = os.path.splitext(os.path.basename(self.executable))[0].rstrip(digits)
+        possible_base[basename] = None
+        possible_base[self.implementation] = None
+        # python is always the final option as in practice is used by multiple implementation as exe name
+        if "python" in possible_base:
+            del possible_base["python"]
+        possible_base["python"] = None
+        for base in possible_base:
+            lower = base.lower()
+            yield lower
+            from virtualenv.info import fs_is_case_sensitive  # noqa: PLC0415
+
+            if fs_is_case_sensitive():
+                if base != lower:
+                    yield base
+                upper = base.upper()
+                if upper != base:
+                    yield upper
+
+
+if __name__ == "__main__":
+    # dump a JSON representation of the current python

-if __name__ == '__main__':
     argv = sys.argv[1:]
+
     if len(argv) >= 1:
         start_cookie = argv[0]
         argv = argv[1:]
     else:
-        start_cookie = ''
+        start_cookie = ""
+
     if len(argv) >= 1:
         end_cookie = argv[0]
         argv = argv[1:]
     else:
-        end_cookie = ''
+        end_cookie = ""
+
     sys.argv = sys.argv[:1] + argv
-    info = PythonInfo()._to_json()
-    sys.stdout.write(''.join((start_cookie[::-1], info, end_cookie[::-1])))
+
+    info = PythonInfo()._to_json()  # noqa: SLF001
+    sys.stdout.write("".join((start_cookie[::-1], info, end_cookie[::-1])))
diff --git a/src/virtualenv/discovery/py_spec.py b/src/virtualenv/discovery/py_spec.py
index fda6851..dcd84f4 100644
--- a/src/virtualenv/discovery/py_spec.py
+++ b/src/virtualenv/discovery/py_spec.py
@@ -1,17 +1,26 @@
 """A Python specification is an abstract requirement definition of an interpreter."""
+
 from __future__ import annotations
+
 import os
 import re
-PATTERN = re.compile(
-    '^(?P<impl>[a-zA-Z]+)?(?P<version>[0-9.]+)?(?:-(?P<arch>32|64))?$')
+
+PATTERN = re.compile(r"^(?P<impl>[a-zA-Z]+)?(?P<version>[0-9.]+)?(?:-(?P<arch>32|64))?$")


 class PythonSpec:
     """Contains specification about a Python Interpreter."""

-    def __init__(self, str_spec: str, implementation: (str | None), major:
-        (int | None), minor: (int | None), micro: (int | None),
-        architecture: (int | None), path: (str | None)) ->None:
+    def __init__(  # noqa: PLR0913
+        self,
+        str_spec: str,
+        implementation: str | None,
+        major: int | None,
+        minor: int | None,
+        micro: int | None,
+        architecture: int | None,
+        path: str | None,
+    ) -> None:
         self.str_spec = str_spec
         self.implementation = implementation
         self.major = major
@@ -20,21 +29,94 @@ class PythonSpec:
         self.architecture = architecture
         self.path = path

-    def generate_re(self, *, windows: bool) ->re.Pattern:
+    @classmethod
+    def from_string_spec(cls, string_spec: str):  # noqa: C901, PLR0912
+        impl, major, minor, micro, arch, path = None, None, None, None, None, None
+        if os.path.isabs(string_spec):  # noqa: PLR1702
+            path = string_spec
+        else:
+            ok = False
+            match = re.match(PATTERN, string_spec)
+            if match:
+
+                def _int_or_none(val):
+                    return None if val is None else int(val)
+
+                try:
+                    groups = match.groupdict()
+                    version = groups["version"]
+                    if version is not None:
+                        versions = tuple(int(i) for i in version.split(".") if i)
+                        if len(versions) > 3:  # noqa: PLR2004
+                            raise ValueError  # noqa: TRY301
+                        if len(versions) == 3:  # noqa: PLR2004
+                            major, minor, micro = versions
+                        elif len(versions) == 2:  # noqa: PLR2004
+                            major, minor = versions
+                        elif len(versions) == 1:
+                            version_data = versions[0]
+                            major = int(str(version_data)[0])  # first digit major
+                            if version_data > 9:  # noqa: PLR2004
+                                minor = int(str(version_data)[1:])
+                    ok = True
+                except ValueError:
+                    pass
+                else:
+                    impl = groups["impl"]
+                    if impl in {"py", "python"}:
+                        impl = None
+                    arch = _int_or_none(groups["arch"])
+
+            if not ok:
+                path = string_spec
+
+        return cls(string_spec, impl, major, minor, micro, arch, path)
+
+    def generate_re(self, *, windows: bool) -> re.Pattern:
         """Generate a regular expression for matching against a filename."""
-        pass
+        version = r"{}(\.{}(\.{})?)?".format(
+            *(r"\d+" if v is None else v for v in (self.major, self.minor, self.micro))
+        )
+        impl = "python" if self.implementation is None else f"python|{re.escape(self.implementation)}"
+        suffix = r"\.exe" if windows else ""
+        version_conditional = (
+            "?"
+            # Windows Python executables are almost always unversioned
+            if windows
+            # Spec is an empty string
+            or self.major is None
+            else ""
+        )
+        # Try matching `direct` first, so the `direct` group is filled when possible.
+        return re.compile(
+            rf"(?P<impl>{impl})(?P<v>{version}){version_conditional}{suffix}$",
+            flags=re.IGNORECASE,
+        )
+
+    @property
+    def is_abs(self):
+        return self.path is not None and os.path.isabs(self.path)

     def satisfies(self, spec):
         """Called when there's a candidate metadata spec to see if compatible - e.g. PEP-514 on Windows."""
-        pass
+        if spec.is_abs and self.is_abs and self.path != spec.path:
+            return False
+        if spec.implementation is not None and spec.implementation.lower() != self.implementation.lower():
+            return False
+        if spec.architecture is not None and spec.architecture != self.architecture:
+            return False
+
+        for our, req in zip((self.major, self.minor, self.micro), (spec.major, spec.minor, spec.micro)):
+            if req is not None and our is not None and our != req:
+                return False
+        return True

-    def __repr__(self) ->str:
+    def __repr__(self) -> str:
         name = type(self).__name__
-        params = ('implementation', 'major', 'minor', 'micro',
-            'architecture', 'path')
-        return (
-            f"{name}({', '.join(f'{k}={getattr(self, k)}' for k in params if getattr(self, k) is not None)})"
-            )
+        params = "implementation", "major", "minor", "micro", "architecture", "path"
+        return f"{name}({', '.join(f'{k}={getattr(self, k)}' for k in params if getattr(self, k) is not None)})"


-__all__ = ['PythonSpec']
+__all__ = [
+    "PythonSpec",
+]
diff --git a/src/virtualenv/discovery/windows/pep514.py b/src/virtualenv/discovery/windows/pep514.py
index 3635215..8bc9e30 100644
--- a/src/virtualenv/discovery/windows/pep514.py
+++ b/src/virtualenv/discovery/windows/pep514.py
@@ -1,9 +1,152 @@
 """Implement https://www.python.org/dev/peps/pep-0514/ to discover interpreters - Windows only."""
+
 from __future__ import annotations
+
 import os
 import re
 import winreg
 from logging import basicConfig, getLogger
+
 LOGGER = getLogger(__name__)
-if __name__ == '__main__':
+
+
+def enum_keys(key):
+    at = 0
+    while True:
+        try:
+            yield winreg.EnumKey(key, at)
+        except OSError:
+            break
+        at += 1
+
+
+def get_value(key, value_name):
+    try:
+        return winreg.QueryValueEx(key, value_name)[0]
+    except OSError:
+        return None
+
+
+def discover_pythons():
+    for hive, hive_name, key, flags, default_arch in [
+        (winreg.HKEY_CURRENT_USER, "HKEY_CURRENT_USER", r"Software\Python", 0, 64),
+        (winreg.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", r"Software\Python", winreg.KEY_WOW64_64KEY, 64),
+        (winreg.HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", r"Software\Python", winreg.KEY_WOW64_32KEY, 32),
+    ]:
+        yield from process_set(hive, hive_name, key, flags, default_arch)
+
+
+def process_set(hive, hive_name, key, flags, default_arch):
+    try:
+        with winreg.OpenKeyEx(hive, key, 0, winreg.KEY_READ | flags) as root_key:
+            for company in enum_keys(root_key):
+                if company == "PyLauncher":  # reserved
+                    continue
+                yield from process_company(hive_name, company, root_key, default_arch)
+    except OSError:
+        pass
+
+
+def process_company(hive_name, company, root_key, default_arch):
+    with winreg.OpenKeyEx(root_key, company) as company_key:
+        for tag in enum_keys(company_key):
+            spec = process_tag(hive_name, company, company_key, tag, default_arch)
+            if spec is not None:
+                yield spec
+
+
+def process_tag(hive_name, company, company_key, tag, default_arch):
+    with winreg.OpenKeyEx(company_key, tag) as tag_key:
+        version = load_version_data(hive_name, company, tag, tag_key)
+        if version is not None:  # if failed to get version bail
+            major, minor, _ = version
+            arch = load_arch_data(hive_name, company, tag, tag_key, default_arch)
+            if arch is not None:
+                exe_data = load_exe(hive_name, company, company_key, tag)
+                if exe_data is not None:
+                    exe, args = exe_data
+                    return company, major, minor, arch, exe, args
+                return None
+            return None
+        return None
+
+
+def load_exe(hive_name, company, company_key, tag):
+    key_path = f"{hive_name}/{company}/{tag}"
+    try:
+        with winreg.OpenKeyEx(company_key, rf"{tag}\InstallPath") as ip_key, ip_key:
+            exe = get_value(ip_key, "ExecutablePath")
+            if exe is None:
+                ip = get_value(ip_key, None)
+                if ip is None:
+                    msg(key_path, "no ExecutablePath or default for it")
+
+                else:
+                    exe = os.path.join(ip, "python.exe")
+            if exe is not None and os.path.exists(exe):
+                args = get_value(ip_key, "ExecutableArguments")
+                return exe, args
+            msg(key_path, f"could not load exe with value {exe}")
+    except OSError:
+        msg(f"{key_path}/InstallPath", "missing")
+    return None
+
+
+def load_arch_data(hive_name, company, tag, tag_key, default_arch):
+    arch_str = get_value(tag_key, "SysArchitecture")
+    if arch_str is not None:
+        key_path = f"{hive_name}/{company}/{tag}/SysArchitecture"
+        try:
+            return parse_arch(arch_str)
+        except ValueError as sys_arch:
+            msg(key_path, sys_arch)
+    return default_arch
+
+
+def parse_arch(arch_str):
+    if isinstance(arch_str, str):
+        match = re.match(r"^(\d+)bit$", arch_str)
+        if match:
+            return int(next(iter(match.groups())))
+        error = f"invalid format {arch_str}"
+    else:
+        error = f"arch is not string: {arch_str!r}"
+    raise ValueError(error)
+
+
+def load_version_data(hive_name, company, tag, tag_key):
+    for candidate, key_path in [
+        (get_value(tag_key, "SysVersion"), f"{hive_name}/{company}/{tag}/SysVersion"),
+        (tag, f"{hive_name}/{company}/{tag}"),
+    ]:
+        if candidate is not None:
+            try:
+                return parse_version(candidate)
+            except ValueError as sys_version:
+                msg(key_path, sys_version)
+    return None
+
+
+def parse_version(version_str):
+    if isinstance(version_str, str):
+        match = re.match(r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?$", version_str)
+        if match:
+            return tuple(int(i) if i is not None else None for i in match.groups())
+        error = f"invalid format {version_str}"
+    else:
+        error = f"version is not string: {version_str!r}"
+    raise ValueError(error)
+
+
+def msg(path, what):
+    LOGGER.warning("PEP-514 violation in Windows Registry at %s error: %s", path, what)
+
+
+def _run():
+    basicConfig()
+    interpreters = [repr(spec) for spec in discover_pythons()]
+    print("\n".join(sorted(interpreters)))  # noqa: T201
+
+
+if __name__ == "__main__":
     _run()
diff --git a/src/virtualenv/info.py b/src/virtualenv/info.py
index 974fc0b..8b217d0 100644
--- a/src/virtualenv/info.py
+++ b/src/virtualenv/info.py
@@ -1,17 +1,65 @@
 from __future__ import annotations
+
 import logging
 import os
 import platform
 import sys
 import tempfile
+
 IMPLEMENTATION = platform.python_implementation()
-IS_PYPY = IMPLEMENTATION == 'PyPy'
-IS_CPYTHON = IMPLEMENTATION == 'CPython'
-IS_WIN = sys.platform == 'win32'
-IS_MAC_ARM64 = sys.platform == 'darwin' and platform.machine() == 'arm64'
-ROOT = os.path.realpath(os.path.join(os.path.abspath(__file__), os.path.
-    pardir, os.path.pardir))
+IS_PYPY = IMPLEMENTATION == "PyPy"
+IS_CPYTHON = IMPLEMENTATION == "CPython"
+IS_WIN = sys.platform == "win32"
+IS_MAC_ARM64 = sys.platform == "darwin" and platform.machine() == "arm64"
+ROOT = os.path.realpath(os.path.join(os.path.abspath(__file__), os.path.pardir, os.path.pardir))
 IS_ZIPAPP = os.path.isfile(ROOT)
 _CAN_SYMLINK = _FS_CASE_SENSITIVE = _CFG_DIR = _DATA_DIR = None
-__all__ = ('IS_CPYTHON', 'IS_MAC_ARM64', 'IS_PYPY', 'IS_WIN', 'IS_ZIPAPP',
-    'ROOT', 'fs_is_case_sensitive', 'fs_path_id', 'fs_supports_symlink')
+
+
+def fs_is_case_sensitive():
+    global _FS_CASE_SENSITIVE  # noqa: PLW0603
+
+    if _FS_CASE_SENSITIVE is None:
+        with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file:
+            _FS_CASE_SENSITIVE = not os.path.exists(tmp_file.name.lower())
+            logging.debug("filesystem is %scase-sensitive", "" if _FS_CASE_SENSITIVE else "not ")
+    return _FS_CASE_SENSITIVE
+
+
+def fs_supports_symlink():
+    global _CAN_SYMLINK  # noqa: PLW0603
+
+    if _CAN_SYMLINK is None:
+        can = False
+        if hasattr(os, "symlink"):
+            if IS_WIN:
+                with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file:
+                    temp_dir = os.path.dirname(tmp_file.name)
+                    dest = os.path.join(temp_dir, f"{tmp_file.name}-{'b'}")
+                    try:
+                        os.symlink(tmp_file.name, dest)
+                        can = True
+                    except (OSError, NotImplementedError):
+                        pass
+                logging.debug("symlink on filesystem does%s work", "" if can else " not")
+            else:
+                can = True
+        _CAN_SYMLINK = can
+    return _CAN_SYMLINK
+
+
+def fs_path_id(path: str) -> str:
+    return path.casefold() if fs_is_case_sensitive() else path
+
+
+__all__ = (
+    "IS_CPYTHON",
+    "IS_MAC_ARM64",
+    "IS_PYPY",
+    "IS_WIN",
+    "IS_ZIPAPP",
+    "ROOT",
+    "fs_is_case_sensitive",
+    "fs_path_id",
+    "fs_supports_symlink",
+)
diff --git a/src/virtualenv/report.py b/src/virtualenv/report.py
index 572a341..9ad52a1 100644
--- a/src/virtualenv/report.py
+++ b/src/virtualenv/report.py
@@ -1,8 +1,50 @@
 from __future__ import annotations
+
 import logging
 import sys
-LEVELS = {(0): logging.CRITICAL, (1): logging.ERROR, (2): logging.WARNING,
-    (3): logging.INFO, (4): logging.DEBUG, (5): logging.NOTSET}
+
+LEVELS = {
+    0: logging.CRITICAL,
+    1: logging.ERROR,
+    2: logging.WARNING,
+    3: logging.INFO,
+    4: logging.DEBUG,
+    5: logging.NOTSET,
+}
+
 MAX_LEVEL = max(LEVELS.keys())
 LOGGER = logging.getLogger()
-__all__ = ['LEVELS', 'MAX_LEVEL', 'setup_report']
+
+
+def setup_report(verbosity, show_pid=False):  # noqa: FBT002
+    _clean_handlers(LOGGER)
+    verbosity = min(verbosity, MAX_LEVEL)  # pragma: no cover
+    level = LEVELS[verbosity]
+    msg_format = "%(message)s"
+    if level <= logging.DEBUG:
+        locate = "module"
+        msg_format = f"%(relativeCreated)d {msg_format} [%(levelname)s %({locate})s:%(lineno)d]"
+    if show_pid:
+        msg_format = f"[%(process)d] {msg_format}"
+    formatter = logging.Formatter(msg_format)
+    stream_handler = logging.StreamHandler(stream=sys.stdout)
+    stream_handler.setLevel(level)
+    LOGGER.setLevel(logging.NOTSET)
+    stream_handler.setFormatter(formatter)
+    LOGGER.addHandler(stream_handler)
+    level_name = logging.getLevelName(level)
+    logging.debug("setup logging to %s", level_name)
+    logging.getLogger("distlib").setLevel(logging.ERROR)
+    return verbosity
+
+
+def _clean_handlers(log):
+    for log_handler in list(log.handlers):  # remove handlers of libraries
+        log.removeHandler(log_handler)
+
+
+__all__ = [
+    "LEVELS",
+    "MAX_LEVEL",
+    "setup_report",
+]
diff --git a/src/virtualenv/run/plugin/activators.py b/src/virtualenv/run/plugin/activators.py
index 3a63907..a0e8669 100644
--- a/src/virtualenv/run/plugin/activators.py
+++ b/src/virtualenv/run/plugin/activators.py
@@ -1,18 +1,62 @@
 from __future__ import annotations
+
 from argparse import ArgumentTypeError
 from collections import OrderedDict
+
 from .base import ComponentBuilder


 class ActivationSelector(ComponentBuilder):
-
-    def __init__(self, interpreter, parser) ->None:
+    def __init__(self, interpreter, parser) -> None:
         self.default = None
-        possible = OrderedDict((k, v) for k, v in self.options(
-            'virtualenv.activate').items() if v.supports(interpreter))
-        super().__init__(interpreter, parser, 'activators', possible)
-        self.parser.description = 'options for activation scripts'
+        possible = OrderedDict(
+            (k, v) for k, v in self.options("virtualenv.activate").items() if v.supports(interpreter)
+        )
+        super().__init__(interpreter, parser, "activators", possible)
+        self.parser.description = "options for activation scripts"
         self.active = None

+    def add_selector_arg_parse(self, name, choices):
+        self.default = ",".join(choices)
+        self.parser.add_argument(
+            f"--{name}",
+            default=self.default,
+            metavar="comma_sep_list",
+            required=False,
+            help="activators to generate - default is all supported",
+            type=self._extract_activators,
+        )
+
+    def _extract_activators(self, entered_str):
+        elements = [e.strip() for e in entered_str.split(",") if e.strip()]
+        missing = [e for e in elements if e not in self.possible]
+        if missing:
+            msg = f"the following activators are not available {','.join(missing)}"
+            raise ArgumentTypeError(msg)
+        return elements
+
+    def handle_selected_arg_parse(self, options):
+        selected_activators = (
+            self._extract_activators(self.default) if options.activators is self.default else options.activators
+        )
+        self.active = {k: v for k, v in self.possible.items() if k in selected_activators}
+        self.parser.add_argument(
+            "--prompt",
+            dest="prompt",
+            metavar="prompt",
+            help=(
+                "provides an alternative prompt prefix for this environment "
+                "(value of . means name of the current working directory)"
+            ),
+            default=None,
+        )
+        for activator in self.active.values():
+            activator.add_parser_arguments(self.parser, self.interpreter)
+
+    def create(self, options):
+        return [activator_class(options) for activator_class in self.active.values()]
+

-__all__ = ['ActivationSelector']
+__all__ = [
+    "ActivationSelector",
+]
diff --git a/src/virtualenv/run/plugin/base.py b/src/virtualenv/run/plugin/base.py
index c9eda3d..f0682dd 100644
--- a/src/virtualenv/run/plugin/base.py
+++ b/src/virtualenv/run/plugin/base.py
@@ -1,23 +1,37 @@
 from __future__ import annotations
+
 import sys
 from collections import OrderedDict
+
 if sys.version_info >= (3, 8):
     from importlib.metadata import entry_points
+
     importlib_metadata_version = ()
 else:
     from importlib_metadata import entry_points, version
-    importlib_metadata_version = tuple(int(i) for i in version(
-        'importlib_metadata').split('.')[:2])
+
+    importlib_metadata_version = tuple(int(i) for i in version("importlib_metadata").split(".")[:2])


 class PluginLoader:
     _OPTIONS = None
     _ENTRY_POINTS = None

+    @classmethod
+    def entry_points_for(cls, key):
+        if sys.version_info >= (3, 10) or importlib_metadata_version >= (3, 6):
+            return OrderedDict((e.name, e.load()) for e in cls.entry_points().select(group=key))
+        return OrderedDict((e.name, e.load()) for e in cls.entry_points().get(key, {}))
+
+    @staticmethod
+    def entry_points():
+        if PluginLoader._ENTRY_POINTS is None:
+            PluginLoader._ENTRY_POINTS = entry_points()
+        return PluginLoader._ENTRY_POINTS

-class ComponentBuilder(PluginLoader):

-    def __init__(self, interpreter, parser, name, possible) ->None:
+class ComponentBuilder(PluginLoader):
+    def __init__(self, interpreter, parser, name, possible) -> None:
         self.interpreter = interpreter
         self.name = name
         self._impl_class = None
@@ -25,5 +39,33 @@ class ComponentBuilder(PluginLoader):
         self.parser = parser.add_argument_group(title=name)
         self.add_selector_arg_parse(name, list(self.possible))

+    @classmethod
+    def options(cls, key):
+        if cls._OPTIONS is None:
+            cls._OPTIONS = cls.entry_points_for(key)
+        return cls._OPTIONS
+
+    def add_selector_arg_parse(self, name, choices):
+        raise NotImplementedError
+
+    def handle_selected_arg_parse(self, options):
+        selected = getattr(options, self.name)
+        if selected not in self.possible:
+            msg = f"No implementation for {self.interpreter}"
+            raise RuntimeError(msg)
+        self._impl_class = self.possible[selected]
+        self.populate_selected_argparse(selected, options.app_data)
+        return selected
+
+    def populate_selected_argparse(self, selected, app_data):
+        self.parser.description = f"options for {self.name} {selected}"
+        self._impl_class.add_parser_arguments(self.parser, self.interpreter, app_data)
+
+    def create(self, options):
+        return self._impl_class(options, self.interpreter)
+

-__all__ = ['ComponentBuilder', 'PluginLoader']
+__all__ = [
+    "ComponentBuilder",
+    "PluginLoader",
+]
diff --git a/src/virtualenv/run/plugin/creators.py b/src/virtualenv/run/plugin/creators.py
index 662d6db..6bb1184 100644
--- a/src/virtualenv/run/plugin/creators.py
+++ b/src/virtualenv/run/plugin/creators.py
@@ -1,9 +1,13 @@
 from __future__ import annotations
+
 from collections import OrderedDict, defaultdict
 from typing import TYPE_CHECKING, NamedTuple
+
 from virtualenv.create.describe import Describe
 from virtualenv.create.via_global_ref.builtin.builtin_way import VirtualenvBuiltin
+
 from .base import ComponentBuilder
+
 if TYPE_CHECKING:
     from virtualenv.create.creator import Creator, CreatorMeta

@@ -16,11 +20,72 @@ class CreatorInfo(NamedTuple):


 class CreatorSelector(ComponentBuilder):
+    def __init__(self, interpreter, parser) -> None:
+        creators, self.key_to_meta, self.describe, self.builtin_key = self.for_interpreter(interpreter)
+        super().__init__(interpreter, parser, "creator", creators)
+
+    @classmethod
+    def for_interpreter(cls, interpreter):
+        key_to_class, key_to_meta, builtin_key, describe = OrderedDict(), {}, None, None
+        errors = defaultdict(list)
+        for key, creator_class in cls.options("virtualenv.create").items():
+            if key == "builtin":
+                msg = "builtin creator is a reserved name"
+                raise RuntimeError(msg)
+            meta = creator_class.can_create(interpreter)
+            if meta:
+                if meta.error:
+                    errors[meta.error].append(creator_class)
+                else:
+                    if "builtin" not in key_to_class and issubclass(creator_class, VirtualenvBuiltin):
+                        builtin_key = key
+                        key_to_class["builtin"] = creator_class
+                        key_to_meta["builtin"] = meta
+                    key_to_class[key] = creator_class
+                    key_to_meta[key] = meta
+            if describe is None and issubclass(creator_class, Describe) and creator_class.can_describe(interpreter):
+                describe = creator_class
+        if not key_to_meta:
+            if errors:
+                rows = [f"{k} for creators {', '.join(i.__name__ for i in v)}" for k, v in errors.items()]
+                raise RuntimeError("\n".join(rows))
+            msg = f"No virtualenv implementation for {interpreter}"
+            raise RuntimeError(msg)
+        return CreatorInfo(
+            key_to_class=key_to_class,
+            key_to_meta=key_to_meta,
+            describe=describe,
+            builtin_key=builtin_key,
+        )
+
+    def add_selector_arg_parse(self, name, choices):
+        # prefer the built-in venv if present, otherwise fallback to first defined type
+        choices = sorted(choices, key=lambda a: 0 if a == "builtin" else 1)
+        default_value = self._get_default(choices)
+        self.parser.add_argument(
+            f"--{name}",
+            choices=choices,
+            default=default_value,
+            required=False,
+            help=f"create environment via{'' if self.builtin_key is None else f' (builtin = {self.builtin_key})'}",
+        )
+
+    @staticmethod
+    def _get_default(choices):
+        return next(iter(choices))
+
+    def populate_selected_argparse(self, selected, app_data):
+        self.parser.description = f"options for {self.name} {selected}"
+        self._impl_class.add_parser_arguments(self.parser, self.interpreter, self.key_to_meta[selected], app_data)

-    def __init__(self, interpreter, parser) ->None:
-        creators, self.key_to_meta, self.describe, self.builtin_key = (self
-            .for_interpreter(interpreter))
-        super().__init__(interpreter, parser, 'creator', creators)
+    def create(self, options):
+        options.meta = self.key_to_meta[getattr(options, self.name)]
+        if not issubclass(self._impl_class, Describe):
+            options.describe = self.describe(options, self.interpreter)
+        return super().create(options)


-__all__ = ['CreatorInfo', 'CreatorSelector']
+__all__ = [
+    "CreatorInfo",
+    "CreatorSelector",
+]
diff --git a/src/virtualenv/run/plugin/discovery.py b/src/virtualenv/run/plugin/discovery.py
index dbdba67..a963042 100644
--- a/src/virtualenv/run/plugin/discovery.py
+++ b/src/virtualenv/run/plugin/discovery.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+
 from .base import PluginLoader


@@ -6,4 +7,34 @@ class Discovery(PluginLoader):
     """Discovery plugins."""


-__all__ = ['Discovery', 'get_discover']
+def get_discover(parser, args):
+    discover_types = Discovery.entry_points_for("virtualenv.discovery")
+    discovery_parser = parser.add_argument_group(
+        title="discovery",
+        description="discover and provide a target interpreter",
+    )
+    choices = _get_default_discovery(discover_types)
+    # prefer the builtin if present, otherwise fallback to first defined type
+    choices = sorted(choices, key=lambda a: 0 if a == "builtin" else 1)
+    discovery_parser.add_argument(
+        "--discovery",
+        choices=choices,
+        default=next(iter(choices)),
+        required=False,
+        help="interpreter discovery method",
+    )
+    options, _ = parser.parse_known_args(args)
+    discover_class = discover_types[options.discovery]
+    discover_class.add_parser_arguments(discovery_parser)
+    options, _ = parser.parse_known_args(args, namespace=options)
+    return discover_class(options)
+
+
+def _get_default_discovery(discover_types):
+    return list(discover_types.keys())
+
+
+__all__ = [
+    "Discovery",
+    "get_discover",
+]
diff --git a/src/virtualenv/run/plugin/seeders.py b/src/virtualenv/run/plugin/seeders.py
index 6ef8879..b1da34c 100644
--- a/src/virtualenv/run/plugin/seeders.py
+++ b/src/virtualenv/run/plugin/seeders.py
@@ -1,12 +1,40 @@
 from __future__ import annotations
+
 from .base import ComponentBuilder


 class SeederSelector(ComponentBuilder):
+    def __init__(self, interpreter, parser) -> None:
+        possible = self.options("virtualenv.seed")
+        super().__init__(interpreter, parser, "seeder", possible)
+
+    def add_selector_arg_parse(self, name, choices):
+        self.parser.add_argument(
+            f"--{name}",
+            choices=choices,
+            default=self._get_default(),
+            required=False,
+            help="seed packages install method",
+        )
+        self.parser.add_argument(
+            "--no-seed",
+            "--without-pip",
+            help="do not install seed packages",
+            action="store_true",
+            dest="no_seed",
+        )
+
+    @staticmethod
+    def _get_default():
+        return "app-data"
+
+    def handle_selected_arg_parse(self, options):
+        return super().handle_selected_arg_parse(options)

-    def __init__(self, interpreter, parser) ->None:
-        possible = self.options('virtualenv.seed')
-        super().__init__(interpreter, parser, 'seeder', possible)
+    def create(self, options):
+        return self._impl_class(options)


-__all__ = ['SeederSelector']
+__all__ = [
+    "SeederSelector",
+]
diff --git a/src/virtualenv/run/session.py b/src/virtualenv/run/session.py
index 33a12c6..9ffd890 100644
--- a/src/virtualenv/run/session.py
+++ b/src/virtualenv/run/session.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+
 import json
 import logging

@@ -6,8 +7,7 @@ import logging
 class Session:
     """Represents a virtual environment creation session."""

-    def __init__(self, verbosity, app_data, interpreter, creator, seeder,
-        activators) ->None:
+    def __init__(self, verbosity, app_data, interpreter, creator, seeder, activators) -> None:  # noqa: PLR0913
         self._verbosity = verbosity
         self._app_data = app_data
         self._interpreter = interpreter
@@ -18,27 +18,51 @@ class Session:
     @property
     def verbosity(self):
         """The verbosity of the run."""
-        pass
+        return self._verbosity

     @property
     def interpreter(self):
         """Create a virtual environment based on this reference interpreter."""
-        pass
+        return self._interpreter

     @property
     def creator(self):
         """The creator used to build the virtual environment (must be compatible with the interpreter)."""
-        pass
+        return self._creator

     @property
     def seeder(self):
         """The mechanism used to provide the seed packages (pip, setuptools, wheel)."""
-        pass
+        return self._seeder

     @property
     def activators(self):
         """Activators used to generate activations scripts."""
-        pass
+        return self._activators
+
+    def run(self):
+        self._create()
+        self._seed()
+        self._activate()
+        self.creator.pyenv_cfg.write()
+
+    def _create(self):
+        logging.info("create virtual environment via %s", self.creator)
+        self.creator.run()
+        logging.debug(_DEBUG_MARKER)
+        logging.debug("%s", _Debug(self.creator))
+
+    def _seed(self):
+        if self.seeder is not None and self.seeder.enabled:
+            logging.info("add seed packages via %s", self.seeder)
+            self.seeder.run(self.creator)
+
+    def _activate(self):
+        if self.activators:
+            active = ", ".join(type(i).__name__.replace("Activator", "") for i in self.activators)
+            logging.info("add activators for %s", active)
+            for activator in self.activators:
+                activator.generate(self.creator)

     def __enter__(self):
         return self
@@ -47,17 +71,19 @@ class Session:
         self._app_data.close()


-_DEBUG_MARKER = '=' * 30 + ' target debug ' + '=' * 30
+_DEBUG_MARKER = "=" * 30 + " target debug " + "=" * 30


 class _Debug:
     """lazily populate debug."""

-    def __init__(self, creator) ->None:
+    def __init__(self, creator) -> None:
         self.creator = creator

-    def __repr__(self) ->str:
+    def __repr__(self) -> str:
         return json.dumps(self.creator.debug, indent=2)


-__all__ = ['Session']
+__all__ = [
+    "Session",
+]
diff --git a/src/virtualenv/seed/embed/base_embed.py b/src/virtualenv/seed/embed/base_embed.py
index 4f02960..864cc49 100644
--- a/src/virtualenv/seed/embed/base_embed.py
+++ b/src/virtualenv/seed/embed/base_embed.py
@@ -1,46 +1,118 @@
 from __future__ import annotations
+
 from abc import ABC
 from pathlib import Path
+
 from virtualenv.seed.seeder import Seeder
 from virtualenv.seed.wheels import Version
+
 PERIODIC_UPDATE_ON_BY_DEFAULT = True


 class BaseEmbed(Seeder, ABC):
-
-    def __init__(self, options) ->None:
+    def __init__(self, options) -> None:
         super().__init__(options, enabled=options.no_seed is False)
+
         self.download = options.download
-        self.extra_search_dir = [i.resolve() for i in options.
-            extra_search_dir if i.exists()]
+        self.extra_search_dir = [i.resolve() for i in options.extra_search_dir if i.exists()]
+
         self.pip_version = options.pip
         self.setuptools_version = options.setuptools
         self.wheel_version = options.wheel
+
         self.no_pip = options.no_pip
         self.no_setuptools = options.no_setuptools
         self.no_wheel = options.no_wheel
         self.app_data = options.app_data
         self.periodic_update = not options.no_periodic_update
+
         if not self.distribution_to_versions():
             self.enabled = False

-    def __repr__(self) ->str:
+    @classmethod
+    def distributions(cls) -> dict[str, Version]:
+        return {
+            "pip": Version.bundle,
+            "setuptools": Version.bundle,
+            "wheel": Version.bundle,
+        }
+
+    def distribution_to_versions(self) -> dict[str, str]:
+        return {
+            distribution: getattr(self, f"{distribution}_version")
+            for distribution in self.distributions()
+            if getattr(self, f"no_{distribution}") is False and getattr(self, f"{distribution}_version") != "none"
+        }
+
+    @classmethod
+    def add_parser_arguments(cls, parser, interpreter, app_data):  # noqa: ARG003
+        group = parser.add_mutually_exclusive_group()
+        group.add_argument(
+            "--no-download",
+            "--never-download",
+            dest="download",
+            action="store_false",
+            help=f"pass to disable download of the latest {'/'.join(cls.distributions())} from PyPI",
+            default=True,
+        )
+        group.add_argument(
+            "--download",
+            dest="download",
+            action="store_true",
+            help=f"pass to enable download of the latest {'/'.join(cls.distributions())} from PyPI",
+            default=False,
+        )
+        parser.add_argument(
+            "--extra-search-dir",
+            metavar="d",
+            type=Path,
+            nargs="+",
+            help="a path containing wheels to extend the internal wheel list (can be set 1+ times)",
+            default=[],
+        )
+        for distribution, default in cls.distributions().items():
+            if interpreter.version_info[:2] >= (3, 12) and distribution in {"wheel", "setuptools"}:
+                default = "none"  # noqa: PLW2901
+            parser.add_argument(
+                f"--{distribution}",
+                dest=distribution,
+                metavar="version",
+                help=f"version of {distribution} to install as seed: embed, bundle, none or exact version",
+                default=default,
+            )
+        for distribution in cls.distributions():
+            parser.add_argument(
+                f"--no-{distribution}",
+                dest=f"no_{distribution}",
+                action="store_true",
+                help=f"do not install {distribution}",
+                default=False,
+            )
+        parser.add_argument(
+            "--no-periodic-update",
+            dest="no_periodic_update",
+            action="store_true",
+            help="disable the periodic (once every 14 days) update of the embedded wheels",
+            default=not PERIODIC_UPDATE_ON_BY_DEFAULT,
+        )
+
+    def __repr__(self) -> str:
         result = self.__class__.__name__
-        result += '('
+        result += "("
         if self.extra_search_dir:
-            result += (
-                f"extra_search_dir={', '.join(str(i) for i in self.extra_search_dir)},"
-                )
-        result += f'download={self.download},'
+            result += f"extra_search_dir={', '.join(str(i) for i in self.extra_search_dir)},"
+        result += f"download={self.download},"
         for distribution in self.distributions():
-            if getattr(self, f'no_{distribution}'):
+            if getattr(self, f"no_{distribution}"):
                 continue
-            version = getattr(self, f'{distribution}_version', None)
-            if version == 'none':
+            version = getattr(self, f"{distribution}_version", None)
+            if version == "none":
                 continue
             ver = f"={version or 'latest'}"
-            result += f' {distribution}{ver},'
-        return result[:-1] + ')'
+            result += f" {distribution}{ver},"
+        return result[:-1] + ")"


-__all__ = ['BaseEmbed']
+__all__ = [
+    "BaseEmbed",
+]
diff --git a/src/virtualenv/seed/embed/pip_invoke.py b/src/virtualenv/seed/embed/pip_invoke.py
index 33b0e2a..2625a01 100644
--- a/src/virtualenv/seed/embed/pip_invoke.py
+++ b/src/virtualenv/seed/embed/pip_invoke.py
@@ -1,16 +1,63 @@
 from __future__ import annotations
+
 import logging
 from contextlib import contextmanager
 from subprocess import Popen
+
 from virtualenv.discovery.cached_py_info import LogCmd
 from virtualenv.seed.embed.base_embed import BaseEmbed
 from virtualenv.seed.wheels import Version, get_wheel, pip_wheel_env_run


 class PipInvoke(BaseEmbed):
-
-    def __init__(self, options) ->None:
+    def __init__(self, options) -> None:
         super().__init__(options)

+    def run(self, creator):
+        if not self.enabled:
+            return
+        for_py_version = creator.interpreter.version_release_str
+        with self.get_pip_install_cmd(creator.exe, for_py_version) as cmd:
+            env = pip_wheel_env_run(self.extra_search_dir, self.app_data, self.env)
+            self._execute(cmd, env)
+
+    @staticmethod
+    def _execute(cmd, env):
+        logging.debug("pip seed by running: %s", LogCmd(cmd, env))
+        process = Popen(cmd, env=env)  # noqa: S603
+        process.communicate()
+        if process.returncode != 0:
+            msg = f"failed seed with code {process.returncode}"
+            raise RuntimeError(msg)
+        return process
+
+    @contextmanager
+    def get_pip_install_cmd(self, exe, for_py_version):
+        cmd = [str(exe), "-m", "pip", "-q", "install", "--only-binary", ":all:", "--disable-pip-version-check"]
+        if not self.download:
+            cmd.append("--no-index")
+        folders = set()
+        for dist, version in self.distribution_to_versions().items():
+            wheel = get_wheel(
+                distribution=dist,
+                version=version,
+                for_py_version=for_py_version,
+                search_dirs=self.extra_search_dir,
+                download=False,
+                app_data=self.app_data,
+                do_periodic_update=self.periodic_update,
+                env=self.env,
+            )
+            if wheel is None:
+                msg = f"could not get wheel for distribution {dist}"
+                raise RuntimeError(msg)
+            folders.add(str(wheel.path.parent))
+            cmd.append(Version.as_pip_req(dist, wheel.version))
+        for folder in sorted(folders):
+            cmd.extend(["--find-links", str(folder)])
+        yield cmd
+

-__all__ = ['PipInvoke']
+__all__ = [
+    "PipInvoke",
+]
diff --git a/src/virtualenv/seed/embed/via_app_data/pip_install/base.py b/src/virtualenv/seed/embed/via_app_data/pip_install/base.py
index 68cd39d..961cd42 100644
--- a/src/virtualenv/seed/embed/via_app_data/pip_install/base.py
+++ b/src/virtualenv/seed/embed/via_app_data/pip_install/base.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+
 import logging
 import os
 import re
@@ -8,13 +9,14 @@ from configparser import ConfigParser
 from itertools import chain
 from pathlib import Path
 from tempfile import mkdtemp
+
 from distlib.scripts import ScriptMaker, enquote_executable
+
 from virtualenv.util.path import safe_delete


 class PipInstall(ABC):
-
-    def __init__(self, wheel, creator, image_folder) ->None:
+    def __init__(self, wheel, creator, image_folder) -> None:
         self._wheel = wheel
         self._creator = creator
         self._image_dir = image_folder
@@ -22,17 +24,181 @@ class PipInstall(ABC):
         self.__dist_info = None
         self._console_entry_points = None

+    @abstractmethod
+    def _sync(self, src, dst):
+        raise NotImplementedError

-class ScriptMakerCustom(ScriptMaker):
+    def install(self, version_info):
+        self._extracted = True
+        self._uninstall_previous_version()
+        # sync image
+        for filename in self._image_dir.iterdir():
+            into = self._creator.purelib / filename.name
+            self._sync(filename, into)
+        # generate console executables
+        consoles = set()
+        script_dir = self._creator.script_dir
+        for name, module in self._console_scripts.items():
+            consoles.update(self._create_console_entry_point(name, module, script_dir, version_info))
+        logging.debug("generated console scripts %s", " ".join(i.name for i in consoles))
+
+    def build_image(self):
+        # 1. first extract the wheel
+        logging.debug("build install image for %s to %s", self._wheel.name, self._image_dir)
+        with zipfile.ZipFile(str(self._wheel)) as zip_ref:
+            self._shorten_path_if_needed(zip_ref)
+            zip_ref.extractall(str(self._image_dir))
+            self._extracted = True
+        # 2. now add additional files not present in the distribution
+        new_files = self._generate_new_files()
+        # 3. finally fix the records file
+        self._fix_records(new_files)
+
+    def _shorten_path_if_needed(self, zip_ref):
+        if os.name == "nt":
+            to_folder = str(self._image_dir)
+            # https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
+            zip_max_len = max(len(i) for i in zip_ref.namelist())
+            path_len = zip_max_len + len(to_folder)
+            if path_len > 260:  # noqa: PLR2004
+                self._image_dir.mkdir(exist_ok=True)  # to get a short path must exist
+
+                from virtualenv.util.path import get_short_path_name  # noqa: PLC0415
+
+                to_folder = get_short_path_name(to_folder)
+                self._image_dir = Path(to_folder)
+
+    def _records_text(self, files):
+        return "\n".join(f"{os.path.relpath(str(rec), str(self._image_dir))},," for rec in files)
+
+    def _generate_new_files(self):
+        new_files = set()
+        installer = self._dist_info / "INSTALLER"
+        installer.write_text("pip\n", encoding="utf-8")
+        new_files.add(installer)
+        # inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226
+        marker = self._image_dir / f"{self._dist_info.stem}.virtualenv"
+        marker.write_text("", encoding="utf-8")
+        new_files.add(marker)
+        folder = mkdtemp()
+        try:
+            to_folder = Path(folder)
+            rel = os.path.relpath(str(self._creator.script_dir), str(self._creator.purelib))
+            version_info = self._creator.interpreter.version_info
+            for name, module in self._console_scripts.items():
+                new_files.update(
+                    Path(os.path.normpath(str(self._image_dir / rel / i.name)))
+                    for i in self._create_console_entry_point(name, module, to_folder, version_info)
+                )
+        finally:
+            safe_delete(folder)
+        return new_files
+
+    @property
+    def _dist_info(self):
+        if self._extracted is False:
+            return None  # pragma: no cover
+        if self.__dist_info is None:
+            files = []
+            for filename in self._image_dir.iterdir():
+                files.append(filename.name)
+                if filename.suffix == ".dist-info":
+                    self.__dist_info = filename
+                    break
+            else:
+                msg = f"no .dist-info at {self._image_dir}, has {', '.join(files)}"
+                raise RuntimeError(msg)  # pragma: no cover
+        return self.__dist_info
+
+    @abstractmethod
+    def _fix_records(self, extra_record_data):
+        raise NotImplementedError

-    def __init__(self, target_dir, version_info, executable, name) ->None:
+    @property
+    def _console_scripts(self):
+        if self._extracted is False:
+            return None  # pragma: no cover
+        if self._console_entry_points is None:
+            self._console_entry_points = {}
+            entry_points = self._dist_info / "entry_points.txt"
+            if entry_points.exists():
+                parser = ConfigParser()
+                with entry_points.open(encoding="utf-8") as file_handler:
+                    parser.read_file(file_handler)
+                if "console_scripts" in parser.sections():
+                    for name, value in parser.items("console_scripts"):
+                        match = re.match(r"(.*?)-?\d\.?\d*", name)
+                        our_name = match.groups(1)[0] if match else name
+                        self._console_entry_points[our_name] = value
+        return self._console_entry_points
+
+    def _create_console_entry_point(self, name, value, to_folder, version_info):
+        result = []
+        maker = ScriptMakerCustom(to_folder, version_info, self._creator.exe, name)
+        specification = f"{name} = {value}"
+        new_files = maker.make(specification)
+        result.extend(Path(i) for i in new_files)
+        return result
+
+    def _uninstall_previous_version(self):
+        dist_name = self._dist_info.stem.split("-")[0]
+        in_folders = chain.from_iterable([i.iterdir() for i in (self._creator.purelib, self._creator.platlib)])
+        paths = (p for p in in_folders if p.stem.split("-")[0] == dist_name and p.suffix == ".dist-info" and p.is_dir())
+        existing_dist = next(paths, None)
+        if existing_dist is not None:
+            self._uninstall_dist(existing_dist)
+
+    @staticmethod
+    def _uninstall_dist(dist):
+        dist_base = dist.parent
+        logging.debug("uninstall existing distribution %s from %s", dist.stem, dist_base)
+
+        top_txt = dist / "top_level.txt"  # add top level packages at folder level
+        paths = (
+            {dist.parent / i.strip() for i in top_txt.read_text(encoding="utf-8").splitlines()}
+            if top_txt.exists()
+            else set()
+        )
+        paths.add(dist)  # add the dist-info folder itself
+
+        base_dirs, record = paths.copy(), dist / "RECORD"  # collect entries in record that we did not register yet
+        for name in (
+            (i.split(",")[0] for i in record.read_text(encoding="utf-8").splitlines()) if record.exists() else ()
+        ):
+            path = dist_base / name
+            if not any(p in base_dirs for p in path.parents):  # only add if not already added as a base dir
+                paths.add(path)
+
+        for path in sorted(paths):  # actually remove stuff in a stable order
+            if path.exists():
+                if path.is_dir() and not path.is_symlink():
+                    safe_delete(path)
+                else:
+                    path.unlink()
+
+    def clear(self):
+        if self._image_dir.exists():
+            safe_delete(self._image_dir)
+
+    def has_image(self):
+        return self._image_dir.exists() and next(self._image_dir.iterdir()) is not None
+
+
+class ScriptMakerCustom(ScriptMaker):
+    def __init__(self, target_dir, version_info, executable, name) -> None:
         super().__init__(None, str(target_dir))
-        self.clobber = True
-        self.set_mode = True
+        self.clobber = True  # overwrite
+        self.set_mode = True  # ensure they are executable
         self.executable = enquote_executable(str(executable))
         self.version_info = version_info.major, version_info.minor
-        self.variants = {'', 'X', 'X.Y'}
+        self.variants = {"", "X", "X.Y"}
         self._name = name

+    def _write_script(self, names, shebang, script_bytes, filenames, ext):  # noqa: PLR0913
+        names.add(f"{self._name}{self.version_info[0]}.{self.version_info[1]}")
+        super()._write_script(names, shebang, script_bytes, filenames, ext)
+

-__all__ = ['PipInstall']
+__all__ = [
+    "PipInstall",
+]
diff --git a/src/virtualenv/seed/embed/via_app_data/pip_install/copy.py b/src/virtualenv/seed/embed/via_app_data/pip_install/copy.py
index c1e008c..b5f01aa 100644
--- a/src/virtualenv/seed/embed/via_app_data/pip_install/copy.py
+++ b/src/virtualenv/seed/embed/via_app_data/pip_install/copy.py
@@ -1,12 +1,40 @@
 from __future__ import annotations
+
 import os
 from pathlib import Path
+
 from virtualenv.util.path import copy
+
 from .base import PipInstall


 class CopyPipInstall(PipInstall):
-    pass
+    def _sync(self, src, dst):
+        copy(src, dst)
+
+    def _generate_new_files(self):
+        # create the pyc files
+        new_files = super()._generate_new_files()
+        new_files.update(self._cache_files())
+        return new_files
+
+    def _cache_files(self):
+        version = self._creator.interpreter.version_info
+        py_c_ext = f".{self._creator.interpreter.implementation.lower()}-{version.major}{version.minor}.pyc"
+        for root, dirs, files in os.walk(str(self._image_dir), topdown=True):
+            root_path = Path(root)
+            for name in files:
+                if name.endswith(".py"):
+                    yield root_path / f"{name[:-3]}{py_c_ext}"
+            for name in dirs:
+                yield root_path / name / "__pycache__"
+
+    def _fix_records(self, new_files):
+        extra_record_data_str = self._records_text(new_files)
+        with (self._dist_info / "RECORD").open("ab") as file_handler:
+            file_handler.write(extra_record_data_str.encode("utf-8"))


-__all__ = ['CopyPipInstall']
+__all__ = [
+    "CopyPipInstall",
+]
diff --git a/src/virtualenv/seed/embed/via_app_data/pip_install/symlink.py b/src/virtualenv/seed/embed/via_app_data/pip_install/symlink.py
index 4d91177..1ffefc6 100644
--- a/src/virtualenv/seed/embed/via_app_data/pip_install/symlink.py
+++ b/src/virtualenv/seed/embed/via_app_data/pip_install/symlink.py
@@ -1,13 +1,58 @@
 from __future__ import annotations
+
 import os
 from stat import S_IREAD, S_IRGRP, S_IROTH
 from subprocess import PIPE, Popen
+
 from virtualenv.util.path import safe_delete, set_tree
+
 from .base import PipInstall


 class SymlinkPipInstall(PipInstall):
-    pass
+    def _sync(self, src, dst):
+        os.symlink(str(src), str(dst))
+
+    def _generate_new_files(self):
+        # create the pyc files, as the build image will be R/O
+        cmd = [str(self._creator.exe), "-m", "compileall", str(self._image_dir)]
+        process = Popen(cmd, stdout=PIPE, stderr=PIPE)  # noqa: S603
+        process.communicate()
+        # the root pyc is shared, so we'll not symlink that - but still add the pyc files to the RECORD for close
+        root_py_cache = self._image_dir / "__pycache__"
+        new_files = set()
+        if root_py_cache.exists():
+            new_files.update(root_py_cache.iterdir())
+            new_files.add(root_py_cache)
+            safe_delete(root_py_cache)
+        core_new_files = super()._generate_new_files()
+        # remove files that are within the image folder deeper than one level (as these will be not linked directly)
+        for file in core_new_files:
+            try:
+                rel = file.relative_to(self._image_dir)
+                if len(rel.parts) > 1:
+                    continue
+            except ValueError:
+                pass
+            new_files.add(file)
+        return new_files
+
+    def _fix_records(self, new_files):
+        new_files.update(i for i in self._image_dir.iterdir())
+        extra_record_data_str = self._records_text(sorted(new_files, key=str))
+        (self._dist_info / "RECORD").write_text(extra_record_data_str, encoding="utf-8")
+
+    def build_image(self):
+        super().build_image()
+        # protect the image by making it read only
+        set_tree(self._image_dir, S_IREAD | S_IRGRP | S_IROTH)
+
+    def clear(self):
+        if self._image_dir.exists():
+            safe_delete(self._image_dir)
+        super().clear()


-__all__ = ['SymlinkPipInstall']
+__all__ = [
+    "SymlinkPipInstall",
+]
diff --git a/src/virtualenv/seed/embed/via_app_data/via_app_data.py b/src/virtualenv/seed/embed/via_app_data/via_app_data.py
index af51fec..7e58bfc 100644
--- a/src/virtualenv/seed/embed/via_app_data/via_app_data.py
+++ b/src/virtualenv/seed/embed/via_app_data/via_app_data.py
@@ -1,5 +1,7 @@
 """Bootstrap."""
+
 from __future__ import annotations
+
 import logging
 import sys
 import traceback
@@ -7,25 +9,136 @@ from contextlib import contextmanager
 from pathlib import Path
 from subprocess import CalledProcessError
 from threading import Lock, Thread
+
 from virtualenv.info import fs_supports_symlink
 from virtualenv.seed.embed.base_embed import BaseEmbed
 from virtualenv.seed.wheels import get_wheel
+
 from .pip_install.copy import CopyPipInstall
 from .pip_install.symlink import SymlinkPipInstall


 class FromAppData(BaseEmbed):
-
-    def __init__(self, options) ->None:
+    def __init__(self, options) -> None:
         super().__init__(options)
         self.symlinks = options.symlink_app_data

-    def __repr__(self) ->str:
-        msg = (
-            f", via={'symlink' if self.symlinks else 'copy'}, app_data_dir={self.app_data}"
-            )
+    @classmethod
+    def add_parser_arguments(cls, parser, interpreter, app_data):
+        super().add_parser_arguments(parser, interpreter, app_data)
+        can_symlink = app_data.transient is False and fs_supports_symlink()
+        sym = "" if can_symlink else "not supported - "
+        parser.add_argument(
+            "--symlink-app-data",
+            dest="symlink_app_data",
+            action="store_true" if can_symlink else "store_false",
+            help=f"{sym} symlink the python packages from the app-data folder (requires seed pip>=19.3)",
+            default=False,
+        )
+
+    def run(self, creator):
+        if not self.enabled:
+            return
+        with self._get_seed_wheels(creator) as name_to_whl:
+            pip_version = name_to_whl["pip"].version_tuple if "pip" in name_to_whl else None
+            installer_class = self.installer_class(pip_version)
+            exceptions = {}
+
+            def _install(name, wheel):
+                try:
+                    logging.debug("install %s from wheel %s via %s", name, wheel, installer_class.__name__)
+                    key = Path(installer_class.__name__) / wheel.path.stem
+                    wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key)
+                    installer = installer_class(wheel.path, creator, wheel_img)
+                    parent = self.app_data.lock / wheel_img.parent
+                    with parent.non_reentrant_lock_for_key(wheel_img.name):
+                        if not installer.has_image():
+                            installer.build_image()
+                    installer.install(creator.interpreter.version_info)
+                except Exception:  # noqa: BLE001
+                    exceptions[name] = sys.exc_info()
+
+            threads = [Thread(target=_install, args=(n, w)) for n, w in name_to_whl.items()]
+            for thread in threads:
+                thread.start()
+            for thread in threads:
+                thread.join()
+            if exceptions:
+                messages = [f"failed to build image {', '.join(exceptions.keys())} because:"]
+                for value in exceptions.values():
+                    exc_type, exc_value, exc_traceback = value
+                    messages.append("".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))
+                raise RuntimeError("\n".join(messages))
+
+    @contextmanager
+    def _get_seed_wheels(self, creator):  # noqa: C901
+        name_to_whl, lock, fail = {}, Lock(), {}
+
+        def _get(distribution, version):
+            for_py_version = creator.interpreter.version_release_str
+            failure, result = None, None
+            # fallback to download in case the exact version is not available
+            for download in [True] if self.download else [False, True]:
+                failure = None
+                try:
+                    result = get_wheel(
+                        distribution=distribution,
+                        version=version,
+                        for_py_version=for_py_version,
+                        search_dirs=self.extra_search_dir,
+                        download=download,
+                        app_data=self.app_data,
+                        do_periodic_update=self.periodic_update,
+                        env=self.env,
+                    )
+                    if result is not None:
+                        break
+                except Exception as exception:
+                    logging.exception("fail")
+                    failure = exception
+            if failure:
+                if isinstance(failure, CalledProcessError):
+                    msg = f"failed to download {distribution}"
+                    if version is not None:
+                        msg += f" version {version}"
+                    msg += f", pip download exit code {failure.returncode}"
+                    output = failure.output + failure.stderr
+                    if output:
+                        msg += "\n"
+                        msg += output
+                else:
+                    msg = repr(failure)
+                logging.error(msg)
+                with lock:
+                    fail[distribution] = version
+            else:
+                with lock:
+                    name_to_whl[distribution] = result
+
+        threads = [
+            Thread(target=_get, args=(distribution, version))
+            for distribution, version in self.distribution_to_versions().items()
+        ]
+        for thread in threads:
+            thread.start()
+        for thread in threads:
+            thread.join()
+        if fail:
+            msg = f"seed failed due to failing to download wheels {', '.join(fail.keys())}"
+            raise RuntimeError(msg)
+        yield name_to_whl
+
+    def installer_class(self, pip_version_tuple):
+        if self.symlinks and pip_version_tuple and pip_version_tuple >= (19, 3):  # symlink support requires pip 19.3+
+            return SymlinkPipInstall
+        return CopyPipInstall
+
+    def __repr__(self) -> str:
+        msg = f", via={'symlink' if self.symlinks else 'copy'}, app_data_dir={self.app_data}"
         base = super().__repr__()
-        return f'{base[:-1]}{msg}{base[-1]}'
+        return f"{base[:-1]}{msg}{base[-1]}"


-__all__ = ['FromAppData']
+__all__ = [
+    "FromAppData",
+]
diff --git a/src/virtualenv/seed/seeder.py b/src/virtualenv/seed/seeder.py
index 744b173..58fd8f4 100644
--- a/src/virtualenv/seed/seeder.py
+++ b/src/virtualenv/seed/seeder.py
@@ -1,11 +1,12 @@
 from __future__ import annotations
+
 from abc import ABC, abstractmethod


 class Seeder(ABC):
     """A seeder will install some seed packages into a virtual environment."""

-    def __init__(self, options, enabled) ->None:
+    def __init__(self, options, enabled) -> None:
         """
         Create.

@@ -24,16 +25,19 @@ class Seeder(ABC):
         :param app_data: the CLI parser
         :param interpreter: the interpreter this virtual environment is based of
         """
-        pass
+        raise NotImplementedError

     @abstractmethod
     def run(self, creator):
         """
         Perform the seed operation.

-        :param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this         virtual environment
+        :param creator: the creator (based of :class:`virtualenv.create.creator.Creator`) we used to create this \
+        virtual environment
         """
-        pass
+        raise NotImplementedError


-__all__ = ['Seeder']
+__all__ = [
+    "Seeder",
+]
diff --git a/src/virtualenv/seed/wheels/acquire.py b/src/virtualenv/seed/wheels/acquire.py
index a3225d0..48fd46a 100644
--- a/src/virtualenv/seed/wheels/acquire.py
+++ b/src/virtualenv/seed/wheels/acquire.py
@@ -1,19 +1,132 @@
 """Bootstrap."""
+
 from __future__ import annotations
+
 import logging
 import sys
 from operator import eq, lt
 from pathlib import Path
 from subprocess import PIPE, CalledProcessError, Popen
+
 from .bundle import from_bundle
 from .periodic_update import add_wheel_to_update_log
 from .util import Version, Wheel, discover_wheels


-def get_wheel(distribution, version, for_py_version, search_dirs, download,
-    app_data, do_periodic_update, env):
+def get_wheel(  # noqa: PLR0913
+    distribution,
+    version,
+    for_py_version,
+    search_dirs,
+    download,
+    app_data,
+    do_periodic_update,
+    env,
+):
     """Get a wheel with the given distribution-version-for_py_version trio, by using the extra search dir + download."""
-    pass
+    # not all wheels are compatible with all python versions, so we need to py version qualify it
+    wheel = None
+
+    if not download or version != Version.bundle:
+        # 1. acquire from bundle
+        wheel = from_bundle(distribution, version, for_py_version, search_dirs, app_data, do_periodic_update, env)
+
+    if download and wheel is None and version != Version.embed:
+        # 2. download from the internet
+        wheel = download_wheel(
+            distribution=distribution,
+            version_spec=Version.as_version_spec(version),
+            for_py_version=for_py_version,
+            search_dirs=search_dirs,
+            app_data=app_data,
+            to_folder=app_data.house,
+            env=env,
+        )
+        if wheel is not None and app_data.can_update:
+            add_wheel_to_update_log(wheel, for_py_version, app_data)
+
+    return wheel
+
+
+def download_wheel(distribution, version_spec, for_py_version, search_dirs, app_data, to_folder, env):  # noqa: PLR0913
+    to_download = f"{distribution}{version_spec or ''}"
+    logging.debug("download wheel %s %s to %s", to_download, for_py_version, to_folder)
+    cmd = [
+        sys.executable,
+        "-m",
+        "pip",
+        "download",
+        "--progress-bar",
+        "off",
+        "--disable-pip-version-check",
+        "--only-binary=:all:",
+        "--no-deps",
+        "--python-version",
+        for_py_version,
+        "-d",
+        str(to_folder),
+        to_download,
+    ]
+    # pip has no interface in python - must be a new sub-process
+    env = pip_wheel_env_run(search_dirs, app_data, env)
+    process = Popen(cmd, env=env, stdout=PIPE, stderr=PIPE, universal_newlines=True, encoding="utf-8")  # noqa: S603
+    out, err = process.communicate()
+    if process.returncode != 0:
+        kwargs = {"output": out, "stderr": err}
+        raise CalledProcessError(process.returncode, cmd, **kwargs)
+    result = _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder, out)
+    logging.debug("downloaded wheel %s", result.name)
+    return result
+
+
+def _find_downloaded_wheel(distribution, version_spec, for_py_version, to_folder, out):
+    for line in out.splitlines():
+        stripped_line = line.lstrip()
+        for marker in ("Saved ", "File was already downloaded "):
+            if stripped_line.startswith(marker):
+                return Wheel(Path(stripped_line[len(marker) :]).absolute())
+    # if for some reason the output does not match fallback to the latest version with that spec
+    return find_compatible_in_house(distribution, version_spec, for_py_version, to_folder)
+
+
+def find_compatible_in_house(distribution, version_spec, for_py_version, in_folder):
+    wheels = discover_wheels(in_folder, distribution, None, for_py_version)
+    start, end = 0, len(wheels)
+    if version_spec is not None and version_spec:
+        if version_spec.startswith("<"):
+            from_pos, op = 1, lt
+        elif version_spec.startswith("=="):
+            from_pos, op = 2, eq
+        else:
+            raise ValueError(version_spec)
+        version = Wheel.as_version_tuple(version_spec[from_pos:])
+        start = next((at for at, w in enumerate(wheels) if op(w.version_tuple, version)), len(wheels))
+
+    return None if start == end else wheels[start]
+
+
+def pip_wheel_env_run(search_dirs, app_data, env):
+    env = env.copy()
+    env.update({"PIP_USE_WHEEL": "1", "PIP_USER": "0", "PIP_NO_INPUT": "1"})
+    wheel = get_wheel(
+        distribution="pip",
+        version=None,
+        for_py_version=f"{sys.version_info.major}.{sys.version_info.minor}",
+        search_dirs=search_dirs,
+        download=False,
+        app_data=app_data,
+        do_periodic_update=False,
+        env=env,
+    )
+    if wheel is None:
+        msg = "could not find the embedded pip"
+        raise RuntimeError(msg)
+    env["PYTHONPATH"] = str(wheel.path)
+    return env


-__all__ = ['download_wheel', 'get_wheel', 'pip_wheel_env_run']
+__all__ = [
+    "download_wheel",
+    "get_wheel",
+    "pip_wheel_env_run",
+]
diff --git a/src/virtualenv/seed/wheels/bundle.py b/src/virtualenv/seed/wheels/bundle.py
index dacec6a..523e45c 100644
--- a/src/virtualenv/seed/wheels/bundle.py
+++ b/src/virtualenv/seed/wheels/bundle.py
@@ -1,18 +1,50 @@
 from __future__ import annotations
+
 from virtualenv.seed.wheels.embed import get_embed_wheel
+
 from .periodic_update import periodic_update
 from .util import Version, Wheel, discover_wheels


-def from_bundle(distribution, version, for_py_version, search_dirs,
-    app_data, do_periodic_update, env):
+def from_bundle(distribution, version, for_py_version, search_dirs, app_data, do_periodic_update, env):  # noqa: PLR0913
     """Load the bundled wheel to a cache directory."""
-    pass
+    of_version = Version.of_version(version)
+    wheel = load_embed_wheel(app_data, distribution, for_py_version, of_version)
+
+    if version != Version.embed:
+        # 2. check if we have upgraded embed
+        if app_data.can_update:
+            per = do_periodic_update
+            wheel = periodic_update(distribution, of_version, for_py_version, wheel, search_dirs, app_data, per, env)
+
+        # 3. acquire from extra search dir
+        found_wheel = from_dir(distribution, of_version, for_py_version, search_dirs)
+        if found_wheel is not None and (wheel is None or found_wheel.version_tuple > wheel.version_tuple):
+            wheel = found_wheel
+    return wheel
+
+
+def load_embed_wheel(app_data, distribution, for_py_version, version):
+    wheel = get_embed_wheel(distribution, for_py_version)
+    if wheel is not None:
+        version_match = version == wheel.version
+        if version is None or version_match:
+            with app_data.ensure_extracted(wheel.path, lambda: app_data.house) as wheel_path:
+                wheel = Wheel(wheel_path)
+        else:  # if version does not match ignore
+            wheel = None
+    return wheel


 def from_dir(distribution, version, for_py_version, directories):
     """Load a compatible wheel from a given folder."""
-    pass
+    for folder in directories:
+        for wheel in discover_wheels(folder, distribution, version, for_py_version):
+            return wheel
+    return None


-__all__ = ['from_bundle', 'load_embed_wheel']
+__all__ = [
+    "from_bundle",
+    "load_embed_wheel",
+]
diff --git a/src/virtualenv/seed/wheels/periodic_update.py b/src/virtualenv/seed/wheels/periodic_update.py
index 8d77ff3..a421079 100644
--- a/src/virtualenv/seed/wheels/periodic_update.py
+++ b/src/virtualenv/seed/wheels/periodic_update.py
@@ -1,5 +1,7 @@
 """Periodically update bundled versions."""
+
 from __future__ import annotations
+
 import json
 import logging
 import os
@@ -14,49 +16,412 @@ from textwrap import dedent
 from threading import Thread
 from urllib.error import URLError
 from urllib.request import urlopen
+
 from virtualenv.app_data import AppDataDiskFolder
 from virtualenv.seed.wheels.embed import BUNDLE_SUPPORT
 from virtualenv.seed.wheels.util import Wheel
 from virtualenv.util.subprocess import CREATE_NO_WINDOW
-GRACE_PERIOD_CI = timedelta(hours=1)
+
+GRACE_PERIOD_CI = timedelta(hours=1)  # prevent version switch in the middle of a CI run
 GRACE_PERIOD_MINOR = timedelta(days=28)
 UPDATE_PERIOD = timedelta(days=14)
 UPDATE_ABORTED_DELAY = timedelta(hours=1)
-DATETIME_FMT = '%Y-%m-%dT%H:%M:%S.%fZ'


-class NewVersion:
+def periodic_update(  # noqa: PLR0913
+    distribution,
+    of_version,
+    for_py_version,
+    wheel,
+    search_dirs,
+    app_data,
+    do_periodic_update,
+    env,
+):
+    if do_periodic_update:
+        handle_auto_update(distribution, for_py_version, wheel, search_dirs, app_data, env)
+
+    now = datetime.now(tz=timezone.utc)
+
+    def _update_wheel(ver):
+        updated_wheel = Wheel(app_data.house / ver.filename)
+        logging.debug("using %supdated wheel %s", "periodically " if updated_wheel else "", updated_wheel)
+        return updated_wheel
+
+    u_log = UpdateLog.from_app_data(app_data, distribution, for_py_version)
+    if of_version is None:
+        for _, group in groupby(u_log.versions, key=lambda v: v.wheel.version_tuple[0:2]):
+            # use only latest patch version per minor, earlier assumed to be buggy
+            all_patches = list(group)
+            ignore_grace_period_minor = any(version for version in all_patches if version.use(now))
+            for version in all_patches:
+                if wheel is not None and Path(version.filename).name == wheel.name:
+                    return wheel
+                if version.use(now, ignore_grace_period_minor):
+                    return _update_wheel(version)
+    else:
+        for version in u_log.versions:
+            if version.wheel.version == of_version:
+                return _update_wheel(version)
+
+    return wheel
+
+
+def handle_auto_update(distribution, for_py_version, wheel, search_dirs, app_data, env):  # noqa: PLR0913
+    embed_update_log = app_data.embed_update_log(distribution, for_py_version)
+    u_log = UpdateLog.from_dict(embed_update_log.read())
+    if u_log.needs_update:
+        u_log.periodic = True
+        u_log.started = datetime.now(tz=timezone.utc)
+        embed_update_log.write(u_log.to_dict())
+        trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, periodic=True, env=env)
+
+
+def add_wheel_to_update_log(wheel, for_py_version, app_data):
+    embed_update_log = app_data.embed_update_log(wheel.distribution, for_py_version)
+    logging.debug("adding %s information to %s", wheel.name, embed_update_log.file)
+    u_log = UpdateLog.from_dict(embed_update_log.read())
+    if any(version.filename == wheel.name for version in u_log.versions):
+        logging.warning("%s already present in %s", wheel.name, embed_update_log.file)
+        return
+    # we don't need a release date for sources other than "periodic"
+    version = NewVersion(wheel.name, datetime.now(tz=timezone.utc), None, "download")
+    u_log.versions.append(version)  # always write at the end for proper updates
+    embed_update_log.write(u_log.to_dict())
+
+
+DATETIME_FMT = "%Y-%m-%dT%H:%M:%S.%fZ"
+

-    def __init__(self, filename, found_date, release_date, source) ->None:
+def dump_datetime(value):
+    return None if value is None else value.strftime(DATETIME_FMT)
+
+
+def load_datetime(value):
+    return None if value is None else datetime.strptime(value, DATETIME_FMT).replace(tzinfo=timezone.utc)
+
+
+class NewVersion:  # noqa: PLW1641
+    def __init__(self, filename, found_date, release_date, source) -> None:
         self.filename = filename
         self.found_date = found_date
         self.release_date = release_date
         self.source = source

-    def __repr__(self) ->str:
+    @classmethod
+    def from_dict(cls, dictionary):
+        return cls(
+            filename=dictionary["filename"],
+            found_date=load_datetime(dictionary["found_date"]),
+            release_date=load_datetime(dictionary["release_date"]),
+            source=dictionary["source"],
+        )
+
+    def to_dict(self):
+        return {
+            "filename": self.filename,
+            "release_date": dump_datetime(self.release_date),
+            "found_date": dump_datetime(self.found_date),
+            "source": self.source,
+        }
+
+    def use(self, now, ignore_grace_period_minor=False, ignore_grace_period_ci=False):  # noqa: FBT002
+        if self.source == "manual":
+            return True
+        if self.source == "periodic" and (self.found_date < now - GRACE_PERIOD_CI or ignore_grace_period_ci):
+            if not ignore_grace_period_minor:
+                compare_from = self.release_date or self.found_date
+                return now - compare_from >= GRACE_PERIOD_MINOR
+            return True
+        return False
+
+    def __repr__(self) -> str:
         return (
-            f'{self.__class__.__name__}(filename={self.filename}), found_date={self.found_date}, release_date={self.release_date}, source={self.source})'
-            )
+            f"{self.__class__.__name__}(filename={self.filename}), found_date={self.found_date}, "
+            f"release_date={self.release_date}, source={self.source})"
+        )

     def __eq__(self, other):
-        return type(self) == type(other) and all(getattr(self, k) ==
-            getattr(other, k) for k in ['filename', 'release_date',
-            'found_date', 'source'])
+        return type(self) == type(other) and all(
+            getattr(self, k) == getattr(other, k) for k in ["filename", "release_date", "found_date", "source"]
+        )

     def __ne__(self, other):
-        return not self == other
+        return not (self == other)

+    @property
+    def wheel(self):
+        return Wheel(Path(self.filename))

-class UpdateLog:

-    def __init__(self, started, completed, versions, periodic) ->None:
+class UpdateLog:
+    def __init__(self, started, completed, versions, periodic) -> None:
         self.started = started
         self.completed = completed
         self.versions = versions
         self.periodic = periodic

+    @classmethod
+    def from_dict(cls, dictionary):
+        if dictionary is None:
+            dictionary = {}
+        return cls(
+            load_datetime(dictionary.get("started")),
+            load_datetime(dictionary.get("completed")),
+            [NewVersion.from_dict(v) for v in dictionary.get("versions", [])],
+            dictionary.get("periodic"),
+        )
+
+    @classmethod
+    def from_app_data(cls, app_data, distribution, for_py_version):
+        raw_json = app_data.embed_update_log(distribution, for_py_version).read()
+        return cls.from_dict(raw_json)
+
+    def to_dict(self):
+        return {
+            "started": dump_datetime(self.started),
+            "completed": dump_datetime(self.completed),
+            "periodic": self.periodic,
+            "versions": [r.to_dict() for r in self.versions],
+        }
+
+    @property
+    def needs_update(self):
+        now = datetime.now(tz=timezone.utc)
+        if self.completed is None:  # never completed
+            return self._check_start(now)
+        if now - self.completed <= UPDATE_PERIOD:
+            return False
+        return self._check_start(now)
+
+    def _check_start(self, now):
+        return self.started is None or now - self.started > UPDATE_ABORTED_DELAY
+
+
+def trigger_update(distribution, for_py_version, wheel, search_dirs, app_data, env, periodic):  # noqa: PLR0913
+    wheel_path = None if wheel is None else str(wheel.path)
+    cmd = [
+        sys.executable,
+        "-c",
+        dedent(
+            """
+        from virtualenv.report import setup_report, MAX_LEVEL
+        from virtualenv.seed.wheels.periodic_update import do_update
+        setup_report(MAX_LEVEL, show_pid=True)
+        do_update({!r}, {!r}, {!r}, {!r}, {!r}, {!r})
+        """,
+        )
+        .strip()
+        .format(distribution, for_py_version, wheel_path, str(app_data), [str(p) for p in search_dirs], periodic),
+    ]
+    debug = env.get("_VIRTUALENV_PERIODIC_UPDATE_INLINE") == "1"
+    pipe = None if debug else DEVNULL
+    kwargs = {"stdout": pipe, "stderr": pipe}
+    if not debug and sys.platform == "win32":
+        kwargs["creationflags"] = CREATE_NO_WINDOW
+    process = Popen(cmd, **kwargs)  # noqa: S603
+    logging.info(
+        "triggered periodic upgrade of %s%s (for python %s) via background process having PID %d",
+        distribution,
+        "" if wheel is None else f"=={wheel.version}",
+        for_py_version,
+        process.pid,
+    )
+    if debug:
+        process.communicate()  # on purpose not called to make it a background process
+    else:
+        # set the returncode here -> no ResourceWarning on main process exit if the subprocess still runs
+        process.returncode = 0
+
+
+def do_update(distribution, for_py_version, embed_filename, app_data, search_dirs, periodic):  # noqa: PLR0913
+    versions = None
+    try:
+        versions = _run_do_update(app_data, distribution, embed_filename, for_py_version, periodic, search_dirs)
+    finally:
+        logging.debug("done %s %s with %s", distribution, for_py_version, versions)
+    return versions
+
+
+def _run_do_update(  # noqa: C901, PLR0913
+    app_data,
+    distribution,
+    embed_filename,
+    for_py_version,
+    periodic,
+    search_dirs,
+):
+    from virtualenv.seed.wheels import acquire  # noqa: PLC0415
+
+    wheel_filename = None if embed_filename is None else Path(embed_filename)
+    embed_version = None if wheel_filename is None else Wheel(wheel_filename).version_tuple
+    app_data = AppDataDiskFolder(app_data) if isinstance(app_data, str) else app_data
+    search_dirs = [Path(p) if isinstance(p, str) else p for p in search_dirs]
+    wheelhouse = app_data.house
+    embed_update_log = app_data.embed_update_log(distribution, for_py_version)
+    u_log = UpdateLog.from_dict(embed_update_log.read())
+    now = datetime.now(tz=timezone.utc)
+
+    update_versions, other_versions = [], []
+    for version in u_log.versions:
+        if version.source in {"periodic", "manual"}:
+            update_versions.append(version)
+        else:
+            other_versions.append(version)
+
+    if periodic:
+        source = "periodic"
+    else:
+        source = "manual"
+        # mark the most recent one as source "manual"
+        if update_versions:
+            update_versions[0].source = source
+
+    if wheel_filename is not None:
+        dest = wheelhouse / wheel_filename.name
+        if not dest.exists():
+            copy2(str(wheel_filename), str(wheelhouse))
+    last, last_version, versions, filenames = None, None, [], set()
+    while last is None or not last.use(now, ignore_grace_period_ci=True):
+        download_time = datetime.now(tz=timezone.utc)
+        dest = acquire.download_wheel(
+            distribution=distribution,
+            version_spec=None if last_version is None else f"<{last_version}",
+            for_py_version=for_py_version,
+            search_dirs=search_dirs,
+            app_data=app_data,
+            to_folder=wheelhouse,
+            env=os.environ,
+        )
+        if dest is None or (update_versions and update_versions[0].filename == dest.name):
+            break
+        release_date = release_date_for_wheel_path(dest.path)
+        last = NewVersion(filename=dest.path.name, release_date=release_date, found_date=download_time, source=source)
+        logging.info("detected %s in %s", last, datetime.now(tz=timezone.utc) - download_time)
+        versions.append(last)
+        filenames.add(last.filename)
+        last_wheel = last.wheel
+        last_version = last_wheel.version
+        if embed_version is not None and embed_version >= last_wheel.version_tuple:
+            break  # stop download if we reach the embed version
+    u_log.periodic = periodic
+    if not u_log.periodic:
+        u_log.started = now
+    # update other_versions by removing version we just found
+    other_versions = [version for version in other_versions if version.filename not in filenames]
+    u_log.versions = versions + update_versions + other_versions
+    u_log.completed = datetime.now(tz=timezone.utc)
+    embed_update_log.write(u_log.to_dict())
+    return versions
+
+
+def release_date_for_wheel_path(dest):
+    wheel = Wheel(dest)
+    # the most accurate is to ask PyPi - e.g. https://pypi.org/pypi/pip/json,
+    # see https://warehouse.pypa.io/api-reference/json/ for more details
+    content = _pypi_get_distribution_info_cached(wheel.distribution)
+    if content is not None:
+        try:
+            upload_time = content["releases"][wheel.version][0]["upload_time"]
+            return datetime.strptime(upload_time, "%Y-%m-%dT%H:%M:%S").replace(tzinfo=timezone.utc)
+        except Exception as exception:  # noqa: BLE001
+            logging.error("could not load release date %s because %r", content, exception)  # noqa: TRY400
+    return None
+
+
+def _request_context():
+    yield None
+    # fallback to non verified HTTPS (the information we request is not sensitive, so fallback)
+    yield ssl._create_unverified_context()  # noqa: S323, SLF001
+

 _PYPI_CACHE = {}
-__all__ = ['NewVersion', 'UpdateLog', 'add_wheel_to_update_log',
-    'do_update', 'dump_datetime', 'load_datetime', 'manual_upgrade',
-    'periodic_update', 'release_date_for_wheel_path', 'trigger_update']
+
+
+def _pypi_get_distribution_info_cached(distribution):
+    if distribution not in _PYPI_CACHE:
+        _PYPI_CACHE[distribution] = _pypi_get_distribution_info(distribution)
+    return _PYPI_CACHE[distribution]
+
+
+def _pypi_get_distribution_info(distribution):
+    content, url = None, f"https://pypi.org/pypi/{distribution}/json"
+    try:
+        for context in _request_context():
+            try:
+                with urlopen(url, context=context) as file_handler:  # noqa: S310
+                    content = json.load(file_handler)
+                break
+            except URLError as exception:
+                logging.error("failed to access %s because %r", url, exception)  # noqa: TRY400
+    except Exception as exception:  # noqa: BLE001
+        logging.error("failed to access %s because %r", url, exception)  # noqa: TRY400
+    return content
+
+
+def manual_upgrade(app_data, env):
+    threads = []
+
+    for for_py_version, distribution_to_package in BUNDLE_SUPPORT.items():
+        # load extra search dir for the given for_py
+        for distribution in distribution_to_package:
+            thread = Thread(target=_run_manual_upgrade, args=(app_data, distribution, for_py_version, env))
+            thread.start()
+            threads.append(thread)
+
+    for thread in threads:
+        thread.join()
+
+
+def _run_manual_upgrade(app_data, distribution, for_py_version, env):
+    start = datetime.now(tz=timezone.utc)
+    from .bundle import from_bundle  # noqa: PLC0415
+
+    current = from_bundle(
+        distribution=distribution,
+        version=None,
+        for_py_version=for_py_version,
+        search_dirs=[],
+        app_data=app_data,
+        do_periodic_update=False,
+        env=env,
+    )
+    logging.warning(
+        "upgrade %s for python %s with current %s",
+        distribution,
+        for_py_version,
+        "" if current is None else current.name,
+    )
+    versions = do_update(
+        distribution=distribution,
+        for_py_version=for_py_version,
+        embed_filename=current.path,
+        app_data=app_data,
+        search_dirs=[],
+        periodic=False,
+    )
+
+    args = [
+        distribution,
+        for_py_version,
+        datetime.now(tz=timezone.utc) - start,
+    ]
+    if versions:
+        args.append("\n".join(f"\t{v}" for v in versions))
+    ver_update = "new entries found:\n%s" if versions else "no new versions found"
+    msg = f"upgraded %s for python %s in %s {ver_update}"
+    logging.warning(msg, *args)
+
+
+__all__ = [
+    "NewVersion",
+    "UpdateLog",
+    "add_wheel_to_update_log",
+    "do_update",
+    "dump_datetime",
+    "load_datetime",
+    "manual_upgrade",
+    "periodic_update",
+    "release_date_for_wheel_path",
+    "trigger_update",
+]
diff --git a/src/virtualenv/seed/wheels/util.py b/src/virtualenv/seed/wheels/util.py
index aced3f7..2bc01ae 100644
--- a/src/virtualenv/seed/wheels/util.py
+++ b/src/virtualenv/seed/wheels/util.py
@@ -1,25 +1,121 @@
 from __future__ import annotations
+
 from operator import attrgetter
 from zipfile import ZipFile


 class Wheel:
-
-    def __init__(self, path) ->None:
+    def __init__(self, path) -> None:
+        # https://www.python.org/dev/peps/pep-0427/#file-name-convention
+        # The wheel filename is {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl
         self.path = path
-        self._parts = path.stem.split('-')
+        self._parts = path.stem.split("-")
+
+    @classmethod
+    def from_path(cls, path):
+        if path is not None and path.suffix == ".whl" and len(path.stem.split("-")) >= 5:  # noqa: PLR2004
+            return cls(path)
+        return None
+
+    @property
+    def distribution(self):
+        return self._parts[0]
+
+    @property
+    def version(self):
+        return self._parts[1]
+
+    @property
+    def version_tuple(self):
+        return self.as_version_tuple(self.version)
+
+    @staticmethod
+    def as_version_tuple(version):
+        result = []
+        for part in version.split(".")[0:3]:
+            try:
+                result.append(int(part))
+            except ValueError:  # noqa: PERF203
+                break
+        if not result:
+            raise ValueError(version)
+        return tuple(result)

-    def __repr__(self) ->str:
-        return f'{self.__class__.__name__}({self.path})'
+    @property
+    def name(self):
+        return self.path.name

-    def __str__(self) ->str:
+    def support_py(self, py_version):
+        name = f"{'-'.join(self.path.stem.split('-')[0:2])}.dist-info/METADATA"
+        with ZipFile(str(self.path), "r") as zip_file:
+            metadata = zip_file.read(name).decode("utf-8")
+        marker = "Requires-Python:"
+        requires = next((i[len(marker) :] for i in metadata.splitlines() if i.startswith(marker)), None)
+        if requires is None:  # if it does not specify a python requires the assumption is compatible
+            return True
+        py_version_int = tuple(int(i) for i in py_version.split("."))
+        for require in (i.strip() for i in requires.split(",")):
+            # https://www.python.org/dev/peps/pep-0345/#version-specifiers
+            for operator, check in [
+                ("!=", lambda v: py_version_int != v),
+                ("==", lambda v: py_version_int == v),
+                ("<=", lambda v: py_version_int <= v),
+                (">=", lambda v: py_version_int >= v),
+                ("<", lambda v: py_version_int < v),
+                (">", lambda v: py_version_int > v),
+            ]:
+                if require.startswith(operator):
+                    ver_str = require[len(operator) :].strip()
+                    version = tuple((int(i) if i != "*" else None) for i in ver_str.split("."))[0:2]
+                    if not check(version):
+                        return False
+                    break
+        return True
+
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}({self.path})"
+
+    def __str__(self) -> str:
         return str(self.path)


+def discover_wheels(from_folder, distribution, version, for_py_version):
+    wheels = []
+    for filename in from_folder.iterdir():
+        wheel = Wheel.from_path(filename)
+        if (
+            wheel
+            and wheel.distribution == distribution
+            and (version is None or wheel.version == version)
+            and wheel.support_py(for_py_version)
+        ):
+            wheels.append(wheel)
+    return sorted(wheels, key=attrgetter("version_tuple", "distribution"), reverse=True)
+
+
 class Version:
-    bundle = 'bundle'
-    embed = 'embed'
-    non_version = bundle, embed
+    #: the version bundled with virtualenv
+    bundle = "bundle"
+    embed = "embed"
+    #: custom version handlers
+    non_version = (bundle, embed)
+
+    @staticmethod
+    def of_version(value):
+        return None if value in Version.non_version else value
+
+    @staticmethod
+    def as_pip_req(distribution, version):
+        return f"{distribution}{Version.as_version_spec(version)}"
+
+    @staticmethod
+    def as_version_spec(version):
+        of_version = Version.of_version(version)
+        return "" if of_version is None else f"=={of_version}"


-__all__ = ['Version', 'Wheel', 'discover_wheels']
+__all__ = [
+    "Version",
+    "Wheel",
+    "discover_wheels",
+]
diff --git a/src/virtualenv/util/error.py b/src/virtualenv/util/error.py
index 1978e83..a317ddc 100644
--- a/src/virtualenv/util/error.py
+++ b/src/virtualenv/util/error.py
@@ -1,11 +1,12 @@
 """Errors."""
+
 from __future__ import annotations


 class ProcessCallFailedError(RuntimeError):
     """Failed a process call."""

-    def __init__(self, code, out, err, cmd) ->None:
+    def __init__(self, code, out, err, cmd) -> None:
         super().__init__(code, out, err, cmd)
         self.code = code
         self.out = out
diff --git a/src/virtualenv/util/lock.py b/src/virtualenv/util/lock.py
index 03295d2..b4dc66a 100644
--- a/src/virtualenv/util/lock.py
+++ b/src/virtualenv/util/lock.py
@@ -1,38 +1,55 @@
 """holds locking functionality that works across processes."""
+
 from __future__ import annotations
+
 import logging
 import os
 from abc import ABC, abstractmethod
 from contextlib import contextmanager, suppress
 from pathlib import Path
 from threading import Lock, RLock
+
 from filelock import FileLock, Timeout


 class _CountedFileLock(FileLock):
-
-    def __init__(self, lock_file) ->None:
+    def __init__(self, lock_file) -> None:
         parent = os.path.dirname(lock_file)
         if not os.path.isdir(parent):
             with suppress(OSError):
                 os.makedirs(parent)
+
         super().__init__(lock_file)
         self.count = 0
         self.thread_safe = RLock()

+    def acquire(self, timeout=None, poll_interval=0.05):
+        if not self.thread_safe.acquire(timeout=-1 if timeout is None else timeout):
+            raise Timeout(self.lock_file)
+        if self.count == 0:
+            super().acquire(timeout, poll_interval)
+        self.count += 1
+
+    def release(self, force=False):  # noqa: FBT002
+        with self.thread_safe:
+            if self.count > 0:
+                self.thread_safe.release()
+            if self.count == 1:
+                super().release(force=force)
+            self.count = max(self.count - 1, 0)
+

 _lock_store = {}
 _store_lock = Lock()


 class PathLockBase(ABC):
-
-    def __init__(self, folder) ->None:
+    def __init__(self, folder) -> None:
         path = Path(folder)
         self.path = path.resolve() if path.exists() else path

-    def __repr__(self) ->str:
-        return f'{self.__class__.__name__}({self.path})'
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}({self.path})"

     def __truediv__(self, other):
         return type(self)(self.path / other)
@@ -45,14 +62,37 @@ class PathLockBase(ABC):
     def __exit__(self, exc_type, exc_val, exc_tb):
         raise NotImplementedError

+    @abstractmethod
+    @contextmanager
+    def lock_for_key(self, name, no_block=False):  # noqa: FBT002
+        raise NotImplementedError

-class ReentrantFileLock(PathLockBase):
+    @abstractmethod
+    @contextmanager
+    def non_reentrant_lock_for_key(self, name):
+        raise NotImplementedError

-    def __init__(self, folder) ->None:
+
+class ReentrantFileLock(PathLockBase):
+    def __init__(self, folder) -> None:
         super().__init__(folder)
         self._lock = None

-    def __del__(self) ->None:
+    def _create_lock(self, name=""):
+        lock_file = str(self.path / f"{name}.lock")
+        with _store_lock:
+            if lock_file not in _lock_store:
+                _lock_store[lock_file] = _CountedFileLock(lock_file)
+            return _lock_store[lock_file]
+
+    @staticmethod
+    def _del_lock(lock):
+        if lock is not None:
+            with _store_lock, lock.thread_safe:
+                if lock.count == 0:
+                    _lock_store.pop(lock.lock_file, None)
+
+    def __del__(self) -> None:
         self._del_lock(self._lock)

     def __enter__(self):
@@ -64,14 +104,63 @@ class ReentrantFileLock(PathLockBase):
         self._del_lock(self._lock)
         self._lock = None

+    def _lock_file(self, lock, no_block=False):  # noqa: FBT002
+        # multiple processes might be trying to get a first lock... so we cannot check if this directory exist without
+        # a lock, but that lock might then become expensive, and it's not clear where that lock should live.
+        # Instead here we just ignore if we fail to create the directory.
+        with suppress(OSError):
+            os.makedirs(str(self.path))
+
+        try:
+            lock.acquire(0.0001)
+        except Timeout:
+            if no_block:
+                raise
+            logging.debug("lock file %s present, will block until released", lock.lock_file)
+            lock.release()  # release the acquire try from above
+            lock.acquire()
+
+    @staticmethod
+    def _release(lock):
+        lock.release()
+
+    @contextmanager
+    def lock_for_key(self, name, no_block=False):  # noqa: FBT002
+        lock = self._create_lock(name)
+        try:
+            try:
+                self._lock_file(lock, no_block)
+                yield
+            finally:
+                self._release(lock)
+        finally:
+            self._del_lock(lock)
+            lock = None
+
+    @contextmanager
+    def non_reentrant_lock_for_key(self, name):
+        with _CountedFileLock(str(self.path / f"{name}.lock")):
+            yield

-class NoOpFileLock(PathLockBase):

+class NoOpFileLock(PathLockBase):
     def __enter__(self):
         raise NotImplementedError

     def __exit__(self, exc_type, exc_val, exc_tb):
         raise NotImplementedError

+    @contextmanager
+    def lock_for_key(self, name, no_block=False):  # noqa: ARG002, FBT002
+        yield
+
+    @contextmanager
+    def non_reentrant_lock_for_key(self, name):  # noqa: ARG002
+        yield
+

-__all__ = ['NoOpFileLock', 'ReentrantFileLock', 'Timeout']
+__all__ = [
+    "NoOpFileLock",
+    "ReentrantFileLock",
+    "Timeout",
+]
diff --git a/src/virtualenv/util/path/_permission.py b/src/virtualenv/util/path/_permission.py
index 880f7f2..8dcad0c 100644
--- a/src/virtualenv/util/path/_permission.py
+++ b/src/virtualenv/util/path/_permission.py
@@ -1,4 +1,30 @@
 from __future__ import annotations
+
 import os
 from stat import S_IXGRP, S_IXOTH, S_IXUSR
-__all__ = 'make_exe', 'set_tree'
+
+
+def make_exe(filename):
+    original_mode = filename.stat().st_mode
+    levels = [S_IXUSR, S_IXGRP, S_IXOTH]
+    for at in range(len(levels), 0, -1):
+        try:
+            mode = original_mode
+            for level in levels[:at]:
+                mode |= level
+            filename.chmod(mode)
+            break
+        except OSError:
+            continue
+
+
+def set_tree(folder, stat):
+    for root, _, files in os.walk(str(folder)):
+        for filename in files:
+            os.chmod(os.path.join(root, filename), stat)
+
+
+__all__ = (
+    "make_exe",
+    "set_tree",
+)
diff --git a/src/virtualenv/util/path/_sync.py b/src/virtualenv/util/path/_sync.py
index 8daa74c..78684f0 100644
--- a/src/virtualenv/util/path/_sync.py
+++ b/src/virtualenv/util/path/_sync.py
@@ -1,4 +1,5 @@
 from __future__ import annotations
+
 import logging
 import os
 import shutil
@@ -6,17 +7,77 @@ import sys
 from stat import S_IWUSR


-class _Debug:
+def ensure_dir(path):
+    if not path.exists():
+        logging.debug("create folder %s", str(path))
+        os.makedirs(str(path))
+
+
+def ensure_safe_to_do(src, dest):
+    if src == dest:
+        msg = f"source and destination is the same {src}"
+        raise ValueError(msg)
+    if not dest.exists():
+        return
+    if dest.is_dir() and not dest.is_symlink():
+        logging.debug("remove directory %s", dest)
+        safe_delete(dest)
+    else:
+        logging.debug("remove file %s", dest)
+        dest.unlink()
+
+
+def symlink(src, dest):
+    ensure_safe_to_do(src, dest)
+    logging.debug("symlink %s", _Debug(src, dest))
+    dest.symlink_to(src, target_is_directory=src.is_dir())
+
+
+def copy(src, dest):
+    ensure_safe_to_do(src, dest)
+    is_dir = src.is_dir()
+    method = copytree if is_dir else shutil.copy
+    logging.debug("copy %s", _Debug(src, dest))
+    method(str(src), str(dest))

-    def __init__(self, src, dest) ->None:
+
+def copytree(src, dest):
+    for root, _, files in os.walk(src):
+        dest_dir = os.path.join(dest, os.path.relpath(root, src))
+        if not os.path.isdir(dest_dir):
+            os.makedirs(dest_dir)
+        for name in files:
+            src_f = os.path.join(root, name)
+            dest_f = os.path.join(dest_dir, name)
+            shutil.copy(src_f, dest_f)
+
+
+def safe_delete(dest):
+    def onerror(func, path, exc_info):  # noqa: ARG001
+        if not os.access(path, os.W_OK):
+            os.chmod(path, S_IWUSR)
+            func(path)
+        else:
+            raise  # noqa: PLE0704
+
+    kwargs = {"onexc" if sys.version_info >= (3, 12) else "onerror": onerror}
+    shutil.rmtree(str(dest), ignore_errors=True, **kwargs)
+
+
+class _Debug:
+    def __init__(self, src, dest) -> None:
         self.src = src
         self.dest = dest

-    def __str__(self) ->str:
-        return (
-            f"{'directory ' if self.src.is_dir() else ''}{self.src!s} to {self.dest!s}"
-            )
+    def __str__(self) -> str:
+        return f"{'directory ' if self.src.is_dir() else ''}{self.src!s} to {self.dest!s}"


-__all__ = ['copy', 'copytree', 'ensure_dir', 'safe_delete', 'symlink',
-    'symlink']
+__all__ = [
+    "copy",
+    "copytree",
+    "ensure_dir",
+    "safe_delete",
+    "symlink",
+    "symlink",
+]
diff --git a/src/virtualenv/util/path/_win.py b/src/virtualenv/util/path/_win.py
index e274b39..aa67ca7 100644
--- a/src/virtualenv/util/path/_win.py
+++ b/src/virtualenv/util/path/_win.py
@@ -3,7 +3,21 @@ from __future__ import annotations

 def get_short_path_name(long_name):
     """Gets the short path name of a given long path - http://stackoverflow.com/a/23598461/200291."""
-    pass
+    import ctypes  # noqa: PLC0415
+    from ctypes import wintypes  # noqa: PLC0415

+    _GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW  # noqa: N806
+    _GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD]
+    _GetShortPathNameW.restype = wintypes.DWORD
+    output_buf_size = 0
+    while True:
+        output_buf = ctypes.create_unicode_buffer(output_buf_size)
+        needed = _GetShortPathNameW(long_name, output_buf, output_buf_size)
+        if output_buf_size >= needed:
+            return output_buf.value
+        output_buf_size = needed

-__all__ = ['get_short_path_name']
+
+__all__ = [
+    "get_short_path_name",
+]
diff --git a/src/virtualenv/util/zipapp.py b/src/virtualenv/util/zipapp.py
index a51e799..958db15 100644
--- a/src/virtualenv/util/zipapp.py
+++ b/src/virtualenv/util/zipapp.py
@@ -1,6 +1,41 @@
 from __future__ import annotations
+
 import logging
 import os
 import zipfile
+
 from virtualenv.info import IS_WIN, ROOT
-__all__ = ['extract', 'read']
+
+
+def read(full_path):
+    sub_file = _get_path_within_zip(full_path)
+    with zipfile.ZipFile(ROOT, "r") as zip_file, zip_file.open(sub_file) as file_handler:
+        return file_handler.read().decode("utf-8")
+
+
+def extract(full_path, dest):
+    logging.debug("extract %s to %s", full_path, dest)
+    sub_file = _get_path_within_zip(full_path)
+    with zipfile.ZipFile(ROOT, "r") as zip_file:
+        info = zip_file.getinfo(sub_file)
+        info.filename = dest.name
+        zip_file.extract(info, str(dest.parent))
+
+
+def _get_path_within_zip(full_path):
+    full_path = os.path.realpath(os.path.abspath(str(full_path)))
+    prefix = f"{ROOT}{os.sep}"
+    if not full_path.startswith(prefix):
+        msg = f"full_path={full_path} should start with prefix={prefix}."
+        raise RuntimeError(msg)
+    sub_file = full_path[len(prefix) :]
+    if IS_WIN:
+        # paths are always UNIX separators, even on Windows, though __file__ still follows platform default
+        sub_file = sub_file.replace(os.sep, "/")
+    return sub_file
+
+
+__all__ = [
+    "extract",
+    "read",
+]