Skip to content
Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

vcpkg GitHub Packages Cache

GitHub Release CI License

This repository contains GitHub Actions for vcpkg binary caching backed by GitHub Packages NuGet feeds. The action makes the normal GITHUB_TOKEN path easy for public repositories while still supporting explicit PAT mode for cross-repository or organization feed use.

The root Marketplace action runs setup. The repository also has two explicit sub-actions:

  • setup configures vcpkg's NuGet binary cache source, provisions the vcpkg-selected NuGet tool, handles platform prerequisites such as Mono, and emits the VCPKG_BINARY_SOURCES value for the caller's build.
  • analyze probes the feed, inspects vcpkg and NuGet state, parses optional build logs, and classifies cache health as a warm hit, partial hit, cold seed, auth failure, quota failure, upload failure, or unknown.

Use LegalizeAdulthood/vcpkg-github-cache@v1 for the Marketplace setup entry point. LegalizeAdulthood/vcpkg-github-cache/setup@v1 is an equivalent setup spelling for callers who prefer explicit sub-action names.

The action deliberately does not wrap the caller's build. Callers keep their own checkout, build, test, and artifact steps; the action centralizes vcpkg bootstrap, cache setup, and diagnostics.

Required Permissions

The default GITHUB_TOKEN path needs package write access:

permissions:
  contents: read
  packages: write

Examples

The setup action exports VCPKG_BINARY_SOURCES for later workflow steps, so the caller build can stay unchanged.

Minimal Setup

Use setup before the vcpkg-backed build:

permissions:
  contents: read
  packages: write

steps:
  - uses: actions/checkout@v6
    with:
      submodules: true

  - uses: LegalizeAdulthood/vcpkg-github-cache@v1
    with:
      token: ${{ github.token }}

  - run: cmake --workflow --preset ci

Setup Plus Analyze

Capture the build log, then run analyze even when the build fails:

steps:
  - uses: actions/checkout@v6
    with:
      submodules: true

  - uses: LegalizeAdulthood/vcpkg-github-cache@v1
    with:
      token: ${{ github.token }}

  - name: Build
    shell: pwsh
    run: |
      cmake --workflow --preset ci 2>&1 |
        Tee-Object -FilePath build.log
      if ($LASTEXITCODE -ne 0) {
        exit $LASTEXITCODE
      }

  - name: Analyze vcpkg package cache
    if: always()
    uses: LegalizeAdulthood/vcpkg-github-cache/analyze@v1
    with:
      token: ${{ github.token }}
      build-log: build.log
      fail-on: "never"

Build Log Capture

The analyzer works without a build log, but a build log lets it separate warm hits, partial hits, cold seeds, and upload failures.

For bash:

- name: Build
  shell: bash
  run: |
    set -o pipefail
    cmake --workflow --preset ci 2>&1 | tee build.log

For pwsh:

- name: Build
  shell: pwsh
  run: |
    cmake --workflow --preset ci 2>&1 |
      Tee-Object -FilePath build.log
    if ($LASTEXITCODE -ne 0) {
      exit $LASTEXITCODE
    }

FreeBSD VM Build

GitHub JavaScript actions run on the Ubuntu host runner, not inside a FreeBSD VM. For VM builds, run setup in emit-script mode on the host, then run the emitted POSIX setup script inside the VM. Copy the build log back to the host and run the normal analyzer there:

permissions:
  contents: read
  packages: write

