Skip to content

cmd/go: build cache management #81830

Open

Description

@seankhliao

build cache primer

cache structure

The go build cache stores action and data entries used by various go commands.
The cache is structured as:

$GOCACHE/
├── 00/
│   ├── <64-hex-ActionID>-a
│   ├── <64-hex-OutputID>-d
│   └── <64-hex-OutputID>-d/
│       └── <binary_name>
├── ...
├── fuzz/
│   └── <import/path>/
│       └── ...
├── trim.txt
├── testexpire.txt

In particular:

  • trim.txt records the last time the build cache was trimmed
  • testexpire.txt records a cutoff date for useable test cache artifacts (go clean -testcache doesn't actually delete test cache entries)

adding to the build cache

cache keys are a hex sha256 hash of cache inputs.
This does mean that we can't know what is cached merely from looking at the cache entry.
Data entries should take up the most space, and each should have a corresponding Action entry,
but the action entry currently only records the hash mapping of action to output without additional details.

ai summary of artifacts and cache keys

3. What Goes into the Cache Key for Each Type of Cached Artifact?

Cache keys (ActionID) are 32-byte salted SHA-256 hashes generated by NewHash. Every hash starts with hashSalt (gover.LocalToolchain()), guaranteeing that different Go toolchain versions or development commits never collide on action keys. Derived subkeys use Subkey(parent, desc) = SHA256("subkey:" + parent + desc).

A. Package Compilation Action Key (Builder.buildActionID)
  • Salt: hashSalt
  • Tag: compile\n
  • Package origin: package directory (if outside GOROOT and not trimmed), or module <path>@<version> (if -trimpath)
  • Language version: go <version> from go.mod
  • Platform: goos <GOOS> goarch <GOARCH>
  • Package metadata: import <ImportPath>, omitdebug, standard, local, prefix, trimpath, forcelibrary
  • C toolchain configuration: C compiler ID, compiler flags, and toolchain hashes if package uses cgo (addCToolchainIDs)
  • Coverage: cover <mode> <toolID("cover")> (if coverage instrumentation enabled)
  • Fuzzing: fuzz instrumentation flags (if fuzzing enabled)
  • Module build info: modinfo <string>
  • Toolchain-specific configuration:
    • For gc: compiler tool ID (b.toolID("compile")), forced gcflags, package gcflags; asm tool ID, forced asmflags, package asmflags (if .s files present); architecture environment variables (GOAMD64, GOARM, GOMIPS, etc.); clean GOEXPERIMENT; compiler debug env vars (GOCLOBBERDEADHASH, GOSSAFUNC, GOSSADIR, GOCOMPILEDEBUG)
    • For gccgo: gccgo tool ID, flags, package path, ar tool ID, assembler tool ID
  • Input file contents: file path and SHA-256 content hash (FileHash) of every GoFiles, CgoFiles, CFiles, CXXFiles, FFiles, MFiles, HFiles, SFiles, SysoFiles, SwigFiles, SwigCXXFiles, and EmbedFiles.
  • Dependency export data: for each direct imported dependency, import <ImportPath> <buildExportID(dep.buildID)> (using the content ID of the dependency's export data, not its action ID, enabling convergence in circular bootstrapping).
  • Preprocessed PGO profile: pgofile <fileHash(pgoProfile)> (if PGO enabled).
B. Link Action Key (Builder.linkActionID)
  • Salt: hashSalt
  • Tag: link\n
  • Platform and mode: resolved buildmode, goos <GOOS>, goarch <GOARCH>
  • Package metadata: import <ImportPath>, omitdebug, standard, local, prefix, defaultgodebug, trimpath
  • Linker configuration: linker tool ID (b.toolID("link")), forced ldflags, package ldflags, arch env variables, clean GOEXPERIMENT, GOROOT (or empty if -trimpath), GO_EXTLINK_ENABLED
  • Dependency packages: for every dependency action a1, packagefile <ImportPath>=<buildObjectID(a1.buildID)> (content ID of package object data).
  • Main package ID: packagemain <buildActionID>/<buildObjectID> (ensures the binary's embedded build ID matches).
  • Shared libraries: packageshlib <ImportPath>=<buildObjectID(shlib)> (if linking against shared libraries).
C. Separate Export Action Key (Builder.exportActionID)
  • Salt: hashSalt
  • Export tool ID: b.toolID("export")
  • importPath, compiler, goVersion
  • Content hashes of all Go source files
  • Export content IDs of dependent packages (packageFile <ImportPath>=<buildExportID(dep.buildID)>).
D. Vet Analysis Key (Builder.vet)
  • Salt: hashSalt
  • Vet tool ID: b.toolID("vet")
  • Active vet checker flags (vetFlags)
  • Root package compile action ID: pkg <a.Deps[0].actionID>
  • Content hashes of .vetx fact export files of all dependencies (vetout <ImportPath> <fileHash(vetx)>).
  • Subkeys:
    • Fix archive: cache.Subkey(id, "fix.zip")
    • Output stdout: cache.Subkey(id, "stdout")
E. Test Result Keys (Two-Level Architecture)
  1. Level 1 Key (testID) (runTestActor.Act):
    • hashSalt
    • Content ID of test executable (or linker action ID proxy) id
    • Test command line flags (cacheArgs)
    • Exec command runner (work.ExecCmd)
    • Value stored: test execution log (# test log).
  2. Level 2 Key (testInputsID) (computeTestInputsID):
    Computed by parsing the recorded # test log:
    • env GODEBUG <hash>
    • getenv <var>: hash of environment variable existence and value
    • chdir <path>: stat hash of directory
    • stat <path>: file stat info (size, mode, mtime, isDir)
    • open <path>: file stat info (plus directory entry stats if directory; rejects caching if modified < 2s ago via errFileTooNew)
  3. Lookup Keys:
    • Test output stdout/stderr: testAndInputKey(testID, testInputsID) = cache.Subkey(testID, "inputs:" + hex(testInputsID)).
    • Coverage profile: coverProfileAndInputKey(testID, testInputsID, covMeta) = cache.Subkey(testID, "coverProfile:" + hex(testInputsID) + ...).
F. Cgo Run & Compile Keys
  • cgoRunActionID (exec.go): cgo tool ID, GOOS, GOARCH, import path, package directory, C/C++/Fortran compiler flags, msan/asan flags, export header flag, and hashes of Cgo/SWIG files.
  • cgoCompileActionID (exec.go): GOOS, GOARCH, source file name and content hash, compiler flags, package origin, trimpath flag, C toolchain IDs.
  • Subkeys: Intermediate object files stored under cache.Subkey(actionID, filename).
G. Preprocessed PGO Profile Key (Builder.pgoActionID)
  • Salt: hashSalt
  • Preprofile tool ID: b.toolID("preprofile")
  • Content hash of raw input pprof file (b.fileHash(input)).
H. C Compiler Flags & Compiler ID
  • gccCompilerID (exec.go): Hash of compiler binary path, stat info of compiler binaries, and probed internal compiler ID.
  • gccSupportsFlag: cache.Subkey(compilerID, "gccSupportsFlag " + flag).
I. Module Index Keys (modindex)
  • Module hash: runtime.Version(), index format version, modroot.
  • Package hash: runtime.Version(), index format version, modroot, pkgdir, directory entries and stat info (rejects caching if mtime is within 2 seconds).

removing from the build cache

On every command exit, trim.txt is checked.
Cache trimming runs at most once per day.
Entries with mtime older than 5 days are removed.

Accessing a cache entry updates mtime (not atime).

go clean -cache drops the entire cache:

problems people have

cache doesn't cache enough

cache caches too much

what to do?

A size based cached could solve both wanting to cache more, and caching too much.
But it would be adversely affected by storing a lot of recent large binaries (e.g. lots of test runs).

A generational cache like https://go.dev/cl/433295 could let us prune churning entries more effectively.

Activity

  1. added this to the Backlog milestone on Sep 29, 2026
  2. prattmic commented on Sep 29, 2026

    @prattmic
    Member

    cc @golang/command-line @matloob @thepudds

  3. added
    NeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.
    on Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    GoCommandcmd/goNeedsDecisionFeedback is required from experts, contributors, and/or the community before a change can be made.

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions