feat: split Go module and build caches

Reuse downloaded modules across Go versions, runner architectures, and
runner images on the same OS instead of duplicating them in each build
cache archive.

Restore and save each entry independently, running operations in parallel
when safe. Serialize restores for overlapping or aliased paths, preserve
the original paths for the post step, and report cache-hit only when both
entries match their primary keys.

Update the documented manual restore keys and generated action bundles.
This commit is contained in:
qmuntal
2026-10-05 15:05:38 +02:00
parent 90ad2b35f6
commit ad9941188f
13 changed files with 1035 additions and 335 deletions

View File

@@ -240,7 +240,16 @@ steps:
```
## Caching
The action restores and saves two independent cache entries:
- **Module sources (`GOMODCACHE`)**: keyed by the runner OS and dependency-file hash. These sources can be reused across Go versions, runner architectures, and runner images on the same OS.
- **Build outputs (`GOCACHE`)**: keyed by the runner OS, runner architecture, Linux runner image (when applicable), Go version, and dependency-file hash.
Both entries use the hash of `cache-dependency-path`, or the root `go.mod` by default. Adding source files or build settings to that input invalidates both entries when those files change. Custom cache locations and the cache service's normal access restrictions still apply.
Each entry is restored and saved independently, with concurrent transfers where safe. An exact hit is not uploaded again, even when the other entry misses. A failure for one entry does not prevent caching the other. The `cache-hit` output is `true` only when both entries are restored on their primary keys.
### Caching in monorepos
```yaml
@@ -281,7 +290,7 @@ steps:
### Multi-target builds
`cache-dependency-path` isn’t limited to dependency files (like `go.sum`). It can also include files that capture build settings (for example, `GOOS`/`GOARCH`). This allows separate caches per target platform (OS/architecture) and helps avoid reusing caches across incompatible builds.
`GOOS` and `GOARCH` are not automatically included in the cache keys. `cache-dependency-path` isn’t limited to dependency files (like `go.sum`); it can also include files that capture these build settings. This prevents jobs for different targets from competing to populate the same immutable cache entry. Because both entries use the same dependency-file hash, this workaround separates the module cache as well as the build cache.
```yaml
env:
@@ -289,16 +298,15 @@ env:
GOARCH: ...
steps:
- run: echo "$GOOS $GOARCH" > env.txt
- uses: actions/checkout@v7
- run: echo "$GOOS $GOARCH" > env.txt
- uses: actions/setup-go@v7
with:
go-version: '1.25'
cache-dependency-path: |
go.sum
env.txt
- run: go run hello.go
- run: go build ./...
```
### Cache invalidation on source changes
@@ -365,20 +373,26 @@ jobs:
if: runner.os == 'Linux'
shell: bash
run: echo "CACHE_OS_SUFFIX=$ImageOS-" >> $GITHUB_ENV
- name: Restore Go cache
id: go-cache
- name: Restore Go module cache
id: go-module-cache
uses: actions/cache/restore@v5
with:
path: |
${{ env.GO_MOD_CACHE }}
${{ env.GO_BUILD_CACHE }}
key: setup-go-${{ runner.os }}-${{ env.ARCH }}-${{ env.CACHE_OS_SUFFIX }}go-${{ steps.setup-go.outputs.go-version }}-${{ hashFiles('**/go.mod') }}
path: ${{ env.GO_MOD_CACHE }}
key: setup-go-modules-${{ runner.os }}-${{ hashFiles('go.mod') }}
- name: Restore Go build cache
id: go-build-cache
uses: actions/cache/restore@v5
with:
path: ${{ env.GO_BUILD_CACHE }}
key: setup-go-build-${{ runner.os }}-${{ env.ARCH }}-${{ env.CACHE_OS_SUFFIX }}go-${{ steps.setup-go.outputs.go-version }}-${{ hashFiles('go.mod') }}
- name: Download modules
run: go mod download
- name: Build
run: go build ./...
```
Use the same file patterns in `hashFiles` as the `cache-dependency-path` of the workflow that saves the caches. The example above matches the default root `go.mod` input.
> If there are several builds on the same repo, it may make sense to create a cache in one build and use it in others. The action [actions/cache/restore](https://github.com/actions/cache/tree/main/restore#only-restore-cache)
should be used in this case.
@@ -407,7 +421,7 @@ jobs:
### `cache-hit`
**cache-hit** output is available with a boolean value that indicates whether a cache hit occurred on the primary key:
**cache-hit** is `true` only when both the module cache and the build cache are restored on their primary keys. If only one entry hits its primary key, the output is `false`, but that entry is still used and is not saved again. An entry restored from a non-primary key can still be saved under its primary key:
```yaml
jobs:
@@ -420,7 +434,7 @@ jobs:
with:
go-version: '1.24'
cache: true
- run: echo "Was the Go cache restored? ${{ steps.go124.outputs.cache-hit }}" # true if cache-hit occurred
- run: echo "Were both Go caches restored? ${{ steps.go124.outputs.cache-hit }}"
```
### Go environment outputs