steps:
  - uses: actions/checkout@v6
    with:
      submodules: true

  - name: Generate vcpkg cache setup script
    id: vc_setup
    uses: LegalizeAdulthood/vcpkg-github-cache@v1
    with:
      token: ${{ github.token }}
      execution-mode: emit-script
      target-os: freebsd
      bootstrap: "true"
      install-nuget: "true"
      install-mono: "true"

  - name: Build on FreeBSD
    uses: vmactions/freebsd-vm@v1
    env:
      VCPKG_GITHUB_CACHE_TOKEN: ${{ github.token }}
      VCPKG_ROOT: vcpkg
    with:
      release: "14.4"
      usesh: true
      sync: rsync
      copyback: true
      envs: VCPKG_GITHUB_CACHE_TOKEN VCPKG_ROOT
      prepare: |
        pkg update
        pkg install -y cmake ninja git pkgconf
      run: |
        set +e
        set -u
        # This staging directory name is arbitrary, not an action convention.
        mkdir -p .freebsd-copyback

        {
          sh "${{ steps.vc_setup.outputs.setup-script }}" &&
          . "${{ steps.vc_setup.outputs.setup-env }}" &&
          cmake --workflow --preset ci
          status=$?
          echo "${status}" > build.status
        } 2>&1 | tee build.log

        cp build.log build.status .freebsd-copyback/
        # Copy project build products needed by host steps here.
        find . -mindepth 1 -maxdepth 1 ! -name .git \
          ! -name .freebsd-copyback -exec rm -rf {} +
        mv .freebsd-copyback/* .
        rmdir .freebsd-copyback

        exit "$(cat build.status)"

  - name: Analyze vcpkg package cache
    if: always()
    uses: LegalizeAdulthood/vcpkg-github-cache/analyze@v1
    with:
      token: ${{ github.token }}
      build-log: build.log
      setup-log: setup.log
      artifact-name: freebsd-cache-${{ github.run_attempt }}
      fail-on: "never"

The emitted setup script configures the target-side NuGet source, uses vcpkg fetch nuget inside the VM, and can bootstrap vcpkg there. The emitted setup.env file exports VCPKG_BINARY_SOURCES; dot-source it before running the project workflow.

The .freebsd-copyback directory in the example is only a local staging directory. The FreeBSD VM action's rsync and scp copyback modes are boolean: copy the VM workspace back, or copy nothing. The example stages only the files needed by host-side steps, removes the rest of the VM workspace, then moves the staged files back to the workspace root before copyback runs. If later host steps need project build products, stage them there too.

OpenBSD VM Build

OpenBSD uses the same emit-script pattern as FreeBSD, but the VM action, release, and package list are OpenBSD-specific. When using sync: rsync and copyback: true, install rsync in the guest so the copyback tool and guest libraries match after package installation:

permissions:
  contents: read
  packages: write

steps:
  - uses: actions/checkout@v6
    with:
      submodules: true

  - name: Generate vcpkg cache setup script
    id: vc_setup
    uses: LegalizeAdulthood/vcpkg-github-cache@v1
    with:
      token: ${{ github.token }}
      execution-mode: emit-script
      target-os: openbsd
      bootstrap: "true"
      install-nuget: "true"
      install-mono: "true"

  - name: Build on OpenBSD
    uses: vmactions/openbsd-vm@v1
    env:
      VCPKG_GITHUB_CACHE_TOKEN: ${{ github.token }}
      VCPKG_ROOT: vcpkg
    with:
      release: "7.9"
      usesh: true
      sync: rsync
      copyback: true
      envs: VCPKG_GITHUB_CACHE_TOKEN VCPKG_ROOT
      prepare: |
        pkg_add -I cmake ninja git gmake bash bison patchelf rsync
      run: |
        set +e
        set -u
        # This staging directory name is arbitrary, not an action convention.
        mkdir -p .openbsd-copyback

        {
          sh "${{ steps.vc_setup.outputs.setup-script }}" &&
          . "${{ steps.vc_setup.outputs.setup-env }}" &&
          cmake --workflow --preset ci
          status=$?
          echo "${status}" > build.status
        } 2>&1 | tee build.log

        cp build.log build.status .openbsd-copyback/
        # Copy project build products needed by host steps here.
        find . -mindepth 1 -maxdepth 1 ! -name .git \
          ! -name .openbsd-copyback -exec rm -rf {} +
        mv .openbsd-copyback/* .
        rmdir .openbsd-copyback

  - name: Analyze vcpkg package cache
    if: always()
    uses: LegalizeAdulthood/vcpkg-github-cache/analyze@v1
    with:
      token: ${{ github.token }}
      build-log: build.log
      setup-log: setup.log
      artifact-name: openbsd-cache-${{ github.run_attempt }}
      fail-on: "never"

  - name: Check OpenBSD build status
    if: always()
    run: |
      if [ ! -f build.status ]; then
        echo "build.status was not copied back from the OpenBSD VM"
        exit 1
      fi
      exit "$(cat build.status)"

The emitted OpenBSD setup script also handles OpenBSD-specific vcpkg tool setup, NuGet metadata, Ninja handling, and runtime library path setup. Project prerequisites remain the caller workflow's responsibility.

NetBSD VM Build

NetBSD uses the same emit-script pattern as the other BSD VMs. The emitted setup script handles generic BSD setup, target-side NuGet metadata, and vcpkg tool package caching. It does not emit the OpenBSD-specific runtime library path or Ninja patches.

permissions:
  contents: read
  packages: write

steps:
  - uses: actions/checkout@v6
    with:
      submodules: true

  - name: Generate vcpkg cache setup script
    id: vc_setup
    uses: LegalizeAdulthood/vcpkg-github-cache@v1
    with:
      token: ${{ github.token }}
      execution-mode: emit-script
      target-os: netbsd
      bootstrap: "true"
      install-nuget: "true"
      install-mono: "true"

  - name: Build on NetBSD
    uses: vmactions/netbsd-vm@v1
    env:
      VCPKG_GITHUB_CACHE_TOKEN: ${{ github.token }}
      VCPKG_ROOT: vcpkg
    with:
      release: "10.1"
      usesh: true
      sync: rsync
      copyback: true
      envs: VCPKG_GITHUB_CACHE_TOKEN VCPKG_ROOT
      prepare: |
        /usr/sbin/pkg_add -u cmake ninja-build git gmake bash bison patchelf
        /usr/sbin/pkg_add -u pkgconf rsync
      run: |
        set +e
        set -u
        # This staging directory name is arbitrary, not an action convention.
        mkdir -p .netbsd-copyback

        {
          sh "${{ steps.vc_setup.outputs.setup-script }}" &&
          . "${{ steps.vc_setup.outputs.setup-env }}" &&
          cmake --workflow --preset ci
          status=$?
          echo "${status}" > build.status
        } 2>&1 | tee build.log

        cp build.log build.status .netbsd-copyback/
        # Copy project build products needed by host steps here.
        find . -mindepth 1 -maxdepth 1 ! -name .git \
          ! -name .netbsd-copyback -exec rm -rf {} +
        mv .netbsd-copyback/* .
        rmdir .netbsd-copyback

  - name: Analyze vcpkg package cache
    if: always()
    uses: LegalizeAdulthood/vcpkg-github-cache/analyze@v1
    with:
      token: ${{ github.token }}
      build-log: build.log
      setup-log: setup.log
      artifact-name: netbsd-cache-${{ github.run_attempt }}
      fail-on: "never"

  - name: Check NetBSD build status
    if: always()
    run: |
      if [ ! -f build.status ]; then
        echo "build.status was not copied back from the NetBSD VM"
        exit 1
      fi
      exit "$(cat build.status)"

Troubleshooting

Enable debug while tuning package permissions. This keeps the analyzer summary concise and uploads a diagnostics artifact with probe details:

- name: Analyze vcpkg package cache
  if: always()
  uses: LegalizeAdulthood/vcpkg-github-cache/analyze@v1
  with:
    token: ${{ github.token }}
    build-log: build.log
    fail-on: "never"
    debug: "true"

Enable trace when setup or analysis makes an unexpected decision, such as choosing the wrong vcpkg root, skipping a tool install, or resolving a different GitHub Packages feed than expected:

- uses: LegalizeAdulthood/vcpkg-github-cache@v1
  with:
    token: ${{ github.token }}
    trace: "true"

- name: Analyze vcpkg package cache
  if: always()
  uses: LegalizeAdulthood/vcpkg-github-cache/analyze@v1
  with:
    token: ${{ github.token }}
    build-log: build.log
    fail-on: "never"
    trace: "true"

Action Reference

GitHub action inputs are strings. Quote boolean values such as "true" and "false" in workflow YAML.

setup Inputs

  • token: required. GitHub token or PAT used for GitHub Packages.
  • token-kind: default "github". Token kind: github or pat.
  • username: optional. NuGet username. Defaults depend on token kind.
  • feed-owner: optional. GitHub owner that hosts the NuGet feed.
  • vcpkg-root: default "vcpkg". Path to the vcpkg checkout.
  • bootstrap: default "true". Bootstrap vcpkg before configuring the cache.
  • install-nuget: default "true". Fetch NuGet with vcpkg when needed.
  • install-mono: default "true". Install Mono when nuget.exe needs it on Unix.
  • source-name: default "GitHubPackages". NuGet source name.
  • access: default "readwrite". vcpkg binary source access mode.
  • execution-mode: default "run". Setup mode: run or emit-script.
  • target-os: default "current". Target OS for emitted setup scripts: current, freebsd, netbsd, or openbsd.
  • script-directory: default ".vcpkg-github-cache". Directory for generated setup files.
  • debug: default "false". Emit additional diagnostics.
  • trace: default "false". Trace action decisions.

setup Outputs

  • feed-url: GitHub Packages NuGet feed URL.
  • binary-sources: value for VCPKG_BINARY_SOURCES.
  • nuget-command: NuGet command selected by setup.
  • vcpkg-version: bootstrapped vcpkg tool version.
  • diagnosis: short setup diagnosis.
  • setup-script: generated setup script path in emit-script mode.
  • setup-env: generated setup environment path in emit-script mode.

analyze Inputs

  • token: required. GitHub token or PAT used for GitHub Packages.
  • token-kind: default "github". Token kind: github or pat.
  • username: optional. NuGet username. Defaults depend on token kind.
  • feed-owner: optional. GitHub owner that hosts the NuGet feed.
  • vcpkg-root: default "vcpkg". Path to the vcpkg checkout.
  • build-log: optional path to a captured build log.
  • setup-log: optional path to a captured setup log.
  • artifact-name: optional diagnostics artifact name. Defaults to a generated name.
  • package-config-glob: default "**/packages.config". Glob used to find packages.config files.
  • fail-on: default "never". Failure policy. One of auth, cache-miss, never, private-package, quota, restore-failure, or upload-failure.
  • debug: default "false". Emit additional diagnostics.
  • trace: default "false". Trace action decisions.

