Repository: tox-dev/tox

Issue:
Non-existent basepython fails unused environment with usedevelop
## Issue

<!-- Describe what's the expected behaviour and what you're observing. -->

With usedevelop=True, and a non-existent basepython in an environment that isn't used, an error happens looking for the non-existent executable.

This seems similar to, but different than https://github.com/tox-dev/tox/issues/2826.


## Environment

Provide at least:

- OS: Mac OSX 13.5.1

<details open>
<summary>Output of <code>pip list</code> of the host Python, where <code>tox</code> is installed</summary>

```console
% pip list
Package       Version
------------- -------
cachetools    5.3.1
chardet       5.2.0
colorama      0.4.6
distlib       0.3.7
filelock      3.12.2
packaging     23.1
pip           23.2.1
platformdirs  3.10.0
pluggy        1.2.0
pyproject-api 1.5.4
setuptools    68.0.0
tomli         2.0.1
tox           4.10.0
virtualenv    20.24.3
wheel         0.41.0
```

</details>

## Output of running tox

<details open>
<summary>Output of <code>tox -rvv</code></summary>

```console
% tox -rvv
.pkg: 476 I find interpreter for spec PythonSpec(major=3, minor=10) [virtualenv/discovery/builtin.py:58]
.pkg: 477 D discover exe for PythonInfo(spec=CPython3.10.13.final.0-64, exe=/usr/local/virtualenvs/tmp-2105dbad4ba1a01/bin/python, platform=darwin, version='3.10.13 (main, Aug 25 2023, 06:52:26) [Clang 14.0.3 (clang-1403.0.22.14.1)]', encoding_fs_io=utf-8-utf-8) in /usr/local/pyenv/pyenv/versions/3.10.13 [virtualenv/discovery/py_info.py:441]
.pkg: 479 D filesystem is not case-sensitive [virtualenv/info.py:26]
.pkg: 481 D got python info of %s from (PosixPath('/usr/local/pyenv/pyenv/versions/3.10.13/bin/python3.10'), PosixPath('/Users/nbatchelder/Library/Application Support/virtualenv/py_info/1/c25eae2dfc5d1b10c1c60ba13c399fed12571f8306176d0f7721e638ddb69d8c.json')) [virtualenv/app_data/via_disk_folder.py:131]
.pkg: 504 I proposed PythonInfo(spec=CPython3.10.13.final.0-64, system=/usr/local/pyenv/pyenv/versions/3.10.13/bin/python3.10, exe=/usr/local/virtualenvs/tmp-2105dbad4ba1a01/bin/python, platform=darwin, version='3.10.13 (main, Aug 25 2023, 06:52:26) [Clang 14.0.3 (clang-1403.0.22.14.1)]', encoding_fs_io=utf-8-utf-8) [virtualenv/discovery/builtin.py:65]
.pkg: 504 D accepted PythonInfo(spec=CPython3.10.13.final.0-64, system=/usr/local/pyenv/pyenv/versions/3.10.13/bin/python3.10, exe=/usr/local/virtualenvs/tmp-2105dbad4ba1a01/bin/python, platform=darwin, version='3.10.13 (main, Aug 25 2023, 06:52:26) [Clang 14.0.3 (clang-1403.0.22.14.1)]', encoding_fs_io=utf-8-utf-8) [virtualenv/discovery/builtin.py:67]
.pkg: 570 I find interpreter for spec PythonSpec(path=/usr/local/virtualenvs/tmp-2105dbad4ba1a01/bin/python) [virtualenv/discovery/builtin.py:58]
.pkg: 570 D discover exe from cache /usr/local/pyenv/pyenv/versions/3.10.13 - exact False: PythonInfo({'architecture': 64, 'base_exec_prefix': '/usr/local/pyenv/pyenv/versions/3.10.13', 'base_prefix': '/usr/local/pyenv/pyenv/versions/3.10.13', 'distutils_install': {'data': '', 'headers': 'include/python3.10/UNKNOWN', 'platlib': 'lib/python3.10/site-packages', 'purelib': 'lib/python3.10/site-packages', 'scripts': 'bin'}, 'exec_prefix': '/usr/local/pyenv/pyenv/versions/3.10.13', 'executable': '/usr/local/virtualenvs/tmp-2105dbad4ba1a01/bin/python', 'file_system_encoding': 'utf-8', 'has_venv': True, 'implementation': 'CPython', 'max_size': 9223372036854775807, 'original_executable': '/usr/local/pyenv/pyenv/versions/3.10.13/bin/python3.10', 'os': 'posix', 'path': ['/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/virtualenv/discovery', '/usr/local/pyenv/pyenv/versions/3.10.13/lib/python310.zip', '/usr/local/pyenv/pyenv/versions/3.10.13/lib/python3.10', '/usr/local/pyenv/pyenv/versions/3.10.13/lib/python3.10/lib-dynload', '/usr/local/pyenv/pyenv/versions/3.10.13/lib/python3.10/site-packages'], 'platform': 'darwin', 'prefix': '/usr/local/pyenv/pyenv/versions/3.10.13', 'real_prefix': None, 'stdout_encoding': 'utf-8', 'sysconfig': {'makefile_filename': '/usr/local/pyenv/pyenv/versions/3.10.13/lib/python3.10/config-3.10-darwin/Makefile'}, 'sysconfig_paths': {'data': '{base}', 'include': '{installed_base}/include/python{py_version_short}{abiflags}', 'platlib': '{platbase}/{platlibdir}/python{py_version_short}/site-packages', 'platstdlib': '{platbase}/{platlibdir}/python{py_version_short}', 'purelib': '{base}/lib/python{py_version_short}/site-packages', 'scripts': '{base}/bin', 'stdlib': '{installed_base}/{platlibdir}/python{py_version_short}'}, 'sysconfig_scheme': None, 'sysconfig_vars': {'PYTHONFRAMEWORK': '', 'abiflags': '', 'base': '/usr/local/pyenv/pyenv/versions/3.10.13', 'installed_base': '/usr/local/pyenv/pyenv/versions/3.10.13', 'platbase': '/usr/local/pyenv/pyenv/versions/3.10.13', 'platlibdir': 'lib', 'py_version_short': '3.10'}, 'system_executable': '/usr/local/pyenv/pyenv/versions/3.10.13/bin/python3.10', 'system_stdlib': '/usr/local/pyenv/pyenv/versions/3.10.13/lib/python3.10', 'system_stdlib_platform': '/usr/local/pyenv/pyenv/versions/3.10.13/lib/python3.10', 'version': '3.10.13 (main, Aug 25 2023, 06:52:26) [Clang 14.0.3 (clang-1403.0.22.14.1)]', 'version_info': VersionInfo(major=3, minor=10, micro=13, releaselevel='final', serial=0), 'version_nodot': '310'}) [virtualenv/discovery/py_info.py:439]
.pkg: 570 I proposed PythonInfo(spec=CPython3.10.13.final.0-64, system=/usr/local/pyenv/pyenv/versions/3.10.13/bin/python3.10, exe=/usr/local/virtualenvs/tmp-2105dbad4ba1a01/bin/python, platform=darwin, version='3.10.13 (main, Aug 25 2023, 06:52:26) [Clang 14.0.3 (clang-1403.0.22.14.1)]', encoding_fs_io=utf-8-utf-8) [virtualenv/discovery/builtin.py:65]
.pkg: 570 D accepted PythonInfo(spec=CPython3.10.13.final.0-64, system=/usr/local/pyenv/pyenv/versions/3.10.13/bin/python3.10, exe=/usr/local/virtualenvs/tmp-2105dbad4ba1a01/bin/python, platform=darwin, version='3.10.13 (main, Aug 25 2023, 06:52:26) [Clang 14.0.3 (clang-1403.0.22.14.1)]', encoding_fs_io=utf-8-utf-8) [virtualenv/discovery/builtin.py:67]
.pkg: 578 I find interpreter for spec PythonSpec(path=/this/doesnt/exist/bin/python) [virtualenv/discovery/builtin.py:58]
Traceback (most recent call last):
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/bin/tox", line 8, in <module>
    sys.exit(run())
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/run.py", line 19, in run
    result = main(sys.argv[1:] if args is None else args)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/run.py", line 45, in main
    return handler(state)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/session/cmd/legacy.py", line 115, in legacy
    return run_sequential(state)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/session/cmd/run/sequential.py", line 24, in run_sequential
    return execute(state, max_workers=1, has_spinner=False, live=True)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/session/cmd/run/common.py", line 236, in execute
    state.envs.ensure_only_run_env_is_active()
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/session/env_select.py", line 416, in ensure_only_run_env_is_active
    envs, active = self._defined_envs, self._env_name_to_active()
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/session/env_select.py", line 273, in _defined_envs
    raise failed[next(iter(failed_to_create))]
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/session/env_select.py", line 250, in _defined_envs
    run_env.package_env = self._build_pkg_env(pkg_name_type, name, env_name_to_active)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/session/env_select.py", line 321, in _build_pkg_env
    name_type = next(child_package_envs)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/tox_env/python/virtual_env/package/pyproject.py", line 150, in register_run_env
    yield from super().register_run_env(run_env)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/tox_env/python/package.py", line 116, in register_run_env
    pkg_env = run_env.conf["wheel_build_env"]
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/config/sets.py", line 118, in __getitem__
    return self.load(item)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/config/sets.py", line 129, in load
    return config_definition.__call__(self._conf, self.loaders, ConfigLoadArgs(chain, self.name, self.env_name))
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/config/of_type.py", line 105, in __call__
    value = self.default(conf, args.env_name) if callable(self.default) else self.default
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/tox_env/python/package.py", line 92, in default_wheel_tag
    run_py = cast(Python, run_env).base_python
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/tox_env/python/api.py", line 250, in base_python
    self._base_python = self._get_python(base_pythons)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/tox_env/python/virtual_env/api.py", line 134, in _get_python
    interpreter = self.creator.interpreter
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/tox_env/python/virtual_env/api.py", line 126, in creator
    return self.session.creator
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/tox/tox_env/python/virtual_env/api.py", line 107, in session
    self._virtualenv_session = session_via_cli(env_dir, options=None, setup_logging=False, env=env)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/virtualenv/run/__init__.py", line 49, in session_via_cli
    parser, elements = build_parser(args, options, setup_logging, env)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/virtualenv/run/__init__.py", line 77, in build_parser
    parser._interpreter = interpreter = discover.interpreter  # noqa: SLF001
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/virtualenv/discovery/discover.py", line 41, in interpreter
    self._interpreter = self.run()
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/virtualenv/discovery/builtin.py", line 46, in run
    result = get_interpreter(python_spec, self.try_first_with, self.app_data, self._env)
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/virtualenv/discovery/builtin.py", line 61, in get_interpreter
    for interpreter, impl_must_match in propose_interpreters(spec, try_first_with, app_data, env):
  File "/usr/local/virtualenvs/tmp-2105dbad4ba1a01/lib/python3.10/site-packages/virtualenv/discovery/builtin.py", line 88, in propose_interpreters
    os.lstat(spec.path)  # Windows Store Python does not work with os.path.exists, but does for os.lstat
FileNotFoundError: [Errno 2] No such file or directory: '/this/doesnt/exist/bin/python'
```

