func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) {
// To avoid spurious remote fetches, try the latest replacement for each
// module (golang.org/issue/26241).
- if index != nil {
- var mods []module.Version
- for mp, mv := range index.highestReplaced {
- if !maybeInModule(path, mp) {
- continue
- }
- if mv == "" {
- // The only replacement is a wildcard that doesn't specify a version, so
- // synthesize a pseudo-version with an appropriate major version and a
- // timestamp below any real timestamp. That way, if the main module is
- // used from within some other module, the user will be able to upgrade
- // the requirement to any real version they choose.
- if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 {
- mv = module.ZeroPseudoVersion(pathMajor[1:])
- } else {
- mv = module.ZeroPseudoVersion("v0")
+ var mods []module.Version
+ for _, v := range MainModules.Versions() {
+ if index := MainModules.Index(v); index != nil {
+ for mp, mv := range index.highestReplaced {
+ if !maybeInModule(path, mp) {
+ continue
}
+ if mv == "" {
+ // The only replacement is a wildcard that doesn't specify a version, so
+ // synthesize a pseudo-version with an appropriate major version and a
+ // timestamp below any real timestamp. That way, if the main module is
+ // used from within some other module, the user will be able to upgrade
+ // the requirement to any real version they choose.
+ if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 {
+ mv = module.ZeroPseudoVersion(pathMajor[1:])
+ } else {
+ mv = module.ZeroPseudoVersion("v0")
+ }
+ }
+ mg, err := rs.Graph(ctx)
+ if err != nil {
+ return module.Version{}, err
+ }
+ if cmpVersion(mg.Selected(mp), mv) >= 0 {
+ // We can't resolve the import by adding mp@mv to the module graph,
+ // because the selected version of mp is already at least mv.
+ continue
+ }
+ mods = append(mods, module.Version{Path: mp, Version: mv})
}
- mg, err := rs.Graph(ctx)
- if err != nil {
- return module.Version{}, err
- }
- if cmpVersion(mg.Selected(mp), mv) >= 0 {
- // We can't resolve the import by adding mp@mv to the module graph,
- // because the selected version of mp is already at least mv.
- continue
- }
- mods = append(mods, module.Version{Path: mp, Version: mv})
}
+ }
- // Every module path in mods is a prefix of the import path.
- // As in QueryPattern, prefer the longest prefix that satisfies the import.
- sort.Slice(mods, func(i, j int) bool {
- return len(mods[i].Path) > len(mods[j].Path)
- })
- for _, m := range mods {
- needSum := true
- root, isLocal, err := fetch(ctx, m, needSum)
- if err != nil {
- if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
- return module.Version{}, &ImportMissingSumError{importPath: path}
- }
- return module.Version{}, err
- }
- if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
- return m, err
- } else if ok {
- if cfg.BuildMod == "readonly" {
- return module.Version{}, &ImportMissingError{Path: path, replaced: m}
- }
- return m, nil
+ // Every module path in mods is a prefix of the import path.
+ // As in QueryPattern, prefer the longest prefix that satisfies the import.
+ sort.Slice(mods, func(i, j int) bool {
+ return len(mods[i].Path) > len(mods[j].Path)
+ })
+ for _, m := range mods {
+ needSum := true
+ root, isLocal, err := fetch(ctx, m, needSum)
+ if err != nil {
+ if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
+ return module.Version{}, &ImportMissingSumError{importPath: path}
}
+ return module.Version{}, err
}
- if len(mods) > 0 && module.CheckPath(path) != nil {
- // The package path is not valid to fetch remotely,
- // so it can only exist in a replaced module,
- // and we know from the above loop that it is not.
- return module.Version{}, &PackageNotInModuleError{
- Mod: mods[0],
- Query: "latest",
- Pattern: path,
- Replacement: Replacement(mods[0]),
+ if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
+ return m, err
+ } else if ok {
+ if cfg.BuildMod == "readonly" {
+ return module.Version{}, &ImportMissingError{Path: path, replaced: m}
}
+ return m, nil
+ }
+ }
+ if len(mods) > 0 && module.CheckPath(path) != nil {
+ // The package path is not valid to fetch remotely,
+ // so it can only exist in a replaced module,
+ // and we know from the above loop that it is not.
+ return module.Version{}, &PackageNotInModuleError{
+ Mod: mods[0],
+ Query: "latest",
+ Pattern: path,
+ Replacement: Replacement(mods[0]),
}
}
"path/filepath"
"strconv"
"strings"
+ "sync"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
// inGorootSrc caches whether modRoot is within GOROOT/src.
// The "std" module is special within GOROOT/src, but not otherwise.
inGorootSrc map[module.Version]bool
+
+ modFiles map[module.Version]*modfile.File
+
+ indexMu sync.Mutex
+ indices map[module.Version]*modFileIndex
}
func (mms *MainModuleSet) PathPrefix(m module.Version) string {
return mms.versions[0]
}
+func (mms *MainModuleSet) GetSingleIndexOrNil() *modFileIndex {
+ if mms == nil {
+ return nil
+ }
+ if len(mms.versions) == 0 {
+ return nil
+ }
+ if len(mms.versions) != 1 {
+ _ = TODOWorkspaces("Check if we're in workspace mode before returning the below error.")
+ panic("internal error: mustGetSingleMainModule called in workspace mode")
+ }
+ return mms.indices[mms.versions[0]]
+}
+
+func (mms *MainModuleSet) Index(m module.Version) *modFileIndex {
+ mms.indexMu.Lock()
+ defer mms.indexMu.Unlock()
+ return mms.indices[m]
+}
+
+func (mms *MainModuleSet) SetIndex(m module.Version, index *modFileIndex) {
+ mms.indexMu.Lock()
+ defer mms.indexMu.Unlock()
+ mms.indices[m] = index
+}
+
+func (mms *MainModuleSet) ModFile(m module.Version) *modfile.File {
+ return mms.modFiles[m]
+}
+
func (mms *MainModuleSet) Len() int {
if mms == nil {
return 0
// in go.mod, edit it before loading.
func ModFile() *modfile.File {
Init()
+ modFile := MainModules.ModFile(MainModules.mustGetSingleMainModule())
if modFile == nil {
die()
}
if len(modRoots) == 0 {
_ = TODOWorkspaces("Instead of creating a fake module with an empty modroot, make MainModules.Len() == 0 mean that we're in module mode but not inside any module.")
mainModule := module.Version{Path: "command-line-arguments"}
- MainModules = makeMainModules([]module.Version{mainModule}, []string{""})
+ MainModules = makeMainModules([]module.Version{mainModule}, []string{""}, []*modfile.File{nil}, []*modFileIndex{nil})
goVersion := LatestGoVersion()
rawGoVersion.Store(mainModule, goVersion)
requirements = newRequirements(modDepthFromGoVersion(goVersion), nil, nil)
var modFiles []*modfile.File
var mainModules []module.Version
+ var indices []*modFileIndex
for _, modroot := range modRoots {
gomod := modFilePath(modroot)
var data []byte
base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
}
- modFile = f // TODO(golang.org/cl/327329): remove the global modFile variable and replace it with multiple modfiles
modFiles = append(modFiles, f)
mainModule := f.Module.Mod
mainModules = append(mainModules, mainModule)
- index = indexModFile(data, f, mainModule, fixed)
+ indices = append(indices, indexModFile(data, f, mainModule, fixed))
if err := module.CheckImportPath(f.Module.Mod.Path); err != nil {
if pathErr, ok := err.(*module.InvalidPathError); ok {
}
}
- MainModules = makeMainModules(mainModules, modRoots)
+ MainModules = makeMainModules(mainModules, modRoots, modFiles, indices)
setDefaultBuildMod() // possibly enable automatic vendoring
rs = requirementsFromModFiles(ctx, modFiles)
if cfg.BuildMod == "vendor" {
readVendorList()
- checkVendorConsistency()
+ index := MainModules.Index(mainModule)
+ modFile := MainModules.ModFile(mainModule)
+ checkVendorConsistency(index, modFile)
rs.initVendor(vendorList)
}
- if index.goVersionV == "" {
+ if MainModules.Index(mainModule).goVersionV == "" {
// TODO(#45551): Do something more principled instead of checking
// cfg.CmdName directly here.
if cfg.BuildMod == "mod" && cfg.CmdName != "mod graph" && cfg.CmdName != "mod why" {
- addGoStmt(mainModule, LatestGoVersion())
+ addGoStmt(MainModules.ModFile(mainModule), mainModule, LatestGoVersion())
if go117EnableLazyLoading {
// We need to add a 'go' version to the go.mod file, but we must assume
// that its existing contents match something between Go 1.11 and 1.16.
}
fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", modPath)
- modFile = new(modfile.File)
+ modFile := new(modfile.File)
modFile.AddModuleStmt(modPath)
- MainModules = makeMainModules([]module.Version{modFile.Module.Mod}, []string{modRoot})
- addGoStmt(modFile.Module.Mod, LatestGoVersion()) // Add the go directive before converted module requirements.
+ MainModules = makeMainModules([]module.Version{modFile.Module.Mod}, []string{modRoot}, []*modfile.File{modFile}, []*modFileIndex{nil})
+ addGoStmt(modFile, modFile.Module.Mod, LatestGoVersion()) // Add the go directive before converted module requirements.
- convertedFrom, err := convertLegacyConfig(modPath)
+ convertedFrom, err := convertLegacyConfig(modFile, modPath)
if convertedFrom != "" {
fmt.Fprintf(os.Stderr, "go: copying requirements from %s\n", base.ShortPath(convertedFrom))
}
// makeMainModules creates a MainModuleSet and associated variables according to
// the given main modules.
-func makeMainModules(ms []module.Version, rootDirs []string) *MainModuleSet {
+func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile.File, indices []*modFileIndex) *MainModuleSet {
for _, m := range ms {
if m.Version != "" {
panic("mainModulesCalled with module.Version with non empty Version field: " + fmt.Sprintf("%#v", m))
inGorootSrc: map[module.Version]bool{},
pathPrefix: map[module.Version]string{},
modRoot: map[module.Version]string{},
+ modFiles: map[module.Version]*modfile.File{},
+ indices: map[module.Version]*modFileIndex{},
}
for i, m := range ms {
mainModules.pathPrefix[m] = m.Path
mainModules.modRoot[m] = rootDirs[i]
+ mainModules.modFiles[m] = modFiles[i]
+ mainModules.indices[m] = indices[i]
if rel := search.InDir(rootDirs[i], cfg.GOROOTsrc); rel != "" {
mainModules.inGorootSrc[m] = true
}
direct := map[string]bool{}
for _, modFile := range modFiles {
- // TODO(golang.org/cl/327329): Use the correct index here.
+ requirement:
for _, r := range modFile.Require {
- if index != nil && index.exclude[r.Mod] {
- if cfg.BuildMod == "mod" {
- fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
- } else {
- fmt.Fprintf(os.Stderr, "go: ignoring requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
+ // TODO(#45713): Maybe join
+ for _, mainModule := range MainModules.Versions() {
+ if index := MainModules.Index(mainModule); index != nil && index.exclude[r.Mod] {
+ if cfg.BuildMod == "mod" {
+ fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
+ } else {
+ fmt.Fprintf(os.Stderr, "go: ignoring requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
+ }
+ continue requirement
}
- continue
}
roots = append(roots, r.Mod)
}
if len(modRoots) == 1 {
+ index := MainModules.GetSingleIndexOrNil()
if fi, err := fsys.Stat(filepath.Join(modRoots[0], "vendor")); err == nil && fi.IsDir() {
modGo := "unspecified"
if index != nil && index.goVersionV != "" {
// convertLegacyConfig imports module requirements from a legacy vendoring
// configuration file, if one is present.
-func convertLegacyConfig(modPath string) (from string, err error) {
+func convertLegacyConfig(modFile *modfile.File, modPath string) (from string, err error) {
noneSelected := func(path string) (version string) { return "none" }
queryPackage := func(path, rev string) (module.Version, error) {
pkgMods, modOnly, err := QueryPattern(context.Background(), path, rev, noneSelected, nil)
// addGoStmt adds a go directive to the go.mod file if it does not already
// include one. The 'go' version added, if any, is the latest version supported
// by this toolchain.
-func addGoStmt(mod module.Version, v string) {
+func addGoStmt(modFile *modfile.File, mod module.Version, v string) {
if modFile.Go != nil && modFile.Go.Version != "" {
return
}
if MainModules.Len() != 1 || MainModules.ModRoot(MainModules.Versions()[0]) == "" {
_ = TODOWorkspaces("also check that workspace mode is off")
// We aren't in a module, so we don't have anywhere to write a go.mod file.
+ _ = TODOWorkspaces("also check that workspace mode is off")
return
}
+ mainModule := MainModules.Versions()[0]
+ modFile := MainModules.ModFile(mainModule)
var list []*modfile.Require
for _, m := range rs.rootModules {
}
modFile.Cleanup()
+ index := MainModules.GetSingleIndexOrNil()
dirty := index.modFileIsDirty(modFile)
if dirty && cfg.BuildMod != "mod" {
// If we're about to fail due to -mod=readonly,
mainModule := MainModules.Versions()[0]
// At this point we have determined to make the go.mod file on disk equal to new.
- index = indexModFile(new, modFile, mainModule, false)
+ MainModules.SetIndex(mainModule, indexModFile(new, modFile, mainModule, false))
// Update go.sum after releasing the side lock and refreshing the index.
// 'go mod init' shouldn't write go.sum, since it will be incomplete.
go117LazyTODO = false
)
-var modFile *modfile.File
-
// modFileGoVersion returns the (non-empty) Go version at which the requirements
// in modFile are intepreted, or the latest Go version if modFile is nil.
func modFileGoVersion() string {
+ _ = TODOWorkspaces("this is obviously wrong.")
+ // Yes we're picking arbitrarily, we'll have to pass through the version
+ // we care about
+ modFile := MainModules.ModFile(MainModules.Versions()[0])
if modFile == nil {
return LatestGoVersion()
}
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
}
// CheckExclusions returns an error equivalent to ErrDisallowed if module m is
// excluded by the main module's go.mod file.
func CheckExclusions(ctx context.Context, m module.Version) error {
- if index != nil && index.exclude[m] {
- return module.VersionError(m, errExcluded)
+ for _, mainModule := range MainModules.Versions() {
+ if index := MainModules.Index(mainModule); index != nil && index.exclude[m] {
+ return module.VersionError(m, errExcluded)
+ }
}
return nil
}
return summary.deprecated, nil
}
+func replacement(mod module.Version, index *modFileIndex) (fromVersion string, to module.Version, ok bool) {
+ if r, ok := index.replace[mod]; ok {
+ return mod.Version, r, true
+ }
+ if r, ok := index.replace[module.Version{Path: mod.Path}]; ok {
+ return "", r, true
+ }
+ return "", module.Version{}, false
+}
+
// 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
+ _ = TODOWorkspaces("support replaces in the go.work file")
+ foundFrom, found, foundModRoot := "", module.Version{}, ""
+ for _, v := range MainModules.Versions() {
+ if index := MainModules.Index(v); index != nil {
+ if from, r, ok := replacement(mod, index); ok {
+ modRoot := MainModules.ModRoot(v)
+ if foundModRoot != "" && foundFrom != from && found != r {
+ _ = TODOWorkspaces("once the go.work file supports replaces, recommend them as a way to override conflicts")
+ base.Errorf("conflicting replacements found for %v in workspace modules defined by %v and %v",
+ mod, modFilePath(foundModRoot), modFilePath(modRoot))
+ return found
+ }
+ found, foundModRoot = r, modRoot
+ }
}
}
- return module.Version{}
+ return found
}
// resolveReplacement returns the module actually used to load the source code
}
}
- if index != nil && len(index.exclude) > 0 {
- // Drop any requirements on excluded versions.
- // Don't modify the cached summary though, since we might need the raw
- // summary separately.
- haveExcludedReqs := false
- for _, r := range summary.require {
- if index.exclude[r] {
- haveExcludedReqs = true
- break
- }
- }
- if haveExcludedReqs {
- s := new(modFileSummary)
- *s = *summary
- s.require = make([]module.Version, 0, len(summary.require))
+ for _, mainModule := range MainModules.Versions() {
+ if index := MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 {
+ // Drop any requirements on excluded versions.
+ // Don't modify the cached summary though, since we might need the raw
+ // summary separately.
+ haveExcludedReqs := false
for _, r := range summary.require {
- if !index.exclude[r] {
- s.require = append(s.require, r)
+ if index.exclude[r] {
+ haveExcludedReqs = true
+ break
+ }
+ }
+ if haveExcludedReqs {
+ s := new(modFileSummary)
+ *s = *summary
+ s.require = make([]module.Version, 0, len(summary.require))
+ for _, r := range summary.require {
+ if !index.exclude[r] {
+ s.require = append(s.require, r)
+ }
}
+ summary = s
}
- summary = s
}
}
return summary, nil
repo = emptyRepo{path: path, err: err}
}
- if index == nil {
- return repo, err
- }
- if _, ok := index.highestReplaced[path]; !ok {
- return repo, err
+ // TODO(#45713): Join all the highestReplaced fields into a single value.
+ for _, mm := range MainModules.Versions() {
+ index := MainModules.Index(mm)
+ if index == nil {
+ continue
+ }
+ if _, ok := index.highestReplaced[path]; ok {
+ return &replacementRepo{repo: repo}, nil
+ }
}
- return &replacementRepo{repo: repo}, nil
+ return repo, err
}
// An emptyRepo is a versionRepo that contains no versions.
}
versions := repoVersions
- if index != nil && len(index.replace) > 0 {
- path := rr.ModulePath()
- for m, _ := range index.replace {
- if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) {
- versions = append(versions, m.Version)
+ for _, mm := range MainModules.Versions() {
+ if index := MainModules.Index(mm); index != nil && len(index.replace) > 0 {
+ path := rr.ModulePath()
+ for m, _ := range index.replace {
+ if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) {
+ versions = append(versions, m.Version)
+ }
}
}
}
func (rr *replacementRepo) Stat(rev string) (*modfetch.RevInfo, error) {
info, err := rr.repo.Stat(rev)
- if err == nil || index == nil || len(index.replace) == 0 {
+ if err == nil {
+ return info, err
+ }
+ var hasReplacements bool
+ for _, v := range MainModules.Versions() {
+ if index := MainModules.Index(v); index != nil && len(index.replace) > 0 {
+ hasReplacements = true
+ }
+ }
+ if !hasReplacements {
return info, err
}
func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) {
info, err := rr.repo.Latest()
+ path := rr.ModulePath()
- if index != nil {
- path := rr.ModulePath()
- if v, ok := index.highestReplaced[path]; ok {
- if v == "" {
- // The only replacement is a wildcard that doesn't specify a version, so
- // synthesize a pseudo-version with an appropriate major version and a
- // timestamp below any real timestamp. That way, if the main module is
- // used from within some other module, the user will be able to upgrade
- // the requirement to any real version they choose.
- if _, pathMajor, ok := module.SplitPathVersion(path); ok && len(pathMajor) > 0 {
- v = module.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
- } else {
- v = module.PseudoVersion("v0", "", time.Time{}, "000000000000")
+ highestReplaced, found := "", false
+ for _, mm := range MainModules.Versions() {
+ if index := MainModules.Index(mm); index != nil {
+ if v, ok := index.highestReplaced[path]; ok {
+ if !found {
+ highestReplaced, found = v, true
+ continue
+ }
+ if semver.Compare(v, highestReplaced) > 0 {
+ highestReplaced = v
}
}
+ }
+ }
- if err != nil || semver.Compare(v, info.Version) > 0 {
- return rr.replacementStat(v)
+ if found {
+ v := highestReplaced
+
+ if v == "" {
+ // The only replacement is a wildcard that doesn't specify a version, so
+ // synthesize a pseudo-version with an appropriate major version and a
+ // timestamp below any real timestamp. That way, if the main module is
+ // used from within some other module, the user will be able to upgrade
+ // the requirement to any real version they choose.
+ if _, pathMajor, ok := module.SplitPathVersion(path); ok && len(pathMajor) > 0 {
+ v = module.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
+ } else {
+ v = module.PseudoVersion("v0", "", time.Time{}, "000000000000")
}
}
+
+ if err != nil || semver.Compare(v, info.Version) > 0 {
+ return rr.replacementStat(v)
+ }
}
return info, err
"cmd/go/internal/base"
+ "golang.org/x/mod/modfile"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
)
// checkVendorConsistency verifies that the vendor/modules.txt file matches (if
// go 1.14) or at least does not contradict (go 1.13 or earlier) the
// requirements and replacements listed in the main module's go.mod file.
-func checkVendorConsistency() {
+func checkVendorConsistency(index *modFileIndex, modFile *modfile.File) {
readVendorList()
pre114 := false