|
| 1 | +Proposal: Unified Cache Manager |
| 2 | +================================ |
| 3 | + |
| 4 | +Problem |
| 5 | +------- |
| 6 | + |
| 7 | +Fromager's caching logic is scattered across multiple modules with no central |
| 8 | +coordination. Prebuilt wheels are checked separately from previously built |
| 9 | +wheels, remote wheel servers are not consulted during local builds, and there |
| 10 | +is no mechanism to share a base cache of common dependencies across |
| 11 | +hardware-specific bootstrap variants (e.g. CUDA, Gaudi). This leads to: |
| 12 | + |
| 13 | +- Redundant builds when wheels already exist in a remote cache or sibling |
| 14 | + variant's output. |
| 15 | +- Duplicated storage of common (non-accelerated) dependencies in every |
| 16 | + variant's output directory. |
| 17 | +- No visibility into cache hit rates, artifact integrity, or staleness. |
| 18 | +- No short-circuit path — even a full cache hit still downloads source and |
| 19 | + sets up a build environment before discovering the wheel exists. |
| 20 | + |
| 21 | +Proposed Solution |
| 22 | +----------------- |
| 23 | + |
| 24 | +Introduce a unified ``CacheManager`` class that centralizes all cache |
| 25 | +operations behind a layered lookup strategy: |
| 26 | + |
| 27 | +Architecture |
| 28 | +~~~~~~~~~~~~ |
| 29 | + |
| 30 | +.. code-block:: text |
| 31 | +
|
| 32 | + CacheManager |
| 33 | + ├── CacheCollection("default") |
| 34 | + │ ├── LocalDirectoryBackend(wheels-repo/downloads/) |
| 35 | + │ ├── LocalDirectoryBackend(wheels-repo/prebuilt/) |
| 36 | + │ └── RemotePEP503Backend(https://cache-server/simple/) [optional] |
| 37 | + └── CacheCollection("gaudi-ubi9") [variant, if active] |
| 38 | + └── LocalDirectoryBackend(wheels-repo-gaudi-ubi9/downloads/) |
| 39 | +
|
| 40 | +Key components: |
| 41 | + |
| 42 | +- ``WheelCacheKey`` — Content-addresses artifacts by canonicalized package |
| 43 | + name, version, and numeric build tag. |
| 44 | +- ``CacheBackend`` protocol — Abstract interface implemented by |
| 45 | + ``LocalDirectoryBackend`` (filesystem) and ``RemotePEP503Backend`` |
| 46 | + (PEP 503 simple repository). |
| 47 | +- ``CacheCollection`` — Named group of backends searched in priority order |
| 48 | + (e.g. "default" searches local then remote). |
| 49 | +- ``StoreRouter`` — Determines which collection owns a given package based on |
| 50 | + the variant's top-level requirements file and optional overrides. |
| 51 | +- ``CacheManager`` — Orchestrates hierarchical lookup across collections with |
| 52 | + fallback from variant to default. |
| 53 | + |
| 54 | +Lookup and store routing |
| 55 | +~~~~~~~~~~~~~~~~~~~~~~~~ |
| 56 | + |
| 57 | +On lookup, the manager searches the active variant collection first, then |
| 58 | +falls back to the default collection. Within each collection, backends are |
| 59 | +queried in registration order (local before remote). |
| 60 | + |
| 61 | +On store, the ``StoreRouter`` routes packages explicitly listed in the |
| 62 | +variant's ``requirements.txt`` to the variant collection directory. All |
| 63 | +unlisted transitive dependencies are stored in the default collection. This |
| 64 | +prevents duplication of common packages across variant outputs. |
| 65 | + |
| 66 | +Short-circuit optimization |
| 67 | +~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 68 | + |
| 69 | +When a cache hit is found during the ``PREPARE_SOURCE`` phase, the |
| 70 | +bootstrapper skips source download, build environment creation, and build |
| 71 | +dependency resolution entirely — proceeding directly to install dependency |
| 72 | +extraction from the cached wheel's metadata. This eliminates the most |
| 73 | +expensive steps for packages that do not need rebuilding. |
| 74 | + |
| 75 | +Remote cache with integrity |
| 76 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 77 | + |
| 78 | +``RemotePEP503Backend`` lazily fetches per-project package indices on first |
| 79 | +access and maintains a session-scoped in-memory index. Downloads are verified |
| 80 | +with streaming SHA256 checksums, use atomic temporary files, and reject |
| 81 | +plaintext HTTP URLs that lack integrity hashes (unless ``--cache-allow-insecure`` |
| 82 | +is passed for development workflows). Filenames are sanitized to prevent path |
| 83 | +traversal attacks. |
| 84 | + |
| 85 | +Observability |
| 86 | +~~~~~~~~~~~~~ |
| 87 | + |
| 88 | +A new ``fromager cache`` CLI command group provides: |
| 89 | + |
| 90 | +- ``cache list`` — Show all cached artifacts with versions and build tags. |
| 91 | +- ``cache stats`` — Display hit/miss counts and rates from the last run. |
| 92 | +- ``cache verify`` — Validate integrity of local cache contents. |
| 93 | +- ``cache invalidate`` — Remove specific artifacts by name/version/tag. |
| 94 | +- ``cache gc`` — Garbage-collect old build tags, keeping only the N most |
| 95 | + recent per package+version. |
| 96 | + |
| 97 | +Scope |
| 98 | +----- |
| 99 | + |
| 100 | +The new cache subsystem is opt-in via ``--use-cache-manager`` on the |
| 101 | +``bootstrap`` command. When disabled, existing behavior is preserved |
| 102 | +unchanged. |
| 103 | + |
| 104 | +Files added or modified: |
| 105 | + |
| 106 | +- ``src/fromager/cache.py`` — New module with all cache classes. |
| 107 | +- ``src/fromager/commands/cache_cmd.py`` — CLI commands and factory function. |
| 108 | +- ``src/fromager/commands/bootstrap.py`` — Wiring ``--use-cache-manager`` and |
| 109 | + ``--cache-allow-insecure`` options. |
| 110 | +- ``src/fromager/bootstrapper.py`` — Short-circuit path and store routing for |
| 111 | + built wheels. |
| 112 | +- ``src/fromager/context.py`` — ``cache`` property on ``WorkContext``. |
| 113 | +- ``src/fromager/requirements_file.py`` — ``CACHED`` source type. |
| 114 | +- ``tests/test_cache.py`` — Unit tests for the cache subsystem. |
| 115 | +- ``tests/test_bootstrapper_iterative.py`` — Integration tests for cache |
| 116 | + dispatch and store routing. |
| 117 | + |
| 118 | +Benefits |
| 119 | +-------- |
| 120 | + |
| 121 | +- Eliminates redundant builds when wheels exist in a remote or sibling cache. |
| 122 | +- Reduces bootstrap time by short-circuiting cached packages (skips source |
| 123 | + download, build env setup, and build dep resolution). |
| 124 | +- Prevents duplication of common dependencies across variant outputs via |
| 125 | + hierarchical store routing. |
| 126 | +- Provides cache observability through dedicated CLI commands. |
| 127 | +- Enforces artifact integrity with SHA256 verification and atomic writes. |
| 128 | + |
| 129 | +Security considerations |
| 130 | +----------------------- |
| 131 | + |
| 132 | +- Remote downloads are verified against SHA256 hashes declared in PEP 503 |
| 133 | + index pages. Mismatched files are deleted and raise an error. |
| 134 | +- Plaintext HTTP URLs without SHA256 hashes are rejected by default. |
| 135 | + The ``--cache-allow-insecure`` flag explicitly opts in for internal or |
| 136 | + development registries. |
| 137 | +- Filenames from remote indices are sanitized to prevent directory traversal. |
| 138 | +- Local cache writes use atomic ``tempfile`` + ``rename`` to prevent readers |
| 139 | + from observing partial files. |
| 140 | +- ``scan()`` skips symlinked wheels to prevent ``invalidate``/``gc`` from |
| 141 | + deleting files outside the cache root. |
| 142 | +- Fetch failures (network errors, hash mismatches) are caught and treated as |
| 143 | + cache misses, falling through to the next backend or a fresh build. |
| 144 | + |
| 145 | +Verification |
| 146 | +------------ |
| 147 | + |
| 148 | +- All existing unit and e2e tests pass unchanged (legacy path preserved). |
| 149 | +- 169 new tests cover cache components, short-circuit logic, store routing, |
| 150 | + and error handling. |
| 151 | +- Validated on walkerpass4 with both local and remote caches: |
| 152 | + pre-populated local cache achieves full hit rate; remote PEP 503 server |
| 153 | + correctly populates local cache and only uncached packages trigger builds. |
| 154 | +- Linting (``ruff``), type checking (``mypy``), and formatting all pass. |
| 155 | + |
| 156 | +Future work |
| 157 | +----------- |
| 158 | + |
| 159 | +- Integration with ``build_tag_hook`` (issue #1059) for platform-suffixed |
| 160 | + cache keys. |
| 161 | +- Automatic detection of accelerated packages via ELF inspection or wheel |
| 162 | + tag analysis. |
| 163 | +- Promotion of ``--use-cache-manager`` to default behavior once proven in |
| 164 | + production. |
0 commit comments