Skip to content

ci(vcpkg): Switch binary cache from local files to GitHub Packages NuGet feed#2542

Open
bobtista wants to merge 3 commits into
TheSuperHackers:mainfrom
bobtista:bobtista/ci/vcpkg-nuget-binary-cache
Open

ci(vcpkg): Switch binary cache from local files to GitHub Packages NuGet feed#2542
bobtista wants to merge 3 commits into
TheSuperHackers:mainfrom
bobtista:bobtista/ci/vcpkg-nuget-binary-cache

Conversation

@bobtista

@bobtista bobtista commented Apr 6, 2026

Copy link
Copy Markdown

Follow up for #2371 and #2515.

Replaces the local file-based vcpkg binary cache (actions/cache + files backend) with a NuGet feed hosted on GitHub Packages. This is a structural change to how vcpkg caching works in CI.

Problem

The previous approach used actions/cache with vcpkg's files binary source. This required our GHA cache key to perfectly predict vcpkg's ABI hash output — two separate systems we were trying to keep in sync by hand. Any discrepancy (compiler updates on the runner, host triplet ABI drift, toolchain changes) caused a doom loop: the GHA cache key matched an old entry, vcpkg found zero usable binaries inside it, rebuilt everything (~17 min for ffmpeg), and the save step was skipped because cache-hit was true. PRs #1862, #1973, #2028, #2371, and #2515 each fixed a specific trigger but left the structural vulnerability intact.

Solution

With NuGet binary caching, vcpkg uses its own ABI hashes directly as package identifiers in the feed. If a compiler update changes an ABI hash, vcpkg asks the feed for a package with the new hash, doesn't find it, builds once, uploads it, and every subsequent run restores it. There is no GHA cache key to get wrong, no save gate, and no doom loop possible. Based on the same pattern being used in the canonical/multipass repo

Changes

  • Replace VCPKG_BINARY_SOURCES from files backend to nuget backend pointing at GitHub Packages
  • Add VCPKG_NUGET_REPOSITORY for GitHub Packages package-repository association
  • Add NuGet source authentication step using GITHUB_TOKEN
  • Add packages: write permission to both ci.yml and build-toolchain.yml
  • Remove: cache key computation, actions/cache/restore, actions/cache/save, local cache directory setup

Test plan

  • Tested on fork (bobtista/GeneralsGameCode) — cold build uploaded packages successfully
  • Second run restored 7 packages from NuGet in 3.7s, ffmpeg installed in 28.8ms
  • Total job time dropped from ~24 min to ~7 min on cache hit
  • First CI run after merge will be a cold build (~24 min), subsequent runs should restore from feed

@greptile-apps

greptile-apps Bot commented Apr 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces the file-based vcpkg binary cache (actions/cache + files backend) with a GitHub Packages NuGet feed, delegating cache key management entirely to vcpkg's own ABI hashes and eliminating the doom-loop caused by GHA cache key / ABI hash mismatches.

  • Removes cache key computation, actions/cache/restore, and actions/cache/save steps from build-toolchain.yml; adds a NuGet source authentication step using GITHUB_TOKEN and the packages: write permission to both workflow files.
  • Tightens the vcpkg step conditions from startsWith(inputs.preset, 'win32') to contains(inputs.preset, 'vcpkg'), so vcpkg setup and NuGet auth are skipped for non-vcpkg presets.
  • Adds fetch-depth: 0 to the detect-changes checkout in ci.yml, enabling dorny/paths-filter to correctly compare commits on push events when running without a token.

Confidence Score: 5/5

Safe to merge; the structural caching change is sound and has been validated on a fork.

The core change delegates cache identity entirely to vcpkg's ABI hashes via GitHub Packages, cleanly removing the hand-rolled key synchronisation that caused repeated doom loops. The NuGet auth pattern matches the canonical vcpkg approach, and step conditions are correctly narrowed to vcpkg presets only.

No files require special attention.

Important Files Changed

Filename Overview
.github/workflows/build-toolchain.yml Replaces file-based cache steps with NuGet source authentication; condition narrowed from win32 to vcpkg-only presets; VCPKG_OVERLAY_TRIPLETS and VCPKG_INSTALL_OPTIONS correctly preserved in the new step.
.github/workflows/ci.yml Adds packages: write permission and fetch-depth: 0 to detect-changes checkout; fetch-depth: 0 fetches full history on every run, which may be slower than a shallow fetch for large repos.

Reviews (3): Last reviewed commit: "ci: Fetch full history for change detect..." | Re-trigger Greptile

@bobtista

bobtista commented Apr 6, 2026

Copy link
Copy Markdown
Author

Greptile Summary

This PR replaces the fragile actions/cache + files vcpkg binary cache with a GitHub Packages NuGet feed, eliminating the GHA cache-key / vcpkg ABI-hash synchronization doom loop described in the PR description. The structural change is sound and fork-tested.

  • build-toolchain.yml: VCPKG_BINARY_SOURCES now points at the NuGet feed; a new step fetches vcpkg's bundled NuGet binary and registers the GitHub Packages source with GITHUB_TOKEN credentials; the old cache-key computation, actions/cache/restore, directory setup, and actions/cache/save steps are all removed.
  • ci.yml: packages: write added so the GITHUB_TOKEN issued to calling jobs has sufficient scope to push packages to GitHub Packages (required because GitHub Actions intersects caller and callee permissions for reusable workflows).
  • Minor: nuget sources add is not idempotent — a step retry or self-hosted runner reuse would cause a "Source already exists" failure. Replacing with an existence check + update fallback would make the step more resilient.