analyze Outputs

  • cache-status: high-level cache result. One of auth-failure, cache-disabled, cold-seed, partial-hit, quota-failure, restore-healthy, restore-miss, tooling-failure, unknown, upload-failure, or warm-hit.
  • diagnosis: short human-readable diagnosis.
  • requested-count: package count from packages.config, if known.
  • restored-count: restored package count, if known.
  • built-count: vcpkg package build count, if known.
  • uploaded-count: successful binary cache upload count, if known.
  • failure-kind: normalized failure kind. Empty for no failure; otherwise one of auth, cache-miss, private-package, quota, restore-failure, tooling-failure, or upload-failure.
  • diagnostics-artifact: diagnostics artifact name, when debug is enabled.

Repository Expectations

Public Repositories

The default path is the workflow GITHUB_TOKEN. The workflow should grant contents: read and packages: write, then pass ${{ github.token }} to both actions.

GitHub's NuGet feed can require authentication even when a package is public. Public visibility means the package avoids private storage quota; it does not guarantee anonymous NuGet access.

Uploads can fail when a package already exists but is not linked to the calling repository with write access. In that case, the analyzer reports the denied packages and links to package settings where GitHub exposes them.

Denied Package Table

When the build log shows NuGet push 403 failures, the analyzer writes a Packages denied write access table to the job summary. The table is an administration checklist for packages that restored or built correctly, but could not be updated by the workflow repository.

