diff options
Diffstat (limited to 'src/cmd/go/internal/modload')
-rw-r--r-- | src/cmd/go/internal/modload/build.go | 66 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/buildlist.go | 92 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/edit.go | 20 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/import.go | 152 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/init.go | 661 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/list.go | 8 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/load.go | 189 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/modfile.go | 169 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/mvs.go | 4 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/query.go | 230 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/search.go | 15 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/vendor.go | 12 |
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) } } |