Skip to content

Commit ec44803

Browse files
Add {home} and {tox_root_name} substitutions (#4020)
Co-authored-by: Bernát Gábor <gaborjbernat@gmail.com>
1 parent 4636193 commit ec44803

7 files changed

Lines changed: 96 additions & 2 deletions

File tree

‎docs/changelog/4020.feature.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add ``{home}`` and ``{tox_root_name}`` substitutions; set :ref:`work_dir` to e.g.
2+
``"{home}/.local/state/tox/{tox_root_name}"`` to keep environments outside of the project tree - by :user:`WhyNotHugo`.

‎docs/explanation.rst‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,27 @@ environments (template name + factor suffix) appear in ``tox list`` and can be r
341341

342342
For the configuration reference, see :ref:`env-base-templates`. For practical recipes, see :ref:`howto_env_base_matrix`.
343343

344+
.. _work-dir-placement:
345+
346+
Work directory placement
347+
========================
348+
349+
Everything tox generates lives under one directory, :ref:`work_dir`: the virtual environments, their logs, packaging
350+
artifacts, and the provisioned tox itself when :ref:`auto-provisioning` kicks in. The default is ``.tox`` next to the
351+
configuration file, which makes a project self-contained: deleting the checkout deletes its environments, and two
352+
checkouts of the same project never share state.
353+
354+
Placing environments in the tree also has costs. Editor indexers and file watchers crawl the virtual environments unless
355+
told otherwise, and backup or cloud-sync tools pick up thousands of files tox can regenerate at will. A project on a
356+
network or otherwise slow filesystem drags each environment operation down with it too. In those situations move the
357+
environments to a per-project directory under your home folder, the way ``virtualenvwrapper`` centralizes its virtual
358+
environments; the ``{home}`` and ``{tox_root_name}`` substitutions in :ref:`work_dir` express this without hard-coding a
359+
path.
360+
361+
The trade-off mirrors the benefit of the default: ``{tox_root_name}`` is only the name of the project directory, so two
362+
projects checked out under the same name resolve to the same work directory, and removing a checkout leaves its
363+
environments behind until you delete them yourself. For the recipe, see :ref:`howto_out_of_tree_envs`.
364+
344365
***************
345366
Main features
346367
***************

‎docs/how-to/usage.rst‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,43 @@ location can be changed via the ``TOX_CONFIG_FILE`` environment variable.
191191
# Force editable install for a specific environment
192192
tox run -e 3.13 -x "testenv:3.13.package=editable"
193193
194+
.. _howto_out_of_tree_envs:
195+
196+
********************************************
197+
Keep environments outside the project tree
198+
********************************************
199+
200+
By default tox creates its environments in a ``.tox`` directory next to the configuration file. To keep them out of the
201+
project tree, point :ref:`work_dir` at a location built from the ``{home}`` and ``{tox_root_name}`` substitutions:
202+
203+
.. tab:: TOML
204+
205+
.. code-block:: toml
206+
207+
work_dir = "{home}/.local/state/tox/{tox_root_name}"
208+
209+
.. tab:: INI (deprecated)
210+
211+
.. code-block:: ini
212+
213+
[tox]
214+
work_dir = {home}/.local/state/tox/{tox_root_name}
215+
216+
With this configuration a project checked out at ``~/src/magic`` keeps its environments in ``~/.local/state/tox/magic``.
217+
Verify the resolved location with:
218+
219+
.. code-block:: bash
220+
221+
$ tox config --core -k work_dir
222+
[tox]
223+
work_dir = /home/user/.local/state/tox/magic
224+
225+
Substitutions resolve in the project configuration file only: the ``--workdir`` CLI flag and the user-level
226+
configuration file take literal paths, so this setting belongs in ``tox.toml``. Since ``{tox_root_name}`` is the name of
227+
the project directory, two projects checked out under the same directory name share a work directory; give one of them
228+
an explicit path if that happens. See :ref:`work-dir-placement` for the trade-offs of moving environments out of the
229+
tree.
230+
194231
**********************************
195232
Use labels to group environments
196233
**********************************

‎docs/reference/config.rst‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,21 @@ the top level of ``tox.toml``. Placing these options in an environment section (
515515
:keys: work_dir, toxworkdir
516516
:default: {tox_root}/.tox
517517
:version_added: 1.0
518+
:version_changed: 4.60
518519

519520
Directory for tox to generate its environments into, will be created if it does not exist.
520521

522+
Use the ``{home}`` and ``{tox_root_name}`` substitutions to keep tox environments outside of the project tree, in a
523+
location unique per project, similar to ``virtualenvwrapper``:
524+
525+
.. code-block:: toml
526+
527+
work_dir = "{home}/.local/state/tox/{tox_root_name}"
528+
529+
The ``--workdir`` CLI flag and the user-level configuration file take the value as a literal path, without
530+
substitution. See :ref:`howto_out_of_tree_envs` for a walk-through and :ref:`work-dir-placement` for the
531+
trade-offs.
532+
521533
.. conf::
522534
:keys: temp_dir
523535
:default: {work_dir}/.tmp
@@ -3181,6 +3193,10 @@ or via ``{name}`` in INI.
31813193
- Description
31823194
- - ``{tox_root}`` / ``{toxinidir}``
31833195
- The directory where the configuration file is located (project root).
3196+
- - ``{tox_root_name}``
3197+
- Name of the project root directory (the last path component of ``{tox_root}``).
3198+
- - ``{home}``
3199+
- The current user's home directory.
31843200
- - ``{work_dir}`` / ``{toxworkdir}``
31853201
- The tox working directory (default: ``{tox_root}/.tox``).
31863202
- - ``{temp_dir}``

‎docs/tutorial/getting-started.rst‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,9 @@ pass after ``--``.
250250
Understanding the output
251251
**************************
252252

253-
On the first run, tox creates virtual environments and installs dependencies. Subsequent runs reuse existing
254-
environments unless dependencies change:
253+
On the first run, tox creates virtual environments and installs dependencies. The environments land in a ``.tox``
254+
directory next to your configuration file; add it to your version control ignore file, or relocate it entirely as shown
255+
in :ref:`howto_out_of_tree_envs`. Subsequent runs reuse existing environments unless dependencies change:
255256

256257
.. code-block:: bash
257258

‎src/tox/config/sets.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ def register_config(self) -> None:
299299
default=self._root,
300300
desc="the root directory (where the configuration file is found)",
301301
)
302+
self.add_constant("tox_root_name", "name of the root directory", self._root.name)
303+
self.add_constant("home", "the user's home directory", Path.home)
302304

303305
self.add_config(
304306
keys=["work_dir", "toxworkdir"],

‎tests/config/test_sets.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,18 @@ def test_config_work_dir(tox_project: ToxProjectCreator, work_dir: str) -> None:
193193
result = project.run("c", *(["--workdir", str(project.path / work_dir)] if work_dir else []))
194194
expected = project.path / work_dir if work_dir else Path("b")
195195
assert expected == result.state.conf.core["work_dir"]
196+
197+
198+
def test_config_work_dir_substitutions(tox_project: ToxProjectCreator, monkeypatch: pytest.MonkeyPatch) -> None:
199+
project = tox_project({"tox.toml": 'work_dir = "{home}/.local/state/tox/{tox_root_name}"'})
200+
home = project.path.parent / "home"
201+
for var in ("HOME", "USERPROFILE"): # Path.home() reads HOME on POSIX, USERPROFILE on Windows
202+
monkeypatch.setenv(var, str(home))
203+
result = project.run("c")
204+
assert result.state.conf.core["work_dir"] == home / ".local" / "state" / "tox" / project.path.name
205+
206+
207+
def test_config_work_dir_substitutions_cli_override(tox_project: ToxProjectCreator) -> None:
208+
project = tox_project({"tox.toml": 'work_dir = "{home}/.local/state/tox/{tox_root_name}"'})
209+
result = project.run("c", "--workdir", str(project.path / "wd"))
210+
assert result.state.conf.core["work_dir"] == project.path / "wd"

0 commit comments

Comments
 (0)