Repository: tox-dev/tox

Issue:
It is not possible for a tox plugin to change the value of tox_root
## Issue

Based on this https://github.com/tox-dev/tox/discussions/2882#discussioncomment-4713455 response to a discussion question, it seems like the recommended way for tox plugins to set config values is to use a memory loader.

This seems to work in most cases, but `tox_root` appears to be an exception.


Reproduced on 4.4.8

## Environment

Provide at least:

- OS: 

```console
Distributor ID:	Ubuntu
Description:	Ubuntu 22.04.1 LTS
Release:	22.04
Codename:	jammy
```

- `pip list` of the host Python where `tox` is installed:

```console
Package       Version
------------- -------
cachetools    5.3.0
chardet       5.1.0
colorama      0.4.6
distlib       0.3.6
filelock      3.10.7
packaging     23.0
pip           22.0.2
platformdirs  3.2.0
pluggy        1.0.0
pyproject_api 1.5.1
setuptools    59.6.0
tomli         2.0.1
tox           4.4.8
virtualenv    20.21.0
```

## Output of running tox

Provide the output of `tox -rvv`:

```console
py: 793 I find interpreter for spec PythonSpec(path=/home/kd/Documents/tox/venv/bin/python3) [virtualenv/discovery/builtin.py:56]
py: 794 D discover exe for PythonInfo(spec=CPython3.10.6.final.0-64, exe=/home/kd/Documents/tox/venv/bin/python3, platform=linux, version='3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0]', encoding_fs_io=utf-8-utf-8) in /usr [virtualenv/discovery/py_info.py:437]
py: 795 D filesystem is case-sensitive [virtualenv/info.py:24]
py: 797 D got python info of /usr/bin/python3.10 from /home/kd/.local/share/virtualenv/py_info/1/8a94588eda9d64d9e9a351ab8144e55b1fabf5113b54e67dd26a8c27df0381b3.json [virtualenv/app_data/via_disk_folder.py:129]
py: 799 I proposed PythonInfo(spec=CPython3.10.6.final.0-64, system=/usr/bin/python3.10, exe=/home/kd/Documents/tox/venv/bin/python3, platform=linux, version='3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0]', encoding_fs_io=utf-8-utf-8) [virtualenv/discovery/builtin.py:63]
py: 799 D accepted PythonInfo(spec=CPython3.10.6.final.0-64, system=/usr/bin/python3.10, exe=/home/kd/Documents/tox/venv/bin/python3, platform=linux, version='3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0]', encoding_fs_io=utf-8-utf-8) [virtualenv/discovery/builtin.py:65]
py: 886 I create virtual environment via CPython3Posix(dest=/home/kd/py, clear=False, no_vcs_ignore=False, global=False) [virtualenv/run/session.py:48]
py: 888 D create folder /home/kd/py/bin [virtualenv/util/path/_sync.py:9]
py: 889 D create folder /home/kd/py/lib/python3.10/site-packages [virtualenv/util/path/_sync.py:9]
py: 890 D write /home/kd/py/pyvenv.cfg [virtualenv/create/pyenv_cfg.py:30]
py: 890 D 	home = /usr/bin [virtualenv/create/pyenv_cfg.py:34]
py: 890 D 	implementation = CPython [virtualenv/create/pyenv_cfg.py:34]
py: 890 D 	version_info = 3.10.6.final.0 [virtualenv/create/pyenv_cfg.py:34]
py: 891 D 	virtualenv = 20.21.0 [virtualenv/create/pyenv_cfg.py:34]
py: 891 D 	include-system-site-packages = false [virtualenv/create/pyenv_cfg.py:34]
py: 891 D 	base-prefix = /usr [virtualenv/create/pyenv_cfg.py:34]
py: 892 D 	base-exec-prefix = /usr [virtualenv/create/pyenv_cfg.py:34]
py: 892 D 	base-executable = /usr/bin/python3.10 [virtualenv/create/pyenv_cfg.py:34]
py: 893 D symlink /usr/bin/python3.10 to /home/kd/py/bin/python [virtualenv/util/path/_sync.py:28]
py: 895 D create virtualenv import hook file /home/kd/py/lib/python3.10/site-packages/_virtualenv.pth [virtualenv/create/via_global_ref/api.py:89]
py: 896 D create /home/kd/py/lib/python3.10/site-packages/_virtualenv.py [virtualenv/create/via_global_ref/api.py:92]
py: 897 D ============================== target debug ============================== [virtualenv/run/session.py:50]
py: 898 D debug via /home/kd/py/bin/python /home/kd/Documents/tox/venv/lib/python3.10/site-packages/virtualenv/create/debug.py [virtualenv/create/creator.py:193]
py: 897 D {
  "sys": {
    "executable": "/home/kd/py/bin/python",
    "_base_executable": "/home/kd/py/bin/python",
    "prefix": "/home/kd/py",
    "base_prefix": "/usr",
    "real_prefix": null,
    "exec_prefix": "/home/kd/py",
    "base_exec_prefix": "/usr",
    "path": [
      "/usr/lib/python310.zip",
      "/usr/lib/python3.10",
      "/usr/lib/python3.10/lib-dynload",
      "/home/kd/py/lib/python3.10/site-packages"
    ],
    "meta_path": [
      "<class '_virtualenv._Finder'>",
      "<class '_frozen_importlib.BuiltinImporter'>",
      "<class '_frozen_importlib.FrozenImporter'>",
      "<class '_frozen_importlib_external.PathFinder'>"
    ],
    "fs_encoding": "utf-8",
    "io_encoding": "utf-8"
  },
  "version": "3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0]",
  "makefile_filename": "/usr/lib/python3.10/config-3.10-x86_64-linux-gnu/Makefile",
  "os": "<module 'os' from '/usr/lib/python3.10/os.py'>",
  "site": "<module 'site' from '/usr/lib/python3.10/site.py'>",
  "datetime": "<module 'datetime' from '/usr/lib/python3.10/datetime.py'>",
  "math": "<module 'math' (built-in)>",
  "json": "<module 'json' from '/usr/lib/python3.10/json/__init__.py'>"
} [virtualenv/run/session.py:51]
py: 1043 I add seed packages via FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/kd/.local/share/virtualenv) [virtualenv/run/session.py:55]
py: 1065 D got embed update of distribution wheel from /home/kd/.local/share/virtualenv/wheel/3.10/embed/3/wheel.json [virtualenv/app_data/via_disk_folder.py:129]
py: 1068 D got embed update of distribution setuptools from /home/kd/.local/share/virtualenv/wheel/3.10/embed/3/setuptools.json [virtualenv/app_data/via_disk_folder.py:129]
py: 1069 D got embed update of distribution pip from /home/kd/.local/share/virtualenv/wheel/3.10/embed/3/pip.json [virtualenv/app_data/via_disk_folder.py:129]
py: 1078 D install wheel from wheel /home/kd/Documents/tox/venv/lib/python3.10/site-packages/virtualenv/seed/wheels/embed/wheel-0.38.4-py3-none-any.whl via CopyPipInstall [virtualenv/seed/embed/via_app_data/via_app_data.py:47]
py: 1079 D install setuptools from wheel /home/kd/Documents/tox/venv/lib/python3.10/site-packages/virtualenv/seed/wheels/embed/setuptools-67.4.0-py3-none-any.whl via CopyPipInstall [virtualenv/seed/embed/via_app_data/via_app_data.py:47]
py: 1082 D install pip from wheel /home/kd/Documents/tox/venv/lib/python3.10/site-packages/virtualenv/seed/wheels/embed/pip-23.0.1-py3-none-any.whl via CopyPipInstall [virtualenv/seed/embed/via_app_data/via_app_data.py:47]
py: 1093 D copy directory /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/pip-23.0.1-py3-none-any/pip to /home/kd/py/lib/python3.10/site-packages/pip [virtualenv/util/path/_sync.py:36]
py: 1098 D copy /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/setuptools-67.4.0-py3-none-any/setuptools-67.4.0.virtualenv to /home/kd/py/lib/python3.10/site-packages/setuptools-67.4.0.virtualenv [virtualenv/util/path/_sync.py:36]
py: 1100 D copy directory /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/wheel-0.38.4-py3-none-any/wheel to /home/kd/py/lib/python3.10/site-packages/wheel [virtualenv/util/path/_sync.py:36]
py: 1104 D copy directory /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/setuptools-67.4.0-py3-none-any/_distutils_hack to /home/kd/py/lib/python3.10/site-packages/_distutils_hack [virtualenv/util/path/_sync.py:36]
py: 1121 D copy directory /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/setuptools-67.4.0-py3-none-any/setuptools-67.4.0.dist-info to /home/kd/py/lib/python3.10/site-packages/setuptools-67.4.0.dist-info [virtualenv/util/path/_sync.py:36]
py: 1154 D copy directory /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/setuptools-67.4.0-py3-none-any/setuptools to /home/kd/py/lib/python3.10/site-packages/setuptools [virtualenv/util/path/_sync.py:36]
py: 1181 D copy /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/wheel-0.38.4-py3-none-any/wheel-0.38.4.virtualenv to /home/kd/py/lib/python3.10/site-packages/wheel-0.38.4.virtualenv [virtualenv/util/path/_sync.py:36]
py: 1187 D copy directory /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/wheel-0.38.4-py3-none-any/wheel-0.38.4.dist-info to /home/kd/py/lib/python3.10/site-packages/wheel-0.38.4.dist-info [virtualenv/util/path/_sync.py:36]
py: 1225 D generated console scripts wheel-3.10 wheel3 wheel3.10 wheel [virtualenv/seed/embed/via_app_data/pip_install/base.py:41]
py: 1506 D copy directory /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/setuptools-67.4.0-py3-none-any/pkg_resources to /home/kd/py/lib/python3.10/site-packages/pkg_resources [virtualenv/util/path/_sync.py:36]
py: 1599 D copy /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/setuptools-67.4.0-py3-none-any/distutils-precedence.pth to /home/kd/py/lib/python3.10/site-packages/distutils-precedence.pth [virtualenv/util/path/_sync.py:36]
py: 1604 D generated console scripts  [virtualenv/seed/embed/via_app_data/pip_install/base.py:41]
py: 1765 D copy directory /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/pip-23.0.1-py3-none-any/pip-23.0.1.dist-info to /home/kd/py/lib/python3.10/site-packages/pip-23.0.1.dist-info [virtualenv/util/path/_sync.py:36]
py: 1770 D copy /home/kd/.local/share/virtualenv/wheel/3.10/image/1/CopyPipInstall/pip-23.0.1-py3-none-any/pip-23.0.1.virtualenv to /home/kd/py/lib/python3.10/site-packages/pip-23.0.1.virtualenv [virtualenv/util/path/_sync.py:36]
py: 1772 D generated console scripts pip pip3 pip-3.10 pip3.10 [virtualenv/seed/embed/via_app_data/pip_install/base.py:41]
py: 1773 I add activators for Bash, CShell, Fish, Nushell, PowerShell, Python [virtualenv/run/session.py:61]
py: 1778 D write /home/kd/py/pyvenv.cfg [virtualenv/create/pyenv_cfg.py:30]
py: 1779 D 	home = /usr/bin [virtualenv/create/pyenv_cfg.py:34]
py: 1779 D 	implementation = CPython [virtualenv/create/pyenv_cfg.py:34]
py: 1779 D 	version_info = 3.10.6.final.0 [virtualenv/create/pyenv_cfg.py:34]
py: 1780 D 	virtualenv = 20.21.0 [virtualenv/create/pyenv_cfg.py:34]
py: 1780 D 	include-system-site-packages = false [virtualenv/create/pyenv_cfg.py:34]
py: 1780 D 	base-prefix = /usr [virtualenv/create/pyenv_cfg.py:34]
py: 1780 D 	base-exec-prefix = /usr [virtualenv/create/pyenv_cfg.py:34]
py: 1780 D 	base-executable = /usr/bin/python3.10 [virtualenv/create/pyenv_cfg.py:34]
  py: OK (1.01 seconds)
  congratulations :) (1.20 seconds)
```

