| // 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 |
| } |