diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bb589fa..2a2c254 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -325,6 +325,7 @@ jobs: with: persist-credentials: false - name: Install latest version + id: setup-uv uses: ./ with: python-version: 3.13.1t @@ -335,6 +336,14 @@ jobs: exit 1 fi shell: bash + - name: Verify output python-version is correct + run: | + if [ "$PYTHON_VERSION" != "3.13.1t" ]; then + exit 1 + fi + shell: bash + env: + PYTHON_VERSION: ${{ steps.setup-uv.outputs.python-version }} - run: uv sync working-directory: __tests__/fixtures/uv-project @@ -986,7 +995,7 @@ jobs: exit 1 fi env: - CACHE_HIT: ${{ steps.restore.outputs.cache-hit }} + CACHE_HIT: ${{ steps.restore.outputs.python-cache-hit }} - run: uv sync --managed-python working-directory: __tests__/fixtures/uv-project diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..02dd134 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +.github/copilot-instructions.md \ No newline at end of file diff --git a/README.md b/README.md index 67768f9..20efa42 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ Have a look under [Advanced Configuration](#advanced-configuration) for detailed - `uvx-path`: The path to the installed uvx binary. - `cache-hit`: A boolean value to indicate a cache entry was found. - `venv`: Path to the activated venv if activate-environment is true. +- `python-version`: The Python version that was set. +- `python-cache-hit`: A boolean value to indicate the Python cache entry was found. ### Python version diff --git a/action-types.yml b/action-types.yml index 5aeb0cf..4fc1d2d 100644 --- a/action-types.yml +++ b/action-types.yml @@ -69,3 +69,7 @@ outputs: type: string venv: type: string + python-version: + type: string + python-cache-hit: + type: boolean diff --git a/action.yml b/action.yml index f5fe6ed..ebeba73 100644 --- a/action.yml +++ b/action.yml @@ -93,6 +93,10 @@ outputs: description: "The cache key used for storing/restoring the cache" venv: description: "Path to the activated venv if activate-environment is true" + python-version: + description: "The Python version that was set." + python-cache-hit: + description: "A boolean value to indicate the Python cache entry was found" runs: using: "node24" main: "dist/setup/index.js" diff --git a/dist/save-cache/index.js b/dist/save-cache/index.js index 3936810..7c28736 100644 --- a/dist/save-cache/index.js +++ b/dist/save-cache/index.js @@ -90599,46 +90599,52 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.STATE_CACHE_MATCHED_KEY = exports.STATE_CACHE_KEY = void 0; +exports.STATE_PYTHON_CACHE_MATCHED_KEY = exports.STATE_CACHE_MATCHED_KEY = exports.STATE_CACHE_KEY = void 0; exports.restoreCache = restoreCache; const cache = __importStar(__nccwpck_require__(5116)); const core = __importStar(__nccwpck_require__(7484)); -const exec = __importStar(__nccwpck_require__(5236)); const hash_files_1 = __nccwpck_require__(9660); const inputs_1 = __nccwpck_require__(9612); const platforms_1 = __nccwpck_require__(8361); exports.STATE_CACHE_KEY = "cache-key"; exports.STATE_CACHE_MATCHED_KEY = "cache-matched-key"; +exports.STATE_PYTHON_CACHE_MATCHED_KEY = "python-cache-matched-key"; const CACHE_VERSION = "2"; -async function restoreCache() { - const cacheKey = await computeKeys(); +async function restoreCache(pythonVersion) { + const cacheKey = await computeKeys(pythonVersion); core.saveState(exports.STATE_CACHE_KEY, cacheKey); core.setOutput("cache-key", cacheKey); if (!inputs_1.restoreCache) { core.info("restore-cache is false. Skipping restore cache step."); + core.setOutput("python-cache-hit", false); return; } - let matchedKey; - core.info(`Trying to restore uv cache from GitHub Actions cache with key: ${cacheKey}`); if (inputs_1.cacheLocalPath === undefined) { throw new Error("cache-local-path is not set. Cannot restore cache without a valid cache path."); } - const cachePaths = [inputs_1.cacheLocalPath.path]; + await restoreCacheFromKey(cacheKey, inputs_1.cacheLocalPath.path, exports.STATE_CACHE_MATCHED_KEY, "cache-hit"); if (inputs_1.cachePython) { - cachePaths.push(inputs_1.pythonDir); + await restoreCacheFromKey(`${cacheKey}-python`, inputs_1.pythonDir, exports.STATE_PYTHON_CACHE_MATCHED_KEY, "python-cache-hit"); } + else { + core.setOutput("python-cache-hit", false); + } +} +async function restoreCacheFromKey(cacheKey, cachePath, stateKey, outputKey) { + core.info(`Trying to restore cache from GitHub Actions cache with key: ${cacheKey}`); + let matchedKey; try { - matchedKey = await cache.restoreCache(cachePaths, cacheKey); + matchedKey = await cache.restoreCache([cachePath], cacheKey); } catch (err) { const message = err.message; core.warning(message); - core.setOutput("cache-hit", false); + core.setOutput(outputKey, false); return; } - handleMatchResult(matchedKey, cacheKey); + handleMatchResult(matchedKey, cacheKey, stateKey, outputKey); } -async function computeKeys() { +async function computeKeys(pythonVersion) { let cacheDependencyPathHash = "-"; if (inputs_1.cacheDependencyGlob !== "") { core.info(`Searching files using cache dependency glob: ${inputs_1.cacheDependencyGlob.split("\n").join(",")}`); @@ -90651,50 +90657,22 @@ async function computeKeys() { cacheDependencyPathHash = "-no-dependency-glob"; } const suffix = inputs_1.cacheSuffix ? `-${inputs_1.cacheSuffix}` : ""; - const pythonVersion = await getPythonVersion(); + const version = pythonVersion ?? "unknown"; const platform = await (0, platforms_1.getPlatform)(); const osNameVersion = (0, platforms_1.getOSNameVersion)(); const pruned = inputs_1.pruneCache ? "-pruned" : ""; const python = inputs_1.cachePython ? "-py" : ""; - return `setup-uv-${CACHE_VERSION}-${(0, platforms_1.getArch)()}-${platform}-${osNameVersion}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`; + return `setup-uv-${CACHE_VERSION}-${(0, platforms_1.getArch)()}-${platform}-${osNameVersion}-${version}${pruned}${python}${cacheDependencyPathHash}${suffix}`; } -async function getPythonVersion() { - if (inputs_1.pythonVersion !== "") { - return inputs_1.pythonVersion; - } - let output = ""; - const options = { - listeners: { - stdout: (data) => { - output += data.toString(); - }, - }, - silent: !core.isDebug(), - }; - try { - const execArgs = ["python", "find", "--directory", inputs_1.workingDirectory]; - await exec.exec("uv", execArgs, options); - const pythonPath = output.trim(); - output = ""; - await exec.exec(pythonPath, ["--version"], options); - // output is like "Python 3.8.10" - return output.split(" ")[1].trim(); - } - catch (error) { - const err = error; - core.debug(`Failed to get python version from uv. Error: ${err.message}`); - return "unknown"; - } -} -function handleMatchResult(matchedKey, primaryKey) { +function handleMatchResult(matchedKey, primaryKey, stateKey, outputKey) { if (!matchedKey) { core.info(`No GitHub Actions cache found for key: ${primaryKey}`); - core.setOutput("cache-hit", false); + core.setOutput(outputKey, false); return; } - core.saveState(exports.STATE_CACHE_MATCHED_KEY, matchedKey); - core.info(`uv cache restored from GitHub Actions cache with key: ${matchedKey}`); - core.setOutput("cache-hit", true); + core.saveState(stateKey, matchedKey); + core.info(`cache restored from GitHub Actions cache with key: ${matchedKey}`); + core.setOutput(outputKey, true); } @@ -90868,46 +90846,17 @@ async function saveCache() { } if (matchedKey === cacheKey) { core.info(`Cache hit occurred on key ${cacheKey}, not saving cache.`); - return; } - if (inputs_1.pruneCache) { - await pruneCache(); + else { + if (inputs_1.pruneCache) { + await pruneCache(); + } + const actualCachePath = getUvCachePath(); + await saveCacheToKey(cacheKey, actualCachePath, restore_cache_1.STATE_CACHE_MATCHED_KEY, "uv cache", `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.`); } - if (inputs_1.cacheLocalPath === undefined) { - throw new Error("cache-local-path is not set. Cannot save cache without a valid cache path."); - } - let actualCachePath = inputs_1.cacheLocalPath.path; - if (process.env.UV_CACHE_DIR && - process.env.UV_CACHE_DIR !== inputs_1.cacheLocalPath.path) { - core.warning(`The environment variable UV_CACHE_DIR has been changed to "${process.env.UV_CACHE_DIR}", by an action or step running after astral-sh/setup-uv. This can lead to unexpected behavior. If you expected this to happen set the cache-local-path input to "${process.env.UV_CACHE_DIR}" instead of "${inputs_1.cacheLocalPath.path}".`); - actualCachePath = process.env.UV_CACHE_DIR; - } - core.info(`Saving cache path: ${actualCachePath}`); - 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.`); - } - const cachePaths = [actualCachePath]; if (inputs_1.cachePython) { - core.info(`Including Python cache path: ${inputs_1.pythonDir}`); - if (!fs.existsSync(inputs_1.pythonDir) && !inputs_1.ignoreNothingToCache) { - throw new Error(`Python cache path ${inputs_1.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(inputs_1.pythonDir); - } - core.info(`Final cache paths: ${cachePaths.join(", ")}`); - try { - await cache.saveCache(cachePaths, cacheKey); - core.info(`cache saved with the key: ${cacheKey}`); - } - catch (e) { - if (e instanceof Error && - e.message === - "Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.") { - core.info("No cacheable paths were found. Ignoring because ignore-nothing-to-save is enabled."); - } - else { - throw e; - } + const pythonCacheKey = `${cacheKey}-python`; + await saveCacheToKey(pythonCacheKey, inputs_1.pythonDir, restore_cache_1.STATE_PYTHON_CACHE_MATCHED_KEY, "Python cache", `Python cache path ${inputs_1.pythonDir} does not exist on disk. This likely indicates that there are no Python installations to cache. Consider disabling the cache input if it is not needed.`); } } async function pruneCache() { @@ -90923,6 +90872,42 @@ async function pruneCache() { const uvPath = core.getState(constants_1.STATE_UV_PATH); await exec.exec(uvPath, execArgs, options); } +function getUvCachePath() { + if (inputs_1.cacheLocalPath === undefined) { + throw new Error("cache-local-path is not set. Cannot save cache without a valid cache path."); + } + if (process.env.UV_CACHE_DIR && + process.env.UV_CACHE_DIR !== inputs_1.cacheLocalPath.path) { + core.warning(`The environment variable UV_CACHE_DIR has been changed to "${process.env.UV_CACHE_DIR}", by an action or step running after astral-sh/setup-uv. This can lead to unexpected behavior. If you expected this to happen set the cache-local-path input to "${process.env.UV_CACHE_DIR}" instead of "${inputs_1.cacheLocalPath.path}".`); + return process.env.UV_CACHE_DIR; + } + return inputs_1.cacheLocalPath.path; +} +async function saveCacheToKey(cacheKey, cachePath, stateKey, cacheName, pathNotExistErrorMessage) { + const matchedKey = core.getState(stateKey); + if (matchedKey === cacheKey) { + core.info(`${cacheName} hit occurred on key ${cacheKey}, not saving cache.`); + return; + } + core.info(`Including ${cacheName} path: ${cachePath}`); + if (!fs.existsSync(cachePath) && !inputs_1.ignoreNothingToCache) { + throw new Error(pathNotExistErrorMessage); + } + try { + await cache.saveCache([cachePath], cacheKey); + core.info(`${cacheName} saved with key: ${cacheKey}`); + } + catch (e) { + if (e instanceof Error && + e.message === + "Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.") { + core.info(`No cacheable ${cacheName} paths were found. Ignoring because ignore-nothing-to-save is enabled.`); + } + else { + throw e; + } + } +} run(); diff --git a/dist/setup/index.js b/dist/setup/index.js index e6bd473..3305e43 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -91502,46 +91502,52 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.STATE_CACHE_MATCHED_KEY = exports.STATE_CACHE_KEY = void 0; +exports.STATE_PYTHON_CACHE_MATCHED_KEY = exports.STATE_CACHE_MATCHED_KEY = exports.STATE_CACHE_KEY = void 0; exports.restoreCache = restoreCache; const cache = __importStar(__nccwpck_require__(5116)); const core = __importStar(__nccwpck_require__(7484)); -const exec = __importStar(__nccwpck_require__(5236)); const hash_files_1 = __nccwpck_require__(9660); const inputs_1 = __nccwpck_require__(9612); const platforms_1 = __nccwpck_require__(8361); exports.STATE_CACHE_KEY = "cache-key"; exports.STATE_CACHE_MATCHED_KEY = "cache-matched-key"; +exports.STATE_PYTHON_CACHE_MATCHED_KEY = "python-cache-matched-key"; const CACHE_VERSION = "2"; -async function restoreCache() { - const cacheKey = await computeKeys(); +async function restoreCache(pythonVersion) { + const cacheKey = await computeKeys(pythonVersion); core.saveState(exports.STATE_CACHE_KEY, cacheKey); core.setOutput("cache-key", cacheKey); if (!inputs_1.restoreCache) { core.info("restore-cache is false. Skipping restore cache step."); + core.setOutput("python-cache-hit", false); return; } - let matchedKey; - core.info(`Trying to restore uv cache from GitHub Actions cache with key: ${cacheKey}`); if (inputs_1.cacheLocalPath === undefined) { throw new Error("cache-local-path is not set. Cannot restore cache without a valid cache path."); } - const cachePaths = [inputs_1.cacheLocalPath.path]; + await restoreCacheFromKey(cacheKey, inputs_1.cacheLocalPath.path, exports.STATE_CACHE_MATCHED_KEY, "cache-hit"); if (inputs_1.cachePython) { - cachePaths.push(inputs_1.pythonDir); + await restoreCacheFromKey(`${cacheKey}-python`, inputs_1.pythonDir, exports.STATE_PYTHON_CACHE_MATCHED_KEY, "python-cache-hit"); } + else { + core.setOutput("python-cache-hit", false); + } +} +async function restoreCacheFromKey(cacheKey, cachePath, stateKey, outputKey) { + core.info(`Trying to restore cache from GitHub Actions cache with key: ${cacheKey}`); + let matchedKey; try { - matchedKey = await cache.restoreCache(cachePaths, cacheKey); + matchedKey = await cache.restoreCache([cachePath], cacheKey); } catch (err) { const message = err.message; core.warning(message); - core.setOutput("cache-hit", false); + core.setOutput(outputKey, false); return; } - handleMatchResult(matchedKey, cacheKey); + handleMatchResult(matchedKey, cacheKey, stateKey, outputKey); } -async function computeKeys() { +async function computeKeys(pythonVersion) { let cacheDependencyPathHash = "-"; if (inputs_1.cacheDependencyGlob !== "") { core.info(`Searching files using cache dependency glob: ${inputs_1.cacheDependencyGlob.split("\n").join(",")}`); @@ -91554,50 +91560,22 @@ async function computeKeys() { cacheDependencyPathHash = "-no-dependency-glob"; } const suffix = inputs_1.cacheSuffix ? `-${inputs_1.cacheSuffix}` : ""; - const pythonVersion = await getPythonVersion(); + const version = pythonVersion ?? "unknown"; const platform = await (0, platforms_1.getPlatform)(); const osNameVersion = (0, platforms_1.getOSNameVersion)(); const pruned = inputs_1.pruneCache ? "-pruned" : ""; const python = inputs_1.cachePython ? "-py" : ""; - return `setup-uv-${CACHE_VERSION}-${(0, platforms_1.getArch)()}-${platform}-${osNameVersion}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`; + return `setup-uv-${CACHE_VERSION}-${(0, platforms_1.getArch)()}-${platform}-${osNameVersion}-${version}${pruned}${python}${cacheDependencyPathHash}${suffix}`; } -async function getPythonVersion() { - if (inputs_1.pythonVersion !== "") { - return inputs_1.pythonVersion; - } - let output = ""; - const options = { - listeners: { - stdout: (data) => { - output += data.toString(); - }, - }, - silent: !core.isDebug(), - }; - try { - const execArgs = ["python", "find", "--directory", inputs_1.workingDirectory]; - await exec.exec("uv", execArgs, options); - const pythonPath = output.trim(); - output = ""; - await exec.exec(pythonPath, ["--version"], options); - // output is like "Python 3.8.10" - return output.split(" ")[1].trim(); - } - catch (error) { - const err = error; - core.debug(`Failed to get python version from uv. Error: ${err.message}`); - return "unknown"; - } -} -function handleMatchResult(matchedKey, primaryKey) { +function handleMatchResult(matchedKey, primaryKey, stateKey, outputKey) { if (!matchedKey) { core.info(`No GitHub Actions cache found for key: ${primaryKey}`); - core.setOutput("cache-hit", false); + core.setOutput(outputKey, false); return; } - core.saveState(exports.STATE_CACHE_MATCHED_KEY, matchedKey); - core.info(`uv cache restored from GitHub Actions cache with key: ${matchedKey}`); - core.setOutput("cache-hit", true); + core.saveState(stateKey, matchedKey); + core.info(`cache restored from GitHub Actions cache with key: ${matchedKey}`); + core.setOutput(outputKey, true); } @@ -96267,6 +96245,34 @@ const constants_1 = __nccwpck_require__(6156); const inputs_1 = __nccwpck_require__(9612); const platforms_1 = __nccwpck_require__(8361); const resolve_1 = __nccwpck_require__(6772); +async function getPythonVersion() { + if (inputs_1.pythonVersion !== "") { + return inputs_1.pythonVersion; + } + let output = ""; + const options = { + listeners: { + stdout: (data) => { + output += data.toString(); + }, + }, + silent: !core.isDebug(), + }; + try { + const execArgs = ["python", "find", "--directory", inputs_1.workingDirectory]; + await exec.exec("uv", execArgs, options); + const pythonPath = output.trim(); + output = ""; + await exec.exec(pythonPath, ["--version"], options); + // output is like "Python 3.8.10" + return output.split(" ")[1].trim(); + } + catch (error) { + const err = error; + core.debug(`Failed to get python version from uv. Error: ${err.message}`); + return "unknown"; + } +} async function run() { detectEmptyWorkdir(); const platform = await (0, platforms_1.getPlatform)(); @@ -96290,8 +96296,10 @@ async function run() { core.setOutput("uv-version", setupResult.version); core.saveState(constants_1.STATE_UV_VERSION, setupResult.version); core.info(`Successfully installed uv version ${setupResult.version}`); + const pythonVersion = await getPythonVersion(); + core.setOutput("python-version", pythonVersion); if (inputs_1.enableCache) { - await (0, restore_cache_1.restoreCache)(); + await (0, restore_cache_1.restoreCache)(pythonVersion); } // https://github.com/nodejs/node/issues/56645#issuecomment-3077594952 await new Promise((resolve) => setTimeout(resolve, 50)); diff --git a/src/cache/restore-cache.ts b/src/cache/restore-cache.ts index cdf4e27..a099aad 100644 --- a/src/cache/restore-cache.ts +++ b/src/cache/restore-cache.ts @@ -1,6 +1,5 @@ import * as cache from "@actions/cache"; import * as core from "@actions/core"; -import * as exec from "@actions/exec"; import { hashFiles } from "../hash/hash-files"; import { cacheDependencyGlob, @@ -9,52 +8,75 @@ import { cacheSuffix, pruneCache, pythonDir, - pythonVersion as pythonVersionInput, restoreCache as shouldRestoreCache, - workingDirectory, } from "../utils/inputs"; import { getArch, getOSNameVersion, getPlatform } from "../utils/platforms"; export const STATE_CACHE_KEY = "cache-key"; export const STATE_CACHE_MATCHED_KEY = "cache-matched-key"; +export const STATE_PYTHON_CACHE_MATCHED_KEY = "python-cache-matched-key"; + const CACHE_VERSION = "2"; -export async function restoreCache(): Promise { - const cacheKey = await computeKeys(); +export async function restoreCache(pythonVersion?: string): Promise { + const cacheKey = await computeKeys(pythonVersion); core.saveState(STATE_CACHE_KEY, cacheKey); core.setOutput("cache-key", cacheKey); if (!shouldRestoreCache) { core.info("restore-cache is false. Skipping restore cache step."); + core.setOutput("python-cache-hit", false); return; } - let matchedKey: string | undefined; - core.info( - `Trying to restore uv cache from GitHub Actions cache with key: ${cacheKey}`, - ); if (cacheLocalPath === undefined) { throw new Error( "cache-local-path is not set. Cannot restore cache without a valid cache path.", ); } - const cachePaths = [cacheLocalPath.path]; + + await restoreCacheFromKey( + cacheKey, + cacheLocalPath.path, + STATE_CACHE_MATCHED_KEY, + "cache-hit", + ); + if (cachePython) { - cachePaths.push(pythonDir); + await restoreCacheFromKey( + `${cacheKey}-python`, + pythonDir, + STATE_PYTHON_CACHE_MATCHED_KEY, + "python-cache-hit", + ); + } else { + core.setOutput("python-cache-hit", false); } +} + +async function restoreCacheFromKey( + cacheKey: string, + cachePath: string, + stateKey: string, + outputKey: string, +): Promise { + core.info( + `Trying to restore cache from GitHub Actions cache with key: ${cacheKey}`, + ); + let matchedKey: string | undefined; try { - matchedKey = await cache.restoreCache(cachePaths, cacheKey); + matchedKey = await cache.restoreCache([cachePath], cacheKey); } catch (err) { const message = (err as Error).message; core.warning(message); - core.setOutput("cache-hit", false); + core.setOutput(outputKey, false); return; } - handleMatchResult(matchedKey, cacheKey); + handleMatchResult(matchedKey, cacheKey, stateKey, outputKey); } -async function computeKeys(): Promise { +async function computeKeys(pythonVersion?: string): Promise { let cacheDependencyPathHash = "-"; if (cacheDependencyGlob !== "") { core.info( @@ -71,58 +93,27 @@ async function computeKeys(): Promise { cacheDependencyPathHash = "-no-dependency-glob"; } const suffix = cacheSuffix ? `-${cacheSuffix}` : ""; - const pythonVersion = await getPythonVersion(); + const version = pythonVersion ?? "unknown"; const platform = await getPlatform(); const osNameVersion = getOSNameVersion(); const pruned = pruneCache ? "-pruned" : ""; const python = cachePython ? "-py" : ""; - return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${osNameVersion}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`; -} - -async function getPythonVersion(): Promise { - if (pythonVersionInput !== "") { - return pythonVersionInput; - } - - let output = ""; - const options: exec.ExecOptions = { - listeners: { - stdout: (data: Buffer) => { - output += data.toString(); - }, - }, - silent: !core.isDebug(), - }; - - try { - const execArgs = ["python", "find", "--directory", workingDirectory]; - await exec.exec("uv", execArgs, options); - const pythonPath = output.trim(); - - output = ""; - await exec.exec(pythonPath, ["--version"], options); - // output is like "Python 3.8.10" - return output.split(" ")[1].trim(); - } catch (error) { - const err = error as Error; - core.debug(`Failed to get python version from uv. Error: ${err.message}`); - return "unknown"; - } + return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${osNameVersion}-${version}${pruned}${python}${cacheDependencyPathHash}${suffix}`; } function handleMatchResult( matchedKey: string | undefined, primaryKey: string, + stateKey: string, + outputKey: string, ): void { if (!matchedKey) { core.info(`No GitHub Actions cache found for key: ${primaryKey}`); - core.setOutput("cache-hit", false); + core.setOutput(outputKey, false); return; } - core.saveState(STATE_CACHE_MATCHED_KEY, matchedKey); - core.info( - `uv cache restored from GitHub Actions cache with key: ${matchedKey}`, - ); - core.setOutput("cache-hit", true); + core.saveState(stateKey, matchedKey); + core.info(`cache restored from GitHub Actions cache with key: ${matchedKey}`); + core.setOutput(outputKey, true); } diff --git a/src/save-cache.ts b/src/save-cache.ts index eee289b..2f0b370 100644 --- a/src/save-cache.ts +++ b/src/save-cache.ts @@ -6,6 +6,7 @@ import * as pep440 from "@renovatebot/pep440"; import { STATE_CACHE_KEY, STATE_CACHE_MATCHED_KEY, + STATE_PYTHON_CACHE_MATCHED_KEY, } from "./cache/restore-cache"; import { STATE_UV_PATH, STATE_UV_VERSION } from "./utils/constants"; import { @@ -52,63 +53,30 @@ async function saveCache(): Promise { } if (matchedKey === cacheKey) { core.info(`Cache hit occurred on key ${cacheKey}, not saving cache.`); - return; - } + } else { + if (shouldPruneCache) { + await pruneCache(); + } - if (shouldPruneCache) { - await pruneCache(); - } - - if (cacheLocalPath === undefined) { - throw new Error( - "cache-local-path is not set. Cannot save cache without a valid cache path.", - ); - } - let actualCachePath = cacheLocalPath.path; - if ( - process.env.UV_CACHE_DIR && - process.env.UV_CACHE_DIR !== cacheLocalPath.path - ) { - core.warning( - `The environment variable UV_CACHE_DIR has been changed to "${process.env.UV_CACHE_DIR}", by an action or step running after astral-sh/setup-uv. This can lead to unexpected behavior. If you expected this to happen set the cache-local-path input to "${process.env.UV_CACHE_DIR}" instead of "${cacheLocalPath.path}".`, - ); - actualCachePath = process.env.UV_CACHE_DIR; - } - - core.info(`Saving cache path: ${actualCachePath}`); - if (!fs.existsSync(actualCachePath) && !ignoreNothingToCache) { - throw new Error( + const actualCachePath = getUvCachePath(); + await saveCacheToKey( + cacheKey, + actualCachePath, + STATE_CACHE_MATCHED_KEY, + "uv cache", `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) { - 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 { - await cache.saveCache(cachePaths, cacheKey); - core.info(`cache saved with the key: ${cacheKey}`); - } catch (e) { - if ( - e instanceof Error && - e.message === - "Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved." - ) { - core.info( - "No cacheable paths were found. Ignoring because ignore-nothing-to-save is enabled.", - ); - } else { - throw e; - } + const pythonCacheKey = `${cacheKey}-python`; + await saveCacheToKey( + pythonCacheKey, + pythonDir, + STATE_PYTHON_CACHE_MATCHED_KEY, + "Python cache", + `Python cache path ${pythonDir} does not exist on disk. This likely indicates that there are no Python installations to cache. Consider disabling the cache input if it is not needed.`, + ); } } @@ -128,4 +96,61 @@ async function pruneCache(): Promise { await exec.exec(uvPath, execArgs, options); } +function getUvCachePath(): string { + if (cacheLocalPath === undefined) { + throw new Error( + "cache-local-path is not set. Cannot save cache without a valid cache path.", + ); + } + if ( + process.env.UV_CACHE_DIR && + process.env.UV_CACHE_DIR !== cacheLocalPath.path + ) { + core.warning( + `The environment variable UV_CACHE_DIR has been changed to "${process.env.UV_CACHE_DIR}", by an action or step running after astral-sh/setup-uv. This can lead to unexpected behavior. If you expected this to happen set the cache-local-path input to "${process.env.UV_CACHE_DIR}" instead of "${cacheLocalPath.path}".`, + ); + return process.env.UV_CACHE_DIR; + } + return cacheLocalPath.path; +} + +async function saveCacheToKey( + cacheKey: string, + cachePath: string, + stateKey: string, + cacheName: string, + pathNotExistErrorMessage: string, +): Promise { + const matchedKey = core.getState(stateKey); + + if (matchedKey === cacheKey) { + core.info( + `${cacheName} hit occurred on key ${cacheKey}, not saving cache.`, + ); + return; + } + + core.info(`Including ${cacheName} path: ${cachePath}`); + if (!fs.existsSync(cachePath) && !ignoreNothingToCache) { + throw new Error(pathNotExistErrorMessage); + } + + try { + await cache.saveCache([cachePath], cacheKey); + core.info(`${cacheName} saved with key: ${cacheKey}`); + } catch (e) { + if ( + e instanceof Error && + e.message === + "Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved." + ) { + core.info( + `No cacheable ${cacheName} paths were found. Ignoring because ignore-nothing-to-save is enabled.`, + ); + } else { + throw e; + } + } +} + run(); diff --git a/src/setup-uv.ts b/src/setup-uv.ts index c6829a9..22d8ca8 100644 --- a/src/setup-uv.ts +++ b/src/setup-uv.ts @@ -36,6 +36,37 @@ import { } from "./utils/platforms"; import { getUvVersionFromFile } from "./version/resolve"; +async function getPythonVersion(): Promise { + if (pythonVersion !== "") { + return pythonVersion; + } + + let output = ""; + const options: exec.ExecOptions = { + listeners: { + stdout: (data: Buffer) => { + output += data.toString(); + }, + }, + silent: !core.isDebug(), + }; + + try { + const execArgs = ["python", "find", "--directory", workingDirectory]; + await exec.exec("uv", execArgs, options); + const pythonPath = output.trim(); + + output = ""; + await exec.exec(pythonPath, ["--version"], options); + // output is like "Python 3.8.10" + return output.split(" ")[1].trim(); + } catch (error) { + const err = error as Error; + core.debug(`Failed to get python version from uv. Error: ${err.message}`); + return "unknown"; + } +} + async function run(): Promise { detectEmptyWorkdir(); const platform = await getPlatform(); @@ -63,8 +94,11 @@ async function run(): Promise { core.saveState(STATE_UV_VERSION, setupResult.version); core.info(`Successfully installed uv version ${setupResult.version}`); + const pythonVersion = await getPythonVersion(); + core.setOutput("python-version", pythonVersion); + if (enableCache) { - await restoreCache(); + await restoreCache(pythonVersion); } // https://github.com/nodejs/node/issues/56645#issuecomment-3077594952 await new Promise((resolve) => setTimeout(resolve, 50));