diff --git a/poetry.lock b/poetry.lock index 1aff3a7216d..c5a34c4e06d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -877,6 +877,17 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "xattr" +version = "0.9.7" +description = "Python wrapper for extended filesystem attributes" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +cffi = ">=1.0.0" + [[package]] name = "zipp" version = "3.8.0" @@ -1479,6 +1490,12 @@ webencodings = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] +xattr = [ + {file = "xattr-0.9.7-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:1b2cd125150aa9bbfb02929627101b3303920a68487e9c865ddd170188ddd796"}, + {file = "xattr-0.9.7-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:e2c72a3a501bac715489180ca2b646e48a1ca3a794c1103dd6f0f987d43f570c"}, + {file = "xattr-0.9.7-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1e11ba8ab86dfe74419704c53722ea9b5915833db07416e7c10db5dfb02218bb"}, + {file = "xattr-0.9.7.tar.gz", hash = "sha256:b0bbca828e04ef2d484a6522ae7b3a7ccad5e43fa1c6f54d78e24bb870f49d44"}, +] zipp = [ {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, diff --git a/pyproject.toml b/pyproject.toml index e8f7edfb0e0..02bfc439bf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,7 @@ shellingham = "^1.1" tomlkit = ">=0.7.0,<1.0.0" # exclude 20.4.5 - 20.4.6 due to https://github.com/pypa/pip/issues/9953 virtualenv = "(>=20.4.3,<20.4.5 || >=20.4.7)" +xattr = { version = "^0.9.7", markers = "sys_platform == 'darwin'" } urllib3 = "^1.26.0" dulwich = "^0.20.35" @@ -157,6 +158,7 @@ module = [ 'requests_toolbelt.*', 'shellingham.*', 'virtualenv.*', + 'xattr.*', ] ignore_missing_imports = true diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 27d920efc68..b671c37253c 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -6,6 +6,7 @@ import json import os import platform +import plistlib import re import subprocess import sys @@ -1087,7 +1088,20 @@ def build_venv( args.append(str(path)) - return virtualenv.cli_run(args) + cli_result = virtualenv.cli_run(args) + + # Exclude the venv folder from from macOS Time Machine backups + # TODO: Add backup-ignore markers for other platforms too + if sys.platform == "darwin": + import xattr + + xattr.setxattr( + str(path), + "com.apple.metadata:com_apple_backup_excludeItem", + plistlib.dumps("com.apple.backupd", fmt=plistlib.FMT_BINARY), + ) + + return cli_result @classmethod def remove_venv(cls, path: Path | str) -> None: diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 4ffdb89212b..ecb0fdc6f80 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -96,6 +96,27 @@ def test_virtualenvs_with_spaces_in_their_path_work_as_expected( assert venv.run("python", "-V", shell=True).startswith("Python") +@pytest.mark.skipif(sys.platform != "darwin", reason="requires darwin") +def test_venv_backup_exclusion(tmp_dir: str, manager: EnvManager): + import xattr + + venv_path = Path(tmp_dir) / "Virtual Env" + + manager.build_venv(str(venv_path)) + + value = ( + b"bplist00_\x10\x11com.apple.backupd" + b"\x08\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00" + b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c" + ) + assert ( + xattr.getxattr( + str(venv_path), "com.apple.metadata:com_apple_backup_excludeItem" + ) + == value + ) + + def test_env_commands_with_spaces_in_their_arg_work_as_expected( tmp_dir: str, manager: EnvManager ):