Confidence Score: 4/5

Safe to merge; the NuGet-based caching approach is sound and fork-tested, with only a minor idempotency edge case on step retries.

The structural approach is correct and well-understood (vcpkg + GitHub Packages NuGet is a documented pattern). packages:write is correctly added to both caller and callee workflows. The only concern is nuget sources add failing on retry or self-hosted runners, which is low-probability on ephemeral GitHub-hosted runners. The first post-merge run will be a cold build but subsequent runs should be fast.

.github/workflows/build-toolchain.yml — specifically the NuGet auth step around lines 119-128 for the idempotency concern.

Important Files Changed

Filename Overview
.github/workflows/build-toolchain.yml Replaces files-based vcpkg binary cache with GitHub Packages NuGet feed; adds packages:write permission, NuGet auth step via vcpkg-bundled nuget binary, and removes old cache key/restore/save steps — minor idempotency concern on nuget sources add
.github/workflows/ci.yml Adds packages:write permission to the caller workflow so the GITHUB_TOKEN can write to GitHub Packages when delegated through the reusable build-toolchain.yml

Sequence Diagram

sequenceDiagram
    participant GHA as GitHub Actions
    participant vRun as lukka/run-vcpkg
    participant vcpkg as vcpkg.exe
    participant NuGet as NuGet CLI
    participant GPkg as GitHub Packages

    GHA->>vRun: Setup vcpkg (doNotCache:true)
    vRun-->>GHA: VCPKG_ROOT set
    GHA->>vcpkg: fetch nuget
    vcpkg-->>GHA: path/to/nuget.exe
    GHA->>NuGet: sources add GitHubPackages (GITHUB_TOKEN)
    NuGet-->>GHA: source registered
    GHA->>NuGet: setapikey (GITHUB_TOKEN)
    NuGet-->>GHA: API key stored
    GHA->>vcpkg: cmake --preset (triggers vcpkg install)
    vcpkg->>GPkg: GET package@{abi-hash}
    alt cache hit
        GPkg-->>vcpkg: package restored (e.g. 28.8ms for ffmpeg)
    else cache miss
        GPkg-->>vcpkg: 404 Not Found
        vcpkg->>vcpkg: build from source
        vcpkg->>GPkg: PUT package@{abi-hash}
    end
Loading

Loading
Prompt To Fix All With AI

This is a comment left during a code review.
Path: .github/workflows/build-toolchain.yml
Line: 120-127

Comment:
**`nuget sources add` is not idempotent**

If this step is retried (e.g., via GitHub Actions "Re-run failed jobs") or runs on a self-hosted runner where the `GitHubPackages` source was previously registered, `nuget sources add` will exit with "Source already exists" and fail the step. For ephemeral GitHub-hosted runners this is fine, but wrapping it in an existence check would make the step resilient to retries:

$src = "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json"
$exists = (& $nuget sources list) -match "GitHubPackages"
if ($exists) {
& $nuget sources update -Name GitHubPackages -Source $src -StorePasswordInClearText
-UserName "${{ github.repository_owner }}" -Password "${{ secrets.GITHUB_TOKEN }}" } else { & $nuget sources add -Name GitHubPackages -Source $src
-StorePasswordInClearText -UserName "${{ github.repository_owner }}"
-Password "${{ secrets.GITHUB_TOKEN }}"
}
& $nuget setapikey "${{ secrets.GITHUB_TOKEN }}" -Source $src


How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "ci(vcpkg): Switch binary cache from loca..." | Re-trigger Greptile

We use ephemeral GitHub-hosted runners, do we need to worry about this? Every job starts with a clean NuGet config

@xezon

xezon commented Jun 26, 2026

Copy link
Copy Markdown

What is now the strategy with vcpkg? We still have it disabled because it caused long CI build times every now and then.

@bobtista

Copy link
Copy Markdown
Author

What is now the strategy with vcpkg? We still have it disabled because it caused long CI build times every now and then.

The strategy is to move the dependency build output into the GitHub Packages NuGet binary cache instead of continuing with handmade caching tweaks trying to use actions/cache as a big shared folder cache. We should have used nuget from the beginning - Microsoft’s vcpkg docs specifically recommend the NuGet provider for this use case.

I tested this on my fork. The first GenCI run still took about 24 minutes for the vcpkg jobs, which I think is expected because it had to build/populate the cache. Then I ran the same workflow again on the same commit, and the vcpkg jobs dropped to about 5.5-8 minutes. The CMake configure step was only around 30-45 seconds on the second run, so ffmpeg was restored from the cache instead of rebuilt.

The workflow is still red on my fork because the replay checks need R2 secrets I don’t have set up, but all six win32-vcpkg build variants passed on both runs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants