From b7255b1fa0ecbddf739e8c4fc6dc64f3de496d2c Mon Sep 17 00:00:00 2001 From: Kevin Stillhammer Date: Sat, 13 Dec 2025 17:08:35 +0100 Subject: [PATCH] fix: remove retired macos-13 from test matrix --- .github/workflows/test.yml | 2 - PLAN.md | 302 ------------------------------------- 2 files changed, 304 deletions(-) delete mode 100644 PLAN.md diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dfedf8a..966901a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -411,8 +411,6 @@ jobs: 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 diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index eaeba63..0000000 --- a/PLAN.md +++ /dev/null @@ -1,302 +0,0 @@ -# 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 { - // ... 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 { - 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