Skip to content

Commit be19526

Browse files
feat: Write CACHEDIR.TAG file (#2803)
Co-authored-by: Bernát Gábor <[email protected]> Co-authored-by: Neil Ramsay <[email protected]>
1 parent b3e2b6f commit be19526

File tree

4 files changed

+89
-0
lines changed

4 files changed

+89
-0
lines changed

docs/changelog/2803.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
write CACHEDIR.TAG file on creation - by "user:`neilramsay`

src/virtualenv/create/creator.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
import os
66
import sys
7+
import textwrap
78
from abc import ABC, abstractmethod
89
from argparse import ArgumentTypeError
910
from ast import literal_eval
@@ -156,10 +157,31 @@ def run(self):
156157
logging.debug("delete %s", self.dest)
157158
safe_delete(self.dest)
158159
self.create()
160+
self.add_cachedir_tag()
159161
self.set_pyenv_cfg()
160162
if not self.no_vcs_ignore:
161163
self.setup_ignore_vcs()
162164

165+
def add_cachedir_tag(self):
166+
"""
167+
Add a Cache Directory Tag file "CACHEDIR.TAG".
168+
169+
The CACHEDIR.TAG file is used by various tools to mark
170+
a directory as cache, so that it can be handled differently.
171+
Some backup tools look for this file to exclude the directory.
172+
173+
See https://bford.info/cachedir/ for more details.
174+
"""
175+
cachedir_tag_file = self.dest / "CACHEDIR.TAG"
176+
if not cachedir_tag_file.exists():
177+
cachedir_tag_text = textwrap.dedent("""
178+
Signature: 8a477f597d28d172789f06886806bc55
179+
# This file is a cache directory tag created by Python virtualenv.
180+
# For information about cache directory tags, see:
181+
# http://www.brynosaurus.com/cachedir/
182+
""").strip()
183+
cachedir_tag_file.write_text(cachedir_tag_text, encoding="utf-8")
184+
163185
def set_pyenv_cfg(self):
164186
self.pyenv_cfg.content = OrderedDict()
165187
self.pyenv_cfg["home"] = os.path.dirname(os.path.abspath(self.interpreter.system_executable))
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import annotations
2+
3+
import shutil
4+
import subprocess
5+
import sys
6+
7+
import pytest
8+
9+
from virtualenv import cli_run
10+
11+
12+
@pytest.fixture(scope="session")
13+
def tar_test_env(tmp_path_factory):
14+
base_path = tmp_path_factory.mktemp("tar-cachedir-test")
15+
cli_run(["--activators", "", "--without-pip", str(base_path / ".venv")])
16+
yield base_path
17+
shutil.rmtree(str(base_path))
18+
19+
20+
def compatible_is_tar_present() -> bool:
21+
try:
22+
tar_result = subprocess.run(args=["tar", "--help"], capture_output=True, encoding="utf-8")
23+
return tar_result.stdout.find("--exclude-caches") > -1
24+
except FileNotFoundError:
25+
return False
26+
27+
28+
@pytest.mark.skipif(sys.platform == "win32", reason="Windows does not have tar")
29+
@pytest.mark.skipif(not compatible_is_tar_present(), reason="Compatible tar is not installed")
30+
def test_cachedir_tag_ignored_by_tag(tar_test_env): # noqa: ARG001
31+
tar_result = subprocess.run(
32+
args=["tar", "--create", "--file", "/dev/null", "--exclude-caches", "--verbose", ".venv"],
33+
capture_output=True,
34+
encoding="utf-8",
35+
)
36+
assert tar_result.stdout == ".venv/\n.venv/CACHEDIR.TAG\n"
37+
assert tar_result.stderr == "tar: .venv/: contains a cache directory tag CACHEDIR.TAG; contents not dumped\n"

tests/unit/create/test_creator.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import stat
1212
import subprocess
1313
import sys
14+
import textwrap
1415
import zipfile
1516
from collections import OrderedDict
1617
from itertools import product
@@ -223,6 +224,34 @@ def list_to_str(iterable):
223224
assert git_ignore.splitlines() == [comment, "*"]
224225

225226

227+
def test_create_cachedir_tag(tmp_path):
228+
cachedir_tag_file = tmp_path / "CACHEDIR.TAG"
229+
cli_run([str(tmp_path), "--without-pip", "--activators", ""])
230+
assert (
231+
cachedir_tag_file.read_text(encoding="utf-8")
232+
== textwrap.dedent("""
233+
Signature: 8a477f597d28d172789f06886806bc55
234+
# This file is a cache directory tag created by Python virtualenv.
235+
# For information about cache directory tags, see:
236+
# http://www.brynosaurus.com/cachedir/
237+
""").strip()
238+
)
239+
240+
241+
def test_create_cachedir_tag_exists(tmp_path):
242+
cachedir_tag_file = tmp_path / "CACHEDIR.TAG"
243+
cachedir_tag_file.write_text("magic", encoding="utf-8")
244+
cli_run([str(tmp_path), "--without-pip", "--activators", ""])
245+
assert cachedir_tag_file.read_text(encoding="utf-8") == "magic"
246+
247+
248+
def test_create_cachedir_tag_exists_override(tmp_path):
249+
cachedir_tag_file = tmp_path / "CACHEDIR.TAG"
250+
cachedir_tag_file.write_text("magic", encoding="utf-8")
251+
cli_run([str(tmp_path), "--without-pip", "--activators", ""])
252+
assert cachedir_tag_file.read_text(encoding="utf-8") == "magic"
253+
254+
226255
def test_create_vcs_ignore_exists(tmp_path):
227256
git_ignore = tmp_path / ".gitignore"
228257
git_ignore.write_text("magic", encoding="utf-8")

0 commit comments

Comments
 (0)