// Copyright 2020 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 modload import ( "cmd/go/internal/base" "cmd/go/internal/cfg" "golang.org/x/mod/modfile" "golang.org/x/mod/module" ) var modFile *modfile.File // A modFileIndex is an index of data corresponding to a modFile // at a specific point in time. type modFileIndex struct { data []byte dataNeedsFix bool // true if fixVersion applied a change while parsing data module module.Version goVersion string require map[module.Version]requireMeta replace map[module.Version]module.Version exclude map[module.Version]bool } // index is the index of the go.mod file as of when it was last read or written. var index *modFileIndex type requireMeta struct { indirect bool } // Allowed reports whether module m is allowed (not excluded) by the main module's go.mod. func Allowed(m module.Version) bool { return index == nil || !index.exclude[m] } // Replacement returns the replacement for mod, if any, from go.mod. // If there is no replacement for mod, Replacement returns // a module.Version with Path == "". func Replacement(mod module.Version) module.Version { if index != nil { if r, ok := index.replace[mod]; ok { return r } if r, ok := index.replace[module.Version{Path: mod.Path}]; ok { return r } } return module.Version{} } // indexModFile rebuilds the index of modFile. // If modFile has been changed since it was first read, // modFile.Cleanup must be called before indexModFile. func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileIndex { i := new(modFileIndex) i.data = data i.dataNeedsFix = needsFix i.module = module.Version{} if modFile.Module != nil { i.module = modFile.Module.Mod } i.goVersion = "" if modFile.Go != nil { i.goVersion = modFile.Go.Version } i.require = make(map[module.Version]requireMeta, len(modFile.Require)) for _, r := range modFile.Require { i.require[r.Mod] = requireMeta{indirect: r.Indirect} } i.replace = make(map[module.Version]module.Version, len(modFile.Replace)) for _, r := range modFile.Replace { if prev, dup := i.replace[r.Old]; dup && prev != r.New { base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New) } i.replace[r.Old] = r.New } i.exclude = make(map[module.Version]bool, len(modFile.Exclude)) for _, x := range modFile.Exclude { i.exclude[x.Mod] = true } return i } // modFileIsDirty reports whether the go.mod file differs meaningfully // from what was indexed. // If modFile has been changed (even cosmetically) since it was first read, // modFile.Cleanup must be called before modFileIsDirty. func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool { if i == nil { return modFile != nil } if i.dataNeedsFix { return true } if modFile.Module == nil { if i.module != (module.Version{}) { return true } } else if modFile.Module.Mod != i.module { return true } if modFile.Go == nil { if i.goVersion != "" { return true } } else if modFile.Go.Version != i.goVersion { if i.goVersion == "" && cfg.BuildMod == "readonly" { // go.mod files did not always require a 'go' version, so do not error out // if one is missing — we may be inside an older module in the module // cache, and should bias toward providing useful behavior. } else { return true } } if len(modFile.Require) != len(i.require) || len(modFile.Replace) != len(i.replace) || len(modFile.Exclude) != len(i.exclude) { return true } for _, r := range modFile.Require { if meta, ok := i.require[r.Mod]; !ok { return true } else if r.Indirect != meta.indirect { if cfg.BuildMod == "readonly" { // The module's requirements are consistent; only the "// indirect" // comments that are wrong. But those are only guaranteed to be accurate // after a "go mod tidy" — it's a good idea to run those before // committing a change, but it's certainly not mandatory. } else { return true } } } for _, r := range modFile.Replace { if r.New != i.replace[r.Old] { return true } } for _, x := range modFile.Exclude { if !i.exclude[x.Mod] { return true } } return false }