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:
setupconfigures vcpkg's NuGet binary cache source, provisions the vcpkg-selected NuGet tool, handles platform prerequisites such as Mono, and emits theVCPKG_BINARY_SOURCESvalue for the caller's build.analyzeprobes 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.
The default GITHUB_TOKEN path needs package write access:
permissions:
contents: read
packages: writeThe setup action exports VCPKG_BINARY_SOURCES for later workflow steps, so
the caller build can stay unchanged.
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 ciCapture 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"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.logFor pwsh:
- name: Build
shell: pwsh
run: |
cmake --workflow --preset ci 2>&1 |
Tee-Object -FilePath build.log
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}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 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 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)"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"GitHub action inputs are strings. Quote boolean values such as "true"
and "false" in workflow YAML.
token: required. GitHub token or PAT used for GitHub Packages.token-kind: default"github". Token kind:githuborpat.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 whennuget.exeneeds it on Unix.source-name: default"GitHubPackages". NuGet source name.access: default"readwrite". vcpkg binary source access mode.execution-mode: default"run". Setup mode:runoremit-script.target-os: default"current". Target OS for emitted setup scripts:current,freebsd,netbsd, oropenbsd.script-directory: default".vcpkg-github-cache". Directory for generated setup files.debug: default"false". Emit additional diagnostics.trace: default"false". Trace action decisions.
feed-url: GitHub Packages NuGet feed URL.binary-sources: value forVCPKG_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 inemit-scriptmode.setup-env: generated setup environment path inemit-scriptmode.
token: required. GitHub token or PAT used for GitHub Packages.token-kind: default"github". Token kind:githuborpat.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 findpackages.configfiles.fail-on: default"never". Failure policy. One ofauth,cache-miss,never,private-package,quota,restore-failure, orupload-failure.debug: default"false". Emit additional diagnostics.trace: default"false". Trace action decisions.
cache-status: high-level cache result. One ofauth-failure,cache-disabled,cold-seed,partial-hit,quota-failure,restore-healthy,restore-miss,tooling-failure,unknown,upload-failure, orwarm-hit.diagnosis: short human-readable diagnosis.requested-count: package count frompackages.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 ofauth,cache-miss,private-package,quota,restore-failure,tooling-failure, orupload-failure.diagnostics-artifact: diagnostics artifact name, whendebugis enabled.
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.
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.nupkgsize, 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 thannone.
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 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.
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.
- Public NuGet packages still usually require authentication. Public means no private package storage quota, not anonymous restore.
permissions: packages: writeonly affectsGITHUB_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 nugetis 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.