Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure tox-created directories contain CACHEDIR.TAG #3342

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/changelog/3342.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Tox now creates ``CACHEDIR.TAG`` files in work directories it creates,
so that tools like ``tar`` can exclude them from e.g. backups where
ephemeral directories are not desired.

Tag files are not created in directories Tox does not itself create.

- by :user:`akx`
40 changes: 31 additions & 9 deletions src/tox/tox_env/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from tox.execute.request import ExecuteRequest
from tox.tox_env.errors import Fail, Recreate, Skip
from tox.tox_env.info import Info
from tox.util.path import ensure_empty_dir
from tox.util.path import ensure_cachedir_dir, ensure_empty_dir

if TYPE_CHECKING:
from tox.config.cli.parser import Parsed
Expand All @@ -42,7 +42,7 @@ class ToxEnvCreateArgs(NamedTuple):
log_handler: ToxHandler


class ToxEnv(ABC):
class ToxEnv(ABC): # noqa: PLR0904
"""A tox environment."""

def __init__(self, create_args: ToxEnvCreateArgs) -> None:
Expand Down Expand Up @@ -111,19 +111,19 @@ def register_config(self) -> None:
self.conf.add_config(
keys=["env_dir", "envdir"],
of_type=Path,
default=lambda conf, name: cast(Path, conf.core["work_dir"]) / self.name, # noqa: ARG005
default=lambda conf, name: self.work_dir / self.name, # noqa: ARG005
desc="directory assigned to the tox environment",
)
self.conf.add_config(
keys=["env_tmp_dir", "envtmpdir"],
of_type=Path,
default=lambda conf, name: cast(Path, conf.core["work_dir"]) / self.name / "tmp", # noqa: ARG005
default=lambda conf, name: self.work_dir / self.name / "tmp", # noqa: ARG005
desc="a folder that is always reset at the start of the run",
)
self.conf.add_config(
keys=["env_log_dir", "envlogdir"],
of_type=Path,
default=lambda conf, name: cast(Path, conf.core["work_dir"]) / self.name / "log", # noqa: ARG005
default=lambda conf, name: self.work_dir / self.name / "log", # noqa: ARG005
desc="a folder for logging where tox will put logs of tool invocation",
)
self.executor.register_conf(self)
Expand Down Expand Up @@ -194,6 +194,16 @@ def env_log_dir(self) -> Path:
""":return: the tox environments log folder"""
return cast(Path, self.conf["env_log_dir"])

@property
def work_dir(self) -> Path:
""":return: the tox work dir folder"""
return cast(Path, self.core["work_dir"])

@property
def temp_dir(self) -> Path:
""":return: the tox work dir folder"""
return cast(Path, self.core["temp_dir"])

@property
def name(self) -> str:
return cast(str, self.conf["env_name"])
Expand Down Expand Up @@ -305,16 +315,27 @@ def _setup_with_env(self) -> None: # noqa: B027 # empty abstract base class
def _done_with_setup(self) -> None: # noqa: B027 # empty abstract base class
"""Called when setup is done."""

def _maybe_ensure_workdir(self) -> None:
if not self.work_dir.is_dir():
# Populate the workdir with a CACHEDIR.TAG file only if we would
# be creating it now. If it already exists, do not touch it.
ensure_cachedir_dir(self.work_dir)

def _handle_env_tmp_dir(self) -> None:
"""Ensure exists and empty."""
env_tmp_dir = self.env_tmp_dir
if env_tmp_dir.exists() and next(env_tmp_dir.iterdir(), None) is not None:
LOGGER.debug("clear env temp folder %s", env_tmp_dir)
ensure_empty_dir(env_tmp_dir)
env_tmp_dir.mkdir(parents=True, exist_ok=True)
if env_tmp_dir.parent == self.work_dir:
self._maybe_ensure_workdir()
ensure_cachedir_dir(env_tmp_dir)

def _handle_core_tmp_dir(self) -> None:
self.core["temp_dir"].mkdir(parents=True, exist_ok=True)
temp_dir = self.temp_dir
if temp_dir.parent == self.work_dir:
self._maybe_ensure_workdir()
ensure_cachedir_dir(temp_dir)

def _clean(self, transitive: bool = False) -> None: # noqa: ARG002, FBT001, FBT002
if self._run_state["clean"]: # pragma: no branch
Expand All @@ -323,6 +344,7 @@ def _clean(self, transitive: bool = False) -> None: # noqa: ARG002, FBT001, FBT
if env_dir.exists():
LOGGER.warning("remove tox env folder %s", env_dir)
ensure_empty_dir(env_dir, except_filename="file.lock")
ensure_cachedir_dir(env_dir)
self._log_id = 0 # we deleted logs, so start over counter
self.cache.reset()
self._run_state.update({"setup": False, "clean": True})
Expand All @@ -342,8 +364,8 @@ def environment_variables(self) -> dict[str, str]:
for key in set_env:
result[key] = set_env.load(key)
result["TOX_ENV_NAME"] = self.name
result["TOX_WORK_DIR"] = str(self.core["work_dir"])
result["TOX_ENV_DIR"] = str(self.conf["env_dir"])
result["TOX_WORK_DIR"] = str(self.work_dir)
result["TOX_ENV_DIR"] = str(self.env_dir)
return result

@staticmethod
Expand Down
9 changes: 7 additions & 2 deletions src/tox/tox_env/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

from filelock import FileLock

from tox.util.path import ensure_cachedir_dir

from .api import ToxEnv, ToxEnvCreateArgs

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

def register_config(self) -> None:
super().register_config()
file_lock_path: Path = self.conf["env_dir"] / "file.lock"
env_dir = self.env_dir
if env_dir.parent == self.work_dir:
self._maybe_ensure_workdir()
ensure_cachedir_dir(env_dir)
file_lock_path: Path = env_dir / "file.lock"
self._file_lock = FileLock(file_lock_path)
file_lock_path.parent.mkdir(parents=True, exist_ok=True)
self.core.add_config(
keys=["package_root", "setupdir"],
of_type=Path,
Expand Down
2 changes: 2 additions & 0 deletions src/tox/tox_env/python/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from tox.tox_env.api import ToxEnv, ToxEnvCreateArgs
from tox.tox_env.errors import Fail, Recreate, Skip
from tox.util.path import ensure_cachedir_dir

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

Expand Down
18 changes: 18 additions & 0 deletions src/tox/util/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
if TYPE_CHECKING:
from pathlib import Path

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


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


def ensure_cachedir_dir(path: Path) -> None:
"""
Ensure that the given path is a directory, exists and
contains a `CACHEDIR.TAG` file.
"""
path.mkdir(parents=True, exist_ok=True)
cachetag = path / "CACHEDIR.TAG"
if not cachetag.is_file():
cachetag.write_bytes(CACHEDIR_TAG_CONTENT)


__all__ = [
"ensure_cachedir_dir",
"ensure_empty_dir",
]
Loading