aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/go/internal/modload
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/internal/modload')
-rw-r--r--src/cmd/go/internal/modload/build.go66
-rw-r--r--src/cmd/go/internal/modload/buildlist.go92
-rw-r--r--src/cmd/go/internal/modload/edit.go20
-rw-r--r--src/cmd/go/internal/modload/import.go152
-rw-r--r--src/cmd/go/internal/modload/init.go661
-rw-r--r--src/cmd/go/internal/modload/list.go8
-rw-r--r--src/cmd/go/internal/modload/load.go189
-rw-r--r--src/cmd/go/internal/modload/modfile.go169
-rw-r--r--src/cmd/go/internal/modload/mvs.go4
-rw-r--r--src/cmd/go/internal/modload/query.go230
-rw-r--r--src/cmd/go/internal/modload/search.go15
-rw-r--r--src/cmd/go/internal/modload/vendor.go12
12 files changed, 1082 insertions, 536 deletions
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index 76e1ad589f..0efd84123a 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -212,20 +212,20 @@ func addDeprecation(ctx context.Context, m *modinfo.ModulePublic) {
// in rs (which may be nil to indicate that m was not loaded from a requirement
// graph).
func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic {
- if m == Target {
+ if m.Version == "" && MainModules.Contains(m.Path) {
info := &modinfo.ModulePublic{
Path: m.Path,
Version: m.Version,
Main: true,
}
- if v, ok := rawGoVersion.Load(Target); ok {
+ if v, ok := rawGoVersion.Load(m); ok {
info.GoVersion = v.(string)
} else {
panic("internal error: GoVersion not set for main module")
}
- if HasModRoot() {
- info.Dir = ModRoot()
- info.GoMod = ModFilePath()
+ if modRoot := MainModules.ModRoot(m); modRoot != "" {
+ info.Dir = modRoot
+ info.GoMod = modFilePath(modRoot)
}
return info
}
@@ -240,7 +240,7 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
}
// completeFromModCache fills in the extra fields in m using the module cache.
- completeFromModCache := func(m *modinfo.ModulePublic) {
+ completeFromModCache := func(m *modinfo.ModulePublic, replacedFrom string) {
checksumOk := func(suffix string) bool {
return rs == nil || m.Version == "" || cfg.BuildMod == "mod" ||
modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix})
@@ -259,7 +259,7 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
if m.GoVersion == "" && checksumOk("/go.mod") {
// Load the go.mod file to determine the Go version, since it hasn't
// already been populated from rawGoVersion.
- if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" {
+ if summary, err := rawGoModSummary(mod, replacedFrom); err == nil && summary.goVersion != "" {
m.GoVersion = summary.goVersion
}
}
@@ -289,11 +289,11 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
if rs == nil {
// If this was an explicitly-versioned argument to 'go mod download' or
// 'go list -m', report the actual requested version, not its replacement.
- completeFromModCache(info) // Will set m.Error in vendor mode.
+ completeFromModCache(info, "") // Will set m.Error in vendor mode.
return info
}
- r := Replacement(m)
+ r, replacedFrom := Replacement(m)
if r.Path == "" {
if cfg.BuildMod == "vendor" {
// It's tempting to fill in the "Dir" field to point within the vendor
@@ -302,7 +302,7 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
// interleave packages from different modules if one module path is a
// prefix of the other.
} else {
- completeFromModCache(info)
+ completeFromModCache(info, "")
}
return info
}
@@ -322,12 +322,12 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
if filepath.IsAbs(r.Path) {
info.Replace.Dir = r.Path
} else {
- info.Replace.Dir = filepath.Join(ModRoot(), r.Path)
+ info.Replace.Dir = filepath.Join(replacedFrom, r.Path)
}
info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
}
if cfg.BuildMod != "vendor" {
- completeFromModCache(info.Replace)
+ completeFromModCache(info.Replace, replacedFrom)
info.Dir = info.Replace.Dir
info.GoMod = info.Replace.GoMod
info.Retracted = info.Replace.Retracted
@@ -340,15 +340,14 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
// for modules providing packages named by path and deps. path and deps must
// name packages that were resolved successfully with LoadPackages.
func PackageBuildInfo(path string, deps []string) string {
- if isStandardImportPath(path) || !Enabled() {
+ if !Enabled() {
return ""
}
-
- target := mustFindModule(loaded, path, path)
+ target, _ := findModule(loaded, path)
mdeps := make(map[module.Version]bool)
for _, dep := range deps {
- if !isStandardImportPath(dep) {
- mdeps[mustFindModule(loaded, path, dep)] = true
+ if m, ok := findModule(loaded, dep); ok {
+ mdeps[m] = true
}
}
var mods []module.Version
@@ -367,14 +366,16 @@ func PackageBuildInfo(path string, deps []string) string {
mv = "(devel)"
}
fmt.Fprintf(&buf, "%s\t%s\t%s", token, m.Path, mv)
- if r := Replacement(m); r.Path == "" {
+ if r, _ := Replacement(m); r.Path == "" {
fmt.Fprintf(&buf, "\t%s\n", modfetch.Sum(m))
} else {
fmt.Fprintf(&buf, "\n=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r))
}
}
- writeEntry("mod", target)
+ if target.Path != "" {
+ writeEntry("mod", target)
+ }
for _, mod := range mods {
writeEntry("dep", mod)
}
@@ -382,38 +383,13 @@ func PackageBuildInfo(path string, deps []string) string {
return buf.String()
}
-// mustFindModule is like findModule, but it calls base.Fatalf if the
-// module can't be found.
-//
-// TODO(jayconrod): remove this. Callers should use findModule and return
-// errors instead of relying on base.Fatalf.
-func mustFindModule(ld *loader, target, path string) module.Version {
- pkg, ok := ld.pkgCache.Get(path).(*loadPkg)
- if ok {
- if pkg.err != nil {
- base.Fatalf("build %v: cannot load %v: %v", target, path, pkg.err)
- }
- return pkg.mod
- }
-
- if path == "command-line-arguments" {
- return Target
- }
-
- base.Fatalf("build %v: cannot find module for path %v", target, path)
- panic("unreachable")
-}
-
// findModule searches for the module that contains the package at path.
// If the package was loaded, its containing module and true are returned.
-// Otherwise, module.Version{} and false are returend.
+// Otherwise, module.Version{} and false are returned.
func findModule(ld *loader, path string) (module.Version, bool) {
if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok {
return pkg.mod, pkg.mod != module.Version{}
}
- if path == "command-line-arguments" {
- return Target, true
- }
return module.Version{}, false
}
diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go
index bf69567316..94414278ab 100644
--- a/src/cmd/go/internal/modload/buildlist.go
+++ b/src/cmd/go/internal/modload/buildlist.go
@@ -40,7 +40,7 @@ type Requirements struct {
depth modDepth
// rootModules is the set of module versions explicitly required by the main
- // module, sorted and capped to length. It may contain duplicates, and may
+ // modules, sorted and capped to length. It may contain duplicates, and may
// contain multiple versions for a given module path.
rootModules []module.Version
maxRootVersion map[string]string
@@ -99,8 +99,8 @@ var requirements *Requirements
// *Requirements before any other method.
func newRequirements(depth modDepth, rootModules []module.Version, direct map[string]bool) *Requirements {
for i, m := range rootModules {
- if m == Target {
- panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is Target", i))
+ if m.Version == "" && MainModules.Contains(m.Path) {
+ panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is a main module", i))
}
if m.Path == "" || m.Version == "" {
panic(fmt.Sprintf("bad requirement: rootModules[%v] = %v", i, m))
@@ -135,9 +135,14 @@ func newRequirements(depth modDepth, rootModules []module.Version, direct map[st
func (rs *Requirements) initVendor(vendorList []module.Version) {
rs.graphOnce.Do(func() {
mg := &ModuleGraph{
- g: mvs.NewGraph(cmpVersion, []module.Version{Target}),
+ g: mvs.NewGraph(cmpVersion, MainModules.Versions()),
}
+ if MainModules.Len() != 1 {
+ panic("There should be exactly one main module in Vendor mode.")
+ }
+ mainModule := MainModules.Versions()[0]
+
if rs.depth == lazy {
// The roots of a lazy module should already include every module in the
// vendor list, because the vendored modules are the same as those
@@ -158,7 +163,7 @@ func (rs *Requirements) initVendor(vendorList []module.Version) {
// Now we can treat the rest of the module graph as effectively “pruned
// out”, like a more aggressive version of lazy loading: in vendor mode,
// the root requirements *are* the complete module graph.
- mg.g.Require(Target, rs.rootModules)
+ mg.g.Require(mainModule, rs.rootModules)
} else {
// The transitive requirements of the main module are not in general available
// from the vendor directory, and we don't actually know how we got from
@@ -170,7 +175,7 @@ func (rs *Requirements) initVendor(vendorList []module.Version) {
// graph, but still distinguishes between direct and indirect
// dependencies.
vendorMod := module.Version{Path: "vendor/modules.txt", Version: ""}
- mg.g.Require(Target, append(rs.rootModules, vendorMod))
+ mg.g.Require(mainModule, append(rs.rootModules, vendorMod))
mg.g.Require(vendorMod, vendorList)
}
@@ -182,8 +187,8 @@ func (rs *Requirements) initVendor(vendorList []module.Version) {
// path, or the zero module.Version and ok=false if the module is not a root
// dependency.
func (rs *Requirements) rootSelected(path string) (version string, ok bool) {
- if path == Target.Path {
- return Target.Version, true
+ if MainModules.Contains(path) {
+ return "", true
}
if v, ok := rs.maxRootVersion[path]; ok {
return v, true
@@ -197,7 +202,7 @@ func (rs *Requirements) rootSelected(path string) (version string, ok bool) {
// selection.
func (rs *Requirements) hasRedundantRoot() bool {
for i, m := range rs.rootModules {
- if m.Path == Target.Path || (i > 0 && m.Path == rs.rootModules[i-1].Path) {
+ if MainModules.Contains(m.Path) || (i > 0 && m.Path == rs.rootModules[i-1].Path) {
return true
}
}
@@ -274,10 +279,17 @@ func readModGraph(ctx context.Context, depth modDepth, roots []module.Version) (
mu sync.Mutex // guards mg.g and hasError during loading
hasError bool
mg = &ModuleGraph{
- g: mvs.NewGraph(cmpVersion, []module.Version{Target}),
+ g: mvs.NewGraph(cmpVersion, MainModules.Versions()),
}
)
- mg.g.Require(Target, roots)
+ for _, m := range MainModules.Versions() {
+ // Require all roots from all main modules.
+ _ = TODOWorkspaces("This flattens a level of the module graph, adding the dependencies " +
+ "of all main modules to a single requirements struct, and losing the information of which " +
+ "main module required which requirement. Rework the requirements struct and change this" +
+ "to reflect the structure of the main modules.")
+ mg.g.Require(m, roots)
+ }
var (
loadQueue = par.NewQueue(runtime.GOMAXPROCS(0))
@@ -404,10 +416,12 @@ func (mg *ModuleGraph) findError() error {
}
func (mg *ModuleGraph) allRootsSelected() bool {
- roots, _ := mg.g.RequiredBy(Target)
- for _, m := range roots {
- if mg.Selected(m.Path) != m.Version {
- return false
+ for _, mm := range MainModules.Versions() {
+ roots, _ := mg.g.RequiredBy(mm)
+ for _, m := range roots {
+ if mg.Selected(m.Path) != m.Version {
+ return false
+ }
}
}
return true
@@ -447,7 +461,7 @@ func LoadModGraph(ctx context.Context, goVersion string) *ModuleGraph {
base.Fatalf("go: %v", err)
}
- commitRequirements(ctx, modFileGoVersion(), rs)
+ commitRequirements(ctx, rs)
return mg
}
@@ -513,7 +527,7 @@ func EditBuildList(ctx context.Context, add, mustSelect []module.Version) (chang
if err != nil {
return false, err
}
- commitRequirements(ctx, modFileGoVersion(), rs)
+ commitRequirements(ctx, rs)
return changed, err
}
@@ -546,10 +560,11 @@ type Conflict struct {
// both retain the same versions of all packages in pkgs and satisfy the
// lazy loading invariants (if applicable).
func tidyRoots(ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) {
+ mainModule := MainModules.mustGetSingleMainModule()
if rs.depth == eager {
- return tidyEagerRoots(ctx, rs.direct, pkgs)
+ return tidyEagerRoots(ctx, mainModule, rs.direct, pkgs)
}
- return tidyLazyRoots(ctx, rs.direct, pkgs)
+ return tidyLazyRoots(ctx, mainModule, rs.direct, pkgs)
}
func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) {
@@ -574,10 +589,10 @@ func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements,
// To ensure that the loading process eventually converges, the caller should
// add any needed roots from the tidy root set (without removing existing untidy
// roots) until the set of roots has converged.
-func tidyLazyRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
+func tidyLazyRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
var (
roots []module.Version
- pathIncluded = map[string]bool{Target.Path: true}
+ pathIncluded = map[string]bool{mainModule.Path: true}
)
// We start by adding roots for every package in "all".
//
@@ -807,7 +822,7 @@ func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requiremen
// We've added or upgraded one or more roots, so load the full module
// graph so that we can update those roots to be consistent with other
// requirements.
- if cfg.BuildMod != "mod" {
+ if mustHaveCompleteRequirements() {
// Our changes to the roots may have moved dependencies into or out of
// the lazy-loading horizon, which could in turn change the selected
// versions of other modules. (Unlike for eager modules, for lazy
@@ -855,7 +870,9 @@ func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requiremen
roots = make([]module.Version, 0, len(rs.rootModules))
rootsUpgraded = false
inRootPaths := make(map[string]bool, len(rs.rootModules)+1)
- inRootPaths[Target.Path] = true
+ for _, mm := range MainModules.Versions() {
+ inRootPaths[mm.Path] = true
+ }
for _, m := range rs.rootModules {
if inRootPaths[m.Path] {
// This root specifies a redundant path. We already retained the
@@ -958,7 +975,7 @@ func spotCheckRoots(ctx context.Context, rs *Requirements, mods map[module.Versi
// tidyEagerRoots returns a minimal set of root requirements that maintains the
// selected version of every module that provided a package in pkgs, and
// includes the selected version of every such module in direct as a root.
-func tidyEagerRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
+func tidyEagerRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
var (
keep []module.Version
keptPath = map[string]bool{}
@@ -981,7 +998,7 @@ func tidyEagerRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg
}
}
- min, err := mvs.Req(Target, rootPaths, &mvsReqs{roots: keep})
+ min, err := mvs.Req(mainModule, rootPaths, &mvsReqs{roots: keep})
if err != nil {
return nil, err
}
@@ -1011,7 +1028,7 @@ func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requireme
return rs, err
}
- if cfg.BuildMod != "mod" {
+ if mustHaveCompleteRequirements() {
// Instead of actually updating the requirements, just check that no updates
// are needed.
if rs == nil {
@@ -1070,7 +1087,7 @@ func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requireme
// This is only for convenience and clarity for end users: in an eager module,
// the choice of explicit vs. implicit dependency has no impact on MVS
// selection (for itself or any other module).
- keep := append(mg.BuildList()[1:], add...)
+ keep := append(mg.BuildList()[MainModules.Len():], add...)
for _, m := range keep {
if direct[m.Path] && !inRootPaths[m.Path] {
rootPaths = append(rootPaths, m.Path)
@@ -1078,16 +1095,25 @@ func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requireme
}
}
- min, err := mvs.Req(Target, rootPaths, &mvsReqs{roots: keep})
- if err != nil {
- return rs, err
+ // TODO(matloob): Make roots into a map.
+ var roots []module.Version
+ for _, mainModule := range MainModules.Versions() {
+ min, err := mvs.Req(mainModule, rootPaths, &mvsReqs{roots: keep})
+ if err != nil {
+ return rs, err
+ }
+ roots = append(roots, min...)
}
- if rs.depth == eager && reflect.DeepEqual(min, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
+ if MainModules.Len() > 1 {
+ module.Sort(roots)
+ }
+ if rs.depth == eager && reflect.DeepEqual(roots, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
// The root set is unchanged and rs was already eager, so keep rs to
// preserve its cached ModuleGraph (if any).
return rs, nil
}
- return newRequirements(eager, min, direct), nil
+
+ return newRequirements(eager, roots, direct), nil
}
// convertDepth returns a version of rs with the given depth.
@@ -1117,5 +1143,5 @@ func convertDepth(ctx context.Context, rs *Requirements, depth modDepth) (*Requi
if err != nil {
return rs, err
}
- return newRequirements(lazy, mg.BuildList()[1:], rs.direct), nil
+ return newRequirements(lazy, mg.BuildList()[MainModules.Len():], rs.direct), nil
}
diff --git a/src/cmd/go/internal/modload/edit.go b/src/cmd/go/internal/modload/edit.go
index c350b9d1b5..796721c90c 100644
--- a/src/cmd/go/internal/modload/edit.go
+++ b/src/cmd/go/internal/modload/edit.go
@@ -75,7 +75,7 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
// We promote the modules in mustSelect to be explicit requirements.
var rootPaths []string
for _, m := range mustSelect {
- if m.Version != "none" && m.Path != Target.Path {
+ if !MainModules.Contains(m.Path) && m.Version != "none" {
rootPaths = append(rootPaths, m.Path)
}
}
@@ -97,7 +97,7 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
}
}
- roots, err = mvs.Req(Target, rootPaths, &mvsReqs{roots: mods})
+ roots, err = mvs.Req(MainModules.mustGetSingleMainModule(), rootPaths, &mvsReqs{roots: mods})
if err != nil {
return nil, false, err
}
@@ -218,8 +218,8 @@ func raiseLimitsForUpgrades(ctx context.Context, maxVersion map[string]string, d
eagerUpgrades = tryUpgrade
} else {
for _, m := range tryUpgrade {
- if m.Path == Target.Path {
- // Target is already considered to be higher than any possible m, so we
+ if MainModules.Contains(m.Path) {
+ // The main module versions are already considered to be higher than any possible m, so we
// won't be upgrading to it anyway and there is no point scanning its
// dependencies.
continue
@@ -318,7 +318,7 @@ func selectPotentiallyImportedModules(ctx context.Context, limiter *versionLimit
mods = make([]module.Version, 0, len(limiter.selected))
for path, v := range limiter.selected {
- if v != "none" && path != Target.Path {
+ if v != "none" && !MainModules.Contains(path) {
mods = append(mods, module.Version{Path: path, Version: v})
}
}
@@ -334,7 +334,7 @@ func selectPotentiallyImportedModules(ctx context.Context, limiter *versionLimit
}
mods = make([]module.Version, 0, len(limiter.selected))
for path, _ := range limiter.selected {
- if path != Target.Path {
+ if !MainModules.Contains(path) {
if v := mg.Selected(path); v != "none" {
mods = append(mods, module.Version{Path: path, Version: v})
}
@@ -415,10 +415,14 @@ func (dq dqState) isDisqualified() bool {
// itself lazy, its unrestricted dependencies are skipped when scanning
// requirements.
func newVersionLimiter(depth modDepth, max map[string]string) *versionLimiter {
+ selected := make(map[string]string)
+ for _, m := range MainModules.Versions() {
+ selected[m.Path] = m.Version
+ }
return &versionLimiter{
depth: depth,
max: max,
- selected: map[string]string{Target.Path: Target.Version},
+ selected: selected,
dqReason: map[module.Version]dqState{},
requiring: map[module.Version][]module.Version{},
}
@@ -492,7 +496,7 @@ func (l *versionLimiter) Select(m module.Version) (conflict module.Version, err
// as is feasible, we don't want to retain test dependencies that are only
// marginally relevant at best.
func (l *versionLimiter) check(m module.Version, depth modDepth) dqState {
- if m.Version == "none" || m == Target {
+ if m.Version == "none" || m == MainModules.mustGetSingleMainModule() {
// version "none" has no requirements, and the dependencies of Target are
// tautological.
return dqState{}
diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go
index d2bbe5cbe0..de47974b9b 100644
--- a/src/cmd/go/internal/modload/import.go
+++ b/src/cmd/go/internal/modload/import.go
@@ -32,6 +32,8 @@ type ImportMissingError struct {
Module module.Version
QueryErr error
+ ImportingMainModule module.Version
+
// isStd indicates whether we would expect to find the package in the standard
// library. This is normally true for all dotless import paths, but replace
// directives can cause us to treat the replaced paths as also being in
@@ -71,6 +73,9 @@ func (e *ImportMissingError) Error() string {
if e.QueryErr != nil {
return fmt.Sprintf("%s: %v", message, e.QueryErr)
}
+ if e.ImportingMainModule.Path != "" && e.ImportingMainModule != MainModules.ModContainingCWD() {
+ return fmt.Sprintf("%s; to add it:\n\tcd %s\n\tgo get %s", message, MainModules.ModRoot(e.ImportingMainModule), e.Path)
+ }
return fmt.Sprintf("%s; to add it:\n\tgo get %s", message, e.Path)
}
@@ -257,11 +262,13 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
// Is the package in the standard library?
pathIsStd := search.IsStandardImportPath(path)
if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
- if targetInGorootSrc {
- if dir, ok, err := dirInModule(path, targetPrefix, ModRoot(), true); err != nil {
- return module.Version{}, dir, err
- } else if ok {
- return Target, dir, nil
+ for _, mainModule := range MainModules.Versions() {
+ if MainModules.InGorootSrc(mainModule) {
+ if dir, ok, err := dirInModule(path, MainModules.PathPrefix(mainModule), MainModules.ModRoot(mainModule), true); err != nil {
+ return module.Version{}, dir, err
+ } else if ok {
+ return mainModule, dir, nil
+ }
}
}
dir := filepath.Join(cfg.GOROOT, "src", path)
@@ -271,8 +278,10 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
// -mod=vendor is special.
// Everything must be in the main module or the main module's vendor directory.
if cfg.BuildMod == "vendor" {
- mainDir, mainOK, mainErr := dirInModule(path, targetPrefix, ModRoot(), true)
- vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(ModRoot(), "vendor"), false)
+ mainModule := MainModules.mustGetSingleMainModule()
+ modRoot := MainModules.ModRoot(mainModule)
+ mainDir, mainOK, mainErr := dirInModule(path, MainModules.PathPrefix(mainModule), modRoot, true)
+ vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(modRoot, "vendor"), false)
if mainOK && vendorOK {
return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: []string{mainDir, vendorDir}}
}
@@ -280,12 +289,12 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
// Note that we're not checking that the package exists.
// We'll leave that for load.
if !vendorOK && mainDir != "" {
- return Target, mainDir, nil
+ return mainModule, mainDir, nil
}
if mainErr != nil {
return module.Version{}, "", mainErr
}
- readVendorList()
+ readVendorList(mainModule)
return vendorPkgModule[path], vendorDir, nil
}
@@ -410,69 +419,72 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
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.
+ replacement, _ := Replacement(mods[0])
+ return module.Version{}, &PackageNotInModuleError{
+ Mod: mods[0],
+ Query: "latest",
+ Pattern: path,
+ Replacement: replacement,
}
}
@@ -638,14 +650,14 @@ func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFile
// The isLocal return value reports whether the replacement,
// if any, is local to the filesystem.
func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, isLocal bool, err error) {
- if mod == Target {
- return ModRoot(), true, nil
+ if modRoot := MainModules.ModRoot(mod); modRoot != "" {
+ return modRoot, true, nil
}
- if r := Replacement(mod); r.Path != "" {
+ if r, replacedFrom := Replacement(mod); r.Path != "" {
if r.Version == "" {
dir = r.Path
if !filepath.IsAbs(dir) {
- dir = filepath.Join(ModRoot(), dir)
+ dir = filepath.Join(replacedFrom, dir)
}
// Ensure that the replacement directory actually exists:
// dirInModule does not report errors for missing modules,
@@ -667,7 +679,7 @@ func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, i
mod = r
}
- if HasModRoot() && cfg.BuildMod == "readonly" && needSum && !modfetch.HaveSum(mod) {
+ if HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() && needSum && !modfetch.HaveSum(mod) {
return "", false, module.VersionError(mod, &sumMissingError{})
}
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 862b007739..b845842a7f 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -17,6 +17,7 @@ import (
"path/filepath"
"strconv"
"strings"
+ "sync"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
@@ -45,26 +46,168 @@ var (
allowMissingModuleImports bool
)
+func TODOWorkspaces(s string) error {
+ return fmt.Errorf("need to support this for workspaces: %s", s)
+}
+
// Variables set in Init.
var (
initialized bool
- modRoot string
- gopath string
+
+ // These are primarily used to initialize the MainModules, and should be
+ // eventually superceded by them but are still used in cases where the module
+ // roots are required but MainModules hasn't been initialized yet. Set to
+ // the modRoots of the main modules.
+ // modRoots != nil implies len(modRoots) > 0
+ modRoots []string
+ gopath string
+ workFileGoVersion string
)
-// Variables set in initTarget (during {Load,Create}ModFile).
+// Variable set in InitWorkfile
var (
- Target module.Version
+ // Set to the path to the go.work file, or "" if workspace mode is disabled.
+ workFilePath string
+)
+
+type MainModuleSet struct {
+ // versions are the module.Version values of each of the main modules.
+ // For each of them, the Path fields are ordinary module paths and the Version
+ // fields are empty strings.
+ versions []module.Version
+
+ // modRoot maps each module in versions to its absolute filesystem path.
+ modRoot map[module.Version]string
- // targetPrefix is the path prefix for packages in Target, without a trailing
- // slash. For most modules, targetPrefix is just Target.Path, but the
+ // pathPrefix is the path prefix for packages in the module, without a trailing
+ // slash. For most modules, pathPrefix is just version.Path, but the
// standard-library module "std" has an empty prefix.
- targetPrefix string
+ pathPrefix map[module.Version]string
- // targetInGorootSrc caches whether modRoot is within GOROOT/src.
+ // inGorootSrc caches whether modRoot is within GOROOT/src.
// The "std" module is special within GOROOT/src, but not otherwise.
- targetInGorootSrc bool
-)
+ inGorootSrc map[module.Version]bool
+
+ modFiles map[module.Version]*modfile.File
+
+ modContainingCWD module.Version
+
+ workFileGoVersion string
+
+ indexMu sync.Mutex
+ indices map[module.Version]*modFileIndex
+}
+
+func (mms *MainModuleSet) PathPrefix(m module.Version) string {
+ return mms.pathPrefix[m]
+}
+
+// Versions returns the module.Version values of each of the main modules.
+// For each of them, the Path fields are ordinary module paths and the Version
+// fields are empty strings.
+// Callers should not modify the returned slice.
+func (mms *MainModuleSet) Versions() []module.Version {
+ if mms == nil {
+ return nil
+ }
+ return mms.versions
+}
+
+func (mms *MainModuleSet) Contains(path string) bool {
+ if mms == nil {
+ return false
+ }
+ for _, v := range mms.versions {
+ if v.Path == path {
+ return true
+ }
+ }
+ return false
+}
+
+func (mms *MainModuleSet) ModRoot(m module.Version) string {
+ if mms == nil {
+ return ""
+ }
+ return mms.modRoot[m]
+}
+
+func (mms *MainModuleSet) InGorootSrc(m module.Version) bool {
+ if mms == nil {
+ return false
+ }
+ return mms.inGorootSrc[m]
+}
+
+func (mms *MainModuleSet) mustGetSingleMainModule() module.Version {
+ if mms == nil || len(mms.versions) == 0 {
+ panic("internal error: mustGetSingleMainModule called in context with no main modules")
+ }
+ if len(mms.versions) != 1 {
+ if inWorkspaceMode() {
+ panic("internal error: mustGetSingleMainModule called in workspace mode")
+ } else {
+ panic("internal error: multiple main modules present outside of workspace mode")
+ }
+ }
+ return mms.versions[0]
+}
+
+func (mms *MainModuleSet) GetSingleIndexOrNil() *modFileIndex {
+ if mms == nil {
+ return nil
+ }
+ if len(mms.versions) == 0 {
+ return nil
+ }
+ return mms.indices[mms.mustGetSingleMainModule()]
+}
+
+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
+ }
+ return len(mms.versions)
+}
+
+// ModContainingCWD returns the main module containing the working directory,
+// or module.Version{} if none of the main modules contain the working
+// directory.
+func (mms *MainModuleSet) ModContainingCWD() module.Version {
+ return mms.modContainingCWD
+}
+
+// GoVersion returns the go version set on the single module, in module mode,
+// or the go.work file in workspace mode.
+func (mms *MainModuleSet) GoVersion() string {
+ if !inWorkspaceMode() {
+ return modFileGoVersion(mms.ModFile(mms.mustGetSingleMainModule()))
+ }
+ v := mms.workFileGoVersion
+ if v == "" {
+ // Fall back to 1.18 for go.work files.
+ v = "1.18"
+ }
+ return v
+}
+
+var MainModules *MainModuleSet
type Root int
@@ -94,6 +237,7 @@ const (
// in go.mod, edit it before loading.
func ModFile() *modfile.File {
Init()
+ modFile := MainModules.ModFile(MainModules.mustGetSingleMainModule())
if modFile == nil {
die()
}
@@ -105,6 +249,26 @@ func BinDir() string {
return filepath.Join(gopath, "bin")
}
+// InitWorkfile initializes the workFilePath variable for commands that
+// operate in workspace mode. It should not be called by other commands,
+// for example 'go mod tidy', that don't operate in workspace mode.
+func InitWorkfile() {
+ switch cfg.WorkFile {
+ case "off":
+ workFilePath = ""
+ case "", "auto":
+ workFilePath = findWorkspaceFile(base.Cwd())
+ default:
+ workFilePath = cfg.WorkFile
+ }
+}
+
+// WorkFilePath returns the path of the go.work file, or "" if not in
+// workspace mode. WorkFilePath must be called after InitWorkfile.
+func WorkFilePath() string {
+ return workFilePath
+}
+
// Init determines whether module mode is enabled, locates the root of the
// current module (if any), sets environment variables for Git subprocesses, and
// configures the cfg, codehost, load, modfetch, and search packages for use
@@ -169,18 +333,18 @@ func Init() {
if os.Getenv("GCM_INTERACTIVE") == "" {
os.Setenv("GCM_INTERACTIVE", "never")
}
-
- if modRoot != "" {
+ if modRoots != nil {
// modRoot set before Init was called ("go mod init" does this).
// No need to search for go.mod.
} else if RootMode == NoRoot {
if cfg.ModFile != "" && !base.InGOFLAGS("-modfile") {
base.Fatalf("go: -modfile cannot be used with commands that ignore the current module")
}
- modRoot = ""
+ modRoots = nil
+ } else if inWorkspaceMode() {
+ // We're in workspace mode.
} else {
- modRoot = findModuleRoot(base.Cwd())
- if modRoot == "" {
+ if modRoot := findModuleRoot(base.Cwd()); modRoot == "" {
if cfg.ModFile != "" {
base.Fatalf("go: cannot find main module, but -modfile was set.\n\t-modfile cannot be used to set the module root directory.")
}
@@ -198,11 +362,12 @@ func Init() {
// will find it and get modules when they're not expecting them.
// It's a bit of a peculiar thing to disallow but quite mysterious
// when it happens. See golang.org/issue/26708.
- modRoot = ""
fmt.Fprintf(os.Stderr, "go: warning: ignoring go.mod in system temp root %v\n", os.TempDir())
if !mustUseModules {
return
}
+ } else {
+ modRoots = []string{modRoot}
}
}
if cfg.ModFile != "" && !strings.HasSuffix(cfg.ModFile, ".mod") {
@@ -212,6 +377,9 @@ func Init() {
// We're in module mode. Set any global variables that need to be set.
cfg.ModulesEnabled = true
setDefaultBuildMod()
+ _ = TODOWorkspaces("In workspace mode, mod will not be readonly for go mod download," +
+ "verify, graph, and why. Implement support for go mod download and add test cases" +
+ "to ensure verify, graph, and why work properly.")
list := filepath.SplitList(cfg.BuildContext.GOPATH)
if len(list) == 0 || list[0] == "" {
base.Fatalf("missing $GOPATH")
@@ -221,7 +389,16 @@ func Init() {
base.Fatalf("$GOPATH/go.mod exists but should not")
}
- if modRoot == "" {
+ if inWorkspaceMode() {
+ var err error
+ workFileGoVersion, modRoots, err = loadWorkFile(workFilePath)
+ if err != nil {
+ base.Fatalf("reading go.work: %v", err)
+ }
+ _ = TODOWorkspaces("Support falling back to individual module go.sum " +
+ "files for sums not in the workspace sum file.")
+ modfetch.GoSumFile = workFilePath + ".sum"
+ } else if modRoots == nil {
// We're in module mode, but not inside a module.
//
// Commands like 'go build', 'go run', 'go list' have no go.mod file to
@@ -240,8 +417,7 @@ func Init() {
//
// See golang.org/issue/32027.
} else {
- modfetch.GoSumFile = strings.TrimSuffix(ModFilePath(), ".mod") + ".sum"
- search.SetModRoot(modRoot)
+ modfetch.GoSumFile = strings.TrimSuffix(modFilePath(modRoots[0]), ".mod") + ".sum"
}
}
@@ -255,7 +431,7 @@ func Init() {
// be called until the command is installed and flags are parsed. Instead of
// calling Init and Enabled, the main package can call this function.
func WillBeEnabled() bool {
- if modRoot != "" || cfg.ModulesEnabled {
+ if modRoots != nil || cfg.ModulesEnabled {
// Already enabled.
return true
}
@@ -297,16 +473,18 @@ func WillBeEnabled() bool {
// (usually through MustModRoot).
func Enabled() bool {
Init()
- return modRoot != "" || cfg.ModulesEnabled
+ return modRoots != nil || cfg.ModulesEnabled
}
-// ModRoot returns the root of the main module.
-// It calls base.Fatalf if there is no main module.
-func ModRoot() string {
- if !HasModRoot() {
- die()
+func VendorDir() string {
+ return filepath.Join(MainModules.ModRoot(MainModules.mustGetSingleMainModule()), "vendor")
+}
+
+func inWorkspaceMode() bool {
+ if !initialized {
+ panic("inWorkspaceMode called before modload.Init called")
}
- return modRoot
+ return workFilePath != ""
}
// HasModRoot reports whether a main module is present.
@@ -314,17 +492,27 @@ func ModRoot() string {
// does not require a main module.
func HasModRoot() bool {
Init()
- return modRoot != ""
+ return modRoots != nil
}
-// ModFilePath returns the effective path of the go.mod file. Normally, this
-// "go.mod" in the directory returned by ModRoot, but the -modfile flag may
-// change its location. ModFilePath calls base.Fatalf if there is no main
-// module, even if -modfile is set.
-func ModFilePath() string {
+// MustHaveModRoot checks that a main module or main modules are present,
+// and calls base.Fatalf if there are no main modules.
+func MustHaveModRoot() {
+ Init()
if !HasModRoot() {
die()
}
+}
+
+// ModFilePath returns the path that would be used for the go.mod
+// file, if in module mode. ModFilePath calls base.Fatalf if there is no main
+// module, even if -modfile is set.
+func ModFilePath() string {
+ MustHaveModRoot()
+ return modFilePath(findModuleRoot(base.Cwd()))
+}
+
+func modFilePath(modRoot string) string {
if cfg.ModFile != "" {
return cfg.ModFile
}
@@ -365,6 +553,35 @@ func (goModDirtyError) Error() string {
var errGoModDirty error = goModDirtyError{}
+func loadWorkFile(path string) (goVersion string, modRoots []string, err error) {
+ _ = TODOWorkspaces("Clean up and write back the go.work file: add module paths for workspace modules.")
+ workDir := filepath.Dir(path)
+ workData, err := lockedfile.Read(path)
+ if err != nil {
+ return "", nil, err
+ }
+ wf, err := modfile.ParseWork(path, workData, nil)
+ if err != nil {
+ return "", nil, err
+ }
+ if wf.Go != nil {
+ goVersion = wf.Go.Version
+ }
+ seen := map[string]bool{}
+ for _, d := range wf.Directory {
+ modRoot := d.Path
+ if !filepath.IsAbs(modRoot) {
+ modRoot = filepath.Join(workDir, modRoot)
+ }
+ if seen[modRoot] {
+ return "", nil, fmt.Errorf("path %s appears multiple times in workspace", modRoot)
+ }
+ seen[modRoot] = true
+ modRoots = append(modRoots, modRoot)
+ }
+ return goVersion, modRoots, nil
+}
+
// LoadModFile sets Target and, if there is a main module, parses the initial
// build list from its go.mod file.
//
@@ -385,7 +602,7 @@ var errGoModDirty error = goModDirtyError{}
func LoadModFile(ctx context.Context) *Requirements {
rs, needCommit := loadModFile(ctx)
if needCommit {
- commitRequirements(ctx, modFileGoVersion(), rs)
+ commitRequirements(ctx, rs)
}
return rs
}
@@ -402,87 +619,110 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
}
Init()
- if modRoot == "" {
- Target = module.Version{Path: "command-line-arguments"}
- targetPrefix = "command-line-arguments"
+ 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{""}, []*modfile.File{nil}, []*modFileIndex{nil}, "")
goVersion := LatestGoVersion()
- rawGoVersion.Store(Target, goVersion)
+ rawGoVersion.Store(mainModule, goVersion)
requirements = newRequirements(modDepthFromGoVersion(goVersion), nil, nil)
return requirements, false
}
- gomod := ModFilePath()
- var data []byte
- var err error
- if gomodActual, ok := fsys.OverlayPath(gomod); ok {
- // Don't lock go.mod if it's part of the overlay.
- // On Plan 9, locking requires chmod, and we don't want to modify any file
- // in the overlay. See #44700.
- data, err = os.ReadFile(gomodActual)
- } else {
- data, err = lockedfile.Read(gomodActual)
- }
- if err != nil {
- base.Fatalf("go: %v", err)
- }
+ var modFiles []*modfile.File
+ var mainModules []module.Version
+ var indices []*modFileIndex
+ for _, modroot := range modRoots {
+ gomod := modFilePath(modroot)
+ var data []byte
+ var err error
+ if gomodActual, ok := fsys.OverlayPath(gomod); ok {
+ // Don't lock go.mod if it's part of the overlay.
+ // On Plan 9, locking requires chmod, and we don't want to modify any file
+ // in the overlay. See #44700.
+ data, err = os.ReadFile(gomodActual)
+ } else {
+ data, err = lockedfile.Read(gomodActual)
+ }
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
- var fixed bool
- f, err := modfile.Parse(gomod, data, fixVersion(ctx, &fixed))
- if err != nil {
- // Errors returned by modfile.Parse begin with file:line.
- base.Fatalf("go: errors parsing go.mod:\n%s\n", err)
- }
- if f.Module == nil {
- // No module declaration. Must add module path.
- base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
- }
+ var fixed bool
+ f, err := modfile.Parse(gomod, data, fixVersion(ctx, &fixed))
+ if err != nil {
+ // Errors returned by modfile.Parse begin with file:line.
+ base.Fatalf("go: errors parsing go.mod:\n%s\n", err)
+ }
+ if f.Module == nil {
+ // No module declaration. Must add module path.
+ base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
+ }
- modFile = f
- initTarget(f.Module.Mod)
- index = indexModFile(data, f, fixed)
+ modFiles = append(modFiles, f)
+ mainModule := f.Module.Mod
+ mainModules = append(mainModules, mainModule)
+ 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 {
- pathErr.Kind = "module"
+ if err := module.CheckImportPath(f.Module.Mod.Path); err != nil {
+ if pathErr, ok := err.(*module.InvalidPathError); ok {
+ pathErr.Kind = "module"
+ }
+ base.Fatalf("go: %v", err)
}
- base.Fatalf("go: %v", err)
}
+ MainModules = makeMainModules(mainModules, modRoots, modFiles, indices, workFileGoVersion)
setDefaultBuildMod() // possibly enable automatic vendoring
- rs = requirementsFromModFile()
+ rs = requirementsFromModFiles(ctx, modFiles)
+
+ if inWorkspaceMode() {
+ // We don't need to do anything for vendor or update the mod file so
+ // return early.
+
+ return rs, true
+ }
+
+ mainModule := MainModules.mustGetSingleMainModule()
+
if cfg.BuildMod == "vendor" {
- readVendorList()
- checkVendorConsistency()
+ readVendorList(mainModule)
+ index := MainModules.Index(mainModule)
+ modFile := MainModules.ModFile(mainModule)
+ checkVendorConsistency(index, modFile)
rs.initVendor(vendorList)
}
+
if rs.hasRedundantRoot() {
// If any module path appears more than once in the roots, we know that the
// go.mod file needs to be updated even though we have not yet loaded any
// transitive dependencies.
+ var err error
rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
if err != nil {
base.Fatalf("go: %v", err)
}
}
- 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(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.
// Go 1.11 through 1.16 have eager requirements, but the latest Go
// version uses lazy requirements instead — so we need to cnvert the
// requirements to be lazy.
+ var err error
rs, err = convertDepth(ctx, rs, lazy)
if err != nil {
base.Fatalf("go: %v", err)
}
}
} else {
- rawGoVersion.Store(Target, modFileGoVersion())
+ rawGoVersion.Store(mainModule, modFileGoVersion(MainModules.ModFile(mainModule)))
}
}
@@ -500,9 +740,10 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
// exactly the same as in the legacy configuration (for example, we can't get
// packages at multiple versions from the same module).
func CreateModFile(ctx context.Context, modPath string) {
- modRoot = base.Cwd()
+ modRoot := base.Cwd()
+ modRoots = []string{modRoot}
Init()
- modFilePath := ModFilePath()
+ modFilePath := modFilePath(modRoot)
if _, err := fsys.Stat(modFilePath); err == nil {
base.Fatalf("go: %s already exists", modFilePath)
}
@@ -533,12 +774,12 @@ func CreateModFile(ctx context.Context, modPath string) {
}
fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", modPath)
- modFile = new(modfile.File)
+ modFile := new(modfile.File)
modFile.AddModuleStmt(modPath)
- initTarget(modFile.Module.Mod)
- addGoStmt(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, modRoot)
if convertedFrom != "" {
fmt.Fprintf(os.Stderr, "go: copying requirements from %s\n", base.ShortPath(convertedFrom))
}
@@ -546,12 +787,12 @@ func CreateModFile(ctx context.Context, modPath string) {
base.Fatalf("go: %v", err)
}
- rs := requirementsFromModFile()
+ rs := requirementsFromModFiles(ctx, []*modfile.File{modFile})
rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
if err != nil {
base.Fatalf("go: %v", err)
}
- commitRequirements(ctx, modFileGoVersion(), rs)
+ commitRequirements(ctx, rs)
// Suggest running 'go mod tidy' unless the project is empty. Even if we
// imported all the correct requirements above, we're probably missing
@@ -577,6 +818,24 @@ func CreateModFile(ctx context.Context, modPath string) {
}
}
+// CreateWorkFile initializes a new workspace by creating a go.work file.
+func CreateWorkFile(ctx context.Context, workFile string, modDirs []string) {
+ _ = TODOWorkspaces("Report an error if the file already exists.")
+
+ goV := LatestGoVersion() // Use current Go version by default
+ workF := new(modfile.WorkFile)
+ workF.Syntax = new(modfile.FileSyntax)
+ workF.AddGoStmt(goV)
+
+ for _, dir := range modDirs {
+ _ = TODOWorkspaces("Add the module path of the module.")
+ workF.AddDirectory(dir, "")
+ }
+
+ data := modfile.Format(workF.Syntax)
+ lockedfile.Write(workFile, bytes.NewReader(data), 0644)
+}
+
// fixVersion returns a modfile.VersionFixer implemented using the Query function.
//
// It resolves commit hashes and branch names to versions,
@@ -639,49 +898,88 @@ func AllowMissingModuleImports() {
allowMissingModuleImports = true
}
-// initTarget sets Target and associated variables according to modFile,
-func initTarget(m module.Version) {
- Target = m
- targetPrefix = m.Path
-
- if rel := search.InDir(base.Cwd(), cfg.GOROOTsrc); rel != "" {
- targetInGorootSrc = true
- if m.Path == "std" {
- // The "std" module in GOROOT/src is the Go standard library. Unlike other
- // modules, the packages in the "std" module have no import-path prefix.
- //
- // Modules named "std" outside of GOROOT/src do not receive this special
- // treatment, so it is possible to run 'go test .' in other GOROOTs to
- // test individual packages using a combination of the modified package
- // and the ordinary standard library.
- // (See https://golang.org/issue/30756.)
- targetPrefix = ""
+// makeMainModules creates a MainModuleSet and associated variables according to
+// the given main modules.
+func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile.File, indices []*modFileIndex, workFileGoVersion string) *MainModuleSet {
+ for _, m := range ms {
+ if m.Version != "" {
+ panic("mainModulesCalled with module.Version with non empty Version field: " + fmt.Sprintf("%#v", m))
+ }
+ }
+ modRootContainingCWD := findModuleRoot(base.Cwd())
+ mainModules := &MainModuleSet{
+ versions: ms[:len(ms):len(ms)],
+ 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{},
+ workFileGoVersion: workFileGoVersion,
+ }
+ 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 mainModules.modRoot[m] == modRootContainingCWD {
+ mainModules.modContainingCWD = m
+ }
+
+ if rel := search.InDir(rootDirs[i], cfg.GOROOTsrc); rel != "" {
+ mainModules.inGorootSrc[m] = true
+ if m.Path == "std" {
+ // The "std" module in GOROOT/src is the Go standard library. Unlike other
+ // modules, the packages in the "std" module have no import-path prefix.
+ //
+ // Modules named "std" outside of GOROOT/src do not receive this special
+ // treatment, so it is possible to run 'go test .' in other GOROOTs to
+ // test individual packages using a combination of the modified package
+ // and the ordinary standard library.
+ // (See https://golang.org/issue/30756.)
+ mainModules.pathPrefix[m] = ""
+ }
}
}
+ return mainModules
}
-// requirementsFromModFile returns the set of non-excluded requirements from
+// requirementsFromModFiles returns the set of non-excluded requirements from
// the global modFile.
-func requirementsFromModFile() *Requirements {
- roots := make([]module.Version, 0, len(modFile.Require))
+func requirementsFromModFiles(ctx context.Context, modFiles []*modfile.File) *Requirements {
+ rootCap := 0
+ for i := range modFiles {
+ rootCap += len(modFiles[i].Require)
+ }
+ roots := make([]module.Version, 0, rootCap)
+ mPathCount := make(map[string]int)
+ for _, m := range MainModules.Versions() {
+ mPathCount[m.Path] = 1
+ }
direct := map[string]bool{}
- 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)
+ for _, modFile := range modFiles {
+ requirement:
+ for _, r := range modFile.Require {
+ // 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 !r.Indirect {
- direct[r.Mod.Path] = true
+ roots = append(roots, r.Mod)
+ if !r.Indirect {
+ direct[r.Mod.Path] = true
+ }
}
}
module.Sort(roots)
- rs := newRequirements(modDepthFromGoVersion(modFileGoVersion()), roots, direct)
+ rs := newRequirements(modDepthFromGoVersion(MainModules.GoVersion()), roots, direct)
return rs
}
@@ -689,6 +987,11 @@ func requirementsFromModFile() *Requirements {
// wasn't provided. setDefaultBuildMod may be called multiple times.
func setDefaultBuildMod() {
if cfg.BuildModExplicit {
+ if inWorkspaceMode() && cfg.BuildMod != "readonly" {
+ base.Fatalf("go: -mod may only be set to readonly when in workspace mode." +
+ "\n\tRemove the -mod flag to use the default readonly value," +
+ "\n\tor set -workfile=off to disable workspace mode.")
+ }
// Don't override an explicit '-mod=' argument.
return
}
@@ -713,7 +1016,7 @@ func setDefaultBuildMod() {
cfg.BuildMod = "readonly"
return
}
- if modRoot == "" {
+ if modRoots == nil {
if allowMissingModuleImports {
cfg.BuildMod = "mod"
} else {
@@ -722,31 +1025,38 @@ func setDefaultBuildMod() {
return
}
- if fi, err := fsys.Stat(filepath.Join(modRoot, "vendor")); err == nil && fi.IsDir() {
- modGo := "unspecified"
- if index != nil && index.goVersionV != "" {
- if semver.Compare(index.goVersionV, "v1.14") >= 0 {
- // The Go version is at least 1.14, and a vendor directory exists.
- // Set -mod=vendor by default.
- cfg.BuildMod = "vendor"
- cfg.BuildModReason = "Go version in go.mod is at least 1.14 and vendor directory exists."
- return
- } else {
- modGo = index.goVersionV[1:]
+ 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 != "" {
+ if semver.Compare(index.goVersionV, "v1.14") >= 0 {
+ // The Go version is at least 1.14, and a vendor directory exists.
+ // Set -mod=vendor by default.
+ cfg.BuildMod = "vendor"
+ cfg.BuildModReason = "Go version in go.mod is at least 1.14 and vendor directory exists."
+ return
+ } else {
+ modGo = index.goVersionV[1:]
+ }
}
- }
- // Since a vendor directory exists, we should record why we didn't use it.
- // This message won't normally be shown, but it may appear with import errors.
- cfg.BuildModReason = fmt.Sprintf("Go version in go.mod is %s, so vendor directory was not used.", modGo)
+ // Since a vendor directory exists, we should record why we didn't use it.
+ // This message won't normally be shown, but it may appear with import errors.
+ cfg.BuildModReason = fmt.Sprintf("Go version in go.mod is %s, so vendor directory was not used.", modGo)
+ }
}
cfg.BuildMod = "readonly"
}
+func mustHaveCompleteRequirements() bool {
+ return cfg.BuildMod != "mod" && !inWorkspaceMode()
+}
+
// 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, modRoot 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)
@@ -777,14 +1087,14 @@ func convertLegacyConfig(modPath string) (from string, err error) {
// 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(v string) {
+func addGoStmt(modFile *modfile.File, mod module.Version, v string) {
if modFile.Go != nil && modFile.Go.Version != "" {
return
}
if err := modFile.AddGoStmt(v); err != nil {
base.Fatalf("go: internal error: %v", err)
}
- rawGoVersion.Store(Target, v)
+ rawGoVersion.Store(mod, v)
}
// LatestGoVersion returns the latest version of the Go language supported by
@@ -835,7 +1145,7 @@ var altConfigs = []string{
".git/config",
}
-func findModuleRoot(dir string) (root string) {
+func findModuleRoot(dir string) (roots string) {
if dir == "" {
panic("dir not set")
}
@@ -855,6 +1165,32 @@ func findModuleRoot(dir string) (root string) {
return ""
}
+func findWorkspaceFile(dir string) (root string) {
+ if dir == "" {
+ panic("dir not set")
+ }
+ dir = filepath.Clean(dir)
+
+ // Look for enclosing go.mod.
+ for {
+ f := filepath.Join(dir, "go.work")
+ if fi, err := fsys.Stat(f); err == nil && !fi.IsDir() {
+ return f
+ }
+ d := filepath.Dir(dir)
+ if d == dir {
+ break
+ }
+ if d == cfg.GOROOT {
+ _ = TODOWorkspaces("If we end up checking in a go.work file to GOROOT/src," +
+ "remove this case.")
+ return "" // As a special case, don't cross GOROOT to find a go.work file.
+ }
+ dir = d
+ }
+ return ""
+}
+
func findAltConfig(dir string) (root, name string) {
if dir == "" {
panic("dir not set")
@@ -1000,12 +1336,13 @@ func WriteGoMod(ctx context.Context) {
if !allowWriteGoMod {
panic("WriteGoMod called while disallowed")
}
- commitRequirements(ctx, modFileGoVersion(), LoadModFile(ctx))
+ commitRequirements(ctx, LoadModFile(ctx))
}
// commitRequirements writes sets the global requirements variable to rs and
// writes its contents back to the go.mod file on disk.
-func commitRequirements(ctx context.Context, goVersion string, rs *Requirements) {
+// goVersion, if non-empty, is used to set the version on the go.mod file.
+func commitRequirements(ctx context.Context, rs *Requirements) {
requirements = rs
if !allowWriteGoMod {
@@ -1013,10 +1350,22 @@ func commitRequirements(ctx context.Context, goVersion string, rs *Requirements)
return
}
- if modRoot == "" {
+ if inWorkspaceMode() {
+ // go.mod files aren't updated in workspace mode, but we still want to
+ // update the go.work.sum file.
+ if err := modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
+ base.Fatalf("go: %v", err)
+ }
+ return
+ }
+
+ if MainModules.Len() != 1 || MainModules.ModRoot(MainModules.Versions()[0]) == "" {
// We aren't in a module, so we don't have anywhere to write a go.mod file.
return
}
+ mainModule := MainModules.mustGetSingleMainModule()
+ modFilePath := modFilePath(MainModules.ModRoot(mainModule))
+ modFile := MainModules.ModFile(mainModule)
var list []*modfile.Require
for _, m := range rs.rootModules {
@@ -1025,16 +1374,17 @@ func commitRequirements(ctx context.Context, goVersion string, rs *Requirements)
Indirect: !rs.direct[m.Path],
})
}
- if goVersion != "" {
- modFile.AddGoStmt(goVersion)
+ if modFile.Go == nil || modFile.Go.Version == "" {
+ modFile.AddGoStmt(modFileGoVersion(modFile))
}
- if semver.Compare("v"+modFileGoVersion(), separateIndirectVersionV) < 0 {
+ if semver.Compare("v"+modFileGoVersion(modFile), separateIndirectVersionV) < 0 {
modFile.SetRequire(list)
} else {
modFile.SetRequireSeparateIndirect(list)
}
modFile.Cleanup()
+ index := MainModules.GetSingleIndexOrNil()
dirty := index.modFileIsDirty(modFile)
if dirty && cfg.BuildMod != "mod" {
// If we're about to fail due to -mod=readonly,
@@ -1048,12 +1398,13 @@ func commitRequirements(ctx context.Context, goVersion string, rs *Requirements)
// Don't write go.mod, but write go.sum in case we added or trimmed sums.
// 'go mod init' shouldn't write go.sum, since it will be incomplete.
if cfg.CmdName != "mod init" {
- modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums))
+ if err := modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
+ base.Fatalf("go: %v", err)
+ }
}
return
}
- gomod := ModFilePath()
- if _, ok := fsys.OverlayPath(gomod); ok {
+ if _, ok := fsys.OverlayPath(modFilePath); ok {
if dirty {
base.Fatalf("go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay")
}
@@ -1066,12 +1417,14 @@ func commitRequirements(ctx context.Context, goVersion string, rs *Requirements)
}
defer func() {
// At this point we have determined to make the go.mod file on disk equal to new.
- index = indexModFile(new, modFile, 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.
if cfg.CmdName != "mod init" {
- modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums))
+ if err := modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
+ base.Fatalf("go: %v", err)
+ }
}
}()
@@ -1083,7 +1436,7 @@ func commitRequirements(ctx context.Context, goVersion string, rs *Requirements)
errNoChange := errors.New("no update needed")
- err = lockedfile.Transform(ModFilePath(), func(old []byte) ([]byte, error) {
+ err = lockedfile.Transform(modFilePath, func(old []byte) ([]byte, error) {
if bytes.Equal(old, new) {
// The go.mod file is already equal to new, possibly as the result of some
// other process.
@@ -1142,7 +1495,8 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) {
if v, ok := rs.rootSelected(prefix); ok && v != "none" {
m := module.Version{Path: prefix, Version: v}
- keep[resolveReplacement(m)] = true
+ r, _ := resolveReplacement(m)
+ keep[r] = true
}
}
continue
@@ -1153,7 +1507,8 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) {
if v := mg.Selected(prefix); v != "none" {
m := module.Version{Path: prefix, Version: v}
- keep[resolveReplacement(m)] = true
+ r, _ := resolveReplacement(m)
+ keep[r] = true
}
}
}
@@ -1165,7 +1520,7 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
// Save sums for the root modules (or their replacements), but don't
// incur the cost of loading the graph just to find and retain the sums.
for _, m := range rs.rootModules {
- r := resolveReplacement(m)
+ r, _ := resolveReplacement(m)
keep[modkey(r)] = true
if which == addBuildListZipSums {
keep[r] = true
@@ -1178,13 +1533,15 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
// The requirements from m's go.mod file are present in the module graph,
// so they are relevant to the MVS result regardless of whether m was
// actually selected.
- keep[modkey(resolveReplacement(m))] = true
+ r, _ := resolveReplacement(m)
+ keep[modkey(r)] = true
}
})
if which == addBuildListZipSums {
for _, m := range mg.BuildList() {
- keep[resolveReplacement(m)] = true
+ r, _ := resolveReplacement(m)
+ keep[r] = true
}
}
}
diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go
index ccdeb9b1d1..9c5018f340 100644
--- a/src/cmd/go/internal/modload/list.go
+++ b/src/cmd/go/internal/modload/list.go
@@ -72,14 +72,18 @@ func ListModules(ctx context.Context, args []string, mode ListMode) ([]*modinfo.
}
if err == nil {
- commitRequirements(ctx, modFileGoVersion(), rs)
+ commitRequirements(ctx, rs)
}
return mods, err
}
func listModules(ctx context.Context, rs *Requirements, args []string, mode ListMode) (_ *Requirements, mods []*modinfo.ModulePublic, mgErr error) {
if len(args) == 0 {
- return rs, []*modinfo.ModulePublic{moduleInfo(ctx, rs, Target, mode)}, nil
+ var ms []*modinfo.ModulePublic
+ for _, m := range MainModules.Versions() {
+ ms = append(ms, moduleInfo(ctx, rs, m, mode))
+ }
+ return rs, ms, nil
}
needFullGraph := false
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index b54f670812..efe6ad1319 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -274,7 +274,9 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
// If we're outside of a module, ensure that the failure mode
// indicates that.
- ModRoot()
+ if !HasModRoot() {
+ die()
+ }
if ld != nil {
m.AddError(err)
@@ -306,7 +308,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
// The initial roots are the packages in the main module.
// loadFromRoots will expand that to "all".
m.Errs = m.Errs[:0]
- matchPackages(ctx, m, opts.Tags, omitStd, []module.Version{Target})
+ matchPackages(ctx, m, opts.Tags, omitStd, MainModules.Versions())
} else {
// Starting with the packages in the main module,
// enumerate the full list of "all".
@@ -401,13 +403,21 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
// loaded.requirements, but here we may have also loaded (and want to
// preserve checksums for) additional entities from compatRS, which are
// only needed for compatibility with ld.TidyCompatibleVersion.
- modfetch.WriteGoSum(keep)
+ if err := modfetch.WriteGoSum(keep, mustHaveCompleteRequirements()); err != nil {
+ base.Fatalf("go: %v", err)
+ }
+ }
+
+ // Update the go.mod file's Go version if necessary.
+ modFile := MainModules.ModFile(MainModules.mustGetSingleMainModule())
+ if ld.GoVersion != "" {
+ modFile.AddGoStmt(ld.GoVersion)
}
}
// Success! Update go.mod and go.sum (if needed) and return the results.
loaded = ld
- commitRequirements(ctx, loaded.GoVersion, loaded.requirements)
+ commitRequirements(ctx, loaded.requirements)
for _, pkg := range ld.pkgs {
if !pkg.isTest() {
@@ -436,14 +446,23 @@ func matchLocalDirs(ctx context.Context, m *search.Match, rs *Requirements) {
if !filepath.IsAbs(dir) {
absDir = filepath.Join(base.Cwd(), dir)
}
- if search.InDir(absDir, cfg.GOROOTsrc) == "" && search.InDir(absDir, ModRoot()) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
+
+ modRoot := findModuleRoot(absDir)
+ found := false
+ for _, mod := range MainModules.Versions() {
+ if MainModules.ModRoot(mod) == modRoot {
+ found = true
+ break
+ }
+ }
+ if !found && search.InDir(absDir, cfg.GOROOTsrc) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
m.Dirs = []string{}
m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir)))
return
}
}
- m.MatchDirs()
+ m.MatchDirs(modRoots)
}
// resolveLocalPackage resolves a filesystem path to a package path.
@@ -485,49 +504,69 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
}
}
- if modRoot != "" && absDir == modRoot {
- if absDir == cfg.GOROOTsrc {
- return "", errPkgIsGorootSrc
+ for _, mod := range MainModules.Versions() {
+ modRoot := MainModules.ModRoot(mod)
+ if modRoot != "" && absDir == modRoot {
+ if absDir == cfg.GOROOTsrc {
+ return "", errPkgIsGorootSrc
+ }
+ return MainModules.PathPrefix(mod), nil
}
- return targetPrefix, nil
}
// Note: The checks for @ here are just to avoid misinterpreting
// the module cache directories (formerly GOPATH/src/mod/foo@v1.5.2/bar).
// It's not strictly necessary but helpful to keep the checks.
- if modRoot != "" && strings.HasPrefix(absDir, modRoot+string(filepath.Separator)) && !strings.Contains(absDir[len(modRoot):], "@") {
- suffix := filepath.ToSlash(absDir[len(modRoot):])
- if strings.HasPrefix(suffix, "/vendor/") {
- if cfg.BuildMod != "vendor" {
- return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
+ var pkgNotFoundErr error
+ pkgNotFoundLongestPrefix := ""
+ for _, mainModule := range MainModules.Versions() {
+ modRoot := MainModules.ModRoot(mainModule)
+ if modRoot != "" && strings.HasPrefix(absDir, modRoot+string(filepath.Separator)) && !strings.Contains(absDir[len(modRoot):], "@") {
+ suffix := filepath.ToSlash(absDir[len(modRoot):])
+ if strings.HasPrefix(suffix, "/vendor/") {
+ if cfg.BuildMod != "vendor" {
+ return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
+ }
+
+ readVendorList(mainModule)
+ pkg := strings.TrimPrefix(suffix, "/vendor/")
+ if _, ok := vendorPkgModule[pkg]; !ok {
+ return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
+ }
+ return pkg, nil
}
- readVendorList()
- pkg := strings.TrimPrefix(suffix, "/vendor/")
- if _, ok := vendorPkgModule[pkg]; !ok {
- return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
+ mainModulePrefix := MainModules.PathPrefix(mainModule)
+ if mainModulePrefix == "" {
+ pkg := strings.TrimPrefix(suffix, "/")
+ if pkg == "builtin" {
+ // "builtin" is a pseudo-package with a real source file.
+ // It's not included in "std", so it shouldn't resolve from "."
+ // within module "std" either.
+ return "", errPkgIsBuiltin
+ }
+ return pkg, nil
}
- return pkg, nil
- }
- if targetPrefix == "" {
- pkg := strings.TrimPrefix(suffix, "/")
- if pkg == "builtin" {
- // "builtin" is a pseudo-package with a real source file.
- // It's not included in "std", so it shouldn't resolve from "."
- // within module "std" either.
- return "", errPkgIsBuiltin
+ pkg := mainModulePrefix + suffix
+ if _, ok, err := dirInModule(pkg, mainModulePrefix, modRoot, true); err != nil {
+ return "", err
+ } else if !ok {
+ // This main module could contain the directory but doesn't. Other main
+ // modules might contain the directory, so wait till we finish the loop
+ // to see if another main module contains directory. But if not,
+ // return an error.
+ if len(mainModulePrefix) > len(pkgNotFoundLongestPrefix) {
+ pkgNotFoundLongestPrefix = mainModulePrefix
+ pkgNotFoundErr = &PackageNotInModuleError{MainModules: []module.Version{mainModule}, Pattern: pkg}
+ }
+ continue
}
return pkg, nil
}
-
- pkg := targetPrefix + suffix
- if _, ok, err := dirInModule(pkg, targetPrefix, modRoot, true); err != nil {
- return "", err
- } else if !ok {
- return "", &PackageNotInModuleError{Mod: Target, Pattern: pkg}
- }
- return pkg, nil
+ }
+ if pkgNotFoundErr != nil {
+ return "", pkgNotFoundErr
}
if sub := search.InDir(absDir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") {
@@ -557,10 +596,10 @@ func pathInModuleCache(ctx context.Context, dir string, rs *Requirements) string
tryMod := func(m module.Version) (string, bool) {
var root string
var err error
- if repl := Replacement(m); repl.Path != "" && repl.Version == "" {
+ if repl, replModRoot := Replacement(m); repl.Path != "" && repl.Version == "" {
root = repl.Path
if !filepath.IsAbs(root) {
- root = filepath.Join(ModRoot(), root)
+ root = filepath.Join(replModRoot, root)
}
} else if repl.Path != "" {
root, err = modfetch.DownloadDir(repl)
@@ -645,14 +684,14 @@ func ImportFromFiles(ctx context.Context, gofiles []string) {
return roots
},
})
- commitRequirements(ctx, loaded.GoVersion, loaded.requirements)
+ commitRequirements(ctx, loaded.requirements)
}
// DirImportPath returns the effective import path for dir,
-// provided it is within the main module, or else returns ".".
-func DirImportPath(ctx context.Context, dir string) string {
+// provided it is within a main module, or else returns ".".
+func (mms *MainModuleSet) DirImportPath(ctx context.Context, dir string) (path string, m module.Version) {
if !HasModRoot() {
- return "."
+ return ".", module.Version{}
}
LoadModFile(ctx) // Sets targetPrefix.
@@ -662,17 +701,32 @@ func DirImportPath(ctx context.Context, dir string) string {
dir = filepath.Clean(dir)
}
- if dir == modRoot {
- return targetPrefix
- }
- if strings.HasPrefix(dir, modRoot+string(filepath.Separator)) {
- suffix := filepath.ToSlash(dir[len(modRoot):])
- if strings.HasPrefix(suffix, "/vendor/") {
- return strings.TrimPrefix(suffix, "/vendor/")
+ var longestPrefix string
+ var longestPrefixPath string
+ var longestPrefixVersion module.Version
+ for _, v := range mms.Versions() {
+ modRoot := mms.ModRoot(v)
+ if dir == modRoot {
+ return mms.PathPrefix(v), v
+ }
+ if strings.HasPrefix(dir, modRoot+string(filepath.Separator)) {
+ pathPrefix := MainModules.PathPrefix(v)
+ if pathPrefix > longestPrefix {
+ longestPrefix = pathPrefix
+ longestPrefixVersion = v
+ suffix := filepath.ToSlash(dir[len(modRoot):])
+ if strings.HasPrefix(suffix, "/vendor/") {
+ longestPrefixPath = strings.TrimPrefix(suffix, "/vendor/")
+ }
+ longestPrefixPath = mms.PathPrefix(v) + suffix
+ }
}
- return targetPrefix + suffix
}
- return "."
+ if len(longestPrefix) > 0 {
+ return longestPrefixPath, longestPrefixVersion
+ }
+
+ return ".", module.Version{}
}
// ImportMap returns the actual package import path
@@ -894,10 +948,7 @@ func (pkg *loadPkg) fromExternalModule() bool {
if pkg.mod.Path == "" {
return false // loaded from the standard library, not a module
}
- if pkg.mod.Path == Target.Path {
- return false // loaded from the main module.
- }
- return true
+ return !MainModules.Contains(pkg.mod.Path)
}
var errMissing = errors.New("cannot find package")
@@ -915,7 +966,7 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
}
if ld.GoVersion == "" {
- ld.GoVersion = modFileGoVersion()
+ ld.GoVersion = MainModules.GoVersion()
if ld.Tidy && semver.Compare("v"+ld.GoVersion, "v"+LatestGoVersion()) > 0 {
ld.errorf("go mod tidy: go.mod file indicates go %s, but maximum supported version is %s\n", ld.GoVersion, LatestGoVersion())
@@ -1168,7 +1219,7 @@ func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err err
}
for _, pkg := range ld.pkgs {
- if pkg.mod != Target {
+ if pkg.mod.Version != "" || !MainModules.Contains(pkg.mod.Path) {
continue
}
for _, dep := range pkg.imports {
@@ -1327,6 +1378,15 @@ func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[mod
var err error
mod, err = queryImport(ctx, pkg.path, ld.requirements)
if err != nil {
+ var ime *ImportMissingError
+ if errors.As(err, &ime) {
+ for curstack := pkg.stack; curstack != nil; curstack = curstack.stack {
+ if MainModules.Contains(curstack.mod.Path) {
+ ime.ImportingMainModule = curstack.mod
+ break
+ }
+ }
+ }
// pkg.err was already non-nil, so we can reasonably attribute the error
// for pkg to either the original error or the one returned by
// queryImport. The existing error indicates only that we couldn't find
@@ -1425,7 +1485,7 @@ func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkg
// so it's ok if we call it more than is strictly necessary.
wantTest := false
switch {
- case ld.allPatternIsRoot && pkg.mod == Target:
+ case ld.allPatternIsRoot && MainModules.Contains(pkg.mod.Path):
// We are loading the "all" pattern, which includes packages imported by
// tests in the main module. This package is in the main module, so we
// need to identify the imports of its test even if LoadTests is not set.
@@ -1446,7 +1506,7 @@ func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkg
if wantTest {
var testFlags loadPkgFlags
- if pkg.mod == Target || (ld.allClosesOverTests && new.has(pkgInAll)) {
+ if MainModules.Contains(pkg.mod.Path) || (ld.allClosesOverTests && new.has(pkgInAll)) {
// Tests of packages in the main module are in "all", in the sense that
// they cause the packages they import to also be in "all". So are tests
// of packages in "all" if "all" closes over test dependencies.
@@ -1593,7 +1653,7 @@ func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
if pkg.dir == "" {
return
}
- if pkg.mod == Target {
+ if MainModules.Contains(pkg.mod.Path) {
// Go ahead and mark pkg as in "all". This provides the invariant that a
// package that is *only* imported by other packages in "all" is always
// marked as such before loading its imports.
@@ -1698,13 +1758,14 @@ func (ld *loader) stdVendor(parentPath, path string) string {
}
if str.HasPathPrefix(parentPath, "cmd") {
- if !ld.VendorModulesInGOROOTSrc || Target.Path != "cmd" {
+ if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("cmd") {
vendorPath := pathpkg.Join("cmd", "vendor", path)
+
if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
return vendorPath
}
}
- } else if !ld.VendorModulesInGOROOTSrc || Target.Path != "std" || str.HasPathPrefix(parentPath, "vendor") {
+ } else if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("std") || str.HasPathPrefix(parentPath, "vendor") {
// If we are outside of the 'std' module, resolve imports from within 'std'
// to the vendor directory.
//
@@ -1753,7 +1814,7 @@ func (ld *loader) checkMultiplePaths() {
firstPath := map[module.Version]string{}
for _, mod := range mods {
- src := resolveReplacement(mod)
+ src, _ := resolveReplacement(mod)
if prev, ok := firstPath[src]; !ok {
firstPath[src] = mod.Path
} else if prev != mod.Path {
@@ -1781,7 +1842,7 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
fmt.Fprintln(os.Stderr)
goFlag := ""
- if ld.GoVersion != modFileGoVersion() {
+ if ld.GoVersion != MainModules.GoVersion() {
goFlag = " -go=" + ld.GoVersion
}
diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go
index 03e02e73b6..463869910c 100644
--- a/src/cmd/go/internal/modload/modfile.go
+++ b/src/cmd/go/internal/modload/modfile.go
@@ -56,11 +56,9 @@ const (
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 {
+// in modFile are interpreted, or the latest Go version if modFile is nil.
+func modFileGoVersion(modFile *modfile.File) string {
if modFile == nil {
return LatestGoVersion()
}
@@ -92,9 +90,6 @@ type modFileIndex struct {
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
}
@@ -137,8 +132,10 @@ var ErrDisallowed = errors.New("disallowed module version")
// 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
}
@@ -170,7 +167,7 @@ func CheckRetractions(ctx context.Context, m module.Version) (err error) {
// Cannot be retracted.
return nil
}
- if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
+ if repl, _ := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
// All versions of the module were replaced.
// Don't load retractions, since we'd just load the replacement.
return nil
@@ -187,11 +184,11 @@ func CheckRetractions(ctx context.Context, m module.Version) (err error) {
// We load the raw file here: the go.mod file may have a different module
// path that we expect if the module or its repository was renamed.
// We still want to apply retractions to other aliases of the module.
- rm, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
+ rm, replacedFrom, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
if err != nil {
return err
}
- summary, err := rawGoModSummary(rm)
+ summary, err := rawGoModSummary(rm, replacedFrom)
if err != nil {
return err
}
@@ -289,51 +286,72 @@ func CheckDeprecation(ctx context.Context, m module.Version) (deprecation string
// Don't look up deprecation.
return "", nil
}
- if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
+ if repl, _ := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
// All versions of the module were replaced.
// We'll look up deprecation separately for the replacement.
return "", nil
}
- latest, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
+ latest, replacedFrom, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
if err != nil {
return "", err
}
- summary, err := rawGoModSummary(latest)
+ summary, err := rawGoModSummary(latest, replacedFrom)
if err != nil {
return "", err
}
return summary.deprecated, nil
}
-// Replacement returns the replacement for mod, if any, from go.mod.
+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, and and the module root
+// directory of the main module containing the replace directive.
// 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
+func Replacement(mod module.Version) (module.Version, string) {
+ _ = 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, foundModRoot
+ }
+ found, foundModRoot = r, modRoot
+ }
}
}
- return module.Version{}
+ return found, foundModRoot
}
// resolveReplacement returns the module actually used to load the source code
// for m: either m itself, or the replacement for m (iff m is replaced).
-func resolveReplacement(m module.Version) module.Version {
- if r := Replacement(m); r.Path != "" {
- return r
+// It also returns the modroot of the module providing the replacement if
+// one was found.
+func resolveReplacement(m module.Version) (module.Version, string) {
+ if r, replacedFrom := Replacement(m); r.Path != "" {
+ return r, replacedFrom
}
- return m
+ return m, ""
}
// 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 {
+func indexModFile(data []byte, modFile *modfile.File, mod module.Version, needsFix bool) *modFileIndex {
i := new(modFileIndex)
i.data = data
i.dataNeedsFix = needsFix
@@ -345,12 +363,12 @@ func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileInd
i.goVersionV = ""
if modFile.Go == nil {
- rawGoVersion.Store(Target, "")
+ rawGoVersion.Store(mod, "")
} else {
// We're going to use the semver package to compare Go versions, so go ahead
// and add the "v" prefix it expects once instead of every time.
i.goVersionV = "v" + modFile.Go.Version
- rawGoVersion.Store(Target, modFile.Go.Version)
+ rawGoVersion.Store(mod, modFile.Go.Version)
}
i.require = make(map[module.Version]requireMeta, len(modFile.Require))
@@ -490,8 +508,8 @@ type retraction struct {
//
// The caller must not modify the returned summary.
func goModSummary(m module.Version) (*modFileSummary, error) {
- if m == Target {
- panic("internal error: goModSummary called on the Target module")
+ if m.Version == "" && MainModules.Contains(m.Path) {
+ panic("internal error: goModSummary called on a main module")
}
if cfg.BuildMod == "vendor" {
@@ -506,7 +524,7 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
// For every module other than the target,
// return the full list of modules from modules.txt.
- readVendorList()
+ readVendorList(MainModules.mustGetSingleMainModule())
// We don't know what versions the vendored module actually relies on,
// so assume that it requires everything.
@@ -514,15 +532,15 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
return summary, nil
}
- actual := resolveReplacement(m)
- if HasModRoot() && cfg.BuildMod == "readonly" && actual.Version != "" {
+ actual, replacedFrom := resolveReplacement(m)
+ if HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() && actual.Version != "" {
key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
if !modfetch.HaveSum(key) {
suggestion := fmt.Sprintf("; to add it:\n\tgo mod download %s", m.Path)
return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
}
}
- summary, err := rawGoModSummary(actual)
+ summary, err := rawGoModSummary(actual, replacedFrom)
if err != nil {
return nil, err
}
@@ -553,27 +571,29 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
}
}
- 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
@@ -584,18 +604,23 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
// its dependencies.
//
// rawGoModSummary cannot be used on the Target module.
-func rawGoModSummary(m module.Version) (*modFileSummary, error) {
- if m == Target {
+
+func rawGoModSummary(m module.Version, replacedFrom string) (*modFileSummary, error) {
+ if m.Path == "" && MainModules.Contains(m.Path) {
panic("internal error: rawGoModSummary called on the Target module")
}
+ type key struct {
+ m module.Version
+ replacedFrom string
+ }
type cached struct {
summary *modFileSummary
err error
}
- c := rawGoModSummaryCache.Do(m, func() interface{} {
+ c := rawGoModSummaryCache.Do(key{m, replacedFrom}, func() interface{} {
summary := new(modFileSummary)
- name, data, err := rawGoModData(m)
+ name, data, err := rawGoModData(m, replacedFrom)
if err != nil {
return cached{nil, err}
}
@@ -645,12 +670,15 @@ var rawGoModSummaryCache par.Cache // module.Version → rawGoModSummary result
//
// Unlike rawGoModSummary, rawGoModData does not cache its results in memory.
// Use rawGoModSummary instead unless you specifically need these bytes.
-func rawGoModData(m module.Version) (name string, data []byte, err error) {
+func rawGoModData(m module.Version, replacedFrom string) (name string, data []byte, err error) {
if m.Version == "" {
// m is a replacement module with only a file path.
dir := m.Path
if !filepath.IsAbs(dir) {
- dir = filepath.Join(ModRoot(), dir)
+ if replacedFrom == "" {
+ panic(fmt.Errorf("missing module root of main module providing replacement with relative path: %v", dir))
+ }
+ dir = filepath.Join(replacedFrom, dir)
}
name = filepath.Join(dir, "go.mod")
if gomodActual, ok := fsys.OverlayPath(name); ok {
@@ -685,19 +713,20 @@ func rawGoModData(m module.Version) (name string, data []byte, err error) {
//
// If the queried latest version is replaced,
// queryLatestVersionIgnoringRetractions returns the replacement.
-func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, err error) {
+func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, replacedFrom string, err error) {
type entry struct {
- latest module.Version
- err error
+ latest module.Version
+ replacedFrom string // if latest is a replacement
+ err error
}
e := latestVersionIgnoringRetractionsCache.Do(path, func() interface{} {
ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
defer span.Done()
- if repl := Replacement(module.Version{Path: path}); repl.Path != "" {
+ if repl, replFrom := Replacement(module.Version{Path: path}); repl.Path != "" {
// All versions of the module were replaced.
// No need to query.
- return &entry{latest: repl}
+ return &entry{latest: repl, replacedFrom: replFrom}
}
// Find the latest version of the module.
@@ -709,12 +738,12 @@ func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (la
return &entry{err: err}
}
latest := module.Version{Path: path, Version: rev.Version}
- if repl := resolveReplacement(latest); repl.Path != "" {
- latest = repl
+ if repl, replFrom := resolveReplacement(latest); repl.Path != "" {
+ latest, replacedFrom = repl, replFrom
}
- return &entry{latest: latest}
+ return &entry{latest: latest, replacedFrom: replacedFrom}
}).(*entry)
- return e.latest, e.err
+ return e.latest, e.replacedFrom, e.err
}
var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersionIgnoringRetractions result
diff --git a/src/cmd/go/internal/modload/mvs.go b/src/cmd/go/internal/modload/mvs.go
index 87619b4ace..40224d534b 100644
--- a/src/cmd/go/internal/modload/mvs.go
+++ b/src/cmd/go/internal/modload/mvs.go
@@ -42,7 +42,7 @@ type mvsReqs struct {
}
func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) {
- if mod == Target {
+ if MainModules.Contains(mod.Path) {
// Use the build list as it existed when r was constructed, not the current
// global build list.
return r.roots, nil
@@ -113,7 +113,7 @@ func versions(ctx context.Context, path string, allowed AllowedFunc) ([]string,
func previousVersion(m module.Version) (module.Version, error) {
// TODO(golang.org/issue/38714): thread tracing context through MVS.
- if m == Target {
+ if MainModules.Contains(m.Path) {
return module.Version{Path: m.Path, Version: "none"}, nil
}
diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go
index d4c906a873..82979fbda1 100644
--- a/src/cmd/go/internal/modload/query.go
+++ b/src/cmd/go/internal/modload/query.go
@@ -110,11 +110,12 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
allowed = func(context.Context, module.Version) error { return nil }
}
- if path == Target.Path && (query == "upgrade" || query == "patch") {
- if err := allowed(ctx, Target); err != nil {
+ if MainModules.Contains(path) && (query == "upgrade" || query == "patch") {
+ m := module.Version{Path: path}
+ if err := allowed(ctx, m); err != nil {
return nil, fmt.Errorf("internal error: main module version is not allowed: %w", err)
}
- return &modfetch.RevInfo{Version: Target.Version}, nil
+ return &modfetch.RevInfo{Version: m.Version}, nil
}
if path == "std" || path == "cmd" {
@@ -512,9 +513,10 @@ func QueryPackages(ctx context.Context, pattern, query string, current func(stri
pkgMods, modOnly, err := QueryPattern(ctx, pattern, query, current, allowed)
if len(pkgMods) == 0 && err == nil {
+ replacement, _ := Replacement(modOnly.Mod)
return nil, &PackageNotInModuleError{
Mod: modOnly.Mod,
- Replacement: Replacement(modOnly.Mod),
+ Replacement: replacement,
Query: query,
Pattern: pattern,
}
@@ -551,7 +553,7 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
return m.Errs[0]
}
- var match func(mod module.Version, root string, isLocal bool) *search.Match
+ var match func(mod module.Version, roots []string, isLocal bool) *search.Match
matchPattern := search.MatchPattern(pattern)
if i := strings.Index(pattern, "..."); i >= 0 {
@@ -559,30 +561,32 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
if base == "." {
return nil, nil, &WildcardInFirstElementError{Pattern: pattern, Query: query}
}
- match = func(mod module.Version, root string, isLocal bool) *search.Match {
+ match = func(mod module.Version, roots []string, isLocal bool) *search.Match {
m := search.NewMatch(pattern)
matchPackages(ctx, m, imports.AnyTags(), omitStd, []module.Version{mod})
return m
}
} else {
- match = func(mod module.Version, root string, isLocal bool) *search.Match {
+ match = func(mod module.Version, roots []string, isLocal bool) *search.Match {
m := search.NewMatch(pattern)
prefix := mod.Path
- if mod == Target {
- prefix = targetPrefix
+ if MainModules.Contains(mod.Path) {
+ prefix = MainModules.PathPrefix(module.Version{Path: mod.Path})
}
- if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil {
- m.AddError(err)
- } else if ok {
- m.Pkgs = []string{pattern}
+ for _, root := range roots {
+ if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil {
+ m.AddError(err)
+ } else if ok {
+ m.Pkgs = []string{pattern}
+ }
}
return m
}
}
- var queryMatchesMainModule bool
- if HasModRoot() {
- m := match(Target, modRoot, true)
+ var mainModuleMatches []module.Version
+ for _, mainModule := range MainModules.Versions() {
+ m := match(mainModule, modRoots, true)
if len(m.Pkgs) > 0 {
if query != "upgrade" && query != "patch" {
return nil, nil, &QueryMatchesPackagesInMainModuleError{
@@ -591,12 +595,12 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
Packages: m.Pkgs,
}
}
- if err := allowed(ctx, Target); err != nil {
- return nil, nil, fmt.Errorf("internal error: package %s is in the main module (%s), but version is not allowed: %w", pattern, Target.Path, err)
+ if err := allowed(ctx, mainModule); err != nil {
+ return nil, nil, fmt.Errorf("internal error: package %s is in the main module (%s), but version is not allowed: %w", pattern, mainModule.Path, err)
}
return []QueryResult{{
- Mod: Target,
- Rev: &modfetch.RevInfo{Version: Target.Version},
+ Mod: mainModule,
+ Rev: &modfetch.RevInfo{Version: mainModule.Version},
Packages: m.Pkgs,
}}, nil, nil
}
@@ -604,15 +608,17 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
return nil, nil, err
}
- if matchPattern(Target.Path) {
- queryMatchesMainModule = true
+ var matchesMainModule bool
+ if matchPattern(mainModule.Path) {
+ mainModuleMatches = append(mainModuleMatches, mainModule)
+ matchesMainModule = true
}
- if (query == "upgrade" || query == "patch") && queryMatchesMainModule {
- if err := allowed(ctx, Target); err == nil {
+ if (query == "upgrade" || query == "patch") && matchesMainModule {
+ if err := allowed(ctx, mainModule); err == nil {
modOnly = &QueryResult{
- Mod: Target,
- Rev: &modfetch.RevInfo{Version: Target.Version},
+ Mod: mainModule,
+ Rev: &modfetch.RevInfo{Version: mainModule.Version},
}
}
}
@@ -625,16 +631,17 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
if len(candidateModules) == 0 {
if modOnly != nil {
return nil, modOnly, nil
- } else if queryMatchesMainModule {
- return nil, nil, &QueryMatchesMainModuleError{
- Pattern: pattern,
- Query: query,
+ } else if len(mainModuleMatches) != 0 {
+ return nil, nil, &QueryMatchesMainModulesError{
+ MainModules: mainModuleMatches,
+ Pattern: pattern,
+ Query: query,
}
} else {
return nil, nil, &PackageNotInModuleError{
- Mod: Target,
- Query: query,
- Pattern: pattern,
+ MainModules: mainModuleMatches,
+ Query: query,
+ Pattern: pattern,
}
}
}
@@ -656,15 +663,16 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
if err != nil {
return r, err
}
- m := match(r.Mod, root, isLocal)
+ m := match(r.Mod, []string{root}, isLocal)
r.Packages = m.Pkgs
if len(r.Packages) == 0 && !matchPattern(path) {
if err := firstError(m); err != nil {
return r, err
}
+ replacement, _ := Replacement(r.Mod)
return r, &PackageNotInModuleError{
Mod: r.Mod,
- Replacement: Replacement(r.Mod),
+ Replacement: replacement,
Query: query,
Pattern: pattern,
}
@@ -684,8 +692,8 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
return err
})
- if queryMatchesMainModule && len(results) == 0 && modOnly == nil && errors.Is(err, fs.ErrNotExist) {
- return nil, nil, &QueryMatchesMainModuleError{
+ if len(mainModuleMatches) > 0 && len(results) == 0 && modOnly == nil && errors.Is(err, fs.ErrNotExist) {
+ return nil, nil, &QueryMatchesMainModulesError{
Pattern: pattern,
Query: query,
}
@@ -701,8 +709,13 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
func modulePrefixesExcludingTarget(path string) []string {
prefixes := make([]string, 0, strings.Count(path, "/")+1)
+ mainModulePrefixes := make(map[string]bool)
+ for _, m := range MainModules.Versions() {
+ mainModulePrefixes[m.Path] = true
+ }
+
for {
- if path != targetPrefix {
+ if !mainModulePrefixes[path] {
if _, _, ok := module.SplitPathVersion(path); ok {
prefixes = append(prefixes, path)
}
@@ -759,7 +772,7 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod
case *PackageNotInModuleError:
// Given the option, prefer to attribute “package not in module”
// to modules other than the main one.
- if noPackage == nil || noPackage.Mod == Target {
+ if noPackage == nil || MainModules.Contains(noPackage.Mod.Path) {
noPackage = rErr
}
case *NoMatchingVersionError:
@@ -878,6 +891,7 @@ func (e *WildcardInFirstElementError) Error() string {
// code for the versions it knows about, and thus did not have the opportunity
// to return a non-400 status code to suppress fallback.
type PackageNotInModuleError struct {
+ MainModules []module.Version
Mod module.Version
Replacement module.Version
Query string
@@ -885,11 +899,15 @@ type PackageNotInModuleError struct {
}
func (e *PackageNotInModuleError) Error() string {
- if e.Mod == Target {
+ if len(e.MainModules) > 0 {
+ prefix := "workspace modules do"
+ if len(e.MainModules) == 1 {
+ prefix = fmt.Sprintf("main module (%s) does", e.MainModules[0])
+ }
if strings.Contains(e.Pattern, "...") {
- return fmt.Sprintf("main module (%s) does not contain packages matching %s", Target.Path, e.Pattern)
+ return fmt.Sprintf("%s not contain packages matching %s", prefix, e.Pattern)
}
- return fmt.Sprintf("main module (%s) does not contain package %s", Target.Path, e.Pattern)
+ return fmt.Sprintf("%s not contain package %s", prefix, e.Pattern)
}
found := ""
@@ -951,7 +969,7 @@ func moduleHasRootPackage(ctx context.Context, m module.Version) (bool, error) {
// we don't need to verify it in go.sum. This makes 'go list -m -u' faster
// and simpler.
func versionHasGoMod(_ context.Context, m module.Version) (bool, error) {
- _, data, err := rawGoModData(m)
+ _, data, err := rawGoModData(m, "")
if err != nil {
return false, err
}
@@ -978,14 +996,18 @@ func lookupRepo(proxy, path string) (repo versionRepo, err error) {
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.
@@ -1024,11 +1046,13 @@ func (rr *replacementRepo) Versions(prefix string) ([]string, error) {
}
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)
+ }
}
}
}
@@ -1046,7 +1070,16 @@ func (rr *replacementRepo) Versions(prefix string) ([]string, error) {
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
}
@@ -1065,7 +1098,7 @@ func (rr *replacementRepo) Stat(rev string) (*modfetch.RevInfo, error) {
}
}
- if r := Replacement(module.Version{Path: path, Version: v}); r.Path == "" {
+ if r, _ := Replacement(module.Version{Path: path, Version: v}); r.Path == "" {
return info, err
}
return rr.replacementStat(v)
@@ -1073,27 +1106,42 @@ func (rr *replacementRepo) Stat(rev string) (*modfetch.RevInfo, error) {
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
@@ -1108,20 +1156,46 @@ func (rr *replacementRepo) replacementStat(v string) (*modfetch.RevInfo, error)
return rev, nil
}
-// A QueryMatchesMainModuleError indicates that a query requests
+// A QueryMatchesMainModulesError indicates that a query requests
// a version of the main module that cannot be satisfied.
// (The main module's version cannot be changed.)
-type QueryMatchesMainModuleError struct {
- Pattern string
- Query string
+type QueryMatchesMainModulesError struct {
+ MainModules []module.Version
+ Pattern string
+ Query string
}
-func (e *QueryMatchesMainModuleError) Error() string {
- if e.Pattern == Target.Path {
+func (e *QueryMatchesMainModulesError) Error() string {
+ if MainModules.Contains(e.Pattern) {
return fmt.Sprintf("can't request version %q of the main module (%s)", e.Query, e.Pattern)
}
- return fmt.Sprintf("can't request version %q of pattern %q that includes the main module (%s)", e.Query, e.Pattern, Target.Path)
+ plural := ""
+ mainModulePaths := make([]string, len(e.MainModules))
+ for i := range e.MainModules {
+ mainModulePaths[i] = e.MainModules[i].Path
+ }
+ if len(e.MainModules) > 1 {
+ plural = "s"
+ }
+ return fmt.Sprintf("can't request version %q of pattern %q that includes the main module%s (%s)", e.Query, e.Pattern, plural, strings.Join(mainModulePaths, ", "))
+}
+
+// A QueryUpgradesAllError indicates that a query requests
+// an upgrade on the all pattern.
+// (The main module's version cannot be changed.)
+type QueryUpgradesAllError struct {
+ MainModules []module.Version
+ Query string
+}
+
+func (e *QueryUpgradesAllError) Error() string {
+ var plural string = ""
+ if len(e.MainModules) != 1 {
+ plural = "s"
+ }
+
+ return fmt.Sprintf("can't request version %q of pattern \"all\" that includes the main module%s", e.Query, plural)
}
// A QueryMatchesPackagesInMainModuleError indicates that a query cannot be
diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go
index 658fc6f55a..799c48e50a 100644
--- a/src/cmd/go/internal/modload/search.go
+++ b/src/cmd/go/internal/modload/search.go
@@ -131,9 +131,10 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
}
if cfg.BuildMod == "vendor" {
- if HasModRoot() {
- walkPkgs(ModRoot(), targetPrefix, pruneGoMod|pruneVendor)
- walkPkgs(filepath.Join(ModRoot(), "vendor"), "", pruneVendor)
+ mod := MainModules.mustGetSingleMainModule()
+ if modRoot := MainModules.ModRoot(mod); modRoot != "" {
+ walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
+ walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
}
return
}
@@ -147,12 +148,12 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
root, modPrefix string
isLocal bool
)
- if mod == Target {
- if !HasModRoot() {
+ if MainModules.Contains(mod.Path) {
+ if MainModules.ModRoot(mod) == "" {
continue // If there is no main module, we can't search in it.
}
- root = ModRoot()
- modPrefix = targetPrefix
+ root = MainModules.ModRoot(mod)
+ modPrefix = MainModules.PathPrefix(mod)
isLocal = true
} else {
var err error
diff --git a/src/cmd/go/internal/modload/vendor.go b/src/cmd/go/internal/modload/vendor.go
index 80713b0812..daa5888075 100644
--- a/src/cmd/go/internal/modload/vendor.go
+++ b/src/cmd/go/internal/modload/vendor.go
@@ -15,6 +15,7 @@ import (
"cmd/go/internal/base"
+ "golang.org/x/mod/modfile"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
)
@@ -35,13 +36,13 @@ type vendorMetadata struct {
}
// readVendorList reads the list of vendored modules from vendor/modules.txt.
-func readVendorList() {
+func readVendorList(mainModule module.Version) {
vendorOnce.Do(func() {
vendorList = nil
vendorPkgModule = make(map[string]module.Version)
vendorVersion = make(map[string]string)
vendorMeta = make(map[module.Version]vendorMetadata)
- data, err := os.ReadFile(filepath.Join(ModRoot(), "vendor/modules.txt"))
+ data, err := os.ReadFile(filepath.Join(MainModules.ModRoot(mainModule), "vendor/modules.txt"))
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
base.Fatalf("go: %s", err)
@@ -134,8 +135,8 @@ func readVendorList() {
// 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() {
- readVendorList()
+func checkVendorConsistency(index *modFileIndex, modFile *modfile.File) {
+ readVendorList(MainModules.mustGetSingleMainModule())
pre114 := false
if semver.Compare(index.goVersionV, "v1.14") < 0 {
@@ -208,7 +209,7 @@ func checkVendorConsistency() {
}
for _, mod := range vendorReplaced {
- r := Replacement(mod)
+ r, _ := Replacement(mod)
if r == (module.Version{}) {
vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in go.mod")
continue
@@ -219,6 +220,7 @@ func checkVendorConsistency() {
}
if vendErrors.Len() > 0 {
+ modRoot := MainModules.ModRoot(MainModules.mustGetSingleMainModule())
base.Fatalf("go: inconsistent vendoring in %s:%s\n\n\tTo ignore the vendor directory, use -mod=readonly or -mod=mod.\n\tTo sync the vendor directory, run:\n\t\tgo mod vendor", modRoot, vendErrors)
}
}