all: REVERSE MERGE dev.cmdgo (220bc44) into master

This commit is a REVERSE MERGE.
It merges dev.cmdgo back into its parent branch, master.
This marks the end of development on dev.cmdgo.

Merge List:

+ 2021-08-27 220bc44a4c [dev.cmdgo] all: merge master (67f7e16) into dev.cmdgo
+ 2021-08-26 de83ef67ac [dev.cmdgo] all: merge master (5e6a7e9) into dev.cmdgo
+ 2021-08-25 de23549a39 [dev.cmdgo] cmd/go: fix calls to modFileGoVersion to pass in modFile
+ 2021-08-25 3b523caf41 [dev.cmdgo] cmd/go: clean up TODOWorkspaces instances
+ 2021-08-25 109c13b64f [dev.cmdgo] all: merge master (c2f96e6) into dev.cmdgo
+ 2021-08-12 e2e1987b31 [dev.cmdgo] cmd/link: fix TestBuildForTvOS
+ 2021-08-12 d397fc1169 [dev.cmdgo] don't give command-line-arguments a module
+ 2021-08-11 aaf914d0e6 [dev.cmdgo] cmd/go: remove modload.ModRoot function
+ 2021-08-06 3025ce2fa8 [dev.cmdgo] cmd/go: address code review comments in test cgo_path_space_quote
+ 2021-08-06 fc8e0cbbba [dev.cmdgo] cmd: update x/tools and remove copy of txtar
+ 2021-07-31 3799012990 [dev.cmdgo] cmd/go: add go mod editwork command
+ 2021-07-30 b3b53e1dad [dev.cmdgo] cmd/go: thread through modroots providing replacements
+ 2021-07-30 47694b59eb [dev.cmdgo] cmd/go: provide a more helpful missing required module error in workspaces
+ 2021-07-30 90830699ae [dev.cmdgo] cmd/go: allow expliticly setting -mod=readonly in workspace mode
+ 2021-07-30 8e2ab05dd3 Merge "[dev.cmdgo] all: merge master (9eee0ed) into dev.cmdgo" into dev.cmdgo
+ 2021-07-30 52e970b1c8 [dev.cmdgo] cmd: support space and quotes in CC and CXX
+ 2021-07-30 3a69cef65a [dev.cmdgo] cmd/internal/str: add utilities for quoting and splitting args
+ 2021-07-30 137089ffb9 [dev.cmdgo] cmd/internal/str: move package from cmd/go/internal/str
+ 2021-07-28 47cdfa95ae [dev.cmdgo] all: merge master (9eee0ed) into dev.cmdgo
+ 2021-07-28 176baafd5b [dev.cmdgo] cmd/go: sort roots when joining multiple main module roots
+ 2021-07-28 288a83dcff [dev.cmdgo] cmd/go: maintain a go.work.sum file
+ 2021-07-27 2c8acf63c2 [dev.cmdgo] cmd/go: make fewer 'go mod' commands update go.mod
+ 2021-07-27 72233d27c4 [dev.cmdgo] cmd/go: add -testsum flag to update go.sum in script tests
+ 2021-07-27 b2205eab0e [dev.cmdgo] cmd/go: add go mod initwork command
+ 2021-07-27 f05f5ceffa [dev.cmdgo] cmd/go: fold index and modFile into MainModules
+ 2021-07-26 7ce257147f [dev.cmdgo] cmd/go: add the workspace mode
+ 2021-07-26 3cd15e02ed [dev.cmdgo] cmd: pull in x/mod on the dev.cmdgo branch
+ 2021-07-22 a627fcd3c4 [dev.cmdgo] cmd/go: replace Target with MainModules, allowing for multiple targets
+ 2021-07-20 ab361499ef [dev.cmdgo] cmd/go/testdata/script: fix a small typo in modfile_flag
+ 2021-07-06 aa4da4f189 [dev.cmdgo] all: merge master (912f075) into dev.cmdgo
+ 2020-12-22 6dc2c16f95 [dev.cmdgo] codereview.cfg: add config for dev.cmdgo

Change-Id: Ic42f1273e42c90954bd61a6e4d6ca193c97bf04c
This commit is contained in:
Michael Matloob 2021-08-27 08:24:11 -04:00
commit acdea4f9f7
49 changed files with 2470 additions and 718 deletions

View file