## Minimal example

If possible, provide a minimal reproducer for the issue:

1. Reproduce minimal tox project

   ```console
     $ tree
    .
    ├── foo/
    ├── toxfile.py
    └── tox.ini
    ```

    Where

    ```ini
    # tox.ini
    [testenv]
    commands=
        python -c "print(\"tox_root:\", '{tox_root}')"
    ```

    ```python
    # toxfile.py
    from tox import plugin
    from tox.config.sets import CoreConfigSet
    from tox.config.loader.memory import MemoryLoader
    from tox.session.state import State
    from pathlib import Path

    @plugin.impl
    def tox_add_core_config(core_conf: CoreConfigSet, state: State):
        core_conf.loaders.insert(0, MemoryLoader(tox_root=Path(__file__).parent / "foo")
    ```

2. Run `tox -qq`


    **Expected Output:**

    ```console
    tox_root:	/home/kd/Documents/tox/repro/foo
    ```

    **Actual Output:**

    ```console
    tox_root:	/home/kd/Documents/tox/repro/
    ```

Discussion:

Patch:
diff --git a/docs/changelog/2966.bugfix.rst b/docs/changelog/2966.bugfix.rst
new file mode 100644
index 00000000..4e4b3d4b
--- /dev/null
+++ b/docs/changelog/2966.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed an issue where a tox plugin couldn't change the value of ``tox_root``.
diff --git a/src/tox/provision.py b/src/tox/provision.py
index e67121bd..3428bc23 100644
--- a/src/tox/provision.py
+++ b/src/tox/provision.py
@@ -84,6 +84,11 @@ def provision(state: State) -> int | bool:
         desc="Name of the virtual environment used to provision a tox.",
         post_process=add_tox_requires_min_version,
     )