</details>

## Minimal example

<!--  If possible, provide a minimal reproducer for the issue. -->

tox.ini:
```
[tox]
envlist = py310

[testenv]
usedevelop = True   # Comment this out to avoid the error
commands = python -c "print('Hello')"

[testenv:never]
basepython = /this/doesnt/exist/bin/python
```

Discussion:

Patch:
diff --git a/docs/changelog/3105.bugfix.rst b/docs/changelog/3105.bugfix.rst
new file mode 100644
index 00000000..dd87507c
--- /dev/null
+++ b/docs/changelog/3105.bugfix.rst
@@ -0,0 +1,1 @@
+Handle ``FileNotFoundError`` when the ``base_python`` interpreter doesn't exist
diff --git a/src/tox/tox_env/python/virtual_env/api.py b/src/tox/tox_env/python/virtual_env/api.py
index 706f7ea4..c1c50724 100644
--- a/src/tox/tox_env/python/virtual_env/api.py
+++ b/src/tox/tox_env/python/virtual_env/api.py
@@ -132,7 +132,7 @@ class VirtualEnv(Python):
         # the base pythons are injected into the virtualenv_env_vars, so we don't need to use it here
         try:
             interpreter = self.creator.interpreter
-        except RuntimeError:  # if can't find
+        except (FileNotFoundError, RuntimeError):  # Unable to find the interpreter
             return None
         return PythonInfo(
             implementation=interpreter.implementation,


Test patch:
diff --git a/tests/tox_env/python/test_python_api.py b/tests/tox_env/python/test_python_api.py
index 159d9d77..e56d9d69 100644
--- a/tests/tox_env/python/test_python_api.py
+++ b/tests/tox_env/python/test_python_api.py
@@ -277,3 +277,10 @@ def test_list_installed_deps_explicit_cli(
         assert "pip==" in result.out
     else:
         assert "pip==" not in result.out
+
+
+def test_usedevelop_with_nonexistent_basepython(tox_project: ToxProjectCreator) -> None:
+    ini = "[testenv]\nusedevelop = true\n[testenv:unused]\nbasepython = /nonexistent/bin/python"
+    project = tox_project({"tox.ini": ini})
+    result = project.run()
+    assert result.code == 0