@ -7,7 +7,7 @@ require (
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 // indirect github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 // indirect
golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e // indirect golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e // indirect
golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 golang.org/x/term v0.0.0-20210503060354-a79de5458b56
golang.org/x/tools v0.1.6-0.20210809225032-337cebd2c151 golang.org/x/tools v0.1.6-0.20210809225032-337cebd2c151

View file

@ -9,8 +9,8 @@ golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e h1:pv3V0NlNSh5Q6AX/StwGLBjc
golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI= golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI=
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a h1:e8qnjKz4EE6OjRki9wTadWSIogINvq10sMcuBRORxMY= golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61 h1:gQY3CVezomIImcWCpxp6Mhj+fXCOZ+gD8/88326LVqw=
golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q= golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View file

@ -167,6 +167,14 @@
// directory, but it is not accessed. When -modfile is specified, an // directory, but it is not accessed. When -modfile is specified, an
// alternate go.sum file is also used: its path is derived from the // alternate go.sum file is also used: its path is derived from the
// -modfile flag by trimming the ".mod" extension and appending ".sum". // -modfile flag by trimming the ".mod" extension and appending ".sum".
// -workfile file
// in module aware mode, use the given go.work file as a workspace file.
// By default or when -workfile is "auto", the go command searches for a
// file named go.work in the current directory and then containing directories
// until one is found. If a valid go.work file is found, the modules
// specified will collectively be used as the main modules. If -workfile
// is "off", or a go.work file is not found in "auto" mode, workspace
// mode is disabled.
// -overlay file // -overlay file
// read a JSON config file that provides an overlay for build operations. // read a JSON config file that provides an overlay for build operations.
// The file is a JSON struct with a single field, named 'Replace', that // The file is a JSON struct with a single field, named 'Replace', that
@ -1024,8 +1032,10 @@
// //
// download download modules to local cache // download download modules to local cache
// edit edit go.mod from tools or scripts // edit edit go.mod from tools or scripts
// editwork edit go.work from tools or scripts
// graph print module requirement graph // graph print module requirement graph
// init initialize new module in current directory // init initialize new module in current directory
// initwork initialize workspace file
// tidy add missing and remove unused modules // tidy add missing and remove unused modules
// vendor make vendored copy of dependencies // vendor make vendored copy of dependencies
// verify verify dependencies have expected content // verify verify dependencies have expected content
@ -1182,6 +1192,77 @@
// See https://golang.org/ref/mod#go-mod-edit for more about 'go mod edit'. // See https://golang.org/ref/mod#go-mod-edit for more about 'go mod edit'.
// //
// //
// Edit go.work from tools or scripts
//
// Usage:
//
// go mod editwork [editing flags] [go.work]
//
// Editwork provides a command-line interface for editing go.work,
// for use primarily by tools or scripts. It only reads go.work;
// it does not look up information about the modules involved.
// If no file is specified, editwork looks for a go.work file in the current
// directory and its parent directories
//
// The editing flags specify a sequence of editing operations.
//
// The -fmt flag reformats the go.work file without making other changes.
// This reformatting is also implied by any other modifications that use or
// rewrite the go.mod file. The only time this flag is needed is if no other
// flags are specified, as in 'go mod editwork -fmt'.
//
// The -directory=path and -dropdirectory=path flags
// add and drop a directory from the go.work files set of module directories.
//
// The -replace=old[@v]=new[@v] flag adds a replacement of the given
// module path and version pair. If the @v in old@v is omitted, a
// replacement without a version on the left side is added, which applies
// to all versions of the old module path. If the @v in new@v is omitted,
// the new path should be a local module root directory, not a module
// path. Note that -replace overrides any redundant replacements for old[@v],
// so omitting @v will drop existing replacements for specific versions.
//
// The -dropreplace=old[@v] flag drops a replacement of the given
// module path and version pair. If the @v is omitted, a replacement without
// a version on the left side is dropped.
//
// The -directory, -dropdirectory, -replace, and -dropreplace,
// editing flags may be repeated, and the changes are applied in the order given.
//
// The -go=version flag sets the expected Go language version.
//
// The -print flag prints the final go.work in its text format instead of
// writing it back to go.mod.
//
// The -json flag prints the final go.work file in JSON format instead of
// writing it back to go.mod. The JSON output corresponds to these Go types:
//
// type Module struct {
// Path string
// Version string
// }
//
// type GoWork struct {
// Go string
// Directory []Directory
// Replace []Replace
// }
//
// type Directory struct {
// Path string
// ModulePath string
// }
//
// type Replace struct {
// Old Module
// New Module
// }
//
// See the workspaces design proposal at
// https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
// more information.
//
//
// Print module requirement graph // Print module requirement graph
// //
// Usage: // Usage:
@ -1221,6 +1302,23 @@
// See https://golang.org/ref/mod#go-mod-init for more about 'go mod init'. // See https://golang.org/ref/mod#go-mod-init for more about 'go mod init'.
// //
// //
// Initialize workspace file
//
// Usage:
//
// go mod initwork [moddirs]
//
// go mod initwork initializes and writes a new go.work file in the current
// directory, in effect creating a new workspace at the current directory.
//
// go mod initwork optionally accepts paths to the workspace modules as arguments.
// If the argument is omitted, an empty workspace with no modules will be created.
//
// See the workspaces design proposal at
// https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
// more information.
//
//
// Add missing and remove unused modules // Add missing and remove unused modules
// //
// Usage: // Usage:

View file

@ -62,6 +62,13 @@ func AddModFlag(flags *flag.FlagSet) {
flags.Var(explicitStringFlag{value: &cfg.BuildMod, explicit: &cfg.BuildModExplicit}, "mod", "") flags.Var(explicitStringFlag{value: &cfg.BuildMod, explicit: &cfg.BuildModExplicit}, "mod", "")
} }
// AddWorkfileFlag adds the workfile flag to the flag set. It enables workspace
// mode for commands that support it by resetting the cfg.WorkFile variable
// to "" (equivalent to auto) rather than off.
func AddWorkfileFlag(flags *flag.FlagSet) {
flags.Var(explicitStringFlag{value: &cfg.WorkFile, explicit: &cfg.WorkFileExplicit}, "workfile", "")
}
// AddModCommonFlags adds the module-related flags common to build commands // AddModCommonFlags adds the module-related flags common to build commands
// and 'go mod' subcommands. // and 'go mod' subcommands.
func AddModCommonFlags(flags *flag.FlagSet) { func AddModCommonFlags(flags *flag.FlagSet) {

View file

@ -47,8 +47,10 @@ var (
BuildWork bool // -work flag BuildWork bool // -work flag
BuildX bool // -x flag BuildX bool // -x flag
ModCacheRW bool // -modcacherw flag ModCacheRW bool // -modcacherw flag
ModFile string // -modfile flag ModFile string // -modfile flag
WorkFile string // -workfile flag
WorkFileExplicit bool // whether -workfile was set explicitly
CmdName string // "build", "install", "list", "mod tidy", etc. CmdName string // "build", "install", "list", "mod tidy", etc.

View file

@ -146,8 +146,9 @@ func findEnv(env []cfg.EnvVar, name string) string {
// ExtraEnvVars returns environment variables that should not leak into child processes. // ExtraEnvVars returns environment variables that should not leak into child processes.
func ExtraEnvVars() []cfg.EnvVar { func ExtraEnvVars() []cfg.EnvVar {
gomod := "" gomod := ""
modload.Init()
if modload.HasModRoot() { if modload.HasModRoot() {
gomod = filepath.Join(modload.ModRoot(), "go.mod") gomod = modload.ModFilePath()
} else if modload.Enabled() { } else if modload.Enabled() {
gomod = os.DevNull gomod = os.DevNull
} }

View file

@ -225,7 +225,8 @@ func downloadPaths(patterns []string) []string {
base.ExitIfErrors() base.ExitIfErrors()
var pkgs []string var pkgs []string
for _, m := range search.ImportPathsQuiet(patterns) { noModRoots := []string{}
for _, m := range search.ImportPathsQuiet(patterns, noModRoots) {
if len(m.Pkgs) == 0 && strings.Contains(m.Pattern(), "...") { if len(m.Pkgs) == 0 && strings.Contains(m.Pattern(), "...") {
pkgs = append(pkgs, m.Pattern()) pkgs = append(pkgs, m.Pattern())
} else { } else {
@ -315,7 +316,8 @@ func download(arg string, parent *load.Package, stk *load.ImportStack, mode int)
if wildcardOkay && strings.Contains(arg, "...") { if wildcardOkay && strings.Contains(arg, "...") {
match := search.NewMatch(arg) match := search.NewMatch(arg)
if match.IsLocal() { if match.IsLocal() {
match.MatchDirs() noModRoots := []string{} // We're in gopath mode, so there are no modroots.
match.MatchDirs(noModRoots)
args = match.Dirs args = match.Dirs
} else { } else {
match.MatchPackages() match.MatchPackages()

View file

@ -316,6 +316,7 @@ For more about modules, see https://golang.org/ref/mod.
func init() { func init() {
CmdList.Run = runList // break init cycle CmdList.Run = runList // break init cycle
work.AddBuildFlags(CmdList, work.DefaultBuildFlags) work.AddBuildFlags(CmdList, work.DefaultBuildFlags)
base.AddWorkfileFlag(&CmdList.Flag)
} }
var ( var (
@ -336,6 +337,8 @@ var (
var nl = []byte{'\n'} var nl = []byte{'\n'}
func runList(ctx context.Context, cmd *base.Command, args []string) { func runList(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
if *listFmt != "" && *listJson == true { if *listFmt != "" && *listJson == true {
base.Fatalf("go list -f cannot be used with -json") base.Fatalf("go list -f cannot be used with -json")
} }

View file

@ -1450,9 +1450,9 @@ func disallowInternal(ctx context.Context, srcDir string, importer *Package, imp
// The importer is a list of command-line files. // The importer is a list of command-line files.
// Pretend that the import path is the import path of the // Pretend that the import path is the import path of the
// directory containing them. // directory containing them.
// If the directory is outside the main module, this will resolve to ".", // If the directory is outside the main modules, this will resolve to ".",
// which is not a prefix of any valid module. // which is not a prefix of any valid module.
importerPath = modload.DirImportPath(ctx, importer.Dir) importerPath, _ = modload.MainModules.DirImportPath(ctx, importer.Dir)
} }
parentOfInternal := p.ImportPath[:i] parentOfInternal := p.ImportPath[:i]
if str.HasPathPrefix(importerPath, parentOfInternal) { if str.HasPathPrefix(importerPath, parentOfInternal) {
@ -2447,7 +2447,8 @@ func PackagesAndErrors(ctx context.Context, opts PackageOpts, patterns []string)
} }
matches, _ = modload.LoadPackages(ctx, modOpts, patterns...) matches, _ = modload.LoadPackages(ctx, modOpts, patterns...)
} else { } else {
matches = search.ImportPaths(patterns) noModRoots := []string{}
matches = search.ImportPaths(patterns, noModRoots)
} }
var ( var (

View file

@ -66,6 +66,7 @@ func init() {
// TODO(jayconrod): https://golang.org/issue/35849 Apply -x to other 'go mod' commands. // TODO(jayconrod): https://golang.org/issue/35849 Apply -x to other 'go mod' commands.
cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "") cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "")
base.AddModCommonFlags(&cmdDownload.Flag) base.AddModCommonFlags(&cmdDownload.Flag)
base.AddWorkfileFlag(&cmdDownload.Flag)
} }
type moduleJSON struct { type moduleJSON struct {
@ -81,6 +82,8 @@ type moduleJSON struct {
} }
func runDownload(ctx context.Context, cmd *base.Command, args []string) { func runDownload(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
// Check whether modules are enabled and whether we're in a module. // Check whether modules are enabled and whether we're in a module.
modload.ForceUseModules = true modload.ForceUseModules = true
if !modload.HasModRoot() && len(args) == 0 { if !modload.HasModRoot() && len(args) == 0 {
@ -91,12 +94,18 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
args = []string{"all"} args = []string{"all"}
} }
if modload.HasModRoot() { if modload.HasModRoot() {
modload.LoadModFile(ctx) // to fill Target modload.LoadModFile(ctx) // to fill MainModules
targetAtUpgrade := modload.Target.Path + "@upgrade"
targetAtPatch := modload.Target.Path + "@patch" if len(modload.MainModules.Versions()) != 1 {
panic(modload.TODOWorkspaces("Support workspace mode in go mod download"))
}
mainModule := modload.MainModules.Versions()[0]
targetAtUpgrade := mainModule.Path + "@upgrade"
targetAtPatch := mainModule.Path + "@patch"
for _, arg := range args { for _, arg := range args {
switch arg { switch arg {
case modload.Target.Path, targetAtUpgrade, targetAtPatch: case mainModule.Path, targetAtUpgrade, targetAtPatch:
os.Stderr.WriteString("go mod download: skipping argument " + arg + " that resolves to the main module\n") os.Stderr.WriteString("go mod download: skipping argument " + arg + " that resolves to the main module\n")
} }
} }

View file

@ -0,0 +1,282 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go mod editwork
package modcmd
import (
"bytes"
"cmd/go/internal/base"
"cmd/go/internal/lockedfile"
"cmd/go/internal/modload"
"context"
"encoding/json"
"errors"
"os"
"strings"
"golang.org/x/mod/modfile"
)
var cmdEditwork = &base.Command{
UsageLine: "go mod editwork [editing flags] [go.work]",
Short: "edit go.work from tools or scripts",
Long: `Editwork provides a command-line interface for editing go.work,
for use primarily by tools or scripts. It only reads go.work;
it does not look up information about the modules involved.
If no file is specified, editwork looks for a go.work file in the current
directory and its parent directories
The editing flags specify a sequence of editing operations.
The -fmt flag reformats the go.work file without making other changes.
This reformatting is also implied by any other modifications that use or
rewrite the go.mod file. The only time this flag is needed is if no other
flags are specified, as in 'go mod editwork -fmt'.
The -directory=path and -dropdirectory=path flags
add and drop a directory from the go.work files set of module directories.
The -replace=old[@v]=new[@v] flag adds a replacement of the given
module path and version pair. If the @v in old@v is omitted, a
replacement without a version on the left side is added, which applies
to all versions of the old module path. If the @v in new@v is omitted,
the new path should be a local module root directory, not a module
path. Note that -replace overrides any redundant replacements for old[@v],
so omitting @v will drop existing replacements for specific versions.
The -dropreplace=old[@v] flag drops a replacement of the given
module path and version pair. If the @v is omitted, a replacement without
a version on the left side is dropped.
The -directory, -dropdirectory, -replace, and -dropreplace,
editing flags may be repeated, and the changes are applied in the order given.
The -go=version flag sets the expected Go language version.
The -print flag prints the final go.work in its text format instead of
writing it back to go.mod.
The -json flag prints the final go.work file in JSON format instead of
writing it back to go.mod. The JSON output corresponds to these Go types:
type Module struct {
Path string
Version string
}
type GoWork struct {
Go string
Directory []Directory
Replace []Replace
}
type Directory struct {
Path string
ModulePath string
}
type Replace struct {
Old Module
New Module
}
See the workspaces design proposal at
https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
more information.
`,
}
var (
editworkFmt = cmdEditwork.Flag.Bool("fmt", false, "")
editworkGo = cmdEditwork.Flag.String("go", "", "")
editworkJSON = cmdEditwork.Flag.Bool("json", false, "")
editworkPrint = cmdEditwork.Flag.Bool("print", false, "")
workedits []func(file *modfile.WorkFile) // edits specified in flags
)
func init() {
cmdEditwork.Run = runEditwork // break init cycle
cmdEditwork.Flag.Var(flagFunc(flagEditworkDirectory), "directory", "")
cmdEditwork.Flag.Var(flagFunc(flagEditworkDropDirectory), "dropdirectory", "")
cmdEditwork.Flag.Var(flagFunc(flagEditworkReplace), "replace", "")
cmdEditwork.Flag.Var(flagFunc(flagEditworkDropReplace), "dropreplace", "")
base.AddWorkfileFlag(&cmdEditwork.Flag)
}
func runEditwork(ctx context.Context, cmd *base.Command, args []string) {
anyFlags :=
*editworkGo != "" ||
*editworkJSON ||
*editworkPrint ||
*editworkFmt ||
len(workedits) > 0
if !anyFlags {
base.Fatalf("go mod edit: no flags specified (see 'go help mod edit').")
}
if *editworkJSON && *editworkPrint {
base.Fatalf("go mod edit: cannot use both -json and -print")
}
if len(args) > 1 {
base.Fatalf("go mod edit: too many arguments")
}
var gowork string
if len(args) == 1 {
gowork = args[0]
} else {
modload.InitWorkfile()
gowork = modload.WorkFilePath()
}
if *editworkGo != "" {
if !modfile.GoVersionRE.MatchString(*editworkGo) {
base.Fatalf(`go mod: invalid -go option; expecting something like "-go %s"`, modload.LatestGoVersion())
}
}
data, err := lockedfile.Read(gowork)
if err != nil {
base.Fatalf("go: %v", err)
}
workFile, err := modfile.ParseWork(gowork, data, nil)
if err != nil {
base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gowork), err)
}
if *editworkGo != "" {
if err := workFile.AddGoStmt(*editworkGo); err != nil {
base.Fatalf("go: internal error: %v", err)
}
}
if len(workedits) > 0 {
for _, edit := range workedits {
edit(workFile)
}
}
workFile.SortBlocks()
workFile.Cleanup() // clean file after edits
if *editworkJSON {
editworkPrintJSON(workFile)
return
}
out := modfile.Format(workFile.Syntax)
if *editworkPrint {
os.Stdout.Write(out)
return
}
err = lockedfile.Transform(gowork, func(lockedData []byte) ([]byte, error) {
if !bytes.Equal(lockedData, data) {
return nil, errors.New("go.work changed during editing; not overwriting")
}
return out, nil
})
if err != nil {
base.Fatalf("go: %v", err)
}
}
// flagEditworkDirectory implements the -directory flag.
func flagEditworkDirectory(arg string) {
workedits = append(workedits, func(f *modfile.WorkFile) {
if err := f.AddDirectory(arg, ""); err != nil {
base.Fatalf("go mod: -directory=%s: %v", arg, err)
}
})
}
// flagEditworkDropDirectory implements the -dropdirectory flag.
func flagEditworkDropDirectory(arg string) {
workedits = append(workedits, func(f *modfile.WorkFile) {
if err := f.DropDirectory(arg); err != nil {
base.Fatalf("go mod: -dropdirectory=%s: %v", arg, err)
}
})
}
// flagReplace implements the -replace flag.
func flagEditworkReplace(arg string) {
var i int
if i = strings.Index(arg, "="); i < 0 {
base.Fatalf("go mod: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
}
old, new := strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
if strings.HasPrefix(new, ">") {
base.Fatalf("go mod: -replace=%s: separator between old and new is =, not =>", arg)
}
oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
if err != nil {
base.Fatalf("go mod: -replace=%s: %v", arg, err)
}
newPath, newVersion, err := parsePathVersionOptional("new", new, true)
if err != nil {
base.Fatalf("go mod: -replace=%s: %v", arg, err)
}
if newPath == new && !modfile.IsDirectoryPath(new) {
base.Fatalf("go mod: -replace=%s: unversioned new path must be local directory", arg)
}
workedits = append(workedits, func(f *modfile.WorkFile) {
if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
base.Fatalf("go mod: -replace=%s: %v", arg, err)
}
})
}
// flagDropReplace implements the -dropreplace flag.
func flagEditworkDropReplace(arg string) {
path, version, err := parsePathVersionOptional("old", arg, true)
if err != nil {
base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
}
workedits = append(workedits, func(f *modfile.WorkFile) {
if err := f.DropReplace(path, version); err != nil {
base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
}
})
}
// editPrintJSON prints the -json output.
func editworkPrintJSON(workFile *modfile.WorkFile) {
var f workfileJSON
if workFile.Go != nil {
f.Go = workFile.Go.Version
}
for _, d := range workFile.Directory {
f.Directory = append(f.Directory, directoryJSON{DiskPath: d.Path, ModPath: d.ModulePath})
}
for _, r := range workFile.Replace {
f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
}
data, err := json.MarshalIndent(&f, "", "\t")
if err != nil {
base.Fatalf("go: internal error: %v", err)
}
data = append(data, '\n')
os.Stdout.Write(data)
}
// workfileJSON is the -json output data structure.
type workfileJSON struct {
Go string `json:",omitempty"`
Directory []directoryJSON
Replace []replaceJSON
}
type directoryJSON struct {
DiskPath string
ModPath string `json:",omitempty"`
}

View file

@ -42,9 +42,12 @@ var (
func init() { func init() {
cmdGraph.Flag.Var(&graphGo, "go", "") cmdGraph.Flag.Var(&graphGo, "go", "")
base.AddModCommonFlags(&cmdGraph.Flag) base.AddModCommonFlags(&cmdGraph.Flag)
base.AddWorkfileFlag(&cmdGraph.Flag)
} }
func runGraph(ctx context.Context, cmd *base.Command, args []string) { func runGraph(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
if len(args) > 0 { if len(args) > 0 {
base.Fatalf("go mod graph: graph takes no arguments") base.Fatalf("go mod graph: graph takes no arguments")
} }

View file

@ -0,0 +1,54 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// go mod initwork
package modcmd
import (
"cmd/go/internal/base"
"cmd/go/internal/modload"
"context"
"path/filepath"
)
var _ = modload.TODOWorkspaces("Add more documentation below. Though this is" +
"enough for those trying workspaces out, there should be more through" +
"documentation if the proposal is accepted and released.")
var cmdInitwork = &base.Command{
UsageLine: "go mod initwork [moddirs]",
Short: "initialize workspace file",
Long: `go mod initwork initializes and writes a new go.work file in the current
directory, in effect creating a new workspace at the current directory.
go mod initwork optionally accepts paths to the workspace modules as arguments.
If the argument is omitted, an empty workspace with no modules will be created.
See the workspaces design proposal at
https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
more information.
`,
Run: runInitwork,
}
func init() {
base.AddModCommonFlags(&cmdInitwork.Flag)
base.AddWorkfileFlag(&cmdInitwork.Flag)
}
func runInitwork(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
modload.ForceUseModules = true
// TODO(matloob): support using the -workfile path
// To do that properly, we'll have to make the module directories
// make dirs relative to workFile path before adding the paths to
// the directory entries
workFile := filepath.Join(base.Cwd(), "go.work")
modload.CreateWorkFile(ctx, workFile, args)
}

View file

@ -23,8 +23,10 @@ See 'go help modules' for an overview of module functionality.
Commands: []*base.Command{ Commands: []*base.Command{
cmdDownload, cmdDownload,
cmdEdit, cmdEdit,
cmdEditwork,
cmdGraph, cmdGraph,
cmdInit, cmdInit,
cmdInitwork,
cmdTidy, cmdTidy,
cmdVendor, cmdVendor,
cmdVerify, cmdVerify,

View file

@ -74,7 +74,7 @@ func runVendor(ctx context.Context, cmd *base.Command, args []string) {
} }
_, pkgs := modload.LoadPackages(ctx, loadOpts, "all") _, pkgs := modload.LoadPackages(ctx, loadOpts, "all")
vdir := filepath.Join(modload.ModRoot(), "vendor") vdir := filepath.Join(modload.VendorDir())
if err := os.RemoveAll(vdir); err != nil { if err := os.RemoveAll(vdir); err != nil {
base.Fatalf("go mod vendor: %v", err) base.Fatalf("go mod vendor: %v", err)
} }
@ -82,7 +82,7 @@ func runVendor(ctx context.Context, cmd *base.Command, args []string) {
modpkgs := make(map[module.Version][]string) modpkgs := make(map[module.Version][]string)
for _, pkg := range pkgs { for _, pkg := range pkgs {
m := modload.PackageModule(pkg) m := modload.PackageModule(pkg)
if m.Path == "" || m == modload.Target { if m.Path == "" || m.Version == "" && modload.MainModules.Contains(m.Path) {
continue continue
} }
modpkgs[m] = append(modpkgs[m], pkg) modpkgs[m] = append(modpkgs[m], pkg)
@ -128,7 +128,8 @@ func runVendor(ctx context.Context, cmd *base.Command, args []string) {
} }
for _, m := range vendorMods { for _, m := range vendorMods {
line := moduleLine(m, modload.Replacement(m)) replacement, _ := modload.Replacement(m)
line := moduleLine(m, replacement)
io.WriteString(w, line) io.WriteString(w, line)
goVersion := "" goVersion := ""

View file

@ -39,9 +39,12 @@ See https://golang.org/ref/mod#go-mod-verify for more about 'go mod verify'.
func init() { func init() {
base.AddModCommonFlags(&cmdVerify.Flag) base.AddModCommonFlags(&cmdVerify.Flag)
base.AddWorkfileFlag(&cmdVerify.Flag)
} }
func runVerify(ctx context.Context, cmd *base.Command, args []string) { func runVerify(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
if len(args) != 0 { if len(args) != 0 {
// NOTE(rsc): Could take a module pattern. // NOTE(rsc): Could take a module pattern.
base.Fatalf("go mod verify: verify takes no arguments") base.Fatalf("go mod verify: verify takes no arguments")

View file

@ -61,9 +61,11 @@ var (
func init() { func init() {
cmdWhy.Run = runWhy // break init cycle cmdWhy.Run = runWhy // break init cycle
base.AddModCommonFlags(&cmdWhy.Flag) base.AddModCommonFlags(&cmdWhy.Flag)
base.AddWorkfileFlag(&cmdWhy.Flag)
} }
func runWhy(ctx context.Context, cmd *base.Command, args []string) { func runWhy(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
modload.ForceUseModules = true modload.ForceUseModules = true
modload.RootMode = modload.NeedRoot modload.RootMode = modload.NeedRoot

View file

@ -693,19 +693,21 @@ func isValidSum(data []byte) bool {
return true return true
} }
var ErrGoSumDirty = errors.New("updates to go.sum needed, disabled by -mod=readonly")
// WriteGoSum writes the go.sum file if it needs to be updated. // WriteGoSum writes the go.sum file if it needs to be updated.
// //
// keep is used to check whether a newly added sum should be saved in go.sum. // keep is used to check whether a newly added sum should be saved in go.sum.
// It should have entries for both module content sums and go.mod sums // It should have entries for both module content sums and go.mod sums
// (version ends with "/go.mod"). Existing sums will be preserved unless they // (version ends with "/go.mod"). Existing sums will be preserved unless they
// have been marked for deletion with TrimGoSum. // have been marked for deletion with TrimGoSum.
func WriteGoSum(keep map[module.Version]bool) { func WriteGoSum(keep map[module.Version]bool, readonly bool) error {
goSum.mu.Lock() goSum.mu.Lock()
defer goSum.mu.Unlock() defer goSum.mu.Unlock()
// If we haven't read the go.sum file yet, don't bother writing it. // If we haven't read the go.sum file yet, don't bother writing it.
if !goSum.enabled { if !goSum.enabled {
return return nil
} }
// Check whether we need to add sums for which keep[m] is true or remove // Check whether we need to add sums for which keep[m] is true or remove
@ -723,10 +725,10 @@ Outer:
} }
} }
if !dirty { if !dirty {
return return nil
} }
if cfg.BuildMod == "readonly" { if readonly {
base.Fatalf("go: updates to go.sum needed, disabled by -mod=readonly") return ErrGoSumDirty
} }
if _, ok := fsys.OverlayPath(GoSumFile); ok { if _, ok := fsys.OverlayPath(GoSumFile); ok {
base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay") base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay")
@ -774,11 +776,12 @@ Outer:
}) })
if err != nil { if err != nil {
base.Fatalf("go: updating go.sum: %v", err) return fmt.Errorf("updating go.sum: %w", err)
} }
goSum.status = make(map[modSum]modSumStatus) goSum.status = make(map[modSum]modSumStatus)
goSum.overwrite = false goSum.overwrite = false
return nil
} }
// TrimGoSum trims go.sum to contain only the modules needed for reproducible // TrimGoSum trims go.sum to contain only the modules needed for reproducible

View file

@ -389,9 +389,11 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
haveExternalExe := false haveExternalExe := false
for _, pkg := range pkgs { for _, pkg := range pkgs {
if pkg.Name == "main" && pkg.Module != nil && pkg.Module.Path != modload.Target.Path { if pkg.Name == "main" && pkg.Module != nil {
haveExternalExe = true if !modload.MainModules.Contains(pkg.Module.Path) {
break haveExternalExe = true
break
}
} }
} }
if haveExternalExe { if haveExternalExe {
@ -675,7 +677,9 @@ func (r *resolver) queryNone(ctx context.Context, q *query) {
if !q.isWildcard() { if !q.isWildcard() {
q.pathOnce(q.pattern, func() pathSet { q.pathOnce(q.pattern, func() pathSet {
if modload.HasModRoot() && q.pattern == modload.Target.Path { hasModRoot := modload.HasModRoot()
if hasModRoot && modload.MainModules.Contains(q.pattern) {
v := module.Version{Path: q.pattern}
// The user has explicitly requested to downgrade their own module to // The user has explicitly requested to downgrade their own module to
// version "none". This is not an entirely unreasonable request: it // version "none". This is not an entirely unreasonable request: it
// could plausibly mean “downgrade away everything that depends on any // could plausibly mean “downgrade away everything that depends on any
@ -686,7 +690,7 @@ func (r *resolver) queryNone(ctx context.Context, q *query) {
// However, neither of those behaviors would be consistent with the // However, neither of those behaviors would be consistent with the
// plain meaning of the query. To try to reduce confusion, reject the // plain meaning of the query. To try to reduce confusion, reject the
// query explicitly. // query explicitly.
return errSet(&modload.QueryMatchesMainModuleError{Pattern: q.pattern, Query: q.version}) return errSet(&modload.QueryMatchesMainModulesError{MainModules: []module.Version{v}, Pattern: q.pattern, Query: q.version})
} }
return pathSet{mod: module.Version{Path: q.pattern, Version: "none"}} return pathSet{mod: module.Version{Path: q.pattern, Version: "none"}}
@ -698,8 +702,8 @@ func (r *resolver) queryNone(ctx context.Context, q *query) {
continue continue
} }
q.pathOnce(curM.Path, func() pathSet { q.pathOnce(curM.Path, func() pathSet {
if modload.HasModRoot() && curM == modload.Target { if modload.HasModRoot() && curM.Version == "" && modload.MainModules.Contains(curM.Path) {
return errSet(&modload.QueryMatchesMainModuleError{Pattern: q.pattern, Query: q.version}) return errSet(&modload.QueryMatchesMainModulesError{MainModules: []module.Version{curM}, Pattern: q.pattern, Query: q.version})
} }
return pathSet{mod: module.Version{Path: curM.Path, Version: "none"}} return pathSet{mod: module.Version{Path: curM.Path, Version: "none"}}
}) })
@ -718,12 +722,21 @@ func (r *resolver) performLocalQueries(ctx context.Context) {
// Absolute paths like C:\foo and relative paths like ../foo... are // Absolute paths like C:\foo and relative paths like ../foo... are
// restricted to matching packages in the main module. // restricted to matching packages in the main module.
pkgPattern := modload.DirImportPath(ctx, q.pattern) pkgPattern, mainModule := modload.MainModules.DirImportPath(ctx, q.pattern)
if pkgPattern == "." { if pkgPattern == "." {
return errSet(fmt.Errorf("%s%s is not within module rooted at %s", q.pattern, absDetail, modload.ModRoot())) modload.MustHaveModRoot()
var modRoots []string
for _, m := range modload.MainModules.Versions() {
modRoots = append(modRoots, modload.MainModules.ModRoot(m))
}
var plural string
if len(modRoots) != 1 {
plural = "s"
}
return errSet(fmt.Errorf("%s%s is not within module%s rooted at %s", q.pattern, absDetail, plural, strings.Join(modRoots, ", ")))
} }
match := modload.MatchInModule(ctx, pkgPattern, modload.Target, imports.AnyTags()) match := modload.MatchInModule(ctx, pkgPattern, mainModule, imports.AnyTags())
if len(match.Errs) > 0 { if len(match.Errs) > 0 {
return pathSet{err: match.Errs[0]} return pathSet{err: match.Errs[0]}
} }
@ -733,13 +746,14 @@ func (r *resolver) performLocalQueries(ctx context.Context) {
return errSet(fmt.Errorf("no package in current directory")) return errSet(fmt.Errorf("no package in current directory"))
} }
if !q.isWildcard() { if !q.isWildcard() {
return errSet(fmt.Errorf("%s%s is not a package in module rooted at %s", q.pattern, absDetail, modload.ModRoot())) modload.MustHaveModRoot()
return errSet(fmt.Errorf("%s%s is not a package in module rooted at %s", q.pattern, absDetail, modload.MainModules.ModRoot(mainModule)))
} }
search.WarnUnmatched([]*search.Match{match}) search.WarnUnmatched([]*search.Match{match})
return pathSet{} return pathSet{}
} }
return pathSet{pkgMods: []module.Version{modload.Target}} return pathSet{pkgMods: []module.Version{mainModule}}
}) })
} }
} }
@ -789,11 +803,12 @@ func (r *resolver) queryWildcard(ctx context.Context, q *query) {
return pathSet{} return pathSet{}
} }
if curM.Path == modload.Target.Path && !versionOkForMainModule(q.version) { if modload.MainModules.Contains(curM.Path) && !versionOkForMainModule(q.version) {
if q.matchesPath(curM.Path) { if q.matchesPath(curM.Path) {
return errSet(&modload.QueryMatchesMainModuleError{ return errSet(&modload.QueryMatchesMainModulesError{
Pattern: q.pattern, MainModules: []module.Version{curM},
Query: q.version, Pattern: q.pattern,
Query: q.version,
}) })
} }
@ -1159,8 +1174,8 @@ func (r *resolver) loadPackages(ctx context.Context, patterns []string, findPack
} }
opts.AllowPackage = func(ctx context.Context, path string, m module.Version) error { opts.AllowPackage = func(ctx context.Context, path string, m module.Version) error {
if m.Path == "" || m == modload.Target { if m.Path == "" || m.Version == "" && modload.MainModules.Contains(m.Path) {
// Packages in the standard library and main module are already at their // Packages in the standard library and main modules are already at their
// latest (and only) available versions. // latest (and only) available versions.
return nil return nil
} }
@ -1370,11 +1385,11 @@ func (r *resolver) disambiguate(cs pathSet) (filtered pathSet, isPackage bool, m
continue continue
} }
if m.Path == modload.Target.Path { if modload.MainModules.Contains(m.Path) {
if m.Version == modload.Target.Version { if m.Version == "" {
return pathSet{}, true, m, true return pathSet{}, true, m, true
} }
// The main module can only be set to its own version. // A main module can only be set to its own version.
continue continue
} }
@ -1610,7 +1625,7 @@ func (r *resolver) checkPackageProblems(ctx context.Context, pkgPatterns []strin
i := i i := i
m := r.buildList[i] m := r.buildList[i]
mActual := m mActual := m
if mRepl := modload.Replacement(m); mRepl.Path != "" { if mRepl, _ := modload.Replacement(m); mRepl.Path != "" {
mActual = mRepl mActual = mRepl
} }
old := module.Version{Path: m.Path, Version: r.initialVersion[m.Path]} old := module.Version{Path: m.Path, Version: r.initialVersion[m.Path]}
@ -1618,7 +1633,7 @@ func (r *resolver) checkPackageProblems(ctx context.Context, pkgPatterns []strin
continue continue
} }
oldActual := old oldActual := old
if oldRepl := modload.Replacement(old); oldRepl.Path != "" { if oldRepl, _ := modload.Replacement(old); oldRepl.Path != "" {
oldActual = oldRepl oldActual = oldRepl
} }
if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(oldActual) { if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(oldActual) {
@ -1744,10 +1759,11 @@ func (r *resolver) resolve(q *query, m module.Version) {
panic("internal error: resolving a module.Version with an empty path") panic("internal error: resolving a module.Version with an empty path")
} }
if m.Path == modload.Target.Path && m.Version != modload.Target.Version { if modload.MainModules.Contains(m.Path) && m.Version != "" {
reportError(q, &modload.QueryMatchesMainModuleError{ reportError(q, &modload.QueryMatchesMainModulesError{
Pattern: q.pattern, MainModules: []module.Version{{Path: m.Path}},
Query: q.version, Pattern: q.pattern,
Query: q.version,
}) })
return return
} }
@ -1775,7 +1791,7 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi
resolved := make([]module.Version, 0, len(r.resolvedVersion)) resolved := make([]module.Version, 0, len(r.resolvedVersion))
for mPath, rv := range r.resolvedVersion { for mPath, rv := range r.resolvedVersion {
if mPath != modload.Target.Path { if !modload.MainModules.Contains(mPath) {
resolved = append(resolved, module.Version{Path: mPath, Version: rv.version}) resolved = append(resolved, module.Version{Path: mPath, Version: rv.version})
} }
} }

View file

@ -192,9 +192,9 @@ func (q *query) validate() error {
// TODO(bcmills): "all@none" seems like a totally reasonable way to // TODO(bcmills): "all@none" seems like a totally reasonable way to
// request that we remove all module requirements, leaving only the main // request that we remove all module requirements, leaving only the main
// module and standard library. Perhaps we should implement that someday. // module and standard library. Perhaps we should implement that someday.
return &modload.QueryMatchesMainModuleError{ return &modload.QueryUpgradesAllError{
Pattern: q.pattern, MainModules: modload.MainModules.Versions(),
Query: q.version, Query: q.version,
} }
} }
} }

View file

@ -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 // in rs (which may be nil to indicate that m was not loaded from a requirement
// graph). // graph).
func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic { 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{ info := &modinfo.ModulePublic{
Path: m.Path, Path: m.Path,
Version: m.Version, Version: m.Version,
Main: true, Main: true,
} }
if v, ok := rawGoVersion.Load(Target); ok { if v, ok := rawGoVersion.Load(m); ok {
info.GoVersion = v.(string) info.GoVersion = v.(string)
} else { } else {
panic("internal error: GoVersion not set for main module") panic("internal error: GoVersion not set for main module")
} }
if HasModRoot() { if modRoot := MainModules.ModRoot(m); modRoot != "" {
info.Dir = ModRoot() info.Dir = modRoot
info.GoMod = ModFilePath() info.GoMod = modFilePath(modRoot)
} }
return info 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 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 { checksumOk := func(suffix string) bool {
return rs == nil || m.Version == "" || cfg.BuildMod == "mod" || return rs == nil || m.Version == "" || cfg.BuildMod == "mod" ||
modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix}) 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") { if m.GoVersion == "" && checksumOk("/go.mod") {
// Load the go.mod file to determine the Go version, since it hasn't // Load the go.mod file to determine the Go version, since it hasn't
// already been populated from rawGoVersion. // 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 m.GoVersion = summary.goVersion
} }
} }
@ -289,11 +289,11 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
if rs == nil { if rs == nil {
// If this was an explicitly-versioned argument to 'go mod download' or // If this was an explicitly-versioned argument to 'go mod download' or
// 'go list -m', report the actual requested version, not its replacement. // '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 return info
} }
r := Replacement(m) r, replacedFrom := Replacement(m)
if r.Path == "" { if r.Path == "" {
if cfg.BuildMod == "vendor" { if cfg.BuildMod == "vendor" {
// It's tempting to fill in the "Dir" field to point within the 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 // interleave packages from different modules if one module path is a
// prefix of the other. // prefix of the other.
} else { } else {
completeFromModCache(info) completeFromModCache(info, "")
} }
return info return info
} }
@ -322,12 +322,12 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
if filepath.IsAbs(r.Path) { if filepath.IsAbs(r.Path) {
info.Replace.Dir = r.Path info.Replace.Dir = r.Path
} else { } 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") info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
} }
if cfg.BuildMod != "vendor" { if cfg.BuildMod != "vendor" {
completeFromModCache(info.Replace) completeFromModCache(info.Replace, replacedFrom)
info.Dir = info.Replace.Dir info.Dir = info.Replace.Dir
info.GoMod = info.Replace.GoMod info.GoMod = info.Replace.GoMod
info.Retracted = info.Replace.Retracted 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 // for modules providing packages named by path and deps. path and deps must
// name packages that were resolved successfully with LoadPackages. // name packages that were resolved successfully with LoadPackages.
func PackageBuildInfo(path string, deps []string) string { func PackageBuildInfo(path string, deps []string) string {
if isStandardImportPath(path) || !Enabled() { if !Enabled() {
return "" return ""
} }
target, _ := findModule(loaded, path)
target := mustFindModule(loaded, path, path)
mdeps := make(map[module.Version]bool) mdeps := make(map[module.Version]bool)
for _, dep := range deps { for _, dep := range deps {
if !isStandardImportPath(dep) { if m, ok := findModule(loaded, dep); ok {
mdeps[mustFindModule(loaded, path, dep)] = true mdeps[m] = true
} }
} }
var mods []module.Version var mods []module.Version
@ -367,14 +366,16 @@ func PackageBuildInfo(path string, deps []string) string {
mv = "(devel)" mv = "(devel)"
} }
fmt.Fprintf(&buf, "%s\t%s\t%s", token, m.Path, mv) 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)) fmt.Fprintf(&buf, "\t%s\n", modfetch.Sum(m))
} else { } else {
fmt.Fprintf(&buf, "\n=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r)) 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 { for _, mod := range mods {
writeEntry("dep", mod) writeEntry("dep", mod)
} }
@ -382,38 +383,13 @@ func PackageBuildInfo(path string, deps []string) string {
return buf.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. // findModule searches for the module that contains the package at path.
// If the package was loaded, its containing module and true are returned. // 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) { func findModule(ld *loader, path string) (module.Version, bool) {
if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok { if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok {
return pkg.mod, pkg.mod != module.Version{} return pkg.mod, pkg.mod != module.Version{}
} }
if path == "command-line-arguments" {
return Target, true
}
return module.Version{}, false return module.Version{}, false
} }

View file

@ -40,7 +40,7 @@ type Requirements struct {
depth modDepth depth modDepth
// rootModules is the set of module versions explicitly required by the main // 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. // contain multiple versions for a given module path.
rootModules []module.Version rootModules []module.Version
maxRootVersion map[string]string maxRootVersion map[string]string
@ -99,8 +99,8 @@ var requirements *Requirements
// *Requirements before any other method. // *Requirements before any other method.
func newRequirements(depth modDepth, rootModules []module.Version, direct map[string]bool) *Requirements { func newRequirements(depth modDepth, rootModules []module.Version, direct map[string]bool) *Requirements {
for i, m := range rootModules { for i, m := range rootModules {
if m == Target { if m.Version == "" && MainModules.Contains(m.Path) {
panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is Target", i)) panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is a main module", i))
} }
if m.Path == "" || m.Version == "" { if m.Path == "" || m.Version == "" {
panic(fmt.Sprintf("bad requirement: rootModules[%v] = %v", i, m)) 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) { func (rs *Requirements) initVendor(vendorList []module.Version) {
rs.graphOnce.Do(func() { rs.graphOnce.Do(func() {
mg := &ModuleGraph{ 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 { if rs.depth == lazy {
// The roots of a lazy module should already include every module in the // The roots of a lazy module should already include every module in the
// vendor list, because the vendored modules are the same as those // 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 // 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, // out”, like a more aggressive version of lazy loading: in vendor mode,
// the root requirements *are* the complete module graph. // the root requirements *are* the complete module graph.
mg.g.Require(Target, rs.rootModules) mg.g.Require(mainModule, rs.rootModules)
} else { } else {
// The transitive requirements of the main module are not in general available // 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 // 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 // graph, but still distinguishes between direct and indirect
// dependencies. // dependencies.
vendorMod := module.Version{Path: "vendor/modules.txt", Version: ""} 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) 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 // path, or the zero module.Version and ok=false if the module is not a root
// dependency. // dependency.
func (rs *Requirements) rootSelected(path string) (version string, ok bool) { func (rs *Requirements) rootSelected(path string) (version string, ok bool) {
if path == Target.Path { if MainModules.Contains(path) {
return Target.Version, true return "", true
} }
if v, ok := rs.maxRootVersion[path]; ok { if v, ok := rs.maxRootVersion[path]; ok {
return v, true return v, true
@ -197,7 +202,7 @@ func (rs *Requirements) rootSelected(path string) (version string, ok bool) {
// selection. // selection.
func (rs *Requirements) hasRedundantRoot() bool { func (rs *Requirements) hasRedundantRoot() bool {
for i, m := range rs.rootModules { 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 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 mu sync.Mutex // guards mg.g and hasError during loading
hasError bool hasError bool
mg = &ModuleGraph{ 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 ( var (
loadQueue = par.NewQueue(runtime.GOMAXPROCS(0)) loadQueue = par.NewQueue(runtime.GOMAXPROCS(0))
@ -404,10 +416,12 @@ func (mg *ModuleGraph) findError() error {
} }
func (mg *ModuleGraph) allRootsSelected() bool { func (mg *ModuleGraph) allRootsSelected() bool {
roots, _ := mg.g.RequiredBy(Target) for _, mm := range MainModules.Versions() {
for _, m := range roots { roots, _ := mg.g.RequiredBy(mm)
if mg.Selected(m.Path) != m.Version { for _, m := range roots {
return false if mg.Selected(m.Path) != m.Version {
return false
}
} }
} }
return true return true
@ -447,7 +461,7 @@ func LoadModGraph(ctx context.Context, goVersion string) *ModuleGraph {
base.Fatalf("go: %v", err) base.Fatalf("go: %v", err)
} }
commitRequirements(ctx, modFileGoVersion(), rs) commitRequirements(ctx, rs)
return mg return mg
} }
@ -513,7 +527,7 @@ func EditBuildList(ctx context.Context, add, mustSelect []module.Version) (chang
if err != nil { if err != nil {
return false, err return false, err
} }
commitRequirements(ctx, modFileGoVersion(), rs) commitRequirements(ctx, rs)
return changed, err return changed, err
} }
@ -546,10 +560,11 @@ type Conflict struct {
// both retain the same versions of all packages in pkgs and satisfy the // both retain the same versions of all packages in pkgs and satisfy the
// lazy loading invariants (if applicable). // lazy loading invariants (if applicable).
func tidyRoots(ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) { func tidyRoots(ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) {
mainModule := MainModules.mustGetSingleMainModule()
if rs.depth == eager { 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) { 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 // To ensure that the loading process eventually converges, the caller should
// add any needed roots from the tidy root set (without removing existing untidy // add any needed roots from the tidy root set (without removing existing untidy
// roots) until the set of roots has converged. // 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 ( var (
roots []module.Version 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". // 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 // 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 // graph so that we can update those roots to be consistent with other
// requirements. // requirements.
if cfg.BuildMod != "mod" { if mustHaveCompleteRequirements() {
// Our changes to the roots may have moved dependencies into or out of // Our changes to the roots may have moved dependencies into or out of
// the lazy-loading horizon, which could in turn change the selected // the lazy-loading horizon, which could in turn change the selected
// versions of other modules. (Unlike for eager modules, for lazy // 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)) roots = make([]module.Version, 0, len(rs.rootModules))
rootsUpgraded = false rootsUpgraded = false
inRootPaths := make(map[string]bool, len(rs.rootModules)+1) 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 { for _, m := range rs.rootModules {
if inRootPaths[m.Path] { if inRootPaths[m.Path] {
// This root specifies a redundant path. We already retained the // 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 // tidyEagerRoots returns a minimal set of root requirements that maintains the
// selected version of every module that provided a package in pkgs, and // 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. // 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 ( var (
keep []module.Version keep []module.Version
keptPath = map[string]bool{} 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 { if err != nil {
return nil, err return nil, err
} }
@ -1011,7 +1028,7 @@ func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requireme
return rs, err return rs, err
} }
if cfg.BuildMod != "mod" { if mustHaveCompleteRequirements() {
// Instead of actually updating the requirements, just check that no updates // Instead of actually updating the requirements, just check that no updates
// are needed. // are needed.
if rs == nil { 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, // 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 // the choice of explicit vs. implicit dependency has no impact on MVS
// selection (for itself or any other module). // selection (for itself or any other module).
keep := append(mg.BuildList()[1:], add...) keep := append(mg.BuildList()[MainModules.Len():], add...)
for _, m := range keep { for _, m := range keep {
if direct[m.Path] && !inRootPaths[m.Path] { if direct[m.Path] && !inRootPaths[m.Path] {
rootPaths = append(rootPaths, 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}) // TODO(matloob): Make roots into a map.
if err != nil { var roots []module.Version
return rs, err 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 // The root set is unchanged and rs was already eager, so keep rs to
// preserve its cached ModuleGraph (if any). // preserve its cached ModuleGraph (if any).
return rs, nil 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. // 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 { if err != nil {
return rs, err return rs, err
} }
return newRequirements(lazy, mg.BuildList()[1:], rs.direct), nil return newRequirements(lazy, mg.BuildList()[MainModules.Len():], rs.direct), nil
} }

View file

@ -75,7 +75,7 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
// We promote the modules in mustSelect to be explicit requirements. // We promote the modules in mustSelect to be explicit requirements.
var rootPaths []string var rootPaths []string
for _, m := range mustSelect { 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) 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 { if err != nil {
return nil, false, err return nil, false, err
} }
@ -218,8 +218,8 @@ func raiseLimitsForUpgrades(ctx context.Context, maxVersion map[string]string, d
eagerUpgrades = tryUpgrade eagerUpgrades = tryUpgrade
} else { } else {
for _, m := range tryUpgrade { for _, m := range tryUpgrade {
if m.Path == Target.Path { if MainModules.Contains(m.Path) {
// Target is already considered to be higher than any possible m, so we // 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 // won't be upgrading to it anyway and there is no point scanning its
// dependencies. // dependencies.
continue continue
@ -318,7 +318,7 @@ func selectPotentiallyImportedModules(ctx context.Context, limiter *versionLimit
mods = make([]module.Version, 0, len(limiter.selected)) mods = make([]module.Version, 0, len(limiter.selected))
for path, v := range 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}) 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)) mods = make([]module.Version, 0, len(limiter.selected))
for path, _ := range limiter.selected { for path, _ := range limiter.selected {
if path != Target.Path { if !MainModules.Contains(path) {
if v := mg.Selected(path); v != "none" { if v := mg.Selected(path); v != "none" {
mods = append(mods, module.Version{Path: path, Version: v}) 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 // itself lazy, its unrestricted dependencies are skipped when scanning
// requirements. // requirements.
func newVersionLimiter(depth modDepth, max map[string]string) *versionLimiter { 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{ return &versionLimiter{
depth: depth, depth: depth,
max: max, max: max,
selected: map[string]string{Target.Path: Target.Version}, selected: selected,
dqReason: map[module.Version]dqState{}, dqReason: map[module.Version]dqState{},
requiring: map[module.Version][]module.Version{}, 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 // as is feasible, we don't want to retain test dependencies that are only
// marginally relevant at best. // marginally relevant at best.
func (l *versionLimiter) check(m module.Version, depth modDepth) dqState { 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 // version "none" has no requirements, and the dependencies of Target are
// tautological. // tautological.
return dqState{} return dqState{}

View file

@ -32,6 +32,8 @@ type ImportMissingError struct {
Module module.Version Module module.Version
QueryErr error QueryErr error
ImportingMainModule module.Version
// isStd indicates whether we would expect to find the package in the standard // 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 // 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 // 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 { if e.QueryErr != nil {
return fmt.Sprintf("%s: %v", message, e.QueryErr) 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) 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? // Is the package in the standard library?
pathIsStd := search.IsStandardImportPath(path) pathIsStd := search.IsStandardImportPath(path)
if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) { if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
if targetInGorootSrc { for _, mainModule := range MainModules.Versions() {
if dir, ok, err := dirInModule(path, targetPrefix, ModRoot(), true); err != nil { if MainModules.InGorootSrc(mainModule) {
return module.Version{}, dir, err if dir, ok, err := dirInModule(path, MainModules.PathPrefix(mainModule), MainModules.ModRoot(mainModule), true); err != nil {
} else if ok { return module.Version{}, dir, err
return Target, dir, nil } else if ok {
return mainModule, dir, nil
}
} }
} }
dir := filepath.Join(cfg.GOROOT, "src", path) 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. // -mod=vendor is special.
// Everything must be in the main module or the main module's vendor directory. // Everything must be in the main module or the main module's vendor directory.
if cfg.BuildMod == "vendor" { if cfg.BuildMod == "vendor" {
mainDir, mainOK, mainErr := dirInModule(path, targetPrefix, ModRoot(), true) mainModule := MainModules.mustGetSingleMainModule()
vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(ModRoot(), "vendor"), false) 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 { if mainOK && vendorOK {
return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: []string{mainDir, vendorDir}} 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. // Note that we're not checking that the package exists.
// We'll leave that for load. // We'll leave that for load.
if !vendorOK && mainDir != "" { if !vendorOK && mainDir != "" {
return Target, mainDir, nil return mainModule, mainDir, nil
} }
if mainErr != nil { if mainErr != nil {
return module.Version{}, "", mainErr return module.Version{}, "", mainErr
} }
readVendorList() readVendorList(mainModule)
return vendorPkgModule[path], vendorDir, nil 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) { func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) {
// To avoid spurious remote fetches, try the latest replacement for each // To avoid spurious remote fetches, try the latest replacement for each
// module (golang.org/issue/26241). // module (golang.org/issue/26241).
if index != nil { var mods []module.Version
var mods []module.Version for _, v := range MainModules.Versions() {
for mp, mv := range index.highestReplaced { if index := MainModules.Index(v); index != nil {
if !maybeInModule(path, mp) { for mp, mv := range index.highestReplaced {
continue 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")
} }
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. // Every module path in mods is a prefix of the import path.
// As in QueryPattern, prefer the longest prefix that satisfies the import. // As in QueryPattern, prefer the longest prefix that satisfies the import.
sort.Slice(mods, func(i, j int) bool { sort.Slice(mods, func(i, j int) bool {
return len(mods[i].Path) > len(mods[j].Path) return len(mods[i].Path) > len(mods[j].Path)
}) })
for _, m := range mods { for _, m := range mods {
needSum := true needSum := true
root, isLocal, err := fetch(ctx, m, needSum) root, isLocal, err := fetch(ctx, m, needSum)
if err != nil { if err != nil {
if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) { if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
return module.Version{}, &ImportMissingSumError{importPath: path} 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
} }
return module.Version{}, err
} }
if len(mods) > 0 && module.CheckPath(path) != nil { if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
// The package path is not valid to fetch remotely, return m, err
// so it can only exist in a replaced module, } else if ok {
// and we know from the above loop that it is not. if cfg.BuildMod == "readonly" {
return module.Version{}, &PackageNotInModuleError{ return module.Version{}, &ImportMissingError{Path: path, replaced: m}
Mod: mods[0],
Query: "latest",
Pattern: path,
Replacement: Replacement(mods[0]),
} }
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, // The isLocal return value reports whether the replacement,
// if any, is local to the filesystem. // if any, is local to the filesystem.
func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, isLocal bool, err error) { func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, isLocal bool, err error) {
if mod == Target { if modRoot := MainModules.ModRoot(mod); modRoot != "" {
return ModRoot(), true, nil return modRoot, true, nil
} }
if r := Replacement(mod); r.Path != "" { if r, replacedFrom := Replacement(mod); r.Path != "" {
if r.Version == "" { if r.Version == "" {
dir = r.Path dir = r.Path
if !filepath.IsAbs(dir) { if !filepath.IsAbs(dir) {
dir = filepath.Join(ModRoot(), dir) dir = filepath.Join(replacedFrom, dir)
} }
// Ensure that the replacement directory actually exists: // Ensure that the replacement directory actually exists:
// dirInModule does not report errors for missing modules, // 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 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{}) return "", false, module.VersionError(mod, &sumMissingError{})
} }

File diff suppressed because it is too large Load diff

View file

@ -72,14 +72,18 @@ func ListModules(ctx context.Context, args []string, mode ListMode) ([]*modinfo.
} }
if err == nil { if err == nil {
commitRequirements(ctx, modFileGoVersion(), rs) commitRequirements(ctx, rs)
} }
return mods, err return mods, err
} }
func listModules(ctx context.Context, rs *Requirements, args []string, mode ListMode) (_ *Requirements, mods []*modinfo.ModulePublic, mgErr error) { func listModules(ctx context.Context, rs *Requirements, args []string, mode ListMode) (_ *Requirements, mods []*modinfo.ModulePublic, mgErr error) {
if len(args) == 0 { 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 needFullGraph := false

View file

@ -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 // If we're outside of a module, ensure that the failure mode
// indicates that. // indicates that.
ModRoot() if !HasModRoot() {
die()
}
if ld != nil { if ld != nil {
m.AddError(err) 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. // The initial roots are the packages in the main module.
// loadFromRoots will expand that to "all". // loadFromRoots will expand that to "all".
m.Errs = m.Errs[:0] m.Errs = m.Errs[:0]
matchPackages(ctx, m, opts.Tags, omitStd, []module.Version{Target}) matchPackages(ctx, m, opts.Tags, omitStd, MainModules.Versions())
} else { } else {
// Starting with the packages in the main module, // Starting with the packages in the main module,
// enumerate the full list of "all". // 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 // loaded.requirements, but here we may have also loaded (and want to
// preserve checksums for) additional entities from compatRS, which are // preserve checksums for) additional entities from compatRS, which are
// only needed for compatibility with ld.TidyCompatibleVersion. // 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. // Success! Update go.mod and go.sum (if needed) and return the results.
loaded = ld loaded = ld
commitRequirements(ctx, loaded.GoVersion, loaded.requirements) commitRequirements(ctx, loaded.requirements)
for _, pkg := range ld.pkgs { for _, pkg := range ld.pkgs {
if !pkg.isTest() { if !pkg.isTest() {
@ -436,14 +446,23 @@ func matchLocalDirs(ctx context.Context, m *search.Match, rs *Requirements) {
if !filepath.IsAbs(dir) { if !filepath.IsAbs(dir) {
absDir = filepath.Join(base.Cwd(), 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.Dirs = []string{}
m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir))) m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir)))
return return
} }
} }
m.MatchDirs() m.MatchDirs(modRoots)
} }
// resolveLocalPackage resolves a filesystem path to a package path. // 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 { for _, mod := range MainModules.Versions() {
if absDir == cfg.GOROOTsrc { modRoot := MainModules.ModRoot(mod)
return "", errPkgIsGorootSrc 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 // Note: The checks for @ here are just to avoid misinterpreting
// the module cache directories (formerly GOPATH/src/mod/foo@v1.5.2/bar). // the module cache directories (formerly GOPATH/src/mod/foo@v1.5.2/bar).
// It's not strictly necessary but helpful to keep the checks. // 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):], "@") { var pkgNotFoundErr error
suffix := filepath.ToSlash(absDir[len(modRoot):]) pkgNotFoundLongestPrefix := ""
if strings.HasPrefix(suffix, "/vendor/") { for _, mainModule := range MainModules.Versions() {
if cfg.BuildMod != "vendor" { modRoot := MainModules.ModRoot(mainModule)
return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir) 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() mainModulePrefix := MainModules.PathPrefix(mainModule)
pkg := strings.TrimPrefix(suffix, "/vendor/") if mainModulePrefix == "" {
if _, ok := vendorPkgModule[pkg]; !ok { pkg := strings.TrimPrefix(suffix, "/")
return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir) 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
}
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 return pkg, nil
} }
}
if targetPrefix == "" { if pkgNotFoundErr != nil {
pkg := strings.TrimPrefix(suffix, "/") return "", pkgNotFoundErr
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
}
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 sub := search.InDir(absDir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") { 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) { tryMod := func(m module.Version) (string, bool) {
var root string var root string
var err error var err error
if repl := Replacement(m); repl.Path != "" && repl.Version == "" { if repl, replModRoot := Replacement(m); repl.Path != "" && repl.Version == "" {
root = repl.Path root = repl.Path
if !filepath.IsAbs(root) { if !filepath.IsAbs(root) {
root = filepath.Join(ModRoot(), root) root = filepath.Join(replModRoot, root)
} }
} else if repl.Path != "" { } else if repl.Path != "" {
root, err = modfetch.DownloadDir(repl) root, err = modfetch.DownloadDir(repl)
@ -645,14 +684,14 @@ func ImportFromFiles(ctx context.Context, gofiles []string) {
return roots return roots
}, },
}) })
commitRequirements(ctx, loaded.GoVersion, loaded.requirements) commitRequirements(ctx, loaded.requirements)
} }
// DirImportPath returns the effective import path for dir, // DirImportPath returns the effective import path for dir,
// provided it is within the main module, or else returns ".". // provided it is within a main module, or else returns ".".
func DirImportPath(ctx context.Context, dir string) string { func (mms *MainModuleSet) DirImportPath(ctx context.Context, dir string) (path string, m module.Version) {
if !HasModRoot() { if !HasModRoot() {
return "." return ".", module.Version{}
} }
LoadModFile(ctx) // Sets targetPrefix. LoadModFile(ctx) // Sets targetPrefix.
@ -662,17 +701,32 @@ func DirImportPath(ctx context.Context, dir string) string {
dir = filepath.Clean(dir) dir = filepath.Clean(dir)
} }
if dir == modRoot { var longestPrefix string
return targetPrefix var longestPrefixPath string
} var longestPrefixVersion module.Version
if strings.HasPrefix(dir, modRoot+string(filepath.Separator)) { for _, v := range mms.Versions() {
suffix := filepath.ToSlash(dir[len(modRoot):]) modRoot := mms.ModRoot(v)
if strings.HasPrefix(suffix, "/vendor/") { if dir == modRoot {
return strings.TrimPrefix(suffix, "/vendor/") 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 // ImportMap returns the actual package import path
@ -894,10 +948,7 @@ func (pkg *loadPkg) fromExternalModule() bool {
if pkg.mod.Path == "" { if pkg.mod.Path == "" {
return false // loaded from the standard library, not a module return false // loaded from the standard library, not a module
} }
if pkg.mod.Path == Target.Path { return !MainModules.Contains(pkg.mod.Path)
return false // loaded from the main module.
}
return true
} }
var errMissing = errors.New("cannot find package") var errMissing = errors.New("cannot find package")
@ -915,7 +966,7 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
} }
if ld.GoVersion == "" { if ld.GoVersion == "" {
ld.GoVersion = modFileGoVersion() ld.GoVersion = MainModules.GoVersion()
if ld.Tidy && semver.Compare("v"+ld.GoVersion, "v"+LatestGoVersion()) > 0 { 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()) 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 { for _, pkg := range ld.pkgs {
if pkg.mod != Target { if pkg.mod.Version != "" || !MainModules.Contains(pkg.mod.Path) {
continue continue
} }
for _, dep := range pkg.imports { for _, dep := range pkg.imports {
@ -1327,6 +1378,15 @@ func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[mod
var err error var err error
mod, err = queryImport(ctx, pkg.path, ld.requirements) mod, err = queryImport(ctx, pkg.path, ld.requirements)
if err != nil { 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 // 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 // for pkg to either the original error or the one returned by
// queryImport. The existing error indicates only that we couldn't find // 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. // so it's ok if we call it more than is strictly necessary.
wantTest := false wantTest := false
switch { 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 // 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 // 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. // 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 { if wantTest {
var testFlags loadPkgFlags 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 // 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 // they cause the packages they import to also be in "all". So are tests
// of packages in "all" if "all" closes over test dependencies. // 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 == "" { if pkg.dir == "" {
return 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 // 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 // package that is *only* imported by other packages in "all" is always
// marked as such before loading its imports. // 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 str.HasPathPrefix(parentPath, "cmd") {
if !ld.VendorModulesInGOROOTSrc || Target.Path != "cmd" { if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("cmd") {
vendorPath := pathpkg.Join("cmd", "vendor", path) vendorPath := pathpkg.Join("cmd", "vendor", path)
if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil { if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
return vendorPath 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' // If we are outside of the 'std' module, resolve imports from within 'std'
// to the vendor directory. // to the vendor directory.
// //
@ -1753,7 +1814,7 @@ func (ld *loader) checkMultiplePaths() {
firstPath := map[module.Version]string{} firstPath := map[module.Version]string{}
for _, mod := range mods { for _, mod := range mods {
src := resolveReplacement(mod) src, _ := resolveReplacement(mod)
if prev, ok := firstPath[src]; !ok { if prev, ok := firstPath[src]; !ok {
firstPath[src] = mod.Path firstPath[src] = mod.Path
} else if prev != mod.Path { } else if prev != mod.Path {
@ -1781,7 +1842,7 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr)
goFlag := "" goFlag := ""
if ld.GoVersion != modFileGoVersion() { if ld.GoVersion != MainModules.GoVersion() {
goFlag = " -go=" + ld.GoVersion goFlag = " -go=" + ld.GoVersion
} }

View file

@ -56,11 +56,9 @@ const (
go117LazyTODO = false go117LazyTODO = false
) )
var modFile *modfile.File
// modFileGoVersion returns the (non-empty) Go version at which the requirements // modFileGoVersion returns the (non-empty) Go version at which the requirements
// in modFile are intepreted, or the latest Go version if modFile is nil. // in modFile are interpreted, or the latest Go version if modFile is nil.
func modFileGoVersion() string { func modFileGoVersion(modFile *modfile.File) string {
if modFile == nil { if modFile == nil {
return LatestGoVersion() return LatestGoVersion()
} }
@ -92,9 +90,6 @@ type modFileIndex struct {
exclude map[module.Version]bool 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 { type requireMeta struct {
indirect bool indirect bool
} }
@ -137,8 +132,10 @@ var ErrDisallowed = errors.New("disallowed module version")
// CheckExclusions returns an error equivalent to ErrDisallowed if module m is // CheckExclusions returns an error equivalent to ErrDisallowed if module m is
// excluded by the main module's go.mod file. // excluded by the main module's go.mod file.
func CheckExclusions(ctx context.Context, m module.Version) error { func CheckExclusions(ctx context.Context, m module.Version) error {
if index != nil && index.exclude[m] { for _, mainModule := range MainModules.Versions() {
return module.VersionError(m, errExcluded) if index := MainModules.Index(mainModule); index != nil && index.exclude[m] {
return module.VersionError(m, errExcluded)
}
} }
return nil return nil
} }
@ -170,7 +167,7 @@ func CheckRetractions(ctx context.Context, m module.Version) (err error) {
// Cannot be retracted. // Cannot be retracted.
return nil 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. // All versions of the module were replaced.
// Don't load retractions, since we'd just load the replacement. // Don't load retractions, since we'd just load the replacement.
return nil 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 // 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. // path that we expect if the module or its repository was renamed.
// We still want to apply retractions to other aliases of the module. // 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 { if err != nil {
return err return err
} }
summary, err := rawGoModSummary(rm) summary, err := rawGoModSummary(rm, replacedFrom)
if err != nil { if err != nil {
return err return err
} }
@ -289,51 +286,72 @@ func CheckDeprecation(ctx context.Context, m module.Version) (deprecation string
// Don't look up deprecation. // Don't look up deprecation.
return "", nil 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. // All versions of the module were replaced.
// We'll look up deprecation separately for the replacement. // We'll look up deprecation separately for the replacement.
return "", nil return "", nil
} }
latest, err := queryLatestVersionIgnoringRetractions(ctx, m.Path) latest, replacedFrom, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
if err != nil { if err != nil {
return "", err return "", err
} }
summary, err := rawGoModSummary(latest) summary, err := rawGoModSummary(latest, replacedFrom)
if err != nil { if err != nil {
return "", err return "", err
} }
return summary.deprecated, nil 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 // If there is no replacement for mod, Replacement returns
// a module.Version with Path == "". // a module.Version with Path == "".
func Replacement(mod module.Version) module.Version { func Replacement(mod module.Version) (module.Version, string) {
if index != nil { _ = TODOWorkspaces("Support replaces in the go.work file.")
if r, ok := index.replace[mod]; ok { foundFrom, found, foundModRoot := "", module.Version{}, ""
return r for _, v := range MainModules.Versions() {
} if index := MainModules.Index(v); index != nil {
if r, ok := index.replace[module.Version{Path: mod.Path}]; ok { if from, r, ok := replacement(mod, index); ok {
return r 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 // 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). // for m: either m itself, or the replacement for m (iff m is replaced).
func resolveReplacement(m module.Version) module.Version { // It also returns the modroot of the module providing the replacement if
if r := Replacement(m); r.Path != "" { // one was found.
return r 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. // indexModFile rebuilds the index of modFile.
// If modFile has been changed since it was first read, // If modFile has been changed since it was first read,
// modFile.Cleanup must be called before indexModFile. // 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 := new(modFileIndex)
i.data = data i.data = data
i.dataNeedsFix = needsFix i.dataNeedsFix = needsFix
@ -345,12 +363,12 @@ func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileInd
i.goVersionV = "" i.goVersionV = ""
if modFile.Go == nil { if modFile.Go == nil {
rawGoVersion.Store(Target, "") rawGoVersion.Store(mod, "")
} else { } else {
// We're going to use the semver package to compare Go versions, so go ahead // 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. // and add the "v" prefix it expects once instead of every time.
i.goVersionV = "v" + modFile.Go.Version 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)) 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. // The caller must not modify the returned summary.
func goModSummary(m module.Version) (*modFileSummary, error) { func goModSummary(m module.Version) (*modFileSummary, error) {
if m == Target { if m.Version == "" && MainModules.Contains(m.Path) {
panic("internal error: goModSummary called on the Target module") panic("internal error: goModSummary called on a main module")
} }
if cfg.BuildMod == "vendor" { if cfg.BuildMod == "vendor" {
@ -506,7 +524,7 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
// For every module other than the target, // For every module other than the target,
// return the full list of modules from modules.txt. // 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, // We don't know what versions the vendored module actually relies on,
// so assume that it requires everything. // so assume that it requires everything.
@ -514,15 +532,15 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
return summary, nil return summary, nil
} }
actual := resolveReplacement(m) actual, replacedFrom := resolveReplacement(m)
if HasModRoot() && cfg.BuildMod == "readonly" && actual.Version != "" { if HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() && actual.Version != "" {
key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"} key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
if !modfetch.HaveSum(key) { if !modfetch.HaveSum(key) {
suggestion := fmt.Sprintf("; to add it:\n\tgo mod download %s", m.Path) suggestion := fmt.Sprintf("; to add it:\n\tgo mod download %s", m.Path)
return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion}) return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
} }
} }
summary, err := rawGoModSummary(actual) summary, err := rawGoModSummary(actual, replacedFrom)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -553,27 +571,29 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
} }
} }
if index != nil && len(index.exclude) > 0 { for _, mainModule := range MainModules.Versions() {
// Drop any requirements on excluded versions. if index := MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 {
// Don't modify the cached summary though, since we might need the raw // Drop any requirements on excluded versions.
// summary separately. // Don't modify the cached summary though, since we might need the raw
haveExcludedReqs := false // summary separately.
for _, r := range summary.require { haveExcludedReqs := false
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 { for _, r := range summary.require {
if !index.exclude[r] { if index.exclude[r] {
s.require = append(s.require, r) haveExcludedReqs = true
break
} }
} }
summary = s 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
}
} }
} }
return summary, nil return summary, nil
@ -584,18 +604,23 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
// its dependencies. // its dependencies.
// //
// rawGoModSummary cannot be used on the Target module. // 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") panic("internal error: rawGoModSummary called on the Target module")
} }
type key struct {
m module.Version
replacedFrom string
}
type cached struct { type cached struct {
summary *modFileSummary summary *modFileSummary
err error err error
} }
c := rawGoModSummaryCache.Do(m, func() interface{} { c := rawGoModSummaryCache.Do(key{m, replacedFrom}, func() interface{} {
summary := new(modFileSummary) summary := new(modFileSummary)
name, data, err := rawGoModData(m) name, data, err := rawGoModData(m, replacedFrom)
if err != nil { if err != nil {
return cached{nil, err} 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. // Unlike rawGoModSummary, rawGoModData does not cache its results in memory.
// Use rawGoModSummary instead unless you specifically need these bytes. // 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 == "" { if m.Version == "" {
// m is a replacement module with only a file path. // m is a replacement module with only a file path.
dir := m.Path dir := m.Path
if !filepath.IsAbs(dir) { 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") name = filepath.Join(dir, "go.mod")
if gomodActual, ok := fsys.OverlayPath(name); ok { 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, // If the queried latest version is replaced,
// queryLatestVersionIgnoringRetractions returns the replacement. // 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 { type entry struct {
latest module.Version latest module.Version
err error replacedFrom string // if latest is a replacement
err error
} }
e := latestVersionIgnoringRetractionsCache.Do(path, func() interface{} { e := latestVersionIgnoringRetractionsCache.Do(path, func() interface{} {
ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path) ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
defer span.Done() 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. // All versions of the module were replaced.
// No need to query. // No need to query.
return &entry{latest: repl} return &entry{latest: repl, replacedFrom: replFrom}
} }
// Find the latest version of the module. // Find the latest version of the module.
@ -709,12 +738,12 @@ func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (la
return &entry{err: err} return &entry{err: err}
} }
latest := module.Version{Path: path, Version: rev.Version} latest := module.Version{Path: path, Version: rev.Version}
if repl := resolveReplacement(latest); repl.Path != "" { if repl, replFrom := resolveReplacement(latest); repl.Path != "" {
latest = repl latest, replacedFrom = repl, replFrom
} }
return &entry{latest: latest} return &entry{latest: latest, replacedFrom: replacedFrom}
}).(*entry) }).(*entry)
return e.latest, e.err return e.latest, e.replacedFrom, e.err
} }
var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersionIgnoringRetractions result var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersionIgnoringRetractions result

View file

@ -42,7 +42,7 @@ type mvsReqs struct {
} }
func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) { 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 // Use the build list as it existed when r was constructed, not the current
// global build list. // global build list.
return r.roots, nil 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) { func previousVersion(m module.Version) (module.Version, error) {
// TODO(golang.org/issue/38714): thread tracing context through MVS. // 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 return module.Version{Path: m.Path, Version: "none"}, nil
} }

View file

@ -110,11 +110,12 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
allowed = func(context.Context, module.Version) error { return nil } allowed = func(context.Context, module.Version) error { return nil }
} }
if path == Target.Path && (query == "upgrade" || query == "patch") { if MainModules.Contains(path) && (query == "upgrade" || query == "patch") {
if err := allowed(ctx, Target); err != nil { 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 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" { 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) pkgMods, modOnly, err := QueryPattern(ctx, pattern, query, current, allowed)
if len(pkgMods) == 0 && err == nil { if len(pkgMods) == 0 && err == nil {
replacement, _ := Replacement(modOnly.Mod)
return nil, &PackageNotInModuleError{ return nil, &PackageNotInModuleError{
Mod: modOnly.Mod, Mod: modOnly.Mod,
Replacement: Replacement(modOnly.Mod), Replacement: replacement,
Query: query, Query: query,
Pattern: pattern, Pattern: pattern,
} }
@ -551,7 +553,7 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
return m.Errs[0] 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) matchPattern := search.MatchPattern(pattern)
if i := strings.Index(pattern, "..."); i >= 0 { if i := strings.Index(pattern, "..."); i >= 0 {
@ -559,30 +561,32 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
if base == "." { if base == "." {
return nil, nil, &WildcardInFirstElementError{Pattern: pattern, Query: query} 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) m := search.NewMatch(pattern)
matchPackages(ctx, m, imports.AnyTags(), omitStd, []module.Version{mod}) matchPackages(ctx, m, imports.AnyTags(), omitStd, []module.Version{mod})
return m return m
} }
} else { } 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) m := search.NewMatch(pattern)
prefix := mod.Path prefix := mod.Path
if mod == Target { if MainModules.Contains(mod.Path) {
prefix = targetPrefix prefix = MainModules.PathPrefix(module.Version{Path: mod.Path})
} }
if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil { for _, root := range roots {
m.AddError(err) if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil {
} else if ok { m.AddError(err)
m.Pkgs = []string{pattern} } else if ok {
m.Pkgs = []string{pattern}
}
} }
return m return m
} }
} }
var queryMatchesMainModule bool var mainModuleMatches []module.Version
if HasModRoot() { for _, mainModule := range MainModules.Versions() {
m := match(Target, modRoot, true) m := match(mainModule, modRoots, true)
if len(m.Pkgs) > 0 { if len(m.Pkgs) > 0 {
if query != "upgrade" && query != "patch" { if query != "upgrade" && query != "patch" {
return nil, nil, &QueryMatchesPackagesInMainModuleError{ return nil, nil, &QueryMatchesPackagesInMainModuleError{
@ -591,12 +595,12 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
Packages: m.Pkgs, Packages: m.Pkgs,
} }
} }
if err := allowed(ctx, Target); err != nil { 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, Target.Path, err) 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{{ return []QueryResult{{
Mod: Target, Mod: mainModule,
Rev: &modfetch.RevInfo{Version: Target.Version}, Rev: &modfetch.RevInfo{Version: mainModule.Version},
Packages: m.Pkgs, Packages: m.Pkgs,
}}, nil, nil }}, nil, nil
} }
@ -604,15 +608,17 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
return nil, nil, err return nil, nil, err
} }
if matchPattern(Target.Path) { var matchesMainModule bool
queryMatchesMainModule = true if matchPattern(mainModule.Path) {
mainModuleMatches = append(mainModuleMatches, mainModule)
matchesMainModule = true
} }
if (query == "upgrade" || query == "patch") && queryMatchesMainModule { if (query == "upgrade" || query == "patch") && matchesMainModule {
if err := allowed(ctx, Target); err == nil { if err := allowed(ctx, mainModule); err == nil {
modOnly = &QueryResult{ modOnly = &QueryResult{
Mod: Target, Mod: mainModule,
Rev: &modfetch.RevInfo{Version: Target.Version}, 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 len(candidateModules) == 0 {
if modOnly != nil { if modOnly != nil {
return nil, modOnly, nil return nil, modOnly, nil
} else if queryMatchesMainModule { } else if len(mainModuleMatches) != 0 {
return nil, nil, &QueryMatchesMainModuleError{ return nil, nil, &QueryMatchesMainModulesError{
Pattern: pattern, MainModules: mainModuleMatches,
Query: query, Pattern: pattern,
Query: query,
} }
} else { } else {
return nil, nil, &PackageNotInModuleError{ return nil, nil, &PackageNotInModuleError{
Mod: Target, MainModules: mainModuleMatches,
Query: query, Query: query,
Pattern: pattern, Pattern: pattern,
} }
} }
} }
@ -656,15 +663,16 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
if err != nil { if err != nil {
return r, err return r, err
} }
m := match(r.Mod, root, isLocal) m := match(r.Mod, []string{root}, isLocal)
r.Packages = m.Pkgs r.Packages = m.Pkgs
if len(r.Packages) == 0 && !matchPattern(path) { if len(r.Packages) == 0 && !matchPattern(path) {
if err := firstError(m); err != nil { if err := firstError(m); err != nil {
return r, err return r, err
} }
replacement, _ := Replacement(r.Mod)
return r, &PackageNotInModuleError{ return r, &PackageNotInModuleError{
Mod: r.Mod, Mod: r.Mod,
Replacement: Replacement(r.Mod), Replacement: replacement,
Query: query, Query: query,
Pattern: pattern, Pattern: pattern,
} }
@ -684,8 +692,8 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
return err return err
}) })
if queryMatchesMainModule && len(results) == 0 && modOnly == nil && errors.Is(err, fs.ErrNotExist) { if len(mainModuleMatches) > 0 && len(results) == 0 && modOnly == nil && errors.Is(err, fs.ErrNotExist) {
return nil, nil, &QueryMatchesMainModuleError{ return nil, nil, &QueryMatchesMainModulesError{
Pattern: pattern, Pattern: pattern,
Query: query, Query: query,
} }
@ -701,8 +709,13 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
func modulePrefixesExcludingTarget(path string) []string { func modulePrefixesExcludingTarget(path string) []string {
prefixes := make([]string, 0, strings.Count(path, "/")+1) prefixes := make([]string, 0, strings.Count(path, "/")+1)
mainModulePrefixes := make(map[string]bool)
for _, m := range MainModules.Versions() {
mainModulePrefixes[m.Path] = true
}
for { for {
if path != targetPrefix { if !mainModulePrefixes[path] {
if _, _, ok := module.SplitPathVersion(path); ok { if _, _, ok := module.SplitPathVersion(path); ok {
prefixes = append(prefixes, path) prefixes = append(prefixes, path)
} }
@ -759,7 +772,7 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod
case *PackageNotInModuleError: case *PackageNotInModuleError:
// Given the option, prefer to attribute “package not in module” // Given the option, prefer to attribute “package not in module”
// to modules other than the main one. // to modules other than the main one.
if noPackage == nil || noPackage.Mod == Target { if noPackage == nil || MainModules.Contains(noPackage.Mod.Path) {
noPackage = rErr noPackage = rErr
} }
case *NoMatchingVersionError: 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 // code for the versions it knows about, and thus did not have the opportunity
// to return a non-400 status code to suppress fallback. // to return a non-400 status code to suppress fallback.
type PackageNotInModuleError struct { type PackageNotInModuleError struct {
MainModules []module.Version
Mod module.Version Mod module.Version
Replacement module.Version Replacement module.Version
Query string Query string
@ -885,11 +899,15 @@ type PackageNotInModuleError struct {
} }
func (e *PackageNotInModuleError) Error() string { func (e *PackageNotInModuleError) Error() string {
if e.Mod == Target { if len(e.MainModules) > 0 {
if strings.Contains(e.Pattern, "...") { prefix := "workspace modules do"
return fmt.Sprintf("main module (%s) does not contain packages matching %s", Target.Path, e.Pattern) if len(e.MainModules) == 1 {
prefix = fmt.Sprintf("main module (%s) does", e.MainModules[0])
} }
return fmt.Sprintf("main module (%s) does not contain package %s", Target.Path, e.Pattern) if strings.Contains(e.Pattern, "...") {
return fmt.Sprintf("%s not contain packages matching %s", prefix, e.Pattern)
}
return fmt.Sprintf("%s not contain package %s", prefix, e.Pattern)
} }
found := "" 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 // we don't need to verify it in go.sum. This makes 'go list -m -u' faster
// and simpler. // and simpler.
func versionHasGoMod(_ context.Context, m module.Version) (bool, error) { func versionHasGoMod(_ context.Context, m module.Version) (bool, error) {
_, data, err := rawGoModData(m) _, data, err := rawGoModData(m, "")
if err != nil { if err != nil {
return false, err return false, err
} }
@ -978,14 +996,18 @@ func lookupRepo(proxy, path string) (repo versionRepo, err error) {
repo = emptyRepo{path: path, err: err} repo = emptyRepo{path: path, err: err}
} }
if index == nil { // TODO(#45713): Join all the highestReplaced fields into a single value.
return repo, err for _, mm := range MainModules.Versions() {
} index := MainModules.Index(mm)
if _, ok := index.highestReplaced[path]; !ok { if index == nil {
return repo, err 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. // An emptyRepo is a versionRepo that contains no versions.
@ -1024,11 +1046,13 @@ func (rr *replacementRepo) Versions(prefix string) ([]string, error) {
} }
versions := repoVersions versions := repoVersions
if index != nil && len(index.replace) > 0 { for _, mm := range MainModules.Versions() {
path := rr.ModulePath() if index := MainModules.Index(mm); index != nil && len(index.replace) > 0 {
for m, _ := range index.replace { path := rr.ModulePath()
if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) { for m, _ := range index.replace {
versions = append(versions, m.Version) 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) { func (rr *replacementRepo) Stat(rev string) (*modfetch.RevInfo, error) {
info, err := rr.repo.Stat(rev) 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 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 info, err
} }
return rr.replacementStat(v) return rr.replacementStat(v)
@ -1073,27 +1106,42 @@ func (rr *replacementRepo) Stat(rev string) (*modfetch.RevInfo, error) {
func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) { func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) {
info, err := rr.repo.Latest() info, err := rr.repo.Latest()
path := rr.ModulePath()
if index != nil { highestReplaced, found := "", false
path := rr.ModulePath() for _, mm := range MainModules.Versions() {
if v, ok := index.highestReplaced[path]; ok { if index := MainModules.Index(mm); index != nil {
if v == "" { if v, ok := index.highestReplaced[path]; ok {
// The only replacement is a wildcard that doesn't specify a version, so if !found {
// synthesize a pseudo-version with an appropriate major version and a highestReplaced, found = v, true
// timestamp below any real timestamp. That way, if the main module is continue
// used from within some other module, the user will be able to upgrade }
// the requirement to any real version they choose. if semver.Compare(v, highestReplaced) > 0 {
if _, pathMajor, ok := module.SplitPathVersion(path); ok && len(pathMajor) > 0 { highestReplaced = v
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 { if found {
return rr.replacementStat(v) 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 return info, err
@ -1108,20 +1156,46 @@ func (rr *replacementRepo) replacementStat(v string) (*modfetch.RevInfo, error)
return rev, nil 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. // a version of the main module that cannot be satisfied.
// (The main module's version cannot be changed.) // (The main module's version cannot be changed.)
type QueryMatchesMainModuleError struct { type QueryMatchesMainModulesError struct {
Pattern string MainModules []module.Version
Query string Pattern string
Query string
} }
func (e *QueryMatchesMainModuleError) Error() string { func (e *QueryMatchesMainModulesError) Error() string {
if e.Pattern == Target.Path { 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 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 // A QueryMatchesPackagesInMainModuleError indicates that a query cannot be

View file

@ -131,9 +131,10 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
} }
if cfg.BuildMod == "vendor" { if cfg.BuildMod == "vendor" {
if HasModRoot() { mod := MainModules.mustGetSingleMainModule()
walkPkgs(ModRoot(), targetPrefix, pruneGoMod|pruneVendor) if modRoot := MainModules.ModRoot(mod); modRoot != "" {
walkPkgs(filepath.Join(ModRoot(), "vendor"), "", pruneVendor) walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
} }
return return
} }
@ -147,12 +148,12 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
root, modPrefix string root, modPrefix string
isLocal bool isLocal bool
) )
if mod == Target { if MainModules.Contains(mod.Path) {
if !HasModRoot() { if MainModules.ModRoot(mod) == "" {
continue // If there is no main module, we can't search in it. continue // If there is no main module, we can't search in it.
} }
root = ModRoot() root = MainModules.ModRoot(mod)
modPrefix = targetPrefix modPrefix = MainModules.PathPrefix(mod)
isLocal = true isLocal = true
} else { } else {
var err error var err error

View file

@ -15,6 +15,7 @@ import (
"cmd/go/internal/base" "cmd/go/internal/base"
"golang.org/x/mod/modfile"
"golang.org/x/mod/module" "golang.org/x/mod/module"
"golang.org/x/mod/semver" "golang.org/x/mod/semver"
) )
@ -35,13 +36,13 @@ type vendorMetadata struct {
} }
// readVendorList reads the list of vendored modules from vendor/modules.txt. // readVendorList reads the list of vendored modules from vendor/modules.txt.
func readVendorList() { func readVendorList(mainModule module.Version) {
vendorOnce.Do(func() { vendorOnce.Do(func() {
vendorList = nil vendorList = nil
vendorPkgModule = make(map[string]module.Version) vendorPkgModule = make(map[string]module.Version)
vendorVersion = make(map[string]string) vendorVersion = make(map[string]string)
vendorMeta = make(map[module.Version]vendorMetadata) 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 err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
base.Fatalf("go: %s", err) base.Fatalf("go: %s", err)
@ -134,8 +135,8 @@ func readVendorList() {
// checkVendorConsistency verifies that the vendor/modules.txt file matches (if // 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 // 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. // requirements and replacements listed in the main module's go.mod file.
func checkVendorConsistency() { func checkVendorConsistency(index *modFileIndex, modFile *modfile.File) {
readVendorList() readVendorList(MainModules.mustGetSingleMainModule())
pre114 := false pre114 := false
if semver.Compare(index.goVersionV, "v1.14") < 0 { if semver.Compare(index.goVersionV, "v1.14") < 0 {
@ -208,7 +209,7 @@ func checkVendorConsistency() {
} }
for _, mod := range vendorReplaced { for _, mod := range vendorReplaced {
r := Replacement(mod) r, _ := Replacement(mod)
if r == (module.Version{}) { if r == (module.Version{}) {
vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in go.mod") vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in go.mod")
continue continue
@ -219,6 +220,7 @@ func checkVendorConsistency() {
} }
if vendErrors.Len() > 0 { 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) 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)
} }
} }

View file

@ -8,6 +8,7 @@ package mvs
import ( import (
"fmt" "fmt"
"reflect"
"sort" "sort"
"sync" "sync"
@ -85,11 +86,11 @@ type DowngradeReqs interface {
// of the list are sorted by path. // of the list are sorted by path.
// //
// See https://research.swtch.com/vgo-mvs for details. // See https://research.swtch.com/vgo-mvs for details.
func BuildList(target module.Version, reqs Reqs) ([]module.Version, error) { func BuildList(targets []module.Version, reqs Reqs) ([]module.Version, error) {
return buildList(target, reqs, nil) return buildList(targets, reqs, nil)
} }
func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) (module.Version, error)) ([]module.Version, error) { func buildList(targets []module.Version, reqs Reqs, upgrade func(module.Version) (module.Version, error)) ([]module.Version, error) {
cmp := func(v1, v2 string) int { cmp := func(v1, v2 string) int {
if reqs.Max(v1, v2) != v1 { if reqs.Max(v1, v2) != v1 {
return -1 return -1
@ -102,7 +103,7 @@ func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) (m
var ( var (
mu sync.Mutex mu sync.Mutex
g = NewGraph(cmp, []module.Version{target}) g = NewGraph(cmp, targets)
upgrades = map[module.Version]module.Version{} upgrades = map[module.Version]module.Version{}
errs = map[module.Version]error{} // (non-nil errors only) errs = map[module.Version]error{} // (non-nil errors only)
) )
@ -110,7 +111,9 @@ func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) (m
// Explore work graph in parallel in case reqs.Required // Explore work graph in parallel in case reqs.Required
// does high-latency network operations. // does high-latency network operations.
var work par.Work var work par.Work
work.Add(target) for _, target := range targets {
work.Add(target)
}
work.Do(10, func(item interface{}) { work.Do(10, func(item interface{}) {
m := item.(module.Version) m := item.(module.Version)
@ -168,12 +171,12 @@ func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) (m
// The final list is the minimum version of each module found in the graph. // The final list is the minimum version of each module found in the graph.
list := g.BuildList() list := g.BuildList()
if v := list[0]; v != target { if vs := list[:len(targets)]; !reflect.DeepEqual(vs, targets) {
// target.Version will be "" for modload, the main client of MVS. // target.Version will be "" for modload, the main client of MVS.
// "" denotes the main module, which has no version. However, MVS treats // "" denotes the main module, which has no version. However, MVS treats
// version strings as opaque, so "" is not a special value here. // version strings as opaque, so "" is not a special value here.
// See golang.org/issue/31491, golang.org/issue/29773. // See golang.org/issue/31491, golang.org/issue/29773.
panic(fmt.Sprintf("mistake: chose version %q instead of target %+v", v, target)) panic(fmt.Sprintf("mistake: chose versions %+v instead of targets %+v", vs, targets))
} }
return list, nil return list, nil
} }
@ -181,8 +184,8 @@ func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) (m
// Req returns the minimal requirement list for the target module, // Req returns the minimal requirement list for the target module,
// with the constraint that all module paths listed in base must // with the constraint that all module paths listed in base must
// appear in the returned list. // appear in the returned list.
func Req(target module.Version, base []string, reqs Reqs) ([]module.Version, error) { func Req(mainModule module.Version, base []string, reqs Reqs) ([]module.Version, error) {
list, err := BuildList(target, reqs) list, err := BuildList([]module.Version{mainModule}, reqs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -194,7 +197,8 @@ func Req(target module.Version, base []string, reqs Reqs) ([]module.Version, err
// Compute postorder, cache requirements. // Compute postorder, cache requirements.
var postorder []module.Version var postorder []module.Version
reqCache := map[module.Version][]module.Version{} reqCache := map[module.Version][]module.Version{}
reqCache[target] = nil reqCache[mainModule] = nil
var walk func(module.Version) error var walk func(module.Version) error
walk = func(m module.Version) error { walk = func(m module.Version) error {
_, ok := reqCache[m] _, ok := reqCache[m]
@ -273,7 +277,7 @@ func Req(target module.Version, base []string, reqs Reqs) ([]module.Version, err
// UpgradeAll returns a build list for the target module // UpgradeAll returns a build list for the target module
// in which every module is upgraded to its latest version. // in which every module is upgraded to its latest version.
func UpgradeAll(target module.Version, reqs UpgradeReqs) ([]module.Version, error) { func UpgradeAll(target module.Version, reqs UpgradeReqs) ([]module.Version, error) {
return buildList(target, reqs, func(m module.Version) (module.Version, error) { return buildList([]module.Version{target}, reqs, func(m module.Version) (module.Version, error) {
if m.Path == target.Path { if m.Path == target.Path {
return target, nil return target, nil
} }
@ -308,7 +312,7 @@ func Upgrade(target module.Version, reqs UpgradeReqs, upgrade ...module.Version)
} }
} }
return buildList(target, &override{target, list, reqs}, func(m module.Version) (module.Version, error) { return buildList([]module.Version{target}, &override{target, list, reqs}, func(m module.Version) (module.Version, error) {
if v, ok := upgradeTo[m.Path]; ok { if v, ok := upgradeTo[m.Path]; ok {
return module.Version{Path: m.Path, Version: v}, nil return module.Version{Path: m.Path, Version: v}, nil
} }
@ -331,7 +335,7 @@ func Downgrade(target module.Version, reqs DowngradeReqs, downgrade ...module.Ve
// //
// In order to generate those new requirements, we need to identify versions // In order to generate those new requirements, we need to identify versions
// for every module in the build list — not just reqs.Required(target). // for every module in the build list — not just reqs.Required(target).
list, err := BuildList(target, reqs) list, err := BuildList([]module.Version{target}, reqs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -446,7 +450,7 @@ List:
// list with the actual versions of the downgraded modules as selected by MVS, // list with the actual versions of the downgraded modules as selected by MVS,
// instead of our initial downgrades. // instead of our initial downgrades.
// (See the downhiddenartifact and downhiddencross test cases). // (See the downhiddenartifact and downhiddencross test cases).
actual, err := BuildList(target, &override{ actual, err := BuildList([]module.Version{target}, &override{
target: target, target: target,
list: downgraded, list: downgraded,
Reqs: reqs, Reqs: reqs,
@ -466,7 +470,7 @@ List:
} }
} }
return BuildList(target, &override{ return BuildList([]module.Version{target}, &override{
target: target, target: target,
list: downgraded, list: downgraded,
Reqs: reqs, Reqs: reqs,

View file

@ -507,7 +507,7 @@ func Test(t *testing.T) {
t.Fatalf("build takes one argument: %q", line) t.Fatalf("build takes one argument: %q", line)
} }
fns = append(fns, func(t *testing.T) { fns = append(fns, func(t *testing.T) {
list, err := BuildList(m(kf[1]), reqs) list, err := BuildList([]module.Version{m(kf[1])}, reqs)
checkList(t, key, list, err, val) checkList(t, key, list, err, val)
}) })
continue continue

View file

@ -65,6 +65,7 @@ func init() {
CmdRun.Run = runRun // break init loop CmdRun.Run = runRun // break init loop
work.AddBuildFlags(CmdRun, work.DefaultBuildFlags) work.AddBuildFlags(CmdRun, work.DefaultBuildFlags)
base.AddWorkfileFlag(&CmdRun.Flag)
CmdRun.Flag.Var((*base.StringsFlag)(&work.ExecCmd), "exec", "") CmdRun.Flag.Var((*base.StringsFlag)(&work.ExecCmd), "exec", "")
} }
@ -73,6 +74,8 @@ func printStderr(args ...interface{}) (int, error) {
} }
func runRun(ctx context.Context, cmd *base.Command, args []string) { func runRun(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
if shouldUseOutsideModuleMode(args) { if shouldUseOutsideModuleMode(args) {
// Set global module flags for 'go run cmd@version'. // Set global module flags for 'go run cmd@version'.
// This must be done before modload.Init, but we need to call work.BuildInit // This must be done before modload.Init, but we need to call work.BuildInit

View file

@ -202,12 +202,6 @@ func (m *Match) MatchPackages() {
} }
} }
var modRoot string
func SetModRoot(dir string) {
modRoot = dir
}
// MatchDirs sets m.Dirs to a non-nil slice containing all directories that // MatchDirs sets m.Dirs to a non-nil slice containing all directories that
// potentially match a local pattern. The pattern must begin with an absolute // potentially match a local pattern. The pattern must begin with an absolute
// path, or "./", or "../". On Windows, the pattern may use slash or backslash // path, or "./", or "../". On Windows, the pattern may use slash or backslash
@ -215,7 +209,7 @@ func SetModRoot(dir string) {
// //
// If any errors may have caused the set of directories to be incomplete, // If any errors may have caused the set of directories to be incomplete,
// MatchDirs appends those errors to m.Errs. // MatchDirs appends those errors to m.Errs.
func (m *Match) MatchDirs() { func (m *Match) MatchDirs(modRoots []string) {
m.Dirs = []string{} m.Dirs = []string{}
if !m.IsLocal() { if !m.IsLocal() {
m.AddError(fmt.Errorf("internal error: MatchDirs: %s is not a valid filesystem pattern", m.pattern)) m.AddError(fmt.Errorf("internal error: MatchDirs: %s is not a valid filesystem pattern", m.pattern))
@ -253,15 +247,24 @@ func (m *Match) MatchDirs() {
// We need to preserve the ./ for pattern matching // We need to preserve the ./ for pattern matching
// and in the returned import paths. // and in the returned import paths.
if modRoot != "" { if len(modRoots) > 1 {
abs, err := filepath.Abs(dir) abs, err := filepath.Abs(dir)
if err != nil { if err != nil {
m.AddError(err) m.AddError(err)
return return
} }
if !hasFilepathPrefix(abs, modRoot) { var found bool
m.AddError(fmt.Errorf("directory %s is outside module root (%s)", abs, modRoot)) for _, modRoot := range modRoots {
return if modRoot != "" && hasFilepathPrefix(abs, modRoot) {
found = true
}
}
if !found {
plural := ""
if len(modRoots) > 1 {
plural = "s"
}
m.AddError(fmt.Errorf("directory %s is outside module root%s (%s)", abs, plural, strings.Join(modRoots, ", ")))
} }
} }
@ -424,19 +427,19 @@ func WarnUnmatched(matches []*Match) {
// ImportPaths returns the matching paths to use for the given command line. // ImportPaths returns the matching paths to use for the given command line.
// It calls ImportPathsQuiet and then WarnUnmatched. // It calls ImportPathsQuiet and then WarnUnmatched.
func ImportPaths(patterns []string) []*Match { func ImportPaths(patterns, modRoots []string) []*Match {
matches := ImportPathsQuiet(patterns) matches := ImportPathsQuiet(patterns, modRoots)
WarnUnmatched(matches) WarnUnmatched(matches)
return matches return matches
} }
// ImportPathsQuiet is like ImportPaths but does not warn about patterns with no matches. // ImportPathsQuiet is like ImportPaths but does not warn about patterns with no matches.
func ImportPathsQuiet(patterns []string) []*Match { func ImportPathsQuiet(patterns, modRoots []string) []*Match {
var out []*Match var out []*Match
for _, a := range CleanPatterns(patterns) { for _, a := range CleanPatterns(patterns) {
m := NewMatch(a) m := NewMatch(a)
if m.IsLocal() { if m.IsLocal() {
m.MatchDirs() m.MatchDirs(modRoots)
// Change the file import path to a regular import path if the package // Change the file import path to a regular import path if the package
// is in GOPATH or GOROOT. We don't report errors here; LoadImport // is in GOPATH or GOROOT. We don't report errors here; LoadImport

View file

@ -29,6 +29,7 @@ import (
"cmd/go/internal/cfg" "cmd/go/internal/cfg"
"cmd/go/internal/load" "cmd/go/internal/load"
"cmd/go/internal/lockedfile" "cmd/go/internal/lockedfile"
"cmd/go/internal/modload"
"cmd/go/internal/search" "cmd/go/internal/search"
"cmd/go/internal/trace" "cmd/go/internal/trace"
"cmd/go/internal/work" "cmd/go/internal/work"
@ -582,6 +583,7 @@ var defaultVetFlags = []string{
} }
func runTest(ctx context.Context, cmd *base.Command, args []string) { func runTest(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
pkgArgs, testArgs = testFlags(args) pkgArgs, testArgs = testFlags(args)
if cfg.DebugTrace != "" { if cfg.DebugTrace != "" {

View file

@ -28,6 +28,7 @@ import (
func init() { func init() {
work.AddBuildFlags(CmdTest, work.OmitVFlag) work.AddBuildFlags(CmdTest, work.OmitVFlag)
base.AddWorkfileFlag(&CmdTest.Flag)
cf := CmdTest.Flag cf := CmdTest.Flag
cf.BoolVar(&testC, "c", false, "") cf.BoolVar(&testC, "c", false, "")

View file

@ -121,6 +121,14 @@ and test commands:
directory, but it is not accessed. When -modfile is specified, an directory, but it is not accessed. When -modfile is specified, an
alternate go.sum file is also used: its path is derived from the alternate go.sum file is also used: its path is derived from the
-modfile flag by trimming the ".mod" extension and appending ".sum". -modfile flag by trimming the ".mod" extension and appending ".sum".
-workfile file
in module aware mode, use the given go.work file as a workspace file.
By default or when -workfile is "auto", the go command searches for a
file named go.work in the current directory and then containing directories
until one is found. If a valid go.work file is found, the modules
specified will collectively be used as the main modules. If -workfile
is "off", or a go.work file is not found in "auto" mode, workspace
mode is disabled.
-overlay file -overlay file
read a JSON config file that provides an overlay for build operations. read a JSON config file that provides an overlay for build operations.
The file is a JSON struct with a single field, named 'Replace', that The file is a JSON struct with a single field, named 'Replace', that
@ -201,6 +209,7 @@ func init() {
AddBuildFlags(CmdBuild, DefaultBuildFlags) AddBuildFlags(CmdBuild, DefaultBuildFlags)
AddBuildFlags(CmdInstall, DefaultBuildFlags) AddBuildFlags(CmdInstall, DefaultBuildFlags)
base.AddWorkfileFlag(&CmdBuild.Flag)
} }
// Note that flags consulted by other parts of the code // Note that flags consulted by other parts of the code
@ -364,6 +373,7 @@ var pkgsFilter = func(pkgs []*load.Package) []*load.Package { return pkgs }
var runtimeVersion = runtime.Version() var runtimeVersion = runtime.Version()
func runBuild(ctx context.Context, cmd *base.Command, args []string) { func runBuild(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
BuildInit() BuildInit()
var b Builder var b Builder
b.Init() b.Init()

View file

@ -0,0 +1,35 @@
# The command-line-arguments package does not belong to a module...
cd a
go list -f '{{.Module}}' ../b/b.go
stdout '^<nil>$'
# ... even if the arguments are sources from that module
go list -f '{{.Module}}' a.go
stdout '^<nil>$'
[short] skip
# check that the version of command-line-arguments doesn't include a module
go build -o a.exe a.go
go version -m a.exe
stdout '^\tpath\tcommand-line-arguments$'
stdout '^\tdep\ta\t\(devel\)\t$'
! stdout mod
-- a/go.mod --
module a
go 1.17
-- a/a.go --
package main
import "a/dep"
func main() {
dep.D()
}
-- a/dep/dep.go --
package dep
func D() {}
-- b/b.go --
package b

View file

@ -251,7 +251,7 @@ stdout 'using example.com/version v1.0.1'
# outside std. # outside std.
go run ./stdonly/stdonly.go go run ./stdonly/stdonly.go
stdout 'path is command-line-arguments$' stdout 'path is command-line-arguments$'
stdout 'main is command-line-arguments \(devel\)' stdout 'main is $'
# 'go generate' should work with file arguments. # 'go generate' should work with file arguments.
[exec:touch] go generate ./needmod/needmod.go [exec:touch] go generate ./needmod/needmod.go

View file

@ -78,7 +78,7 @@ cmp go.mod go.mod.orig
cmp go.sum go.sum.orig cmp go.sum go.sum.orig
# If the altnernate mod file does not have a ".mod" suffix, an error # If the alternate mod file does not have a ".mod" suffix, an error
# should be reported. # should be reported.
cp go.alt.mod goaltmod cp go.alt.mod goaltmod
! go mod tidy -modfile=goaltmod ! go mod tidy -modfile=goaltmod

View file

@ -28,6 +28,13 @@ go version -m fortune.exe
stdout '^\tpath\trsc.io/fortune' stdout '^\tpath\trsc.io/fortune'
stdout '^\tmod\trsc.io/fortune\tv1.0.0' stdout '^\tmod\trsc.io/fortune\tv1.0.0'
# Check the build info of a binary built from $GOROOT/src/cmd
go build -o test2json.exe cmd/test2json
go version -m test2json.exe
stdout '^test2json.exe: .+'
stdout '^\tpath\tcmd/test2json$'
! stdout 'mod'
# Repeat the test with -buildmode=pie. # Repeat the test with -buildmode=pie.
[!buildmode:pie] stop [!buildmode:pie] stop
go build -buildmode=pie -o external.exe rsc.io/fortune go build -buildmode=pie -o external.exe rsc.io/fortune

140
src/cmd/go/testdata/script/work.txt vendored Normal file
View file

@ -0,0 +1,140 @@
go mod initwork ./a ./b
cmp go.work go.work.want
! go run example.com/b
stderr 'a(\\|/)a.go:4:8: no required module provides package rsc.io/quote; to add it:\n\tcd '$WORK(\\|/)gopath(\\|/)src(\\|/)a'\n\tgo get rsc.io/quote'
cd a
go get rsc.io/quote
go env GOMOD # go env GOMOD reports the module in a single module context
stdout $GOPATH(\\|/)src(\\|/)a(\\|/)go.mod
cd ..
go run example.com/b
stdout 'Hello, world.'
# And try from a different directory
cd c
go run example.com/b
stdout 'Hello, world.'
cd $GOPATH/src
go list all # all includes both modules
stdout 'example.com/a'
stdout 'example.com/b'
# -mod can only be set to readonly in workspace mode
go list -mod=readonly all
! go list -mod=mod all
stderr '^go: -mod may only be set to readonly when in workspace mode'
go list -mod=mod -workfile=off all
# Test that duplicates in the directory list return an error
cp go.work go.work.backup
cp go.work.dup go.work
! go run example.com/b
stderr 'reading go.work: path .* appears multiple times in workspace'
cp go.work.backup go.work
cp go.work.d go.work
go run example.com/d
# Test that we don't run into "newRequirements called with unsorted roots"
# panic with unsorted main modules.
cp go.work.backwards go.work
go run example.com/d
# Test that command-line-arguments work inside and outside modules.
# This exercises the code that determines which module command-line-arguments
# belongs to.
go list ./b/main.go
go build -n -workfile=off -o foo foo.go
go build -n -o foo foo.go
-- go.work.dup --
go 1.18
directory (
a
b
../src/a
)
-- go.work.want --
go 1.18
directory (
./a
./b
)
-- go.work.d --
go 1.18
directory (
a
b
d
)
-- a/go.mod --
module example.com/a
-- a/a.go --
package a
import "fmt"
import "rsc.io/quote"
func HelloFromA() {
fmt.Println(quote.Hello())
}
-- b/go.mod --
module example.com/b
-- b/main.go --
package main
import "example.com/a"
func main() {
a.HelloFromA()
}
-- b/lib/hello.go --
package lib
import "example.com/a"
func Hello() {
a.HelloFromA()
}
-- c/README --
Create this directory so we can cd to
it and make sure paths are interpreted
relative to the go.work, not the cwd.
-- d/go.mod --
module example.com/d
-- d/main.go --
package main
import "example.com/b/lib"
func main() {
lib.Hello()
}
-- go.work.backwards --
go 1.18
directory (
d
b
a
)
-- foo.go --
package main
import "fmt"
func main() {
fmt.Println("Hello, World")
}

157
src/cmd/go/testdata/script/work_edit.txt vendored Normal file
View file

@ -0,0 +1,157 @@
# Test editing go.work files.
go mod initwork m
cmp go.work go.work.want_initial
go mod editwork -directory n
cmp go.work go.work.want_directory_n
go mod editwork -go 1.18
cmp go.work go.work.want_go_118
go mod editwork -dropdirectory m
cmp go.work go.work.want_dropdirectory_m
go mod editwork -replace=x.1@v1.3.0=y.1@v1.4.0 -replace='x.1@v1.4.0 = ../z'
cmp go.work go.work.want_add_replaces
go mod editwork -directory n -directory ../a -directory /b -directory c -directory c
cmp go.work go.work.want_multidirectory
go mod editwork -dropdirectory /b -dropdirectory n
cmp go.work go.work.want_multidropdirectory
go mod editwork -dropreplace='x.1@v1.4.0'
cmp go.work go.work.want_dropreplace
go mod editwork -print -go 1.19 -directory b -dropdirectory c -replace 'x.1@v1.4.0 = ../z' -dropreplace x.1 -dropreplace x.1@v1.3.0
cmp stdout go.work.want_print
go mod editwork -json -go 1.19 -directory b -dropdirectory c -replace 'x.1@v1.4.0 = ../z' -dropreplace x.1 -dropreplace x.1@v1.3.0
cmp stdout go.work.want_json
go mod editwork -print -fmt -workfile unformatted
cmp stdout formatted
-- go.work.want_initial --
go 1.18
directory m
-- go.work.want_directory_n --
go 1.18
directory (
m
n
)
-- go.work.want_go_118 --
go 1.18
directory (
m
n
)
-- go.work.want_dropdirectory_m --
go 1.18
directory n
-- go.work.want_add_replaces --
go 1.18
directory n
replace (
x.1 v1.3.0 => y.1 v1.4.0
x.1 v1.4.0 => ../z
)
-- go.work.want_multidirectory --
go 1.18
directory (
../a
/b
c
n
)
replace (
x.1 v1.3.0 => y.1 v1.4.0
x.1 v1.4.0 => ../z
)
-- go.work.want_multidropdirectory --
go 1.18
directory (
../a
c
)
replace (
x.1 v1.3.0 => y.1 v1.4.0
x.1 v1.4.0 => ../z
)
-- go.work.want_dropreplace --
go 1.18
directory (
../a
c
)
replace x.1 v1.3.0 => y.1 v1.4.0
-- go.work.want_print --
go 1.19
directory (
../a
b
)
replace x.1 v1.4.0 => ../z
-- go.work.want_json --
{
"Go": "1.19",
"Directory": [
{
"DiskPath": "../a"
},
{
"DiskPath": "b"
}
],
"Replace": [
{
"Old": {
"Path": "x.1",
"Version": "v1.4.0"
},
"New": {
"Path": "../z"
}
}
]
}
-- unformatted --
go 1.18
directory (
a
b
c
)
replace (
x.1 v1.3.0 => y.1 v1.4.0
x.1 v1.4.0 => ../z
)
-- formatted --
go 1.18
directory (
a
b
c
)
replace (
x.1 v1.3.0 => y.1 v1.4.0
x.1 v1.4.0 => ../z
)

33
src/cmd/go/testdata/script/work_sum.txt vendored Normal file
View file

@ -0,0 +1,33 @@
# Test adding sums to go.work.sum when sum isn't in go.mod.
go run .
cmp go.work.sum want.sum
-- want.sum --
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:pvCbr/wm8HzDD3fVywevekufpn6tCGPY3spdHeZJEsw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote v1.5.2 h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0=
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
rsc.io/sampler v1.3.0 h1:HLGR/BgEtI3r0uymSP/nl2uPLsUnNJX8toRyhfpBTII=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-- go.work --
go 1.18
directory .
-- go.mod --
go 1.18
module example.com/hi
require "rsc.io/quote" v1.5.2
-- main.go --
package main
import (
"fmt"
"rsc.io/quote"
)
func main() {
fmt.Println(quote.Hello())
}

View file

@ -423,68 +423,12 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a
} }
case "replace": case "replace":
arrow := 2 replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
if len(args) >= 2 && args[1] == "=>" { if wrappederr != nil {
arrow = 1 *errs = append(*errs, *wrappederr)
}
if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb)
return return
} }
s, err := parseString(&args[0]) f.Replace = append(f.Replace, replace)
if err != nil {
errorf("invalid quoted string: %v", err)
return
}
pathMajor, err := modulePathMajor(s)
if err != nil {
wrapModPathError(s, err)
return
}
var v string
if arrow == 2 {
v, err = parseVersion(verb, s, &args[1], fix)
if err != nil {
wrapError(err)
return
}
if err := module.CheckPathMajor(v, pathMajor); err != nil {
wrapModPathError(s, err)
return
}
}
ns, err := parseString(&args[arrow+1])
if err != nil {
errorf("invalid quoted string: %v", err)
return
}
nv := ""
if len(args) == arrow+2 {
if !IsDirectoryPath(ns) {
errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)")
return
}
if filepath.Separator == '/' && strings.Contains(ns, `\`) {
errorf("replacement directory appears to be Windows path (on a non-windows system)")
return
}
}
if len(args) == arrow+3 {
nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
if err != nil {
wrapError(err)
return
}
if IsDirectoryPath(ns) {
errorf("replacement module directory path %q cannot have version", ns)
return
}
}
f.Replace = append(f.Replace, &Replace{
Old: module.Version{Path: s, Version: v},
New: module.Version{Path: ns, Version: nv},
Syntax: line,
})
case "retract": case "retract":
rationale := parseDirectiveComment(block, line) rationale := parseDirectiveComment(block, line)
@ -515,6 +459,83 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a
} }
} }
func parseReplace(filename string, line *Line, verb string, args []string, fix VersionFixer) (*Replace, *Error) {
wrapModPathError := func(modPath string, err error) *Error {
return &Error{
Filename: filename,
Pos: line.Start,
ModPath: modPath,
Verb: verb,
Err: err,
}
}
wrapError := func(err error) *Error {
return &Error{
Filename: filename,
Pos: line.Start,
Err: err,
}
}
errorf := func(format string, args ...interface{}) *Error {
return wrapError(fmt.Errorf(format, args...))
}
arrow := 2
if len(args) >= 2 && args[1] == "=>" {
arrow = 1
}
if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
return nil, errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb)
}
s, err := parseString(&args[0])
if err != nil {
return nil, errorf("invalid quoted string: %v", err)
}
pathMajor, err := modulePathMajor(s)
if err != nil {
return nil, wrapModPathError(s, err)
}
var v string
if arrow == 2 {
v, err = parseVersion(verb, s, &args[1], fix)
if err != nil {
return nil, wrapError(err)
}
if err := module.CheckPathMajor(v, pathMajor); err != nil {
return nil, wrapModPathError(s, err)
}
}
ns, err := parseString(&args[arrow+1])
if err != nil {
return nil, errorf("invalid quoted string: %v", err)
}
nv := ""
if len(args) == arrow+2 {
if !IsDirectoryPath(ns) {
return nil, errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)")
}
if filepath.Separator == '/' && strings.Contains(ns, `\`) {
return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)")
}
}
if len(args) == arrow+3 {
nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
if err != nil {
return nil, wrapError(err)
}
if IsDirectoryPath(ns) {
return nil, errorf("replacement module directory path %q cannot have version", ns)
}
}
return &Replace{
Old: module.Version{Path: s, Version: v},
New: module.Version{Path: ns, Version: nv},
Syntax: line,
}, nil
}
// fixRetract applies fix to each retract directive in f, appending any errors // fixRetract applies fix to each retract directive in f, appending any errors
// to errs. // to errs.
// //
@ -556,6 +577,63 @@ func (f *File) fixRetract(fix VersionFixer, errs *ErrorList) {
} }
} }
func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer) {
wrapError := func(err error) {
*errs = append(*errs, Error{
Filename: f.Syntax.Name,
Pos: line.Start,
Err: err,
})
}
errorf := func(format string, args ...interface{}) {
wrapError(fmt.Errorf(format, args...))
}
switch verb {
default:
errorf("unknown directive: %s", verb)
case "go":
if f.Go != nil {
errorf("repeated go statement")
return
}
if len(args) != 1 {
errorf("go directive expects exactly one argument")
return
} else if !GoVersionRE.MatchString(args[0]) {
errorf("invalid go version '%s': must match format 1.23", args[0])
return
}
f.Go = &Go{Syntax: line}
f.Go.Version = args[0]
case "directory":
if len(args) != 1 {
errorf("usage: %s local/dir", verb)
return
}
s, err := parseString(&args[0])
if err != nil {
errorf("invalid quoted string: %v", err)
return
}
f.Directory = append(f.Directory, &Directory{
Path: s,
Syntax: line,
})
case "replace":
replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
if wrappederr != nil {
*errs = append(*errs, *wrappederr)
return
}
f.Replace = append(f.Replace, replace)
}
}
// IsDirectoryPath reports whether the given path should be interpreted // IsDirectoryPath reports whether the given path should be interpreted
// as a directory path. Just like on the go command line, relative paths // as a directory path. Just like on the go command line, relative paths
// and rooted paths are directory paths; the rest are module paths. // and rooted paths are directory paths; the rest are module paths.
@ -1165,6 +1243,10 @@ func (f *File) DropExclude(path, vers string) error {
} }
func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error { func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
}
func addReplace(syntax *FileSyntax, replace *[]*Replace, oldPath, oldVers, newPath, newVers string) error {
need := true need := true
old := module.Version{Path: oldPath, Version: oldVers} old := module.Version{Path: oldPath, Version: oldVers}
new := module.Version{Path: newPath, Version: newVers} new := module.Version{Path: newPath, Version: newVers}
@ -1178,12 +1260,12 @@ func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
} }
var hint *Line var hint *Line
for _, r := range f.Replace { for _, r := range *replace {
if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) { if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
if need { if need {
// Found replacement for old; update to use new. // Found replacement for old; update to use new.
r.New = new r.New = new
f.Syntax.updateLine(r.Syntax, tokens...) syntax.updateLine(r.Syntax, tokens...)
need = false need = false
continue continue
} }
@ -1196,7 +1278,7 @@ func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
} }
} }
if need { if need {
f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)}) *replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)})
} }
return nil return nil
} }
@ -1282,30 +1364,36 @@ func (f *File) SortBlocks() {
// retract directives are not de-duplicated since comments are // retract directives are not de-duplicated since comments are
// meaningful, and versions may be retracted multiple times. // meaningful, and versions may be retracted multiple times.
func (f *File) removeDups() { func (f *File) removeDups() {
removeDups(f.Syntax, &f.Exclude, &f.Replace)
}
func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace) {
kill := make(map[*Line]bool) kill := make(map[*Line]bool)
// Remove duplicate excludes. // Remove duplicate excludes.
haveExclude := make(map[module.Version]bool) if exclude != nil {
for _, x := range f.Exclude { haveExclude := make(map[module.Version]bool)
if haveExclude[x.Mod] { for _, x := range *exclude {
kill[x.Syntax] = true if haveExclude[x.Mod] {
continue kill[x.Syntax] = true
continue
}
haveExclude[x.Mod] = true
} }
haveExclude[x.Mod] = true var excl []*Exclude
} for _, x := range *exclude {
var excl []*Exclude if !kill[x.Syntax] {
for _, x := range f.Exclude { excl = append(excl, x)
if !kill[x.Syntax] { }
excl = append(excl, x)
} }
*exclude = excl
} }
f.Exclude = excl
// Remove duplicate replacements. // Remove duplicate replacements.
// Later replacements take priority over earlier ones. // Later replacements take priority over earlier ones.
haveReplace := make(map[module.Version]bool) haveReplace := make(map[module.Version]bool)
for i := len(f.Replace) - 1; i >= 0; i-- { for i := len(*replace) - 1; i >= 0; i-- {
x := f.Replace[i] x := (*replace)[i]
if haveReplace[x.Old] { if haveReplace[x.Old] {
kill[x.Syntax] = true kill[x.Syntax] = true
continue continue
@ -1313,18 +1401,18 @@ func (f *File) removeDups() {
haveReplace[x.Old] = true haveReplace[x.Old] = true
} }
var repl []*Replace var repl []*Replace
for _, x := range f.Replace { for _, x := range *replace {
if !kill[x.Syntax] { if !kill[x.Syntax] {
repl = append(repl, x) repl = append(repl, x)
} }
} }
f.Replace = repl *replace = repl
// Duplicate require and retract directives are not removed. // Duplicate require and retract directives are not removed.
// Drop killed statements from the syntax tree. // Drop killed statements from the syntax tree.
var stmts []Expr var stmts []Expr
for _, stmt := range f.Syntax.Stmt { for _, stmt := range syntax.Stmt {
switch stmt := stmt.(type) { switch stmt := stmt.(type) {
case *Line: case *Line:
if kill[stmt] { if kill[stmt] {
@ -1344,7 +1432,7 @@ func (f *File) removeDups() {
} }
stmts = append(stmts, stmt) stmts = append(stmts, stmt)
} }
f.Syntax.Stmt = stmts syntax.Stmt = stmts
} }
// lineLess returns whether li should be sorted before lj. It sorts // lineLess returns whether li should be sorted before lj. It sorts

234
src/cmd/vendor/golang.org/x/mod/modfile/work.go generated vendored Normal file
View file

@ -0,0 +1,234 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modfile
import (
"fmt"
"sort"
"strings"
)
// A WorkFile is the parsed, interpreted form of a go.work file.
type WorkFile struct {
Go *Go
Directory []*Directory
Replace []*Replace
Syntax *FileSyntax
}
// A Directory is a single directory statement.
type Directory struct {
Path string // Directory path of module.
ModulePath string // Module path in the comment.
Syntax *Line
}
// ParseWork parses and returns a go.work file.
//
// file is the name of the file, used in positions and errors.
//
// data is the content of the file.
//
// fix is an optional function that canonicalizes module versions.
// If fix is nil, all module versions must be canonical (module.CanonicalVersion
// must return the same string).
func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error) {
fs, err := parse(file, data)
if err != nil {
return nil, err
}
f := &WorkFile{
Syntax: fs,
}
var errs ErrorList
for _, x := range fs.Stmt {
switch x := x.(type) {
case *Line:
f.add(&errs, x, x.Token[0], x.Token[1:], fix)
case *LineBlock:
if len(x.Token) > 1 {
errs = append(errs, Error{
Filename: file,
Pos: x.Start,
Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
})
continue
}
switch x.Token[0] {
default:
errs = append(errs, Error{
Filename: file,
Pos: x.Start,
Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
})
continue
case "directory", "replace":
for _, l := range x.Line {
f.add(&errs, l, x.Token[0], l.Token, fix)
}
}
}
}
if len(errs) > 0 {
return nil, errs
}
return f, nil
}
// Cleanup cleans up the file f after any edit operations.
// To avoid quadratic behavior, modifications like DropRequire
// clear the entry but do not remove it from the slice.
// Cleanup cleans out all the cleared entries.
func (f *WorkFile) Cleanup() {
w := 0
for _, r := range f.Directory {
if r.Path != "" {
f.Directory[w] = r
w++
}
}
f.Directory = f.Directory[:w]
w = 0
for _, r := range f.Replace {
if r.Old.Path != "" {
f.Replace[w] = r
w++
}
}
f.Replace = f.Replace[:w]
f.Syntax.Cleanup()
}
func (f *WorkFile) AddGoStmt(version string) error {
if !GoVersionRE.MatchString(version) {
return fmt.Errorf("invalid language version string %q", version)
}
if f.Go == nil {
stmt := &Line{Token: []string{"go", version}}
f.Go = &Go{
Version: version,
Syntax: stmt,
}
// Find the first non-comment-only block that's and add
// the go statement before it. That will keep file comments at the top.
i := 0
for i = 0; i < len(f.Syntax.Stmt); i++ {
if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok {
break
}
}
f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...)
} else {
f.Go.Version = version
f.Syntax.updateLine(f.Go.Syntax, "go", version)
}
return nil
}
func (f *WorkFile) AddDirectory(diskPath, modulePath string) error {
need := true
for _, d := range f.Directory {
if d.Path == diskPath {
if need {
d.ModulePath = modulePath
f.Syntax.updateLine(d.Syntax, "directory", AutoQuote(diskPath))
need = false
} else {
d.Syntax.markRemoved()
*d = Directory{}
}
}
}
if need {
f.AddNewDirectory(diskPath, modulePath)
}
return nil
}
func (f *WorkFile) AddNewDirectory(diskPath, modulePath string) {
line := f.Syntax.addLine(nil, "directory", AutoQuote(diskPath))
f.Directory = append(f.Directory, &Directory{Path: diskPath, ModulePath: modulePath, Syntax: line})
}
func (f *WorkFile) SetDirectory(dirs []*Directory) {
need := make(map[string]string)
for _, d := range dirs {
need[d.Path] = d.ModulePath
}
for _, d := range f.Directory {
if modulePath, ok := need[d.Path]; ok {
d.ModulePath = modulePath
} else {
d.Syntax.markRemoved()
*d = Directory{}
}
}
// TODO(#45713): Add module path to comment.
for diskPath, modulePath := range need {
f.AddNewDirectory(diskPath, modulePath)
}
f.SortBlocks()
}
func (f *WorkFile) DropDirectory(path string) error {
for _, d := range f.Directory {
if d.Path == path {
d.Syntax.markRemoved()
*d = Directory{}
}
}
return nil
}
func (f *WorkFile) AddReplace(oldPath, oldVers, newPath, newVers string) error {
return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
}
func (f *WorkFile) DropReplace(oldPath, oldVers string) error {
for _, r := range f.Replace {
if r.Old.Path == oldPath && r.Old.Version == oldVers {
r.Syntax.markRemoved()
*r = Replace{}
}
}
return nil
}
func (f *WorkFile) SortBlocks() {
f.removeDups() // otherwise sorting is unsafe
for _, stmt := range f.Syntax.Stmt {
block, ok := stmt.(*LineBlock)
if !ok {
continue
}
sort.SliceStable(block.Line, func(i, j int) bool {
return lineLess(block.Line[i], block.Line[j])
})
}
}
// removeDups removes duplicate replace directives.
//
// Later replace directives take priority.
//
// require directives are not de-duplicated. That's left up to higher-level
// logic (MVS).
//
// retract directives are not de-duplicated since comments are
// meaningful, and versions may be retracted multiple times.
func (f *WorkFile) removeDups() {
removeDups(f.Syntax, nil, &f.Replace)
}

View file

@ -28,7 +28,7 @@ golang.org/x/arch/x86/x86asm
## explicit; go 1.17 ## explicit; go 1.17
golang.org/x/crypto/ed25519 golang.org/x/crypto/ed25519
golang.org/x/crypto/ed25519/internal/edwards25519 golang.org/x/crypto/ed25519/internal/edwards25519
# golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a # golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61
## explicit; go 1.17 ## explicit; go 1.17
golang.org/x/mod/internal/lazyregexp golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile golang.org/x/mod/modfile