+
+    from tox.plugin.manager import MANAGER
+
+    MANAGER.tox_add_core_config(state.conf.core, state)
+
     requires: list[Requirement] = state.conf.core["requires"]
     missing = _get_missing(requires)
 
@@ -100,10 +105,6 @@ def provision(state: State) -> int | bool:
     state.conf.memory_seed_loaders[provision_tox_env].append(loader)
     state.envs._mark_provision(bool(missing), provision_tox_env)
 
-    from tox.plugin.manager import MANAGER
-
-    MANAGER.tox_add_core_config(state.conf.core, state)
-
     if not missing:
         return False
 


Test patch:
diff --git a/tests/plugin/test_plugin.py b/tests/plugin/test_plugin.py
index 78a24540..0d00602f 100644
--- a/tests/plugin/test_plugin.py
+++ b/tests/plugin/test_plugin.py
@@ -3,6 +3,7 @@ from __future__ import annotations
 import logging
 import os
 import sys
+from pathlib import Path
 from typing import Any
 from unittest.mock import patch
 
@@ -102,6 +103,33 @@ def test_plugin_hooks_and_order(tox_project: ToxProjectCreator, mocker: MockerFi
     assert result.out.splitlines() == expected, result.out
 
 
+@pytest.mark.parametrize(
+    "dir_name",
+    [
+        "tox_root",
+        "work_dir",
+        "temp_dir",
+    ],
+)
+def test_plugin_can_set_core_conf(
+    tox_project: ToxProjectCreator,
+    mocker: MockerFixture,
+    dir_name: str,
+    tmp_path: Path,
+) -> None:
+    @impl
+    def tox_add_core_config(core_conf: CoreConfigSet, state: State) -> None:  # noqa: U100
+        core_conf.loaders.insert(0, MemoryLoader(**{dir_name: tmp_path}))
+
+    register_inline_plugin(mocker, tox_add_core_config)
+
+    project = tox_project({})
+    result = project.run("c")
+    result.assert_success()
+
+    assert result.state.conf.core[dir_name] == tmp_path
+
+
 def test_plugin_can_read_env_list(tox_project: ToxProjectCreator, mocker: MockerFixture) -> None:
     @impl
     def tox_add_core_config(core_conf: CoreConfigSet, state: State) -> None:  # noqa: U100