Skip to content

Commit 6d2eb15

Browse files
merlinz01eifinger
andauthored
Cache python installs (#621)
This pull request introduces support for caching Python installs in the GitHub Action, allowing users to cache not only dependencies but also the Python interpreter itself. This works by setting the `UV_PYTHON_INSTALL_DIR` to a subdirectory of the dependency cache path so that Python installs are directed there. Fixes #135 --------- Co-authored-by: Kevin Stillhammer <[email protected]>
1 parent 3495667 commit 6d2eb15

8 files changed

Lines changed: 197 additions & 23 deletions

File tree

.github/workflows/test.yml

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
with:
2626
persist-credentials: false
2727
- name: Actionlint
28-
uses: eifinger/actionlint-action@23c85443d840cd73bbecb9cddfc933cc21649a38 # v1.9.1
28+
uses: eifinger/actionlint-action@23c85443d840cd73bbecb9cddfc933cc21649a38 # v1.9.1
2929
- name: Run zizmor
3030
uses: zizmorcore/zizmor-action@e673c3917a1aef3c65c972347ed84ccd013ecda4 # v0.2.0
3131
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
@@ -269,13 +269,7 @@ jobs:
269269
runs-on: ${{ matrix.os }}
270270
strategy:
271271
matrix:
272-
os:
273-
[
274-
ubuntu-latest,
275-
macos-latest,
276-
macos-14,
277-
windows-latest,
278-
]
272+
os: [ubuntu-latest, macos-latest, macos-14, windows-latest]
279273
steps:
280274
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
281275
with:
@@ -334,7 +328,7 @@ jobs:
334328
runs-on: ${{ matrix.os }}
335329
strategy:
336330
matrix:
337-
os: [ ubuntu-latest, macos-latest, windows-latest ]
331+
os: [ubuntu-latest, macos-latest, windows-latest]
338332
steps:
339333
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
340334
with:
@@ -371,8 +365,8 @@ jobs:
371365
runs-on: ${{ matrix.os }}
372366
strategy:
373367
matrix:
374-
enable-cache: [ "true", "false", "auto" ]
375-
os: [ "ubuntu-latest", "selfhosted-ubuntu-arm64", "windows-latest" ]
368+
enable-cache: ["true", "false", "auto"]
369+
os: ["ubuntu-latest", "selfhosted-ubuntu-arm64", "windows-latest"]
376370
steps:
377371
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
378372
with:
@@ -389,8 +383,8 @@ jobs:
389383
runs-on: ${{ matrix.os }}
390384
strategy:
391385
matrix:
392-
enable-cache: [ "true", "false", "auto" ]
393-
os: [ "ubuntu-latest", "selfhosted-ubuntu-arm64", "windows-latest" ]
386+
enable-cache: ["true", "false", "auto"]
387+
os: ["ubuntu-latest", "selfhosted-ubuntu-arm64", "windows-latest"]
394388
needs: test-setup-cache
395389
steps:
396390
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -836,6 +830,68 @@ jobs:
836830
exit 1
837831
fi
838832
833+
test-cache-python-installs:
834+
runs-on: ubuntu-latest
835+
steps:
836+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
837+
with:
838+
persist-credentials: false
839+
- name: Verify Python install dir is not populated
840+
run: |
841+
if [ -d ~/.local/share/uv/python ]; then
842+
echo "Python install dir should not exist"
843+
exit 1
844+
fi
845+
- name: Setup uv with cache
846+
uses: ./
847+
with:
848+
enable-cache: true
849+
cache-python: true
850+
cache-suffix: ${{ github.run_id }}-${{ github.run_attempt }}-test-cache-python-installs
851+
- run: uv sync --managed-python
852+
working-directory: __tests__/fixtures/uv-project
853+
- name: Verify Python install dir exists
854+
run: |
855+
if [ ! -d ~/.local/share/uv/python ]; then
856+
echo "Python install dir should exist"
857+
exit 1
858+
fi
859+
test-restore-python-installs:
860+
runs-on: ubuntu-latest
861+
needs: test-cache-python-installs
862+
steps:
863+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
864+
with:
865+
persist-credentials: false
866+
- name: Verify Python install dir does not exist
867+
run: |
868+
if [ -d ~/.local/share/uv/python ]; then
869+
echo "Python install dir should not exist"
870+
exit 1
871+
fi
872+
- name: Restore with cache
873+
id: restore
874+
uses: ./
875+
with:
876+
enable-cache: true
877+
cache-python: true
878+
cache-suffix: ${{ github.run_id }}-${{ github.run_attempt }}-test-cache-python-installs
879+
- name: Verify Python install dir exists
880+
run: |
881+
if [ ! -d ~/.local/share/uv/python ]; then
882+
echo "Python install dir should exist"
883+
exit 1
884+
fi
885+
- name: Cache was hit
886+
run: |
887+
if [ "$CACHE_HIT" != "true" ]; then
888+
exit 1
889+
fi
890+
env:
891+
CACHE_HIT: ${{ steps.restore.outputs.cache-hit }}
892+
- run: uv sync --managed-python
893+
working-directory: __tests__/fixtures/uv-project
894+
839895
all-tests-passed:
840896
runs-on: ubuntu-latest
841897
needs:
@@ -878,6 +934,8 @@ jobs:
878934
- test-relative-path
879935
- test-cache-prune-force
880936
- test-cache-dir-from-file
937+
- test-cache-python-installs
938+
- test-restore-python-installs
881939
if: always()
882940
steps:
883941
- name: All tests passed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Set up your GitHub Actions workflow with a specific version of [uv](https://docs
2626
- [Save cache](#save-cache)
2727
- [Local cache path](#local-cache-path)
2828
- [Disable cache pruning](#disable-cache-pruning)
29+
- [Cache Python installs](#cache-python-installs)
2930
- [Ignore nothing to cache](#ignore-nothing-to-cache)
3031
- [GitHub authentication token](#github-authentication-token)
3132
- [UV_TOOL_DIR](#uv_tool_dir)
@@ -355,6 +356,20 @@ input.
355356
prune-cache: false
356357
```
357358

359+
### Cache Python installs
360+
361+
By default, the Python install dir (`uv python dir` / `UV_PYTHON_INSTALL_DIR`) is not cached,
362+
for the same reason that the dependency cache is pruned.
363+
If you want to cache Python installs along with your dependencies, set the `cache-python` input to `true`.
364+
365+
```yaml
366+
- name: Cache Python installs
367+
uses: astral-sh/setup-uv@v6
368+
with:
369+
enable-cache: true
370+
cache-python: true
371+
```
372+
358373
### Ignore nothing to cache
359374

360375
By default, the action will fail if caching is enabled but there is nothing to upload (the uv cache directory does not exist).

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ inputs:
5656
prune-cache:
5757
description: "Prune cache before saving."
5858
default: "true"
59+
cache-python:
60+
description: "Upload managed Python installations to the Github Actions cache."
61+
default: "false"
5962
ignore-nothing-to-cache:
6063
description: "Ignore when nothing is found to cache."
6164
default: "false"

dist/save-cache/index.js

Lines changed: 36 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/setup/index.js

Lines changed: 25 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cache/restore-cache.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { hashFiles } from "../hash/hash-files";
55
import {
66
cacheDependencyGlob,
77
cacheLocalPath,
8+
cachePython,
89
cacheSuffix,
10+
getUvPythonDir,
911
pruneCache,
1012
pythonVersion as pythonVersionInput,
1113
restoreCache as shouldRestoreCache,
@@ -30,8 +32,12 @@ export async function restoreCache(): Promise<void> {
3032
core.info(
3133
`Trying to restore uv cache from GitHub Actions cache with key: ${cacheKey}`,
3234
);
35+
const cachePaths = [cacheLocalPath];
36+
if (cachePython) {
37+
cachePaths.push(await getUvPythonDir());
38+
}
3339
try {
34-
matchedKey = await cache.restoreCache([cacheLocalPath], cacheKey);
40+
matchedKey = await cache.restoreCache(cachePaths, cacheKey);
3541
} catch (err) {
3642
const message = (err as Error).message;
3743
core.warning(message);
@@ -62,7 +68,8 @@ async function computeKeys(): Promise<string> {
6268
const pythonVersion = await getPythonVersion();
6369
const platform = await getPlatform();
6470
const pruned = pruneCache ? "-pruned" : "";
65-
return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${pythonVersion}${pruned}${cacheDependencyPathHash}${suffix}`;
71+
const python = cachePython ? "-py" : "";
72+
return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
6673
}
6774

6875
async function getPythonVersion(): Promise<string> {

src/save-cache.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
import { STATE_UV_PATH, STATE_UV_VERSION } from "./utils/constants";
1111
import {
1212
cacheLocalPath,
13+
cachePython,
1314
enableCache,
15+
getUvPythonDir,
1416
ignoreNothingToCache,
1517
pruneCache as shouldPruneCache,
1618
saveCache as shouldSaveCache,
@@ -68,8 +70,22 @@ async function saveCache(): Promise<void> {
6870
`Cache path ${actualCachePath} does not exist on disk. This likely indicates that there are no dependencies to cache. Consider disabling the cache input if it is not needed.`,
6971
);
7072
}
73+
74+
const cachePaths = [actualCachePath];
75+
if (cachePython) {
76+
const pythonDir = await getUvPythonDir();
77+
core.info(`Including Python cache path: ${pythonDir}`);
78+
if (!fs.existsSync(pythonDir) && !ignoreNothingToCache) {
79+
throw new Error(
80+
`Python cache path ${pythonDir} does not exist on disk. This likely indicates that there are no dependencies to cache. Consider disabling the cache input if it is not needed.`,
81+
);
82+
}
83+
cachePaths.push(pythonDir);
84+
}
85+
86+
core.info(`Final cache paths: ${cachePaths.join(", ")}`);
7187
try {
72-
await cache.saveCache([actualCachePath], cacheKey);
88+
await cache.saveCache(cachePaths, cacheKey);
7389
core.info(`cache saved with the key: ${cacheKey}`);
7490
} catch (e) {
7591
if (

0 commit comments

Comments
 (0)