Add cache-python input for caching Python installs

This commit is contained in:
Merlin 2025-10-08 11:30:41 -04:00 committed by merlinz01
parent eb1897b8dc
commit f50e500f4e
7 changed files with 126 additions and 10 deletions

View file

@ -26,6 +26,7 @@ Set up your GitHub Actions workflow with a specific version of [uv](https://docs
- [Save cache](#save-cache) - [Save cache](#save-cache)
- [Local cache path](#local-cache-path) - [Local cache path](#local-cache-path)
- [Disable cache pruning](#disable-cache-pruning) - [Disable cache pruning](#disable-cache-pruning)
- [Cache Python installs](#cache-python-installs)
- [Ignore nothing to cache](#ignore-nothing-to-cache) - [Ignore nothing to cache](#ignore-nothing-to-cache)
- [GitHub authentication token](#github-authentication-token) - [GitHub authentication token](#github-authentication-token)
- [UV_TOOL_DIR](#uv_tool_dir) - [UV_TOOL_DIR](#uv_tool_dir)
@ -355,6 +356,20 @@ input.
prune-cache: false prune-cache: false
``` ```
### Cache Python installs
By default, the Python install dir (`uv python dir` / `UV_PYTHON_INSTALL_DIR`) is not cached,
for the same reason that the dependency cache is pruned.
If you want to cache Python installs along with your dependencies, set the `cache-python` input to `true`.
```yaml
- name: Cache Python installs
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
cache-python: true
```
### Ignore nothing to cache ### Ignore nothing to cache
By default, the action will fail if caching is enabled but there is nothing to upload (the uv cache directory does not exist). By default, the action will fail if caching is enabled but there is nothing to upload (the uv cache directory does not exist).

View file

@ -56,6 +56,9 @@ inputs:
prune-cache: prune-cache:
description: "Prune cache before saving." description: "Prune cache before saving."
default: "true" default: "true"
cache-python:
description: "Upload managed Python installations to the Github Actions cache."
default: "false"
ignore-nothing-to-cache: ignore-nothing-to-cache:
description: "Ignore when nothing is found to cache." description: "Ignore when nothing is found to cache."
default: "false" default: "false"

40
dist/save-cache/index.js generated vendored
View file

@ -90593,8 +90593,12 @@ async function restoreCache() {
} }
let matchedKey; let matchedKey;
core.info(`Trying to restore uv cache from GitHub Actions cache with key: ${cacheKey}`); core.info(`Trying to restore uv cache from GitHub Actions cache with key: ${cacheKey}`);
const cachePaths = [inputs_1.cacheLocalPath];
if (inputs_1.cachePython) {
cachePaths.push(await (0, inputs_1.getUvPythonDir)());
}
try { try {
matchedKey = await cache.restoreCache([inputs_1.cacheLocalPath], cacheKey); matchedKey = await cache.restoreCache(cachePaths, cacheKey);
} }
catch (err) { catch (err) {
const message = err.message; const message = err.message;
@ -90620,7 +90624,8 @@ async function computeKeys() {
const pythonVersion = await getPythonVersion(); const pythonVersion = await getPythonVersion();
const platform = await (0, platforms_1.getPlatform)(); const platform = await (0, platforms_1.getPlatform)();
const pruned = inputs_1.pruneCache ? "-pruned" : ""; const pruned = inputs_1.pruneCache ? "-pruned" : "";
return `setup-uv-${CACHE_VERSION}-${(0, platforms_1.getArch)()}-${platform}-${pythonVersion}${pruned}${cacheDependencyPathHash}${suffix}`; const python = inputs_1.cachePython ? "-py" : "";
return `setup-uv-${CACHE_VERSION}-${(0, platforms_1.getArch)()}-${platform}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
} }
async function getPythonVersion() { async function getPythonVersion() {
if (inputs_1.pythonVersion !== "") { if (inputs_1.pythonVersion !== "") {
@ -90844,8 +90849,18 @@ async function saveCache() {
if (!fs.existsSync(actualCachePath) && !inputs_1.ignoreNothingToCache) { if (!fs.existsSync(actualCachePath) && !inputs_1.ignoreNothingToCache) {
throw new Error(`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.`); throw new Error(`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.`);
} }
const cachePaths = [actualCachePath];
if (inputs_1.cachePython) {
const pythonDir = await (0, inputs_1.getUvPythonDir)();
core.info(`Including Python cache path: ${pythonDir}`);
if (!fs.existsSync(pythonDir) && !inputs_1.ignoreNothingToCache) {
throw new Error(`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.`);
}
cachePaths.push(pythonDir);
}
core.info(`Final cache paths: ${cachePaths.join(", ")}`);
try { try {
await cache.saveCache([actualCachePath], cacheKey); await cache.saveCache(cachePaths, cacheKey);
core.info(`cache saved with the key: ${cacheKey}`); core.info(`cache saved with the key: ${cacheKey}`);
} }
catch (e) { catch (e) {
@ -90996,9 +91011,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.addProblemMatchers = exports.manifestFile = exports.githubToken = exports.toolDir = exports.toolBinDir = exports.ignoreEmptyWorkdir = exports.ignoreNothingToCache = exports.pruneCache = exports.cacheDependencyGlob = exports.cacheLocalPath = exports.cacheSuffix = exports.saveCache = exports.restoreCache = exports.enableCache = exports.checkSum = exports.activateEnvironment = exports.pythonVersion = exports.versionFile = exports.version = exports.workingDirectory = void 0; exports.addProblemMatchers = exports.manifestFile = exports.githubToken = exports.toolDir = exports.toolBinDir = exports.ignoreEmptyWorkdir = exports.ignoreNothingToCache = exports.cachePython = exports.pruneCache = exports.cacheDependencyGlob = exports.cacheLocalPath = exports.cacheSuffix = exports.saveCache = exports.restoreCache = exports.enableCache = exports.checkSum = exports.activateEnvironment = exports.pythonVersion = exports.versionFile = exports.version = exports.workingDirectory = void 0;
exports.getUvPythonDir = getUvPythonDir;
const node_path_1 = __importDefault(__nccwpck_require__(6760)); const node_path_1 = __importDefault(__nccwpck_require__(6760));
const core = __importStar(__nccwpck_require__(7484)); const core = __importStar(__nccwpck_require__(7484));
const exec = __importStar(__nccwpck_require__(5236));
const config_file_1 = __nccwpck_require__(5465); const config_file_1 = __nccwpck_require__(5465);
exports.workingDirectory = core.getInput("working-directory"); exports.workingDirectory = core.getInput("working-directory");
exports.version = core.getInput("version"); exports.version = core.getInput("version");
@ -91013,6 +91030,7 @@ exports.cacheSuffix = core.getInput("cache-suffix") || "";
exports.cacheLocalPath = getCacheLocalPath(); exports.cacheLocalPath = getCacheLocalPath();
exports.cacheDependencyGlob = getCacheDependencyGlob(); exports.cacheDependencyGlob = getCacheDependencyGlob();
exports.pruneCache = core.getInput("prune-cache") === "true"; exports.pruneCache = core.getInput("prune-cache") === "true";
exports.cachePython = core.getInput("cache-python") === "true";
exports.ignoreNothingToCache = core.getInput("ignore-nothing-to-cache") === "true"; exports.ignoreNothingToCache = core.getInput("ignore-nothing-to-cache") === "true";
exports.ignoreEmptyWorkdir = core.getInput("ignore-empty-workdir") === "true"; exports.ignoreEmptyWorkdir = core.getInput("ignore-empty-workdir") === "true";
exports.toolBinDir = getToolBinDir(); exports.toolBinDir = getToolBinDir();
@ -91106,6 +91124,20 @@ function getCacheDirFromConfig() {
} }
return undefined; return undefined;
} }
async function getUvPythonDir() {
if (process.env.UV_PYTHON_INSTALL_DIR !== undefined) {
core.info(`Using UV_PYTHON_INSTALL_DIR from environment: ${process.env.UV_PYTHON_INSTALL_DIR}`);
return process.env.UV_PYTHON_INSTALL_DIR;
}
core.info("Determining UV_PYTHON_INSTALL_DIR using `uv python dir`...");
const result = await exec.getExecOutput("uv", ["python", "dir"]);
if (result.exitCode !== 0) {
throw new Error(`Failed to get uv python dir: ${result.stderr || result.stdout}`);
}
const dir = result.stdout.trim();
core.info(`Determined UV_PYTHON_INSTALL_DIR: ${dir}`);
return dir;
}
function getCacheDependencyGlob() { function getCacheDependencyGlob() {
const cacheDependencyGlobInput = core.getInput("cache-dependency-glob"); const cacheDependencyGlobInput = core.getInput("cache-dependency-glob");
if (cacheDependencyGlobInput !== "") { if (cacheDependencyGlobInput !== "") {

28
dist/setup/index.js generated vendored
View file

@ -125151,8 +125151,12 @@ async function restoreCache() {
} }
let matchedKey; let matchedKey;
core.info(`Trying to restore uv cache from GitHub Actions cache with key: ${cacheKey}`); core.info(`Trying to restore uv cache from GitHub Actions cache with key: ${cacheKey}`);
const cachePaths = [inputs_1.cacheLocalPath];
if (inputs_1.cachePython) {
cachePaths.push(await (0, inputs_1.getUvPythonDir)());
}
try { try {
matchedKey = await cache.restoreCache([inputs_1.cacheLocalPath], cacheKey); matchedKey = await cache.restoreCache(cachePaths, cacheKey);
} }
catch (err) { catch (err) {
const message = err.message; const message = err.message;
@ -125178,7 +125182,8 @@ async function computeKeys() {
const pythonVersion = await getPythonVersion(); const pythonVersion = await getPythonVersion();
const platform = await (0, platforms_1.getPlatform)(); const platform = await (0, platforms_1.getPlatform)();
const pruned = inputs_1.pruneCache ? "-pruned" : ""; const pruned = inputs_1.pruneCache ? "-pruned" : "";
return `setup-uv-${CACHE_VERSION}-${(0, platforms_1.getArch)()}-${platform}-${pythonVersion}${pruned}${cacheDependencyPathHash}${suffix}`; const python = inputs_1.cachePython ? "-py" : "";
return `setup-uv-${CACHE_VERSION}-${(0, platforms_1.getArch)()}-${platform}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
} }
async function getPythonVersion() { async function getPythonVersion() {
if (inputs_1.pythonVersion !== "") { if (inputs_1.pythonVersion !== "") {
@ -129708,9 +129713,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.addProblemMatchers = exports.manifestFile = exports.githubToken = exports.toolDir = exports.toolBinDir = exports.ignoreEmptyWorkdir = exports.ignoreNothingToCache = exports.pruneCache = exports.cacheDependencyGlob = exports.cacheLocalPath = exports.cacheSuffix = exports.saveCache = exports.restoreCache = exports.enableCache = exports.checkSum = exports.activateEnvironment = exports.pythonVersion = exports.versionFile = exports.version = exports.workingDirectory = void 0; exports.addProblemMatchers = exports.manifestFile = exports.githubToken = exports.toolDir = exports.toolBinDir = exports.ignoreEmptyWorkdir = exports.ignoreNothingToCache = exports.cachePython = exports.pruneCache = exports.cacheDependencyGlob = exports.cacheLocalPath = exports.cacheSuffix = exports.saveCache = exports.restoreCache = exports.enableCache = exports.checkSum = exports.activateEnvironment = exports.pythonVersion = exports.versionFile = exports.version = exports.workingDirectory = void 0;
exports.getUvPythonDir = getUvPythonDir;
const node_path_1 = __importDefault(__nccwpck_require__(76760)); const node_path_1 = __importDefault(__nccwpck_require__(76760));
const core = __importStar(__nccwpck_require__(37484)); const core = __importStar(__nccwpck_require__(37484));
const exec = __importStar(__nccwpck_require__(95236));
const config_file_1 = __nccwpck_require__(27846); const config_file_1 = __nccwpck_require__(27846);
exports.workingDirectory = core.getInput("working-directory"); exports.workingDirectory = core.getInput("working-directory");
exports.version = core.getInput("version"); exports.version = core.getInput("version");
@ -129725,6 +129732,7 @@ exports.cacheSuffix = core.getInput("cache-suffix") || "";
exports.cacheLocalPath = getCacheLocalPath(); exports.cacheLocalPath = getCacheLocalPath();
exports.cacheDependencyGlob = getCacheDependencyGlob(); exports.cacheDependencyGlob = getCacheDependencyGlob();
exports.pruneCache = core.getInput("prune-cache") === "true"; exports.pruneCache = core.getInput("prune-cache") === "true";
exports.cachePython = core.getInput("cache-python") === "true";
exports.ignoreNothingToCache = core.getInput("ignore-nothing-to-cache") === "true"; exports.ignoreNothingToCache = core.getInput("ignore-nothing-to-cache") === "true";
exports.ignoreEmptyWorkdir = core.getInput("ignore-empty-workdir") === "true"; exports.ignoreEmptyWorkdir = core.getInput("ignore-empty-workdir") === "true";
exports.toolBinDir = getToolBinDir(); exports.toolBinDir = getToolBinDir();
@ -129818,6 +129826,20 @@ function getCacheDirFromConfig() {
} }
return undefined; return undefined;
} }
async function getUvPythonDir() {
if (process.env.UV_PYTHON_INSTALL_DIR !== undefined) {
core.info(`Using UV_PYTHON_INSTALL_DIR from environment: ${process.env.UV_PYTHON_INSTALL_DIR}`);
return process.env.UV_PYTHON_INSTALL_DIR;
}
core.info("Determining UV_PYTHON_INSTALL_DIR using `uv python dir`...");
const result = await exec.getExecOutput("uv", ["python", "dir"]);
if (result.exitCode !== 0) {
throw new Error(`Failed to get uv python dir: ${result.stderr || result.stdout}`);
}
const dir = result.stdout.trim();
core.info(`Determined UV_PYTHON_INSTALL_DIR: ${dir}`);
return dir;
}
function getCacheDependencyGlob() { function getCacheDependencyGlob() {
const cacheDependencyGlobInput = core.getInput("cache-dependency-glob"); const cacheDependencyGlobInput = core.getInput("cache-dependency-glob");
if (cacheDependencyGlobInput !== "") { if (cacheDependencyGlobInput !== "") {

View file

@ -5,7 +5,9 @@ import { hashFiles } from "../hash/hash-files";
import { import {
cacheDependencyGlob, cacheDependencyGlob,
cacheLocalPath, cacheLocalPath,
cachePython,
cacheSuffix, cacheSuffix,
getUvPythonDir,
pruneCache, pruneCache,
pythonVersion as pythonVersionInput, pythonVersion as pythonVersionInput,
restoreCache as shouldRestoreCache, restoreCache as shouldRestoreCache,
@ -30,8 +32,12 @@ export async function restoreCache(): Promise<void> {
core.info( core.info(
`Trying to restore uv cache from GitHub Actions cache with key: ${cacheKey}`, `Trying to restore uv cache from GitHub Actions cache with key: ${cacheKey}`,
); );
const cachePaths = [cacheLocalPath];
if (cachePython) {
cachePaths.push(await getUvPythonDir());
}
try { try {
matchedKey = await cache.restoreCache([cacheLocalPath], cacheKey); matchedKey = await cache.restoreCache(cachePaths, cacheKey);
} catch (err) { } catch (err) {
const message = (err as Error).message; const message = (err as Error).message;
core.warning(message); core.warning(message);
@ -62,7 +68,8 @@ async function computeKeys(): Promise<string> {
const pythonVersion = await getPythonVersion(); const pythonVersion = await getPythonVersion();
const platform = await getPlatform(); const platform = await getPlatform();
const pruned = pruneCache ? "-pruned" : ""; const pruned = pruneCache ? "-pruned" : "";
return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${pythonVersion}${pruned}${cacheDependencyPathHash}${suffix}`; const python = cachePython ? "-py" : "";
return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
} }
async function getPythonVersion(): Promise<string> { async function getPythonVersion(): Promise<string> {

View file

@ -10,7 +10,9 @@ import {
import { STATE_UV_PATH, STATE_UV_VERSION } from "./utils/constants"; import { STATE_UV_PATH, STATE_UV_VERSION } from "./utils/constants";
import { import {
cacheLocalPath, cacheLocalPath,
cachePython,
enableCache, enableCache,
getUvPythonDir,
ignoreNothingToCache, ignoreNothingToCache,
pruneCache as shouldPruneCache, pruneCache as shouldPruneCache,
saveCache as shouldSaveCache, saveCache as shouldSaveCache,
@ -68,8 +70,22 @@ async function saveCache(): Promise<void> {
`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.`, `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.`,
); );
} }
const cachePaths = [actualCachePath];
if (cachePython) {
const pythonDir = await getUvPythonDir();
core.info(`Including Python cache path: ${pythonDir}`);
if (!fs.existsSync(pythonDir) && !ignoreNothingToCache) {
throw new Error(
`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.`,
);
}
cachePaths.push(pythonDir);
}
core.info(`Final cache paths: ${cachePaths.join(", ")}`);
try { try {
await cache.saveCache([actualCachePath], cacheKey); await cache.saveCache(cachePaths, cacheKey);
core.info(`cache saved with the key: ${cacheKey}`); core.info(`cache saved with the key: ${cacheKey}`);
} catch (e) { } catch (e) {
if ( if (

View file

@ -1,5 +1,6 @@
import path from "node:path"; import path from "node:path";
import * as core from "@actions/core"; import * as core from "@actions/core";
import * as exec from "@actions/exec";
import { getConfigValueFromTomlFile } from "./config-file"; import { getConfigValueFromTomlFile } from "./config-file";
export const workingDirectory = core.getInput("working-directory"); export const workingDirectory = core.getInput("working-directory");
@ -15,6 +16,7 @@ export const cacheSuffix = core.getInput("cache-suffix") || "";
export const cacheLocalPath = getCacheLocalPath(); export const cacheLocalPath = getCacheLocalPath();
export const cacheDependencyGlob = getCacheDependencyGlob(); export const cacheDependencyGlob = getCacheDependencyGlob();
export const pruneCache = core.getInput("prune-cache") === "true"; export const pruneCache = core.getInput("prune-cache") === "true";
export const cachePython = core.getInput("cache-python") === "true";
export const ignoreNothingToCache = export const ignoreNothingToCache =
core.getInput("ignore-nothing-to-cache") === "true"; core.getInput("ignore-nothing-to-cache") === "true";
export const ignoreEmptyWorkdir = export const ignoreEmptyWorkdir =
@ -123,6 +125,25 @@ function getCacheDirFromConfig(): string | undefined {
return undefined; return undefined;
} }
export async function getUvPythonDir(): Promise<string> {
if (process.env.UV_PYTHON_INSTALL_DIR !== undefined) {
core.info(
`Using UV_PYTHON_INSTALL_DIR from environment: ${process.env.UV_PYTHON_INSTALL_DIR}`,
);
return process.env.UV_PYTHON_INSTALL_DIR;
}
core.info("Determining UV_PYTHON_INSTALL_DIR using `uv python dir`...");
const result = await exec.getExecOutput("uv", ["python", "dir"]);
if (result.exitCode !== 0) {
throw new Error(
`Failed to get uv python dir: ${result.stderr || result.stdout}`,
);
}
const dir = result.stdout.trim();
core.info(`Determined UV_PYTHON_INSTALL_DIR: ${dir}`);
return dir;
}
function getCacheDependencyGlob(): string { function getCacheDependencyGlob(): string {
const cacheDependencyGlobInput = core.getInput("cache-dependency-glob"); const cacheDependencyGlobInput = core.getInput("cache-dependency-glob");
if (cacheDependencyGlobInput !== "") { if (cacheDependencyGlobInput !== "") {