mirror of
https://github.com/astral-sh/setup-uv.git
synced 2026-02-07 18:17:24 +00:00
feat: add OS version to cache key to prevent binary incompatibility
Adds OS name and version (e.g., ubuntu-22.04, macos-14, windows-2022) to the cache key to prevent binary incompatibility issues when GitHub runner images change. Changes: - Add getOSNameVersion() to detect OS name and version - Include OS version in cache key (bump CACHE_VERSION to 2) - Add cache-key output for debugging and testing - Add test-cache-key-os-version workflow job - Update test-musl to verify alpine in cache key - Document cache key components in docs/caching.md Closes #703
This commit is contained in:
parent
e8b52af86e
commit
7ace4a0ece
8 changed files with 610 additions and 7 deletions
53
.github/workflows/test.yml
vendored
53
.github/workflows/test.yml
vendored
|
|
@ -385,10 +385,62 @@ jobs:
|
|||
with:
|
||||
persist-credentials: false
|
||||
- name: Install latest version
|
||||
id: setup-uv
|
||||
uses: ./
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Verify cache key contains alpine
|
||||
run: |
|
||||
echo "Cache key: $CACHE_KEY"
|
||||
if echo "$CACHE_KEY" | grep -qv "alpine"; then
|
||||
echo "Cache key does not contain 'alpine'"
|
||||
exit 1
|
||||
fi
|
||||
shell: sh
|
||||
env:
|
||||
CACHE_KEY: ${{ steps.setup-uv.outputs.cache-key }}
|
||||
- run: uv sync
|
||||
working-directory: __tests__/fixtures/uv-project
|
||||
|
||||
test-cache-key-os-version:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
expected-os: "ubuntu-22.04"
|
||||
- os: ubuntu-24.04
|
||||
expected-os: "ubuntu-24.04"
|
||||
- os: macos-13
|
||||
expected-os: "macos-13"
|
||||
- os: macos-14
|
||||
expected-os: "macos-14"
|
||||
- os: macos-15
|
||||
expected-os: "macos-15"
|
||||
- os: windows-2022
|
||||
expected-os: "windows-2022"
|
||||
- os: windows-2025
|
||||
expected-os: "windows-2025"
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup uv
|
||||
id: setup-uv
|
||||
uses: ./
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Verify cache key contains OS version
|
||||
run: |
|
||||
echo "Cache key: $CACHE_KEY"
|
||||
if [[ "$CACHE_KEY" != *"${{ matrix.expected-os }}"* ]]; then
|
||||
echo "Cache key does not contain expected OS version: ${{ matrix.expected-os }}"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
env:
|
||||
CACHE_KEY: ${{ steps.setup-uv.outputs.cache-key }}
|
||||
|
||||
test-setup-cache:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
|
|
@ -1002,6 +1054,7 @@ jobs:
|
|||
- test-python-version
|
||||
- test-activate-environment
|
||||
- test-musl
|
||||
- test-cache-key-os-version
|
||||
- test-cache-local
|
||||
- test-cache-local-cache-disabled
|
||||
- test-cache-local-cache-disabled-but-explicit-path
|
||||
|
|
|
|||
302
PLAN.md
Normal file
302
PLAN.md
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
# Plan: OS-Version Specific Cache Keys
|
||||
|
||||
## Issue
|
||||
|
||||
GitHub Issue #703: Users need OS-version specific cache keys to avoid binary incompatibility when GitHub runner images change.
|
||||
|
||||
**Problem**: The current cache key uses `process.platform` which produces generic identifiers like `unknown-linux-gnu`. This causes issues when:
|
||||
- Workflows run on different runner OS versions (e.g., `ubuntu-20.04` vs `ubuntu-22.04`)
|
||||
- GitHub updates the runner image
|
||||
- Cached binary artifacts (compiled native extensions) are incompatible with different `glibc` versions
|
||||
|
||||
**Solution**: Include OS name and version in cache keys (e.g., `ubuntu-22.04`, `macos-14`, `windows-2022`).
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Add OS Version Detection (`src/utils/platforms.ts`)
|
||||
|
||||
Add a new `getOSNameVersion()` function that detects the OS name and version:
|
||||
|
||||
**Linux**:
|
||||
- Read `/etc/os-release` (fallback to `/usr/lib/os-release`)
|
||||
- Parse `ID` field (e.g., `ubuntu`, `debian`, `fedora`, `alpine`)
|
||||
- Parse `VERSION_ID` field (e.g., `22.04`, `24.04`)
|
||||
- Return: `ubuntu-22.04`, `alpine-3.18`, etc.
|
||||
|
||||
**macOS**:
|
||||
- Use `os.release()` to get Darwin kernel version
|
||||
- Convert to macOS version: `macOS version = Darwin major version - 9`
|
||||
- Return: `macos-14`, `macos-15`, etc.
|
||||
|
||||
**Windows**:
|
||||
- Use `os.version()` which returns e.g., `Windows Server 2022 Datacenter`
|
||||
- Parse with regex to extract version number
|
||||
- Return: `windows-2022`, `windows-2025`, etc.
|
||||
|
||||
**Error Handling**: If OS detection fails, the action will fail with a clear error message.
|
||||
|
||||
```typescript
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
|
||||
export function getOSNameVersion(): string {
|
||||
const platform = process.platform;
|
||||
|
||||
if (platform === "linux") {
|
||||
return getLinuxOSNameVersion();
|
||||
} else if (platform === "darwin") {
|
||||
return getMacOSNameVersion();
|
||||
} else if (platform === "win32") {
|
||||
return getWindowsNameVersion();
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
|
||||
function getLinuxOSNameVersion(): string {
|
||||
const files = ["/etc/os-release", "/usr/lib/os-release"];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = fs.readFileSync(file, "utf8");
|
||||
const id = parseOsReleaseValue(content, "ID");
|
||||
const versionId = parseOsReleaseValue(content, "VERSION_ID");
|
||||
|
||||
if (id && versionId) {
|
||||
return `${id}-${versionId}`;
|
||||
}
|
||||
} catch {
|
||||
// Try next file
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"Failed to determine Linux distribution. " +
|
||||
"Could not read /etc/os-release or /usr/lib/os-release"
|
||||
);
|
||||
}
|
||||
|
||||
function parseOsReleaseValue(content: string, key: string): string | undefined {
|
||||
const regex = new RegExp(`^${key}=["']?([^"'\\n]*)["']?$`, "m");
|
||||
const match = content.match(regex);
|
||||
return match?.[1];
|
||||
}
|
||||
|
||||
function getMacOSNameVersion(): string {
|
||||
const darwinVersion = parseInt(os.release().split(".")[0], 10);
|
||||
if (isNaN(darwinVersion)) {
|
||||
throw new Error(`Failed to parse macOS version from: ${os.release()}`);
|
||||
}
|
||||
const macosVersion = darwinVersion - 9;
|
||||
return `macos-${macosVersion}`;
|
||||
}
|
||||
|
||||
function getWindowsNameVersion(): string {
|
||||
const version = os.version();
|
||||
const match = version.match(/Windows(?: Server)? (\d+)/);
|
||||
if (!match) {
|
||||
throw new Error(`Failed to parse Windows version from: ${version}`);
|
||||
}
|
||||
return `windows-${match[1]}`;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Update Cache Key Generation (`src/cache/restore-cache.ts`)
|
||||
|
||||
**Bump cache version** from `"1"` to `"2"` to invalidate old caches.
|
||||
|
||||
**Update `computeKeys()`** to include OS version:
|
||||
|
||||
```typescript
|
||||
import { getArch, getOSNameVersion, getPlatform } from "../utils/platforms";
|
||||
|
||||
const CACHE_VERSION = "2";
|
||||
|
||||
async function computeKeys(): Promise<string> {
|
||||
// ... existing code for cacheDependencyPathHash ...
|
||||
|
||||
const suffix = cacheSuffix ? `-${cacheSuffix}` : "";
|
||||
const pythonVersion = await getPythonVersion();
|
||||
const platform = await getPlatform();
|
||||
const osNameVersion = getOSNameVersion(); // NEW
|
||||
const pruned = pruneCache ? "-pruned" : "";
|
||||
const python = cachePython ? "-py" : "";
|
||||
return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${osNameVersion}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
|
||||
}
|
||||
```
|
||||
|
||||
**Add `cache-key` output** in `restoreCache()`:
|
||||
|
||||
```typescript
|
||||
export async function restoreCache(): Promise<void> {
|
||||
const cacheKey = await computeKeys();
|
||||
core.saveState(STATE_CACHE_KEY, cacheKey);
|
||||
core.setOutput("cache-key", cacheKey); // NEW
|
||||
// ... rest of function
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Add Output Definition (`action.yml`)
|
||||
|
||||
Add new output:
|
||||
|
||||
```yaml
|
||||
outputs:
|
||||
uv-version:
|
||||
description: "The installed uv version. Useful when using latest."
|
||||
uv-path:
|
||||
description: "The path to the installed uv binary."
|
||||
uvx-path:
|
||||
description: "The path to the installed uvx binary."
|
||||
cache-hit:
|
||||
description: "A boolean value to indicate a cache entry was found"
|
||||
cache-key:
|
||||
description: "The cache key used for storing/restoring the cache"
|
||||
venv:
|
||||
description: "Path to the activated venv if activate-environment is true"
|
||||
```
|
||||
|
||||
### 4. Add Workflow Tests (`.github/workflows/test.yml`)
|
||||
|
||||
Add new test job to verify cache keys contain expected OS versions:
|
||||
|
||||
```yaml
|
||||
test-cache-key-os-version:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
expected-os: "ubuntu-22.04"
|
||||
- os: ubuntu-24.04
|
||||
expected-os: "ubuntu-24.04"
|
||||
- os: macos-13
|
||||
expected-os: "macos-13"
|
||||
- os: macos-14
|
||||
expected-os: "macos-14"
|
||||
- os: macos-15
|
||||
expected-os: "macos-15"
|
||||
- os: windows-2022
|
||||
expected-os: "windows-2022"
|
||||
- os: windows-2025
|
||||
expected-os: "windows-2025"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup uv
|
||||
id: setup-uv
|
||||
uses: ./
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Verify cache key contains OS version
|
||||
run: |
|
||||
echo "Cache key: $CACHE_KEY"
|
||||
if [[ "$CACHE_KEY" != *"${{ matrix.expected-os }}"* ]]; then
|
||||
echo "Cache key does not contain expected OS version: ${{ matrix.expected-os }}"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
env:
|
||||
CACHE_KEY: ${{ steps.setup-uv.outputs.cache-key }}
|
||||
```
|
||||
|
||||
Update existing `test-musl` job to also verify cache key:
|
||||
|
||||
```yaml
|
||||
test-musl:
|
||||
runs-on: ubuntu-latest
|
||||
container: alpine
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install latest version
|
||||
id: setup-uv
|
||||
uses: ./
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Verify cache key contains alpine
|
||||
run: |
|
||||
echo "Cache key: $CACHE_KEY"
|
||||
if echo "$CACHE_KEY" | grep -qv "alpine"; then
|
||||
echo "Cache key does not contain 'alpine'"
|
||||
exit 1
|
||||
fi
|
||||
shell: sh
|
||||
env:
|
||||
CACHE_KEY: ${{ steps.setup-uv.outputs.cache-key }}
|
||||
- run: uv sync
|
||||
working-directory: __tests__/fixtures/uv-project
|
||||
```
|
||||
|
||||
Add `test-cache-key-os-version` to `all-tests-passed` needs list.
|
||||
|
||||
### 5. Update Documentation (`docs/caching.md`)
|
||||
|
||||
Add section explaining the new cache key behavior:
|
||||
|
||||
```markdown
|
||||
## Cache key components
|
||||
|
||||
The cache key is automatically generated based on:
|
||||
|
||||
- **Architecture**: CPU architecture (e.g., `x86_64`, `aarch64`)
|
||||
- **Platform**: OS platform type (e.g., `unknown-linux-gnu`, `unknown-linux-musl`, `apple-darwin`, `pc-windows-msvc`)
|
||||
- **OS version**: OS name and version (e.g., `ubuntu-22.04`, `macos-14`, `windows-2022`)
|
||||
- **Python version**: The Python version in use
|
||||
- **Cache options**: Whether pruning and Python caching are enabled
|
||||
- **Dependency hash**: Hash of files matching `cache-dependency-glob`
|
||||
- **Suffix**: Optional `cache-suffix` if provided
|
||||
|
||||
This ensures that caches are not shared between different OS versions, preventing binary incompatibility issues when runner images change.
|
||||
|
||||
The computed cache key is available as the `cache-key` output:
|
||||
|
||||
\`\`\`yaml
|
||||
- name: Setup uv
|
||||
id: setup-uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Print cache key
|
||||
run: echo "Cache key: ${{ steps.setup-uv.outputs.cache-key }}"
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
## Cache Key Format
|
||||
|
||||
**Before (v1)**:
|
||||
```
|
||||
setup-uv-1-x86_64-unknown-linux-gnu-3.11.0-pruned-abc123
|
||||
```
|
||||
|
||||
**After (v2)**:
|
||||
```
|
||||
setup-uv-2-x86_64-unknown-linux-gnu-ubuntu-22.04-3.11.0-pruned-abc123
|
||||
```
|
||||
|
||||
The existing `platform` component (`unknown-linux-gnu`) is kept because it distinguishes glibc vs musl libc, which is still important.
|
||||
|
||||
## Files to Modify
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `src/utils/platforms.ts` | Add `getOSNameVersion()`, `getLinuxOSNameVersion()`, `getMacOSNameVersion()`, `getWindowsNameVersion()`, `parseOsReleaseValue()` |
|
||||
| `src/cache/restore-cache.ts` | Import `getOSNameVersion`; bump `CACHE_VERSION` to `"2"`; add OS version to cache key; add `cache-key` output |
|
||||
| `action.yml` | Add `cache-key` output definition |
|
||||
| `.github/workflows/test.yml` | Add `test-cache-key-os-version` job; update `test-musl` to verify cache key; update `all-tests-passed` needs |
|
||||
| `docs/caching.md` | Document cache key components and new `cache-key` output |
|
||||
|
||||
## Backwards Compatibility
|
||||
|
||||
- Bumping `CACHE_VERSION` to `"2"` ensures old caches are not reused
|
||||
- Users will experience a one-time cache miss after upgrading
|
||||
- This is intentional to ensure all caches have OS-version awareness
|
||||
|
||||
## Error Handling
|
||||
|
||||
If OS detection fails, the action fails with a clear error message. This is intentional because:
|
||||
1. Using an incompatible cache is worse than no cache
|
||||
2. If `/etc/os-release` doesn't exist, we're on an unusual system that may have other issues
|
||||
3. Clear errors help users understand what's happening
|
||||
|
|
@ -89,6 +89,8 @@ outputs:
|
|||
description: "The path to the installed uvx binary."
|
||||
cache-hit:
|
||||
description: "A boolean value to indicate a cache entry was found"
|
||||
cache-key:
|
||||
description: "The cache key used for storing/restoring the cache"
|
||||
venv:
|
||||
description: "Path to the activated venv if activate-environment is true"
|
||||
runs:
|
||||
|
|
|
|||
77
dist/save-cache/index.js
generated
vendored
77
dist/save-cache/index.js
generated
vendored
|
|
@ -90609,10 +90609,11 @@ 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";
|
||||
const CACHE_VERSION = "1";
|
||||
const CACHE_VERSION = "2";
|
||||
async function restoreCache() {
|
||||
const cacheKey = await computeKeys();
|
||||
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.");
|
||||
return;
|
||||
|
|
@ -90652,9 +90653,10 @@ async function computeKeys() {
|
|||
const suffix = inputs_1.cacheSuffix ? `-${inputs_1.cacheSuffix}` : "";
|
||||
const pythonVersion = await getPythonVersion();
|
||||
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}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
|
||||
return `setup-uv-${CACHE_VERSION}-${(0, platforms_1.getArch)()}-${platform}-${osNameVersion}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
|
||||
}
|
||||
async function getPythonVersion() {
|
||||
if (inputs_1.pythonVersion !== "") {
|
||||
|
|
@ -91282,9 +91284,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.getArch = getArch;
|
||||
exports.getPlatform = getPlatform;
|
||||
exports.getOSNameVersion = getOSNameVersion;
|
||||
const node_fs_1 = __importDefault(__nccwpck_require__(3024));
|
||||
const node_os_1 = __importDefault(__nccwpck_require__(8161));
|
||||
const core = __importStar(__nccwpck_require__(7484));
|
||||
const exec = __importStar(__nccwpck_require__(5236));
|
||||
function getArch() {
|
||||
|
|
@ -91342,6 +91350,63 @@ async function isMuslOs() {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns OS name and version for cache key differentiation.
|
||||
* Examples: "ubuntu-22.04", "macos-14", "windows-2022"
|
||||
* Throws if OS detection fails.
|
||||
*/
|
||||
function getOSNameVersion() {
|
||||
const platform = process.platform;
|
||||
if (platform === "linux") {
|
||||
return getLinuxOSNameVersion();
|
||||
}
|
||||
if (platform === "darwin") {
|
||||
return getMacOSNameVersion();
|
||||
}
|
||||
if (platform === "win32") {
|
||||
return getWindowsNameVersion();
|
||||
}
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
function getLinuxOSNameVersion() {
|
||||
const files = ["/etc/os-release", "/usr/lib/os-release"];
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = node_fs_1.default.readFileSync(file, "utf8");
|
||||
const id = parseOsReleaseValue(content, "ID");
|
||||
const versionId = parseOsReleaseValue(content, "VERSION_ID");
|
||||
if (id && versionId) {
|
||||
return `${id}-${versionId}`;
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Try next file
|
||||
}
|
||||
}
|
||||
throw new Error("Failed to determine Linux distribution. " +
|
||||
"Could not read /etc/os-release or /usr/lib/os-release");
|
||||
}
|
||||
function parseOsReleaseValue(content, key) {
|
||||
const regex = new RegExp(`^${key}=["']?([^"'\\n]*)["']?$`, "m");
|
||||
const match = content.match(regex);
|
||||
return match?.[1];
|
||||
}
|
||||
function getMacOSNameVersion() {
|
||||
const darwinVersion = Number.parseInt(node_os_1.default.release().split(".")[0], 10);
|
||||
if (Number.isNaN(darwinVersion)) {
|
||||
throw new Error(`Failed to parse macOS version from: ${node_os_1.default.release()}`);
|
||||
}
|
||||
const macosVersion = darwinVersion - 9;
|
||||
return `macos-${macosVersion}`;
|
||||
}
|
||||
function getWindowsNameVersion() {
|
||||
const version = node_os_1.default.version();
|
||||
const match = version.match(/Windows(?: Server)? (\d+)/);
|
||||
if (!match) {
|
||||
throw new Error(`Failed to parse Windows version from: ${version}`);
|
||||
}
|
||||
return `windows-${match[1]}`;
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
|
@ -91482,6 +91547,14 @@ module.exports = require("node:fs");
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ 8161:
|
||||
/***/ ((module) => {
|
||||
|
||||
"use strict";
|
||||
module.exports = require("node:os");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 6760:
|
||||
/***/ ((module) => {
|
||||
|
||||
|
|
|
|||
77
dist/setup/index.js
generated
vendored
77
dist/setup/index.js
generated
vendored
|
|
@ -91512,10 +91512,11 @@ 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";
|
||||
const CACHE_VERSION = "1";
|
||||
const CACHE_VERSION = "2";
|
||||
async function restoreCache() {
|
||||
const cacheKey = await computeKeys();
|
||||
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.");
|
||||
return;
|
||||
|
|
@ -91555,9 +91556,10 @@ async function computeKeys() {
|
|||
const suffix = inputs_1.cacheSuffix ? `-${inputs_1.cacheSuffix}` : "";
|
||||
const pythonVersion = await getPythonVersion();
|
||||
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}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
|
||||
return `setup-uv-${CACHE_VERSION}-${(0, platforms_1.getArch)()}-${platform}-${osNameVersion}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
|
||||
}
|
||||
async function getPythonVersion() {
|
||||
if (inputs_1.pythonVersion !== "") {
|
||||
|
|
@ -96791,9 +96793,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.getArch = getArch;
|
||||
exports.getPlatform = getPlatform;
|
||||
exports.getOSNameVersion = getOSNameVersion;
|
||||
const node_fs_1 = __importDefault(__nccwpck_require__(3024));
|
||||
const node_os_1 = __importDefault(__nccwpck_require__(8161));
|
||||
const core = __importStar(__nccwpck_require__(7484));
|
||||
const exec = __importStar(__nccwpck_require__(5236));
|
||||
function getArch() {
|
||||
|
|
@ -96851,6 +96859,63 @@ async function isMuslOs() {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns OS name and version for cache key differentiation.
|
||||
* Examples: "ubuntu-22.04", "macos-14", "windows-2022"
|
||||
* Throws if OS detection fails.
|
||||
*/
|
||||
function getOSNameVersion() {
|
||||
const platform = process.platform;
|
||||
if (platform === "linux") {
|
||||
return getLinuxOSNameVersion();
|
||||
}
|
||||
if (platform === "darwin") {
|
||||
return getMacOSNameVersion();
|
||||
}
|
||||
if (platform === "win32") {
|
||||
return getWindowsNameVersion();
|
||||
}
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
function getLinuxOSNameVersion() {
|
||||
const files = ["/etc/os-release", "/usr/lib/os-release"];
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = node_fs_1.default.readFileSync(file, "utf8");
|
||||
const id = parseOsReleaseValue(content, "ID");
|
||||
const versionId = parseOsReleaseValue(content, "VERSION_ID");
|
||||
if (id && versionId) {
|
||||
return `${id}-${versionId}`;
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Try next file
|
||||
}
|
||||
}
|
||||
throw new Error("Failed to determine Linux distribution. " +
|
||||
"Could not read /etc/os-release or /usr/lib/os-release");
|
||||
}
|
||||
function parseOsReleaseValue(content, key) {
|
||||
const regex = new RegExp(`^${key}=["']?([^"'\\n]*)["']?$`, "m");
|
||||
const match = content.match(regex);
|
||||
return match?.[1];
|
||||
}
|
||||
function getMacOSNameVersion() {
|
||||
const darwinVersion = Number.parseInt(node_os_1.default.release().split(".")[0], 10);
|
||||
if (Number.isNaN(darwinVersion)) {
|
||||
throw new Error(`Failed to parse macOS version from: ${node_os_1.default.release()}`);
|
||||
}
|
||||
const macosVersion = darwinVersion - 9;
|
||||
return `macos-${macosVersion}`;
|
||||
}
|
||||
function getWindowsNameVersion() {
|
||||
const version = node_os_1.default.version();
|
||||
const match = version.match(/Windows(?: Server)? (\d+)/);
|
||||
if (!match) {
|
||||
throw new Error(`Failed to parse Windows version from: ${version}`);
|
||||
}
|
||||
return `windows-${match[1]}`;
|
||||
}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
|
@ -97216,6 +97281,14 @@ module.exports = require("node:fs");
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ 8161:
|
||||
/***/ ((module) => {
|
||||
|
||||
"use strict";
|
||||
module.exports = require("node:os");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 6760:
|
||||
/***/ ((module) => {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,34 @@
|
|||
|
||||
This document covers all caching-related configuration options for setup-uv.
|
||||
|
||||
## Cache key
|
||||
|
||||
The cache key is automatically generated based on:
|
||||
|
||||
- **Architecture**: CPU architecture (e.g., `x86_64`, `aarch64`)
|
||||
- **Platform**: OS platform type (e.g., `unknown-linux-gnu`, `unknown-linux-musl`, `apple-darwin`,
|
||||
`pc-windows-msvc`)
|
||||
- **OS version**: OS name and version (e.g., `ubuntu-22.04`, `macos-14`, `windows-2022`)
|
||||
- **Python version**: The Python version in use
|
||||
- **Cache options**: Whether pruning and Python caching are enabled
|
||||
- **Dependency hash**: Hash of files matching `cache-dependency-glob`
|
||||
- **Suffix**: Optional `cache-suffix` if provided
|
||||
|
||||
Including the OS version ensures that caches are not shared between different OS versions,
|
||||
preventing binary incompatibility issues when runner images change.
|
||||
|
||||
The computed cache key is available as the `cache-key` output:
|
||||
|
||||
```yaml
|
||||
- name: Setup uv
|
||||
id: setup-uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Print cache key
|
||||
run: echo "Cache key: ${{ steps.setup-uv.outputs.cache-key }}"
|
||||
```
|
||||
|
||||
## Enable caching
|
||||
|
||||
> [!NOTE]
|
||||
|
|
|
|||
8
src/cache/restore-cache.ts
vendored
8
src/cache/restore-cache.ts
vendored
|
|
@ -13,15 +13,16 @@ import {
|
|||
restoreCache as shouldRestoreCache,
|
||||
workingDirectory,
|
||||
} from "../utils/inputs";
|
||||
import { getArch, getPlatform } from "../utils/platforms";
|
||||
import { getArch, getOSNameVersion, getPlatform } from "../utils/platforms";
|
||||
|
||||
export const STATE_CACHE_KEY = "cache-key";
|
||||
export const STATE_CACHE_MATCHED_KEY = "cache-matched-key";
|
||||
const CACHE_VERSION = "1";
|
||||
const CACHE_VERSION = "2";
|
||||
|
||||
export async function restoreCache(): Promise<void> {
|
||||
const cacheKey = await computeKeys();
|
||||
core.saveState(STATE_CACHE_KEY, cacheKey);
|
||||
core.setOutput("cache-key", cacheKey);
|
||||
|
||||
if (!shouldRestoreCache) {
|
||||
core.info("restore-cache is false. Skipping restore cache step.");
|
||||
|
|
@ -72,9 +73,10 @@ async function computeKeys(): Promise<string> {
|
|||
const suffix = cacheSuffix ? `-${cacheSuffix}` : "";
|
||||
const pythonVersion = await getPythonVersion();
|
||||
const platform = await getPlatform();
|
||||
const osNameVersion = getOSNameVersion();
|
||||
const pruned = pruneCache ? "-pruned" : "";
|
||||
const python = cachePython ? "-py" : "";
|
||||
return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
|
||||
return `setup-uv-${CACHE_VERSION}-${getArch()}-${platform}-${osNameVersion}-${pythonVersion}${pruned}${python}${cacheDependencyPathHash}${suffix}`;
|
||||
}
|
||||
|
||||
async function getPythonVersion(): Promise<string> {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import * as core from "@actions/core";
|
||||
import * as exec from "@actions/exec";
|
||||
export type Platform =
|
||||
|
|
@ -74,3 +76,71 @@ async function isMuslOs(): Promise<boolean> {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns OS name and version for cache key differentiation.
|
||||
* Examples: "ubuntu-22.04", "macos-14", "windows-2022"
|
||||
* Throws if OS detection fails.
|
||||
*/
|
||||
export function getOSNameVersion(): string {
|
||||
const platform = process.platform;
|
||||
|
||||
if (platform === "linux") {
|
||||
return getLinuxOSNameVersion();
|
||||
}
|
||||
if (platform === "darwin") {
|
||||
return getMacOSNameVersion();
|
||||
}
|
||||
if (platform === "win32") {
|
||||
return getWindowsNameVersion();
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
|
||||
function getLinuxOSNameVersion(): string {
|
||||
const files = ["/etc/os-release", "/usr/lib/os-release"];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = fs.readFileSync(file, "utf8");
|
||||
const id = parseOsReleaseValue(content, "ID");
|
||||
const versionId = parseOsReleaseValue(content, "VERSION_ID");
|
||||
|
||||
if (id && versionId) {
|
||||
return `${id}-${versionId}`;
|
||||
}
|
||||
} catch {
|
||||
// Try next file
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"Failed to determine Linux distribution. " +
|
||||
"Could not read /etc/os-release or /usr/lib/os-release",
|
||||
);
|
||||
}
|
||||
|
||||
function parseOsReleaseValue(content: string, key: string): string | undefined {
|
||||
const regex = new RegExp(`^${key}=["']?([^"'\\n]*)["']?$`, "m");
|
||||
const match = content.match(regex);
|
||||
return match?.[1];
|
||||
}
|
||||
|
||||
function getMacOSNameVersion(): string {
|
||||
const darwinVersion = Number.parseInt(os.release().split(".")[0], 10);
|
||||
if (Number.isNaN(darwinVersion)) {
|
||||
throw new Error(`Failed to parse macOS version from: ${os.release()}`);
|
||||
}
|
||||
const macosVersion = darwinVersion - 9;
|
||||
return `macos-${macosVersion}`;
|
||||
}
|
||||
|
||||
function getWindowsNameVersion(): string {
|
||||
const version = os.version();
|
||||
const match = version.match(/Windows(?: Server)? (\d+)/);
|
||||
if (!match) {
|
||||
throw new Error(`Failed to parse Windows version from: ${version}`);
|
||||
}
|
||||
return `windows-${match[1]}`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue