Skip to content
Switch branches/tags
Go to file
… requirements

This is intended to be a pure-refactoring change, with very little
observable change in the behavior of the 'go' command. A few error
messages have prefixes changed (by virtue of being attached to
packages or modules instead of the build list overall), and
'go list -m' (without arguments) no longer loads the complete module
graph in order to provide the name of the (local) main module.

The previous modload.buildList variable contained a flattened build
list, from which the go.mod file was reconstructed using various
heuristics and metadata cobbled together from the original go.mod
file, the package loader (which was occasionally constructed without
actually loading packages, for the sole purpose of populating
otherwise-unrelated metadata!), and the updated build list.

This change replaces that variable with a new package-level variable,
named "requirements". The new variable is structured to match the
structure of the go.mod file: it explicitly specifies the roots of the
module graph, from which the complete module graph and complete build
list can be reconstructed (and cached) on demand. Similarly, the
"direct" markings on the go.mod requirements are now stored alongside
the requirements themselves, rather than side-channeled through the

The requirements are now plumbed explicitly though the modload
package, with accesses to the package-level variable occurring only
within top-level exported functions. The structured requirements are
logically immutable, so a new copy of the requirements is constructed
whenever the requirements are changed, substantially reducing implicit
communication-by-sharing in the package.

For #36460
Updates #40775

Change-Id: I97bb0381708f9d3e42af385b5c88a7038e1f0556
Trust: Bryan C. Mills <>
Run-TryBot: Bryan C. Mills <>
TryBot-Result: Go Bot <>
Reviewed-by: Michael Matloob <>
6 contributors

Users who have contributed to this file

@jayconrod @rsc @matloob @bcmills @mvdan @iWdGo
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modcmd
import (
var cmdVerify = &base.Command{
UsageLine: "go mod verify",
Short: "verify dependencies have expected content",
Long: `
Verify checks that the dependencies of the current module,
which are stored in a local downloaded source cache, have not been
modified since being downloaded. If all the modules are unmodified,
verify prints "all modules verified." Otherwise it reports which
modules have been changed and causes 'go mod' to exit with a
non-zero status.
See for more about 'go mod verify'.
Run: runVerify,
func init() {
func runVerify(ctx context.Context, cmd *base.Command, args []string) {
if len(args) != 0 {
// NOTE(rsc): Could take a module pattern.
base.Fatalf("go mod verify: verify takes no arguments")
modload.ForceUseModules = true
modload.RootMode = modload.NeedRoot
// Only verify up to GOMAXPROCS zips at once.
type token struct{}
sem := make(chan token, runtime.GOMAXPROCS(0))
// Use a slice of result channels, so that the output is deterministic.
mods := modload.LoadModGraph(ctx).BuildList()[1:]
errsChans := make([]<-chan []error, len(mods))
for i, mod := range mods {
sem <- token{}
errsc := make(chan []error, 1)
errsChans[i] = errsc
mod := mod // use a copy to avoid data races
go func() {
errsc <- verifyMod(mod)
ok := true
for _, errsc := range errsChans {
errs := <-errsc
for _, err := range errs {
base.Errorf("%s", err)
ok = false
if ok {
fmt.Printf("all modules verified\n")
func verifyMod(mod module.Version) []error {
var errs []error
zip, zipErr := modfetch.CachePath(mod, "zip")
if zipErr == nil {
_, zipErr = os.Stat(zip)
dir, dirErr := modfetch.DownloadDir(mod)
data, err := os.ReadFile(zip + "hash")
if err != nil {
if zipErr != nil && errors.Is(zipErr, fs.ErrNotExist) &&
dirErr != nil && errors.Is(dirErr, fs.ErrNotExist) {
// Nothing downloaded yet. Nothing to verify.
return nil
errs = append(errs, fmt.Errorf("%s %s: missing ziphash: %v", mod.Path, mod.Version, err))
return errs
h := string(bytes.TrimSpace(data))
if zipErr != nil && errors.Is(zipErr, fs.ErrNotExist) {
// ok
} else {
hZ, err := dirhash.HashZip(zip, dirhash.DefaultHash)
if err != nil {
errs = append(errs, fmt.Errorf("%s %s: %v", mod.Path, mod.Version, err))
return errs
} else if hZ != h {
errs = append(errs, fmt.Errorf("%s %s: zip has been modified (%v)", mod.Path, mod.Version, zip))
if dirErr != nil && errors.Is(dirErr, fs.ErrNotExist) {
// ok
} else {
hD, err := dirhash.HashDir(dir, mod.Path+"@"+mod.Version, dirhash.DefaultHash)
if err != nil {
errs = append(errs, fmt.Errorf("%s %s: %v", mod.Path, mod.Version, err))
return errs
if hD != h {
errs = append(errs, fmt.Errorf("%s %s: dir has been modified (%v)", mod.Path, mod.Version, dir))
return errs