The table always includes:

  • Package ID: the GitHub Packages NuGet package name. When available, this links to the package settings page.
  • Version: the denied package version. Long vcpkg ABI suffixes are stripped when the remaining version is still meaningful.

The table may also include:

  • Size: the built .nupkg size, when the package file is available.
  • Build Time: the vcpkg package build or handle time from the build log.
  • Repository: the repository currently linked to the package. When available, this links to that repository.
  • Versions: the package version count reported by GitHub package metadata.
  • Visibility: the package visibility reported by GitHub package metadata.
  • Quota Risk: shown only when at least one package has a quota risk other than none.

To resolve a denied package, open the linked Package ID settings page, find the package access controls, and grant the workflow repository read and write access. Package access is package-level, so granting access once allows that repository to upload future versions of the same package. If a package settings link is absent, use the package ID and feed owner to find the package in GitHub Packages, then update the same repository access settings there. GitHub documents this under package access.

Private Repositories

Private packages use GitHub Packages storage and transfer quota. A package published by a PAT, or any package whose visibility is private or unknown, is treated as quota risk until GitHub package metadata proves otherwise.

Linking a package to a repository grants repository access permissions. It does not necessarily make the package public, and it does not move quota usage out of private package billing.

The analyzer probes package metadata when package names are available. It reports package visibility, repository association, version count, and quota risk so cache administration can be prioritized.

Forked Pull Requests

Treat forked pull requests as read-only for package caching. GitHub can withhold repository secrets and limit GITHUB_TOKEN permissions for fork events. Cache restore may still work, but cache writes should not be used as the success condition for a forked pull request.

The analyzer should make these runs diagnosable without turning expected write restrictions into noisy build failures. Do not rely on forked pull requests to seed new binary cache packages.

Gotchas

  • Public NuGet packages still usually require authentication. Public means no private package storage quota, not anonymous restore.
  • permissions: packages: write only affects GITHUB_TOKEN. PAT mode depends on the PAT scopes and the user's package permissions.
  • Package access is package-level, not version-level. If the workflow repository cannot write an existing package, it cannot upload a new version of that package.
  • Repository linking grants package access permissions. It does not necessarily make a package public or move it out of private package billing.
  • Package visibility is sticky. Switching token kinds does not make an existing private package public.
  • PAT-created NuGet packages may be private even when the workflow runs in a public repository. Treat private or unknown visibility as quota risk.
  • GitHub package quota failures can block downloads as well as uploads.
  • Package metadata and NuGet list/search behavior are helpful hints, not proof that vcpkg can restore the exact package it needs.
  • vcpkg fetch nuget is the source of truth for the NuGet tool vcpkg will use.
  • vcpkg package identity includes ABI details, toolchain, triplet, port files, and helper ports. Pinning vcpkg and CMake reduces drift, but runner image and compiler changes can still change package identities.

Reference Documentation

About

GitHub Action for vcpkg binary caching with GitHub Packages NuGet feeds.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages