diff --git a/docs/usage/caching.md b/docs/usage/caching.md index cd41f098..ac305930 100644 --- a/docs/usage/caching.md +++ b/docs/usage/caching.md @@ -32,7 +32,7 @@ When all of these match a previous cache entry, the cached results are printed d ## Cache storage -The computation cache exists within the `.tach` directory in your project root. The directory is managed by Tach, and your cached results are stored on-disk on each machine where tasks are run. +The computation cache exists within the directory defined by the `TACH_CACHE_DIR` environment variable (default is `.tach`). The directory is managed by Tach, and your cached results are stored on-disk on each machine where tasks are run. We are currently working on a _remote cache_ backend, which will allow multiple developers and CI environments to share a centralized cache to maximize the hit rate. If you are interested in this functionality, reach out through a [GitHub issue](https://github.com/gauge-sh/tach/issues) or via email: [evan@gauge.sh](mailto://evan@gauge.sh); [caelean@gauge.sh](mailto://caelean@gauge.sh)! diff --git a/python/tach/cache/access.py b/python/tach/cache/access.py index c70ccc3b..83181d75 100644 --- a/python/tach/cache/access.py +++ b/python/tach/cache/access.py @@ -3,23 +3,23 @@ import uuid from typing import TYPE_CHECKING -from tach.cache.setup import resolve_dot_tach +from tach.cache.setup import get_cache_path, resolve_cache_path if TYPE_CHECKING: from pathlib import Path def get_uid(project_root: Path) -> uuid.UUID | None: - info_path = project_root / ".tach" / "tach.info" + info_path = get_cache_path(project_root) / "tach.info" if not info_path.exists(): - resolve_dot_tach(project_root) + resolve_cache_path(project_root) contents = info_path.read_text().strip() uid = uuid.UUID(contents) return uid def get_latest_version(project_root: Path) -> str | None: - latest_version_path = project_root / ".tach" / ".latest-version" + latest_version_path = get_cache_path(project_root) / ".latest-version" if not latest_version_path.exists(): return version = latest_version_path.read_text().strip() diff --git a/python/tach/cache/setup.py b/python/tach/cache/setup.py index 48d29a9f..efefbab2 100644 --- a/python/tach/cache/setup.py +++ b/python/tach/cache/setup.py @@ -1,12 +1,27 @@ from __future__ import annotations import uuid +from os import getenv, name as os_name from pathlib import Path +from re import match, compile from tach import __version__ -def resolve_dot_tach(project_root: Path) -> Path | None: +def get_cache_path(project_root: Path) -> Path: + env_value = getenv("TACH_CACHE_DIR") + + if env_value is None: + return project_root / ".tach" + + path_is_not_absolute_re = compile(r'^(?![a-zA-Z]:[\\/]|\\\\|/|~[/\\])') if os_name == 'nt' else compile(r'^[^/~]') + if match(path_is_not_absolute_re, env_value): + return project_root / env_value + + return Path(env_value) + + +def resolve_cache_path(project_root: Path) -> Path | None: def _create(path: Path, is_file: bool = False, file_content: str = "") -> None: if not path.exists(): if is_file: @@ -14,11 +29,11 @@ def _create(path: Path, is_file: bool = False, file_content: str = "") -> None: else: path.mkdir() - # Create .tach - tach_path = project_root / ".tach" - _create(tach_path) + # Create cache dir + cache_dir = get_cache_path(project_root) + _create(cache_dir) # Create info - info_path = tach_path / "tach.info" + info_path = cache_dir / "tach.info" _create(info_path, is_file=True, file_content=str(uuid.uuid4())) # Create .gitignore gitignore_content = """ @@ -27,9 +42,9 @@ def _create(path: Path, is_file: bool = False, file_content: str = "") -> None: # gitignore all content, including this .gitignore * """ - gitignore_path = tach_path / ".gitignore" + gitignore_path = cache_dir / ".gitignore" _create(gitignore_path, is_file=True, file_content=gitignore_content) # Create version - version_path = tach_path / ".latest-version" + version_path = cache_dir / ".latest-version" _create(version_path, is_file=True, file_content=__version__) - return Path(tach_path) + return Path(cache_dir) diff --git a/python/tach/logging/worker.py b/python/tach/logging/worker.py index b6dd4bd4..f845eb6e 100644 --- a/python/tach/logging/worker.py +++ b/python/tach/logging/worker.py @@ -10,6 +10,8 @@ from typing import Any from urllib import error, parse, request +from tach.cache.setup import get_cache_path + LOGGING_URL = "https://vmilasesnyvpalekembc.supabase.co" PUBLIC_ANON_CLIENT_KEY = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZtaWxhc2Vzbnl2cGFsZWtlbWJjIiwicm9" @@ -29,7 +31,7 @@ def update_latest_version(project_root: Path) -> None: return except (error.URLError, KeyError): return - (project_root / ".tach" / ".latest-version").write_text(latest_version) + (get_cache_path(project_root) / ".latest-version").write_text(latest_version) def log_request(url: str, data: dict[str, Any]) -> None: @@ -140,7 +142,7 @@ def create_managed_subprocess(project_root: Path, timeout: int = 5) -> Path: Launches the worker as a completely separate process using subprocess.Popen. Returns the path to the temporary file for message passing. """ - tach_dir = project_root / ".tach" + tach_dir = get_cache_path(project_root) tach_dir.mkdir(parents=True, exist_ok=True) # Cannot use FIFO because it is not supported on Windows diff --git a/python/tests/test_cache.py b/python/tests/test_cache.py index 0dab3f09..6a2087c9 100644 --- a/python/tests/test_cache.py +++ b/python/tests/test_cache.py @@ -4,29 +4,29 @@ from unittest.mock import patch from tach.cache.access import get_latest_version, get_uid -from tach.cache.setup import resolve_dot_tach +from tach.cache.setup import get_cache_path, resolve_cache_path -def test_resolve_dot_tach(tmp_path): +def test_resolve_cache_path(tmp_path): project_path = tmp_path / "project" project_path.mkdir(parents=True, exist_ok=True) version = "1.0.0" with patch("tach.cache.setup.__version__", version): - result = resolve_dot_tach(project_path) + result = resolve_cache_path(project_path) - tach_path = project_path / ".tach" - assert tach_path.exists() - assert (tach_path / "tach.info").exists() - assert (tach_path / "tach.info").read_text().strip() != "" - assert (tach_path / ".gitignore").exists() - assert (tach_path / ".latest-version").exists() - assert (tach_path / ".latest-version").read_text().strip() == version - assert result == tach_path + cache_path = get_cache_path(project_path) + assert cache_path.exists() + assert (cache_path / "tach.info").exists() + assert (cache_path / "tach.info").read_text().strip() != "" + assert (cache_path / ".gitignore").exists() + assert (cache_path / ".latest-version").exists() + assert (cache_path / ".latest-version").read_text().strip() == version + assert result == cache_path -@patch("tach.cache.access.resolve_dot_tach") -def test_get_uid(mock_resolve_dot_tach, tmp_path): +@patch("tach.cache.access.resolve_cache_path") +def test_get_uid(mock_resolve_cache_path, tmp_path): project_path = tmp_path / "project" tach_info_path = project_path / ".tach" / "tach.info" tach_info_path.parent.mkdir(parents=True, exist_ok=True) @@ -39,7 +39,8 @@ def test_get_uid(mock_resolve_dot_tach, tmp_path): def test_get_latest_version(tmp_path): project_path = tmp_path / "project" - latest_version_path = project_path / ".tach" / ".latest-version" + cache_path = get_cache_path(project_path) + latest_version_path = cache_path / ".latest-version" latest_version_path.parent.mkdir(parents=True, exist_ok=True) version = "1.0.0" latest_version_path.write_text(version) diff --git a/src/cache.rs b/src/cache.rs index d70406cf..93bcce79 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -35,7 +35,8 @@ impl FromIterator for CacheKey { } } -static CACHE_DIR: &str = ".tach"; +static ENV_KEY_CACHE_DIR: &str = "TACH_CACHE_DIR"; +static DEFAULT_CACHE_DIR: &str = ".tach"; pub type ComputationCacheValue = (Vec<(u8, String)>, u8); @@ -47,7 +48,12 @@ fn build_computation_cache>( .set_disk_directory( project_root .as_ref() - .join(CACHE_DIR) + .join( + match env::var(ENV_KEY_CACHE_DIR) { + Ok(env_value) => env_value, + Err() => DEFAULT_CACHE_DIR, + } + ) .join("computation-cache"), ) .build()?,