Skip to content

Commit 1037d6d

Browse files
committed
Ensure tox-created directories contain CACHEDIR.TAG
Fixes #3334
1 parent 1b167be commit 1037d6d

File tree

5 files changed

+49
-5
lines changed

5 files changed

+49
-5
lines changed

docs/changelog/3342.feature.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Tox now creates ``CACHEDIR.TAG`` files in work directories it creates,
2+
so that tools like ``tar`` can exclude them from e.g. backups where
3+
ephemeral directories are not desired.
4+
5+
Tag files are not created in directories Tox does not itself create.
6+
7+
- by :user:`akx`

src/tox/tox_env/api.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from tox.execute.request import ExecuteRequest
1818
from tox.tox_env.errors import Fail, Recreate, Skip
1919
from tox.tox_env.info import Info
20-
from tox.util.path import ensure_empty_dir
20+
from tox.util.path import ensure_cachedir_dir, ensure_empty_dir
2121

2222
if TYPE_CHECKING:
2323
from tox.config.cli.parser import Parsed
@@ -315,16 +315,27 @@ def _setup_with_env(self) -> None: # noqa: B027 # empty abstract base class
315315
def _done_with_setup(self) -> None: # noqa: B027 # empty abstract base class
316316
"""Called when setup is done."""
317317

318+
def _maybe_ensure_workdir(self) -> None:
319+
if not self.work_dir.is_dir():
320+
# Populate the workdir with a CACHEDIR.TAG file only if we would
321+
# be creating it now. If it already exists, do not touch it.
322+
ensure_cachedir_dir(self.work_dir)
323+
318324
def _handle_env_tmp_dir(self) -> None:
319325
"""Ensure exists and empty."""
320326
env_tmp_dir = self.env_tmp_dir
321327
if env_tmp_dir.exists() and next(env_tmp_dir.iterdir(), None) is not None:
322328
LOGGER.debug("clear env temp folder %s", env_tmp_dir)
323329
ensure_empty_dir(env_tmp_dir)
324-
env_tmp_dir.mkdir(parents=True, exist_ok=True)
330+
if env_tmp_dir.parent == self.work_dir:
331+
self._maybe_ensure_workdir()
332+
ensure_cachedir_dir(env_tmp_dir)
325333

326334
def _handle_core_tmp_dir(self) -> None:
327-
self.temp_dir.mkdir(parents=True, exist_ok=True)
335+
temp_dir = self.temp_dir
336+
if temp_dir.parent == self.work_dir:
337+
self._maybe_ensure_workdir()
338+
ensure_cachedir_dir(temp_dir)
328339

329340
def _clean(self, transitive: bool = False) -> None: # noqa: ARG002, FBT001, FBT002
330341
if self._run_state["clean"]: # pragma: no branch
@@ -333,6 +344,7 @@ def _clean(self, transitive: bool = False) -> None: # noqa: ARG002, FBT001, FBT
333344
if env_dir.exists():
334345
LOGGER.warning("remove tox env folder %s", env_dir)
335346
ensure_empty_dir(env_dir, except_filename="file.lock")
347+
ensure_cachedir_dir(env_dir)
336348
self._log_id = 0 # we deleted logs, so start over counter
337349
self.cache.reset()
338350
self._run_state.update({"setup": False, "clean": True})

src/tox/tox_env/package.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
from filelock import FileLock
1212

13+
from tox.util.path import ensure_cachedir_dir
14+
1315
from .api import ToxEnv, ToxEnvCreateArgs
1416

1517
if TYPE_CHECKING:
@@ -67,9 +69,12 @@ def __getattribute__(self, name: str) -> Any:
6769

6870
def register_config(self) -> None:
6971
super().register_config()
70-
file_lock_path: Path = self.env_dir / "file.lock"
72+
env_dir = self.env_dir
73+
if env_dir.parent == self.work_dir:
74+
self._maybe_ensure_workdir()
75+
ensure_cachedir_dir(env_dir)
76+
file_lock_path: Path = env_dir / "file.lock"
7177
self._file_lock = FileLock(file_lock_path)
72-
file_lock_path.parent.mkdir(parents=True, exist_ok=True)
7378
self.core.add_config(
7479
keys=["package_root", "setupdir"],
7580
of_type=Path,

src/tox/tox_env/python/api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from tox.tox_env.api import ToxEnv, ToxEnvCreateArgs
1515
from tox.tox_env.errors import Fail, Recreate, Skip
16+
from tox.util.path import ensure_cachedir_dir
1617

1718
if TYPE_CHECKING:
1819
from tox.config.main import Config
@@ -235,6 +236,7 @@ def ensure_python_env(self) -> None:
235236
with self.cache.compare(conf, Python.__name__) as (eq, old):
236237
if old is None: # does not exist -> create
237238
self.create_python_env()
239+
ensure_cachedir_dir(self.env_dir)
238240
elif eq is False: # pragma: no branch # exists but changed -> recreate
239241
raise Recreate(self._diff_msg(conf, old))
240242

src/tox/util/path.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
if TYPE_CHECKING:
77
from pathlib import Path
88

9+
CACHEDIR_TAG_CONTENT = b"""Signature: 8a477f597d28d172789f06886806bc55
10+
# This file is a cache directory tag created by the Tox automation project (https://tox.wiki/).
11+
# For information about cache directory tags, see:
12+
# http://www.brynosaurus.com/cachedir/
13+
"""
14+
915

1016
def ensure_empty_dir(path: Path, except_filename: str | None = None) -> None:
1117
if path.exists():
@@ -24,6 +30,18 @@ def ensure_empty_dir(path: Path, except_filename: str | None = None) -> None:
2430
path.mkdir(parents=True)
2531

2632

33+
def ensure_cachedir_dir(path: Path) -> None:
34+
"""
35+
Ensure that the given path is a directory, exists and
36+
contains a `CACHEDIR.TAG` file.
37+
"""
38+
path.mkdir(parents=True, exist_ok=True)
39+
cachetag = path / "CACHEDIR.TAG"
40+
if not cachetag.is_file():
41+
cachetag.write_bytes(CACHEDIR_TAG_CONTENT)
42+
43+
2744
__all__ = [
45+
"ensure_cachedir_dir",
2846
"ensure_empty_dir",
2947
]

0 commit comments

Comments
 (0)