[dev.cmdgo] all: merge master (67f7e16) into dev.cmdgo
Merge List:
+ 2021-08-27 67f7e16bcc encoding/gob: optimize decoding of []byte
+ 2021-08-27 2c60a99f72 cmd/compile/internal/syntax: make valid type parameter list in presence of errors
+ 2021-08-27 d350a66532 cmd/compile: eagerly CalcStructSize for synthetic ABI types
+ 2021-08-27 d7e2e2ec2b cmd/compile: delay fillinMethods to deal with mutually-recursive types
+ 2021-08-27 c927599783 cmd/compile: eliminate repetitive code
+ 2021-08-27 62f88b6dc8 cmd/compile: add types.RecalcSize
+ 2021-08-27 e7eee5e265 cmd/compile: remove ssagen/pgen_test.go
+ 2021-08-27 f153b6739b cmd/compile: use typecheck.InitUniverse in unit tests
+ 2021-08-26 967a8017f7 cmd/compile: move types init code into package types
+ 2021-08-26 af80af22b5 cmd/compile/internal/types2: do not declare new methods on instantiated types
+ 2021-08-26 03db2c2413 cmd/compile/internal/types2: implement TypeList.String (debugging support)
+ 2021-08-26 c9e05fdcf7 cmd/compile: fix reference to generic type needed by crawler
+ 2021-08-26 eb6a07fcf9 cmd/compile: unexport Type.Vargen
+ 2021-08-26 3836983779 cmd/compile/internal/types: unexport Type.Extra
+ 2021-08-26 1f8d4562de cmd/compile: change typecheck.iscmp into ir.Op.IsCmp
Change-Id: I95c040a0e984a13a3b12c50458148007221ee300
diff --git a/codereview.cfg b/codereview.cfg
index 77a74f1..c5bef5e 100644
--- a/codereview.cfg
+++ b/codereview.cfg
@@ -1 +1,2 @@
-branch: master
+branch: dev.cmdgo
+parent-branch: master
diff --git a/src/cmd/go.mod b/src/cmd/go.mod
index ccfff09..b12d199 100644
--- a/src/cmd/go.mod
+++ b/src/cmd/go.mod
@@ -7,7 +7,7 @@
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 // indirect
golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e
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/term v0.0.0-20210503060354-a79de5458b56
golang.org/x/tools v0.1.6-0.20210809225032-337cebd2c151
diff --git a/src/cmd/go.sum b/src/cmd/go.sum
index f4d41f0..1db50ca 100644
--- a/src/cmd/go.sum
+++ b/src/cmd/go.sum
@@ -9,8 +9,8 @@
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/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.20210608190319-0f08993efd8a/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61 h1:gQY3CVezomIImcWCpxp6Mhj+fXCOZ+gD8/88326LVqw=
+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-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go
index 592ea49..425aa83 100644
--- a/src/cmd/go/alldocs.go
+++ b/src/cmd/go/alldocs.go
@@ -167,6 +167,14 @@
// directory, but it is not accessed. When -modfile is specified, an
// alternate go.sum file is also used: its path is derived from the
// -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
// 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
@@ -1024,8 +1032,10 @@
//
// download download modules to local cache
// edit edit go.mod from tools or scripts
+// editwork edit go.work from tools or scripts
// graph print module requirement graph
// init initialize new module in current directory
+// initwork initialize workspace file
// tidy add missing and remove unused modules
// vendor make vendored copy of dependencies
// 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'.
//
//
+// 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
//
// Usage:
@@ -1221,6 +1302,23 @@
// 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
//
// Usage:
diff --git a/src/cmd/go/internal/base/flag.go b/src/cmd/go/internal/base/flag.go
index 6914efa..7e5121b 100644
--- a/src/cmd/go/internal/base/flag.go
+++ b/src/cmd/go/internal/base/flag.go
@@ -62,6 +62,13 @@
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
// and 'go mod' subcommands.
func AddModCommonFlags(flags *flag.FlagSet) {
diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go
index 57a3c1f..da616ee 100644
--- a/src/cmd/go/internal/cfg/cfg.go
+++ b/src/cmd/go/internal/cfg/cfg.go
@@ -47,8 +47,10 @@
BuildWork bool // -work flag
BuildX bool // -x flag
- ModCacheRW bool // -modcacherw flag
- ModFile string // -modfile flag
+ ModCacheRW bool // -modcacherw flag
+ ModFile string // -modfile flag
+ WorkFile string // -workfile flag
+ WorkFileExplicit bool // whether -workfile was set explicitly
CmdName string // "build", "install", "list", "mod tidy", etc.
diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go
index 483ce2a..d23d539 100644
--- a/src/cmd/go/internal/envcmd/env.go
+++ b/src/cmd/go/internal/envcmd/env.go
@@ -146,8 +146,9 @@
// ExtraEnvVars returns environment variables that should not leak into child processes.
func ExtraEnvVars() []cfg.EnvVar {
gomod := ""
+ modload.Init()
if modload.HasModRoot() {
- gomod = filepath.Join(modload.ModRoot(), "go.mod")
+ gomod = modload.ModFilePath()
} else if modload.Enabled() {
gomod = os.DevNull
}
diff --git a/src/cmd/go/internal/get/get.go b/src/cmd/go/internal/get/get.go
index 836364e..075594b 100644
--- a/src/cmd/go/internal/get/get.go
+++ b/src/cmd/go/internal/get/get.go
@@ -225,7 +225,8 @@
base.ExitIfErrors()
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(), "...") {
pkgs = append(pkgs, m.Pattern())
} else {
@@ -315,7 +316,8 @@
if wildcardOkay && strings.Contains(arg, "...") {
match := search.NewMatch(arg)
if match.IsLocal() {
- match.MatchDirs()
+ noModRoots := []string{} // We're in gopath mode, so there are no modroots.
+ match.MatchDirs(noModRoots)
args = match.Dirs
} else {
match.MatchPackages()
diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go
index 4b8c0e9..704d61e 100644
--- a/src/cmd/go/internal/list/list.go
+++ b/src/cmd/go/internal/list/list.go
@@ -316,6 +316,7 @@
func init() {
CmdList.Run = runList // break init cycle
work.AddBuildFlags(CmdList, work.DefaultBuildFlags)
+ base.AddWorkfileFlag(&CmdList.Flag)
}
var (
@@ -336,6 +337,8 @@
var nl = []byte{'\n'}
func runList(ctx context.Context, cmd *base.Command, args []string) {
+ modload.InitWorkfile()
+
if *listFmt != "" && *listJson == true {
base.Fatalf("go list -f cannot be used with -json")
}
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go
index e44e71b..f0613a4 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -1450,9 +1450,9 @@
// The importer is a list of command-line files.
// Pretend that the import path is the import path of the
// 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.
- importerPath = modload.DirImportPath(ctx, importer.Dir)
+ importerPath, _ = modload.MainModules.DirImportPath(ctx, importer.Dir)
}
parentOfInternal := p.ImportPath[:i]
if str.HasPathPrefix(importerPath, parentOfInternal) {
@@ -2447,7 +2447,8 @@
}
matches, _ = modload.LoadPackages(ctx, modOpts, patterns...)
} else {
- matches = search.ImportPaths(patterns)
+ noModRoots := []string{}
+ matches = search.ImportPaths(patterns, noModRoots)
}
var (
diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go
index 0e5af852..ff56d05 100644
--- a/src/cmd/go/internal/modcmd/download.go
+++ b/src/cmd/go/internal/modcmd/download.go
@@ -66,6 +66,7 @@
// TODO(jayconrod): https://golang.org/issue/35849 Apply -x to other 'go mod' commands.
cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "")
base.AddModCommonFlags(&cmdDownload.Flag)
+ base.AddWorkfileFlag(&cmdDownload.Flag)
}
type moduleJSON struct {
@@ -81,6 +82,8 @@
}
func runDownload(ctx context.Context, cmd *base.Command, args []string) {
+ modload.InitWorkfile()
+
// Check whether modules are enabled and whether we're in a module.
modload.ForceUseModules = true
if !modload.HasModRoot() && len(args) == 0 {
@@ -91,12 +94,18 @@
args = []string{"all"}
}
if modload.HasModRoot() {
- modload.LoadModFile(ctx) // to fill Target
- targetAtUpgrade := modload.Target.Path + "@upgrade"
- targetAtPatch := modload.Target.Path + "@patch"
+ modload.LoadModFile(ctx) // to fill MainModules
+
+ 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 {
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")
}
}
diff --git a/src/cmd/go/internal/modcmd/editwork.go b/src/cmd/go/internal/modcmd/editwork.go
new file mode 100644
index 0000000..f05d924
--- /dev/null
+++ b/src/cmd/go/internal/modcmd/editwork.go
@@ -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"`
+}
diff --git a/src/cmd/go/internal/modcmd/graph.go b/src/cmd/go/internal/modcmd/graph.go
index ac81f26..2cbabae 100644
--- a/src/cmd/go/internal/modcmd/graph.go
+++ b/src/cmd/go/internal/modcmd/graph.go
@@ -42,9 +42,12 @@
func init() {
cmdGraph.Flag.Var(&graphGo, "go", "")
base.AddModCommonFlags(&cmdGraph.Flag)
+ base.AddWorkfileFlag(&cmdGraph.Flag)
}
func runGraph(ctx context.Context, cmd *base.Command, args []string) {
+ modload.InitWorkfile()
+
if len(args) > 0 {
base.Fatalf("go mod graph: graph takes no arguments")
}
diff --git a/src/cmd/go/internal/modcmd/initwork.go b/src/cmd/go/internal/modcmd/initwork.go
new file mode 100644
index 0000000..4182aa0
--- /dev/null
+++ b/src/cmd/go/internal/modcmd/initwork.go
@@ -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)
+}
diff --git a/src/cmd/go/internal/modcmd/mod.go b/src/cmd/go/internal/modcmd/mod.go
index d72d0ca..29aad58 100644
--- a/src/cmd/go/internal/modcmd/mod.go
+++ b/src/cmd/go/internal/modcmd/mod.go
@@ -23,8 +23,10 @@
Commands: []*base.Command{
cmdDownload,
cmdEdit,
+ cmdEditwork,
cmdGraph,
cmdInit,
+ cmdInitwork,
cmdTidy,
cmdVendor,
cmdVerify,
diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go
index 4e01dfa..1effcea 100644
--- a/src/cmd/go/internal/modcmd/vendor.go
+++ b/src/cmd/go/internal/modcmd/vendor.go
@@ -74,7 +74,7 @@
}
_, pkgs := modload.LoadPackages(ctx, loadOpts, "all")
- vdir := filepath.Join(modload.ModRoot(), "vendor")
+ vdir := filepath.Join(modload.VendorDir())
if err := os.RemoveAll(vdir); err != nil {
base.Fatalf("go mod vendor: %v", err)
}
@@ -82,7 +82,7 @@
modpkgs := make(map[module.Version][]string)
for _, pkg := range pkgs {
m := modload.PackageModule(pkg)
- if m.Path == "" || m == modload.Target {
+ if m.Path == "" || m.Version == "" && modload.MainModules.Contains(m.Path) {
continue
}
modpkgs[m] = append(modpkgs[m], pkg)
@@ -128,7 +128,8 @@
}
for _, m := range vendorMods {
- line := moduleLine(m, modload.Replacement(m))
+ replacement, _ := modload.Replacement(m)
+ line := moduleLine(m, replacement)
io.WriteString(w, line)
goVersion := ""
diff --git a/src/cmd/go/internal/modcmd/verify.go b/src/cmd/go/internal/modcmd/verify.go
index 5a6eca3..14c4d76 100644
--- a/src/cmd/go/internal/modcmd/verify.go
+++ b/src/cmd/go/internal/modcmd/verify.go
@@ -39,9 +39,12 @@
func init() {
base.AddModCommonFlags(&cmdVerify.Flag)
+ base.AddWorkfileFlag(&cmdVerify.Flag)
}
func runVerify(ctx context.Context, cmd *base.Command, args []string) {
+ modload.InitWorkfile()
+
if len(args) != 0 {
// NOTE(rsc): Could take a module pattern.
base.Fatalf("go mod verify: verify takes no arguments")
diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go
index 3b14b27..eef5fa5 100644
--- a/src/cmd/go/internal/modcmd/why.go
+++ b/src/cmd/go/internal/modcmd/why.go
@@ -61,9 +61,11 @@
func init() {
cmdWhy.Run = runWhy // break init cycle
base.AddModCommonFlags(&cmdWhy.Flag)
+ base.AddWorkfileFlag(&cmdWhy.Flag)
}
func runWhy(ctx context.Context, cmd *base.Command, args []string) {
+ modload.InitWorkfile()
modload.ForceUseModules = true
modload.RootMode = modload.NeedRoot
diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go
index d3d30d9..408b286 100644
--- a/src/cmd/go/internal/modfetch/fetch.go
+++ b/src/cmd/go/internal/modfetch/fetch.go
@@ -693,19 +693,21 @@
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.
//
// 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
// (version ends with "/go.mod"). Existing sums will be preserved unless they
// 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()
defer goSum.mu.Unlock()
// If we haven't read the go.sum file yet, don't bother writing it.
if !goSum.enabled {
- return
+ return nil
}
// Check whether we need to add sums for which keep[m] is true or remove
@@ -723,10 +725,10 @@
}
}
if !dirty {
- return
+ return nil
}
- if cfg.BuildMod == "readonly" {
- base.Fatalf("go: updates to go.sum needed, disabled by -mod=readonly")
+ if readonly {
+ return ErrGoSumDirty
}
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")
@@ -774,11 +776,12 @@
})
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.overwrite = false
+ return nil
}
// TrimGoSum trims go.sum to contain only the modules needed for reproducible
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index 9672e55..37912ce 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -389,9 +389,11 @@
haveExternalExe := false
for _, pkg := range pkgs {
- if pkg.Name == "main" && pkg.Module != nil && pkg.Module.Path != modload.Target.Path {
- haveExternalExe = true
- break
+ if pkg.Name == "main" && pkg.Module != nil {
+ if !modload.MainModules.Contains(pkg.Module.Path) {
+ haveExternalExe = true
+ break
+ }
}
}
if haveExternalExe {
@@ -675,7 +677,9 @@
if !q.isWildcard() {
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
// version "none". This is not an entirely unreasonable request: it
// could plausibly mean “downgrade away everything that depends on any
@@ -686,7 +690,7 @@
// However, neither of those behaviors would be consistent with the
// plain meaning of the query. To try to reduce confusion, reject the
// 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"}}
@@ -698,8 +702,8 @@
continue
}
q.pathOnce(curM.Path, func() pathSet {
- if modload.HasModRoot() && curM == modload.Target {
- return errSet(&modload.QueryMatchesMainModuleError{Pattern: q.pattern, Query: q.version})
+ if modload.HasModRoot() && curM.Version == "" && modload.MainModules.Contains(curM.Path) {
+ return errSet(&modload.QueryMatchesMainModulesError{MainModules: []module.Version{curM}, Pattern: q.pattern, Query: q.version})
}
return pathSet{mod: module.Version{Path: curM.Path, Version: "none"}}
})
@@ -718,12 +722,21 @@
// Absolute paths like C:\foo and relative paths like ../foo... are
// restricted to matching packages in the main module.
- pkgPattern := modload.DirImportPath(ctx, q.pattern)
+ pkgPattern, mainModule := modload.MainModules.DirImportPath(ctx, q.pattern)
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 {
return pathSet{err: match.Errs[0]}
}
@@ -733,13 +746,14 @@
return errSet(fmt.Errorf("no package in current directory"))
}
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})
return pathSet{}
}
- return pathSet{pkgMods: []module.Version{modload.Target}}
+ return pathSet{pkgMods: []module.Version{mainModule}}
})
}
}
@@ -789,11 +803,12 @@
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) {
- return errSet(&modload.QueryMatchesMainModuleError{
- Pattern: q.pattern,
- Query: q.version,
+ return errSet(&modload.QueryMatchesMainModulesError{
+ MainModules: []module.Version{curM},
+ Pattern: q.pattern,
+ Query: q.version,
})
}
@@ -1159,8 +1174,8 @@
}
opts.AllowPackage = func(ctx context.Context, path string, m module.Version) error {
- if m.Path == "" || m == modload.Target {
- // Packages in the standard library and main module are already at their
+ if m.Path == "" || m.Version == "" && modload.MainModules.Contains(m.Path) {
+ // Packages in the standard library and main modules are already at their
// latest (and only) available versions.
return nil
}
@@ -1370,11 +1385,11 @@
continue
}
- if m.Path == modload.Target.Path {
- if m.Version == modload.Target.Version {
+ if modload.MainModules.Contains(m.Path) {
+ if m.Version == "" {
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
}
@@ -1610,7 +1625,7 @@
i := i
m := r.buildList[i]
mActual := m
- if mRepl := modload.Replacement(m); mRepl.Path != "" {
+ if mRepl, _ := modload.Replacement(m); mRepl.Path != "" {
mActual = mRepl
}
old := module.Version{Path: m.Path, Version: r.initialVersion[m.Path]}
@@ -1618,7 +1633,7 @@
continue
}
oldActual := old
- if oldRepl := modload.Replacement(old); oldRepl.Path != "" {
+ if oldRepl, _ := modload.Replacement(old); oldRepl.Path != "" {
oldActual = oldRepl
}
if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(oldActual) {
@@ -1744,10 +1759,11 @@
panic("internal error: resolving a module.Version with an empty path")
}
- if m.Path == modload.Target.Path && m.Version != modload.Target.Version {
- reportError(q, &modload.QueryMatchesMainModuleError{
- Pattern: q.pattern,
- Query: q.version,
+ if modload.MainModules.Contains(m.Path) && m.Version != "" {
+ reportError(q, &modload.QueryMatchesMainModulesError{
+ MainModules: []module.Version{{Path: m.Path}},
+ Pattern: q.pattern,
+ Query: q.version,
})
return
}
@@ -1775,7 +1791,7 @@
resolved := make([]module.Version, 0, len(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})
}
}
diff --git a/src/cmd/go/internal/modget/query.go b/src/cmd/go/internal/modget/query.go
index 0a66517..7604190 100644
--- a/src/cmd/go/internal/modget/query.go
+++ b/src/cmd/go/internal/modget/query.go
@@ -192,9 +192,9 @@
// TODO(bcmills): "all@none" seems like a totally reasonable way to
// request that we remove all module requirements, leaving only the main
// module and standard library. Perhaps we should implement that someday.
- return &modload.QueryMatchesMainModuleError{
- Pattern: q.pattern,
- Query: q.version,
+ return &modload.QueryUpgradesAllError{
+ MainModules: modload.MainModules.Versions(),
+ Query: q.version,
}
}
}
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index 76e1ad5..0efd841 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -212,20 +212,20 @@
// in rs (which may be nil to indicate that m was not loaded from a requirement
// graph).
func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic {
- if m == Target {
+ if m.Version == "" && MainModules.Contains(m.Path) {
info := &modinfo.ModulePublic{
Path: m.Path,
Version: m.Version,
Main: true,
}
- if v, ok := rawGoVersion.Load(Target); ok {
+ if v, ok := rawGoVersion.Load(m); ok {
info.GoVersion = v.(string)
} else {
panic("internal error: GoVersion not set for main module")
}
- if HasModRoot() {
- info.Dir = ModRoot()
- info.GoMod = ModFilePath()
+ if modRoot := MainModules.ModRoot(m); modRoot != "" {
+ info.Dir = modRoot
+ info.GoMod = modFilePath(modRoot)
}
return info
}
@@ -240,7 +240,7 @@
}
// completeFromModCache fills in the extra fields in m using the module cache.
- completeFromModCache := func(m *modinfo.ModulePublic) {
+ completeFromModCache := func(m *modinfo.ModulePublic, replacedFrom string) {
checksumOk := func(suffix string) bool {
return rs == nil || m.Version == "" || cfg.BuildMod == "mod" ||
modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix})
@@ -259,7 +259,7 @@
if m.GoVersion == "" && checksumOk("/go.mod") {
// Load the go.mod file to determine the Go version, since it hasn't
// already been populated from rawGoVersion.
- if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" {
+ if summary, err := rawGoModSummary(mod, replacedFrom); err == nil && summary.goVersion != "" {
m.GoVersion = summary.goVersion
}
}
@@ -289,11 +289,11 @@
if rs == nil {
// If this was an explicitly-versioned argument to 'go mod download' or
// 'go list -m', report the actual requested version, not its replacement.
- completeFromModCache(info) // Will set m.Error in vendor mode.
+ completeFromModCache(info, "") // Will set m.Error in vendor mode.
return info
}
- r := Replacement(m)
+ r, replacedFrom := Replacement(m)
if r.Path == "" {
if cfg.BuildMod == "vendor" {
// It's tempting to fill in the "Dir" field to point within the vendor
@@ -302,7 +302,7 @@
// interleave packages from different modules if one module path is a
// prefix of the other.
} else {
- completeFromModCache(info)
+ completeFromModCache(info, "")
}
return info
}
@@ -322,12 +322,12 @@
if filepath.IsAbs(r.Path) {
info.Replace.Dir = r.Path
} else {
- info.Replace.Dir = filepath.Join(ModRoot(), r.Path)
+ info.Replace.Dir = filepath.Join(replacedFrom, r.Path)
}
info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
}
if cfg.BuildMod != "vendor" {
- completeFromModCache(info.Replace)
+ completeFromModCache(info.Replace, replacedFrom)
info.Dir = info.Replace.Dir
info.GoMod = info.Replace.GoMod
info.Retracted = info.Replace.Retracted
@@ -340,15 +340,14 @@
// for modules providing packages named by path and deps. path and deps must
// name packages that were resolved successfully with LoadPackages.
func PackageBuildInfo(path string, deps []string) string {
- if isStandardImportPath(path) || !Enabled() {
+ if !Enabled() {
return ""
}
-
- target := mustFindModule(loaded, path, path)
+ target, _ := findModule(loaded, path)
mdeps := make(map[module.Version]bool)
for _, dep := range deps {
- if !isStandardImportPath(dep) {
- mdeps[mustFindModule(loaded, path, dep)] = true
+ if m, ok := findModule(loaded, dep); ok {
+ mdeps[m] = true
}
}
var mods []module.Version
@@ -367,14 +366,16 @@
mv = "(devel)"
}
fmt.Fprintf(&buf, "%s\t%s\t%s", token, m.Path, mv)
- if r := Replacement(m); r.Path == "" {
+ if r, _ := Replacement(m); r.Path == "" {
fmt.Fprintf(&buf, "\t%s\n", modfetch.Sum(m))
} else {
fmt.Fprintf(&buf, "\n=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r))
}
}
- writeEntry("mod", target)
+ if target.Path != "" {
+ writeEntry("mod", target)
+ }
for _, mod := range mods {
writeEntry("dep", mod)
}
@@ -382,38 +383,13 @@
return buf.String()
}
-// mustFindModule is like findModule, but it calls base.Fatalf if the
-// module can't be found.
-//
-// TODO(jayconrod): remove this. Callers should use findModule and return
-// errors instead of relying on base.Fatalf.
-func mustFindModule(ld *loader, target, path string) module.Version {
- pkg, ok := ld.pkgCache.Get(path).(*loadPkg)
- if ok {
- if pkg.err != nil {
- base.Fatalf("build %v: cannot load %v: %v", target, path, pkg.err)
- }
- return pkg.mod
- }
-
- if path == "command-line-arguments" {
- return Target
- }
-
- base.Fatalf("build %v: cannot find module for path %v", target, path)
- panic("unreachable")
-}
-
// findModule searches for the module that contains the package at path.
// If the package was loaded, its containing module and true are returned.
-// Otherwise, module.Version{} and false are returend.
+// Otherwise, module.Version{} and false are returned.
func findModule(ld *loader, path string) (module.Version, bool) {
if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok {
return pkg.mod, pkg.mod != module.Version{}
}
- if path == "command-line-arguments" {
- return Target, true
- }
return module.Version{}, false
}
diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go
index bf69567..94414278 100644
--- a/src/cmd/go/internal/modload/buildlist.go
+++ b/src/cmd/go/internal/modload/buildlist.go
@@ -40,7 +40,7 @@
depth modDepth
// rootModules is the set of module versions explicitly required by the main
- // module, sorted and capped to length. It may contain duplicates, and may
+ // modules, sorted and capped to length. It may contain duplicates, and may
// contain multiple versions for a given module path.
rootModules []module.Version
maxRootVersion map[string]string
@@ -99,8 +99,8 @@
// *Requirements before any other method.
func newRequirements(depth modDepth, rootModules []module.Version, direct map[string]bool) *Requirements {
for i, m := range rootModules {
- if m == Target {
- panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is Target", i))
+ if m.Version == "" && MainModules.Contains(m.Path) {
+ panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is a main module", i))
}
if m.Path == "" || m.Version == "" {
panic(fmt.Sprintf("bad requirement: rootModules[%v] = %v", i, m))
@@ -135,9 +135,14 @@
func (rs *Requirements) initVendor(vendorList []module.Version) {
rs.graphOnce.Do(func() {
mg := &ModuleGraph{
- g: mvs.NewGraph(cmpVersion, []module.Version{Target}),
+ g: mvs.NewGraph(cmpVersion, MainModules.Versions()),
}
+ if MainModules.Len() != 1 {
+ panic("There should be exactly one main module in Vendor mode.")
+ }
+ mainModule := MainModules.Versions()[0]
+
if rs.depth == lazy {
// The roots of a lazy module should already include every module in the
// vendor list, because the vendored modules are the same as those
@@ -158,7 +163,7 @@
// Now we can treat the rest of the module graph as effectively “pruned
// out”, like a more aggressive version of lazy loading: in vendor mode,
// the root requirements *are* the complete module graph.
- mg.g.Require(Target, rs.rootModules)
+ mg.g.Require(mainModule, rs.rootModules)
} else {
// The transitive requirements of the main module are not in general available
// from the vendor directory, and we don't actually know how we got from
@@ -170,7 +175,7 @@
// graph, but still distinguishes between direct and indirect
// dependencies.
vendorMod := module.Version{Path: "vendor/modules.txt", Version: ""}
- mg.g.Require(Target, append(rs.rootModules, vendorMod))
+ mg.g.Require(mainModule, append(rs.rootModules, vendorMod))
mg.g.Require(vendorMod, vendorList)
}
@@ -182,8 +187,8 @@
// path, or the zero module.Version and ok=false if the module is not a root
// dependency.
func (rs *Requirements) rootSelected(path string) (version string, ok bool) {
- if path == Target.Path {
- return Target.Version, true
+ if MainModules.Contains(path) {
+ return "", true
}
if v, ok := rs.maxRootVersion[path]; ok {
return v, true
@@ -197,7 +202,7 @@
// selection.
func (rs *Requirements) hasRedundantRoot() bool {
for i, m := range rs.rootModules {
- if m.Path == Target.Path || (i > 0 && m.Path == rs.rootModules[i-1].Path) {
+ if MainModules.Contains(m.Path) || (i > 0 && m.Path == rs.rootModules[i-1].Path) {
return true
}
}
@@ -274,10 +279,17 @@
mu sync.Mutex // guards mg.g and hasError during loading
hasError bool
mg = &ModuleGraph{
- g: mvs.NewGraph(cmpVersion, []module.Version{Target}),
+ g: mvs.NewGraph(cmpVersion, MainModules.Versions()),
}
)
- mg.g.Require(Target, roots)
+ for _, m := range MainModules.Versions() {
+ // Require all roots from all main modules.
+ _ = TODOWorkspaces("This flattens a level of the module graph, adding the dependencies " +
+ "of all main modules to a single requirements struct, and losing the information of which " +
+ "main module required which requirement. Rework the requirements struct and change this" +
+ "to reflect the structure of the main modules.")
+ mg.g.Require(m, roots)
+ }
var (
loadQueue = par.NewQueue(runtime.GOMAXPROCS(0))
@@ -404,10 +416,12 @@
}
func (mg *ModuleGraph) allRootsSelected() bool {
- roots, _ := mg.g.RequiredBy(Target)
- for _, m := range roots {
- if mg.Selected(m.Path) != m.Version {
- return false
+ for _, mm := range MainModules.Versions() {
+ roots, _ := mg.g.RequiredBy(mm)
+ for _, m := range roots {
+ if mg.Selected(m.Path) != m.Version {
+ return false
+ }
}
}
return true
@@ -447,7 +461,7 @@
base.Fatalf("go: %v", err)
}
- commitRequirements(ctx, modFileGoVersion(), rs)
+ commitRequirements(ctx, rs)
return mg
}
@@ -513,7 +527,7 @@
if err != nil {
return false, err
}
- commitRequirements(ctx, modFileGoVersion(), rs)
+ commitRequirements(ctx, rs)
return changed, err
}
@@ -546,10 +560,11 @@
// both retain the same versions of all packages in pkgs and satisfy the
// lazy loading invariants (if applicable).
func tidyRoots(ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) {
+ mainModule := MainModules.mustGetSingleMainModule()
if rs.depth == eager {
- return tidyEagerRoots(ctx, rs.direct, pkgs)
+ return tidyEagerRoots(ctx, mainModule, rs.direct, pkgs)
}
- return tidyLazyRoots(ctx, rs.direct, pkgs)
+ return tidyLazyRoots(ctx, mainModule, rs.direct, pkgs)
}
func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) {
@@ -574,10 +589,10 @@
// To ensure that the loading process eventually converges, the caller should
// add any needed roots from the tidy root set (without removing existing untidy
// roots) until the set of roots has converged.
-func tidyLazyRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
+func tidyLazyRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
var (
roots []module.Version
- pathIncluded = map[string]bool{Target.Path: true}
+ pathIncluded = map[string]bool{mainModule.Path: true}
)
// We start by adding roots for every package in "all".
//
@@ -807,7 +822,7 @@
// We've added or upgraded one or more roots, so load the full module
// graph so that we can update those roots to be consistent with other
// requirements.
- if cfg.BuildMod != "mod" {
+ if mustHaveCompleteRequirements() {
// Our changes to the roots may have moved dependencies into or out of
// the lazy-loading horizon, which could in turn change the selected
// versions of other modules. (Unlike for eager modules, for lazy
@@ -855,7 +870,9 @@
roots = make([]module.Version, 0, len(rs.rootModules))
rootsUpgraded = false
inRootPaths := make(map[string]bool, len(rs.rootModules)+1)
- inRootPaths[Target.Path] = true
+ for _, mm := range MainModules.Versions() {
+ inRootPaths[mm.Path] = true
+ }
for _, m := range rs.rootModules {
if inRootPaths[m.Path] {
// This root specifies a redundant path. We already retained the
@@ -958,7 +975,7 @@
// tidyEagerRoots returns a minimal set of root requirements that maintains the
// selected version of every module that provided a package in pkgs, and
// includes the selected version of every such module in direct as a root.
-func tidyEagerRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
+func tidyEagerRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
var (
keep []module.Version
keptPath = map[string]bool{}
@@ -981,7 +998,7 @@
}
}
- min, err := mvs.Req(Target, rootPaths, &mvsReqs{roots: keep})
+ min, err := mvs.Req(mainModule, rootPaths, &mvsReqs{roots: keep})
if err != nil {
return nil, err
}
@@ -1011,7 +1028,7 @@
return rs, err
}
- if cfg.BuildMod != "mod" {
+ if mustHaveCompleteRequirements() {
// Instead of actually updating the requirements, just check that no updates
// are needed.
if rs == nil {
@@ -1070,7 +1087,7 @@
// This is only for convenience and clarity for end users: in an eager module,
// the choice of explicit vs. implicit dependency has no impact on MVS
// selection (for itself or any other module).
- keep := append(mg.BuildList()[1:], add...)
+ keep := append(mg.BuildList()[MainModules.Len():], add...)
for _, m := range keep {
if direct[m.Path] && !inRootPaths[m.Path] {
rootPaths = append(rootPaths, m.Path)
@@ -1078,16 +1095,25 @@
}
}
- min, err := mvs.Req(Target, rootPaths, &mvsReqs{roots: keep})
- if err != nil {
- return rs, err
+ // TODO(matloob): Make roots into a map.
+ var roots []module.Version
+ for _, mainModule := range MainModules.Versions() {
+ min, err := mvs.Req(mainModule, rootPaths, &mvsReqs{roots: keep})
+ if err != nil {
+ return rs, err
+ }
+ roots = append(roots, min...)
}
- if rs.depth == eager && reflect.DeepEqual(min, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
+ if MainModules.Len() > 1 {
+ module.Sort(roots)
+ }
+ if rs.depth == eager && reflect.DeepEqual(roots, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
// The root set is unchanged and rs was already eager, so keep rs to
// preserve its cached ModuleGraph (if any).
return rs, nil
}
- return newRequirements(eager, min, direct), nil
+
+ return newRequirements(eager, roots, direct), nil
}
// convertDepth returns a version of rs with the given depth.
@@ -1117,5 +1143,5 @@
if err != nil {
return rs, err
}
- return newRequirements(lazy, mg.BuildList()[1:], rs.direct), nil
+ return newRequirements(lazy, mg.BuildList()[MainModules.Len():], rs.direct), nil
}
diff --git a/src/cmd/go/internal/modload/edit.go b/src/cmd/go/internal/modload/edit.go
index c350b9d..796721c 100644
--- a/src/cmd/go/internal/modload/edit.go
+++ b/src/cmd/go/internal/modload/edit.go
@@ -75,7 +75,7 @@
// We promote the modules in mustSelect to be explicit requirements.
var rootPaths []string
for _, m := range mustSelect {
- if m.Version != "none" && m.Path != Target.Path {
+ if !MainModules.Contains(m.Path) && m.Version != "none" {
rootPaths = append(rootPaths, m.Path)
}
}
@@ -97,7 +97,7 @@
}
}
- roots, err = mvs.Req(Target, rootPaths, &mvsReqs{roots: mods})
+ roots, err = mvs.Req(MainModules.mustGetSingleMainModule(), rootPaths, &mvsReqs{roots: mods})
if err != nil {
return nil, false, err
}
@@ -218,8 +218,8 @@
eagerUpgrades = tryUpgrade
} else {
for _, m := range tryUpgrade {
- if m.Path == Target.Path {
- // Target is already considered to be higher than any possible m, so we
+ if MainModules.Contains(m.Path) {
+ // The main module versions are already considered to be higher than any possible m, so we
// won't be upgrading to it anyway and there is no point scanning its
// dependencies.
continue
@@ -318,7 +318,7 @@
mods = make([]module.Version, 0, len(limiter.selected))
for path, v := range limiter.selected {
- if v != "none" && path != Target.Path {
+ if v != "none" && !MainModules.Contains(path) {
mods = append(mods, module.Version{Path: path, Version: v})
}
}
@@ -334,7 +334,7 @@
}
mods = make([]module.Version, 0, len(limiter.selected))
for path, _ := range limiter.selected {
- if path != Target.Path {
+ if !MainModules.Contains(path) {
if v := mg.Selected(path); v != "none" {
mods = append(mods, module.Version{Path: path, Version: v})
}
@@ -415,10 +415,14 @@
// itself lazy, its unrestricted dependencies are skipped when scanning
// requirements.
func newVersionLimiter(depth modDepth, max map[string]string) *versionLimiter {
+ selected := make(map[string]string)
+ for _, m := range MainModules.Versions() {
+ selected[m.Path] = m.Version
+ }
return &versionLimiter{
depth: depth,
max: max,
- selected: map[string]string{Target.Path: Target.Version},
+ selected: selected,
dqReason: map[module.Version]dqState{},
requiring: map[module.Version][]module.Version{},
}
@@ -492,7 +496,7 @@
// as is feasible, we don't want to retain test dependencies that are only
// marginally relevant at best.
func (l *versionLimiter) check(m module.Version, depth modDepth) dqState {
- if m.Version == "none" || m == Target {
+ if m.Version == "none" || m == MainModules.mustGetSingleMainModule() {
// version "none" has no requirements, and the dependencies of Target are
// tautological.
return dqState{}
diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go
index d2bbe5c..de47974 100644
--- a/src/cmd/go/internal/modload/import.go
+++ b/src/cmd/go/internal/modload/import.go
@@ -32,6 +32,8 @@
Module module.Version
QueryErr error
+ ImportingMainModule module.Version
+
// isStd indicates whether we would expect to find the package in the standard
// library. This is normally true for all dotless import paths, but replace
// directives can cause us to treat the replaced paths as also being in
@@ -71,6 +73,9 @@
if e.QueryErr != nil {
return fmt.Sprintf("%s: %v", message, e.QueryErr)
}
+ if e.ImportingMainModule.Path != "" && e.ImportingMainModule != MainModules.ModContainingCWD() {
+ return fmt.Sprintf("%s; to add it:\n\tcd %s\n\tgo get %s", message, MainModules.ModRoot(e.ImportingMainModule), e.Path)
+ }
return fmt.Sprintf("%s; to add it:\n\tgo get %s", message, e.Path)
}
@@ -257,11 +262,13 @@
// Is the package in the standard library?
pathIsStd := search.IsStandardImportPath(path)
if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
- if targetInGorootSrc {
- if dir, ok, err := dirInModule(path, targetPrefix, ModRoot(), true); err != nil {
- return module.Version{}, dir, err
- } else if ok {
- return Target, dir, nil
+ for _, mainModule := range MainModules.Versions() {
+ if MainModules.InGorootSrc(mainModule) {
+ if dir, ok, err := dirInModule(path, MainModules.PathPrefix(mainModule), MainModules.ModRoot(mainModule), true); err != nil {
+ return module.Version{}, dir, err
+ } else if ok {
+ return mainModule, dir, nil
+ }
}
}
dir := filepath.Join(cfg.GOROOT, "src", path)
@@ -271,8 +278,10 @@
// -mod=vendor is special.
// Everything must be in the main module or the main module's vendor directory.
if cfg.BuildMod == "vendor" {
- mainDir, mainOK, mainErr := dirInModule(path, targetPrefix, ModRoot(), true)
- vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(ModRoot(), "vendor"), false)
+ mainModule := MainModules.mustGetSingleMainModule()
+ modRoot := MainModules.ModRoot(mainModule)
+ mainDir, mainOK, mainErr := dirInModule(path, MainModules.PathPrefix(mainModule), modRoot, true)
+ vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(modRoot, "vendor"), false)
if mainOK && vendorOK {
return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: []string{mainDir, vendorDir}}
}
@@ -280,12 +289,12 @@
// Note that we're not checking that the package exists.
// We'll leave that for load.
if !vendorOK && mainDir != "" {
- return Target, mainDir, nil
+ return mainModule, mainDir, nil
}
if mainErr != nil {
return module.Version{}, "", mainErr
}
- readVendorList()
+ readVendorList(mainModule)
return vendorPkgModule[path], vendorDir, nil
}
@@ -410,69 +419,72 @@
func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) {
// To avoid spurious remote fetches, try the latest replacement for each
// module (golang.org/issue/26241).
- if index != nil {
- var mods []module.Version
- for mp, mv := range index.highestReplaced {
- if !maybeInModule(path, mp) {
- continue
- }
- if mv == "" {
- // The only replacement is a wildcard that doesn't specify a version, so
- // synthesize a pseudo-version with an appropriate major version and a
- // timestamp below any real timestamp. That way, if the main module is
- // used from within some other module, the user will be able to upgrade
- // the requirement to any real version they choose.
- if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 {
- mv = module.ZeroPseudoVersion(pathMajor[1:])
- } else {
- mv = module.ZeroPseudoVersion("v0")
+ var mods []module.Version
+ for _, v := range MainModules.Versions() {
+ if index := MainModules.Index(v); index != nil {
+ for mp, mv := range index.highestReplaced {
+ if !maybeInModule(path, mp) {
+ continue
}
+ if mv == "" {
+ // The only replacement is a wildcard that doesn't specify a version, so
+ // synthesize a pseudo-version with an appropriate major version and a
+ // timestamp below any real timestamp. That way, if the main module is
+ // used from within some other module, the user will be able to upgrade
+ // the requirement to any real version they choose.
+ if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 {
+ mv = module.ZeroPseudoVersion(pathMajor[1:])
+ } else {
+ mv = module.ZeroPseudoVersion("v0")
+ }
+ }
+ mg, err := rs.Graph(ctx)
+ if err != nil {
+ return module.Version{}, err
+ }
+ if cmpVersion(mg.Selected(mp), mv) >= 0 {
+ // We can't resolve the import by adding mp@mv to the module graph,
+ // because the selected version of mp is already at least mv.
+ continue
+ }
+ mods = append(mods, module.Version{Path: mp, Version: mv})
}
- mg, err := rs.Graph(ctx)
- if err != nil {
- return module.Version{}, err
- }
- if cmpVersion(mg.Selected(mp), mv) >= 0 {
- // We can't resolve the import by adding mp@mv to the module graph,
- // because the selected version of mp is already at least mv.
- continue
- }
- mods = append(mods, module.Version{Path: mp, Version: mv})
}
+ }
- // Every module path in mods is a prefix of the import path.
- // As in QueryPattern, prefer the longest prefix that satisfies the import.
- sort.Slice(mods, func(i, j int) bool {
- return len(mods[i].Path) > len(mods[j].Path)
- })
- for _, m := range mods {
- needSum := true
- root, isLocal, err := fetch(ctx, m, needSum)
- if err != nil {
- if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
- return module.Version{}, &ImportMissingSumError{importPath: path}
- }
- return module.Version{}, err
+ // Every module path in mods is a prefix of the import path.
+ // As in QueryPattern, prefer the longest prefix that satisfies the import.
+ sort.Slice(mods, func(i, j int) bool {
+ return len(mods[i].Path) > len(mods[j].Path)
+ })
+ for _, m := range mods {
+ needSum := true
+ root, isLocal, err := fetch(ctx, m, needSum)
+ if err != nil {
+ if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
+ return module.Version{}, &ImportMissingSumError{importPath: path}
}
- 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 {
- // The package path is not valid to fetch remotely,
- // so it can only exist in a replaced module,
- // and we know from the above loop that it is not.
- return module.Version{}, &PackageNotInModuleError{
- Mod: mods[0],
- Query: "latest",
- Pattern: path,
- Replacement: Replacement(mods[0]),
+ if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
+ return m, err
+ } else if ok {
+ if cfg.BuildMod == "readonly" {
+ return module.Version{}, &ImportMissingError{Path: path, replaced: m}
}
+ return m, nil
+ }
+ }
+ if len(mods) > 0 && module.CheckPath(path) != nil {
+ // The package path is not valid to fetch remotely,
+ // so it can only exist in a replaced module,
+ // and we know from the above loop that it is not.
+ replacement, _ := Replacement(mods[0])
+ return module.Version{}, &PackageNotInModuleError{
+ Mod: mods[0],
+ Query: "latest",
+ Pattern: path,
+ Replacement: replacement,
}
}
@@ -638,14 +650,14 @@
// The isLocal return value reports whether the replacement,
// if any, is local to the filesystem.
func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, isLocal bool, err error) {
- if mod == Target {
- return ModRoot(), true, nil
+ if modRoot := MainModules.ModRoot(mod); modRoot != "" {
+ return modRoot, true, nil
}
- if r := Replacement(mod); r.Path != "" {
+ if r, replacedFrom := Replacement(mod); r.Path != "" {
if r.Version == "" {
dir = r.Path
if !filepath.IsAbs(dir) {
- dir = filepath.Join(ModRoot(), dir)
+ dir = filepath.Join(replacedFrom, dir)
}
// Ensure that the replacement directory actually exists:
// dirInModule does not report errors for missing modules,
@@ -667,7 +679,7 @@
mod = r
}
- if HasModRoot() && cfg.BuildMod == "readonly" && needSum && !modfetch.HaveSum(mod) {
+ if HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() && needSum && !modfetch.HaveSum(mod) {
return "", false, module.VersionError(mod, &sumMissingError{})
}
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 862b007..b845842 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -17,6 +17,7 @@
"path/filepath"
"strconv"
"strings"
+ "sync"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
@@ -45,27 +46,169 @@
allowMissingModuleImports bool
)
+func TODOWorkspaces(s string) error {
+ return fmt.Errorf("need to support this for workspaces: %s", s)
+}
+
// Variables set in Init.
var (
initialized bool
- modRoot string
- gopath string
+
+ // These are primarily used to initialize the MainModules, and should be
+ // eventually superceded by them but are still used in cases where the module
+ // roots are required but MainModules hasn't been initialized yet. Set to
+ // the modRoots of the main modules.
+ // modRoots != nil implies len(modRoots) > 0
+ modRoots []string
+ gopath string
+ workFileGoVersion string
)
-// Variables set in initTarget (during {Load,Create}ModFile).
+// Variable set in InitWorkfile
var (
- Target module.Version
-
- // targetPrefix is the path prefix for packages in Target, without a trailing
- // slash. For most modules, targetPrefix is just Target.Path, but the
- // standard-library module "std" has an empty prefix.
- targetPrefix string
-
- // targetInGorootSrc caches whether modRoot is within GOROOT/src.
- // The "std" module is special within GOROOT/src, but not otherwise.
- targetInGorootSrc bool
+ // Set to the path to the go.work file, or "" if workspace mode is disabled.
+ workFilePath string
)
+type MainModuleSet struct {
+ // versions are the module.Version values of each of the main modules.
+ // For each of them, the Path fields are ordinary module paths and the Version
+ // fields are empty strings.
+ versions []module.Version
+
+ // modRoot maps each module in versions to its absolute filesystem path.
+ modRoot map[module.Version]string
+
+ // pathPrefix is the path prefix for packages in the module, without a trailing
+ // slash. For most modules, pathPrefix is just version.Path, but the
+ // standard-library module "std" has an empty prefix.
+ pathPrefix map[module.Version]string
+
+ // inGorootSrc caches whether modRoot is within GOROOT/src.
+ // The "std" module is special within GOROOT/src, but not otherwise.
+ inGorootSrc map[module.Version]bool
+
+ modFiles map[module.Version]*modfile.File
+
+ modContainingCWD module.Version
+
+ workFileGoVersion string
+
+ indexMu sync.Mutex
+ indices map[module.Version]*modFileIndex
+}
+
+func (mms *MainModuleSet) PathPrefix(m module.Version) string {
+ return mms.pathPrefix[m]
+}
+
+// Versions returns the module.Version values of each of the main modules.
+// For each of them, the Path fields are ordinary module paths and the Version
+// fields are empty strings.
+// Callers should not modify the returned slice.
+func (mms *MainModuleSet) Versions() []module.Version {
+ if mms == nil {
+ return nil
+ }
+ return mms.versions
+}
+
+func (mms *MainModuleSet) Contains(path string) bool {
+ if mms == nil {
+ return false
+ }
+ for _, v := range mms.versions {
+ if v.Path == path {
+ return true
+ }
+ }
+ return false
+}
+
+func (mms *MainModuleSet) ModRoot(m module.Version) string {
+ if mms == nil {
+ return ""
+ }
+ return mms.modRoot[m]
+}
+
+func (mms *MainModuleSet) InGorootSrc(m module.Version) bool {
+ if mms == nil {
+ return false
+ }
+ return mms.inGorootSrc[m]
+}
+
+func (mms *MainModuleSet) mustGetSingleMainModule() module.Version {
+ if mms == nil || len(mms.versions) == 0 {
+ panic("internal error: mustGetSingleMainModule called in context with no main modules")
+ }
+ if len(mms.versions) != 1 {
+ if inWorkspaceMode() {
+ panic("internal error: mustGetSingleMainModule called in workspace mode")
+ } else {
+ panic("internal error: multiple main modules present outside of workspace mode")
+ }
+ }
+ return mms.versions[0]
+}
+
+func (mms *MainModuleSet) GetSingleIndexOrNil() *modFileIndex {
+ if mms == nil {
+ return nil
+ }
+ if len(mms.versions) == 0 {
+ return nil
+ }
+ return mms.indices[mms.mustGetSingleMainModule()]
+}
+
+func (mms *MainModuleSet) Index(m module.Version) *modFileIndex {
+ mms.indexMu.Lock()
+ defer mms.indexMu.Unlock()
+ return mms.indices[m]
+}
+
+func (mms *MainModuleSet) SetIndex(m module.Version, index *modFileIndex) {
+ mms.indexMu.Lock()
+ defer mms.indexMu.Unlock()
+ mms.indices[m] = index
+}
+
+func (mms *MainModuleSet) ModFile(m module.Version) *modfile.File {
+ return mms.modFiles[m]
+}
+
+func (mms *MainModuleSet) Len() int {
+ if mms == nil {
+ return 0
+ }
+ return len(mms.versions)
+}
+
+// ModContainingCWD returns the main module containing the working directory,
+// or module.Version{} if none of the main modules contain the working
+// directory.
+func (mms *MainModuleSet) ModContainingCWD() module.Version {
+ return mms.modContainingCWD
+}
+
+// GoVersion returns the go version set on the single module, in module mode,
+// or the go.work file in workspace mode.
+func (mms *MainModuleSet) GoVersion() string {
+ if !inWorkspaceMode() {
+ return modFileGoVersion(mms.ModFile(mms.mustGetSingleMainModule()))
+ }
+ v := mms.workFileGoVersion
+ if v == "" {
+ // Fall back to 1.18 for go.work files.
+ v = "1.18"
+ }
+ return v
+}
+
+var MainModules *MainModuleSet
+
type Root int
const (
@@ -94,6 +237,7 @@
// in go.mod, edit it before loading.
func ModFile() *modfile.File {
Init()
+ modFile := MainModules.ModFile(MainModules.mustGetSingleMainModule())
if modFile == nil {
die()
}
@@ -105,6 +249,26 @@
return filepath.Join(gopath, "bin")
}
+// InitWorkfile initializes the workFilePath variable for commands that
+// operate in workspace mode. It should not be called by other commands,
+// for example 'go mod tidy', that don't operate in workspace mode.
+func InitWorkfile() {
+ switch cfg.WorkFile {
+ case "off":
+ workFilePath = ""
+ case "", "auto":
+ workFilePath = findWorkspaceFile(base.Cwd())
+ default:
+ workFilePath = cfg.WorkFile
+ }
+}
+
+// WorkFilePath returns the path of the go.work file, or "" if not in
+// workspace mode. WorkFilePath must be called after InitWorkfile.
+func WorkFilePath() string {
+ return workFilePath
+}
+
// Init determines whether module mode is enabled, locates the root of the
// current module (if any), sets environment variables for Git subprocesses, and
// configures the cfg, codehost, load, modfetch, and search packages for use
@@ -169,18 +333,18 @@
if os.Getenv("GCM_INTERACTIVE") == "" {
os.Setenv("GCM_INTERACTIVE", "never")
}
-
- if modRoot != "" {
+ if modRoots != nil {
// modRoot set before Init was called ("go mod init" does this).
// No need to search for go.mod.
} else if RootMode == NoRoot {
if cfg.ModFile != "" && !base.InGOFLAGS("-modfile") {
base.Fatalf("go: -modfile cannot be used with commands that ignore the current module")
}
- modRoot = ""
+ modRoots = nil
+ } else if inWorkspaceMode() {
+ // We're in workspace mode.
} else {
- modRoot = findModuleRoot(base.Cwd())
- if modRoot == "" {
+ if modRoot := findModuleRoot(base.Cwd()); modRoot == "" {
if cfg.ModFile != "" {
base.Fatalf("go: cannot find main module, but -modfile was set.\n\t-modfile cannot be used to set the module root directory.")
}
@@ -198,11 +362,12 @@
// will find it and get modules when they're not expecting them.
// It's a bit of a peculiar thing to disallow but quite mysterious
// when it happens. See golang.org/issue/26708.
- modRoot = ""
fmt.Fprintf(os.Stderr, "go: warning: ignoring go.mod in system temp root %v\n", os.TempDir())
if !mustUseModules {
return
}
+ } else {
+ modRoots = []string{modRoot}
}
}
if cfg.ModFile != "" && !strings.HasSuffix(cfg.ModFile, ".mod") {
@@ -212,6 +377,9 @@
// We're in module mode. Set any global variables that need to be set.
cfg.ModulesEnabled = true
setDefaultBuildMod()
+ _ = TODOWorkspaces("In workspace mode, mod will not be readonly for go mod download," +
+ "verify, graph, and why. Implement support for go mod download and add test cases" +
+ "to ensure verify, graph, and why work properly.")
list := filepath.SplitList(cfg.BuildContext.GOPATH)
if len(list) == 0 || list[0] == "" {
base.Fatalf("missing $GOPATH")
@@ -221,7 +389,16 @@
base.Fatalf("$GOPATH/go.mod exists but should not")
}
- if modRoot == "" {
+ if inWorkspaceMode() {
+ var err error
+ workFileGoVersion, modRoots, err = loadWorkFile(workFilePath)
+ if err != nil {
+ base.Fatalf("reading go.work: %v", err)
+ }
+ _ = TODOWorkspaces("Support falling back to individual module go.sum " +
+ "files for sums not in the workspace sum file.")
+ modfetch.GoSumFile = workFilePath + ".sum"
+ } else if modRoots == nil {
// We're in module mode, but not inside a module.
//
// Commands like 'go build', 'go run', 'go list' have no go.mod file to
@@ -240,8 +417,7 @@
//
// See golang.org/issue/32027.
} else {
- modfetch.GoSumFile = strings.TrimSuffix(ModFilePath(), ".mod") + ".sum"
- search.SetModRoot(modRoot)
+ modfetch.GoSumFile = strings.TrimSuffix(modFilePath(modRoots[0]), ".mod") + ".sum"
}
}
@@ -255,7 +431,7 @@
// be called until the command is installed and flags are parsed. Instead of
// calling Init and Enabled, the main package can call this function.
func WillBeEnabled() bool {
- if modRoot != "" || cfg.ModulesEnabled {
+ if modRoots != nil || cfg.ModulesEnabled {
// Already enabled.
return true
}
@@ -297,16 +473,18 @@
// (usually through MustModRoot).
func Enabled() bool {
Init()
- return modRoot != "" || cfg.ModulesEnabled
+ return modRoots != nil || cfg.ModulesEnabled
}
-// ModRoot returns the root of the main module.
-// It calls base.Fatalf if there is no main module.
-func ModRoot() string {
- if !HasModRoot() {
- die()
+func VendorDir() string {
+ return filepath.Join(MainModules.ModRoot(MainModules.mustGetSingleMainModule()), "vendor")
+}
+
+func inWorkspaceMode() bool {
+ if !initialized {
+ panic("inWorkspaceMode called before modload.Init called")
}
- return modRoot
+ return workFilePath != ""
}
// HasModRoot reports whether a main module is present.
@@ -314,17 +492,27 @@
// does not require a main module.
func HasModRoot() bool {
Init()
- return modRoot != ""
+ return modRoots != nil
}
-// ModFilePath returns the effective path of the go.mod file. Normally, this
-// "go.mod" in the directory returned by ModRoot, but the -modfile flag may
-// change its location. ModFilePath calls base.Fatalf if there is no main
-// module, even if -modfile is set.
-func ModFilePath() string {
+// MustHaveModRoot checks that a main module or main modules are present,
+// and calls base.Fatalf if there are no main modules.
+func MustHaveModRoot() {
+ Init()
if !HasModRoot() {
die()
}
+}
+
+// ModFilePath returns the path that would be used for the go.mod
+// file, if in module mode. ModFilePath calls base.Fatalf if there is no main
+// module, even if -modfile is set.
+func ModFilePath() string {
+ MustHaveModRoot()
+ return modFilePath(findModuleRoot(base.Cwd()))
+}
+
+func modFilePath(modRoot string) string {
if cfg.ModFile != "" {
return cfg.ModFile
}
@@ -365,6 +553,35 @@
var errGoModDirty error = goModDirtyError{}
+func loadWorkFile(path string) (goVersion string, modRoots []string, err error) {
+ _ = TODOWorkspaces("Clean up and write back the go.work file: add module paths for workspace modules.")
+ workDir := filepath.Dir(path)
+ workData, err := lockedfile.Read(path)
+ if err != nil {
+ return "", nil, err
+ }
+ wf, err := modfile.ParseWork(path, workData, nil)
+ if err != nil {
+ return "", nil, err
+ }
+ if wf.Go != nil {
+ goVersion = wf.Go.Version
+ }
+ seen := map[string]bool{}
+ for _, d := range wf.Directory {
+ modRoot := d.Path
+ if !filepath.IsAbs(modRoot) {
+ modRoot = filepath.Join(workDir, modRoot)
+ }
+ if seen[modRoot] {
+ return "", nil, fmt.Errorf("path %s appears multiple times in workspace", modRoot)
+ }
+ seen[modRoot] = true
+ modRoots = append(modRoots, modRoot)
+ }
+ return goVersion, modRoots, nil
+}
+
// LoadModFile sets Target and, if there is a main module, parses the initial
// build list from its go.mod file.
//
@@ -385,7 +602,7 @@
func LoadModFile(ctx context.Context) *Requirements {
rs, needCommit := loadModFile(ctx)
if needCommit {
- commitRequirements(ctx, modFileGoVersion(), rs)
+ commitRequirements(ctx, rs)
}
return rs
}
@@ -402,87 +619,110 @@
}
Init()
- if modRoot == "" {
- Target = module.Version{Path: "command-line-arguments"}
- targetPrefix = "command-line-arguments"
+ if len(modRoots) == 0 {
+ _ = TODOWorkspaces("Instead of creating a fake module with an empty modroot, make MainModules.Len() == 0 mean that we're in module mode but not inside any module.")
+ mainModule := module.Version{Path: "command-line-arguments"}
+ MainModules = makeMainModules([]module.Version{mainModule}, []string{""}, []*modfile.File{nil}, []*modFileIndex{nil}, "")
goVersion := LatestGoVersion()
- rawGoVersion.Store(Target, goVersion)
+ rawGoVersion.Store(mainModule, goVersion)
requirements = newRequirements(modDepthFromGoVersion(goVersion), nil, nil)
return requirements, false
}
- gomod := ModFilePath()
- var data []byte
- var err error
- if gomodActual, ok := fsys.OverlayPath(gomod); ok {
- // Don't lock go.mod if it's part of the overlay.
- // On Plan 9, locking requires chmod, and we don't want to modify any file
- // in the overlay. See #44700.
- data, err = os.ReadFile(gomodActual)
- } else {
- data, err = lockedfile.Read(gomodActual)
- }
- if err != nil {
- base.Fatalf("go: %v", err)
- }
-
- var fixed bool
- f, err := modfile.Parse(gomod, data, fixVersion(ctx, &fixed))
- if err != nil {
- // Errors returned by modfile.Parse begin with file:line.
- base.Fatalf("go: errors parsing go.mod:\n%s\n", err)
- }
- if f.Module == nil {
- // No module declaration. Must add module path.
- base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
- }
-
- modFile = f
- initTarget(f.Module.Mod)
- index = indexModFile(data, f, fixed)
-
- if err := module.CheckImportPath(f.Module.Mod.Path); err != nil {
- if pathErr, ok := err.(*module.InvalidPathError); ok {
- pathErr.Kind = "module"
+ var modFiles []*modfile.File
+ var mainModules []module.Version
+ var indices []*modFileIndex
+ for _, modroot := range modRoots {
+ gomod := modFilePath(modroot)
+ var data []byte
+ var err error
+ if gomodActual, ok := fsys.OverlayPath(gomod); ok {
+ // Don't lock go.mod if it's part of the overlay.
+ // On Plan 9, locking requires chmod, and we don't want to modify any file
+ // in the overlay. See #44700.
+ data, err = os.ReadFile(gomodActual)
+ } else {
+ data, err = lockedfile.Read(gomodActual)
}
- base.Fatalf("go: %v", err)
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
+
+ var fixed bool
+ f, err := modfile.Parse(gomod, data, fixVersion(ctx, &fixed))
+ if err != nil {
+ // Errors returned by modfile.Parse begin with file:line.
+ base.Fatalf("go: errors parsing go.mod:\n%s\n", err)
+ }
+ if f.Module == nil {
+ // No module declaration. Must add module path.
+ base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
+ }
+
+ modFiles = append(modFiles, f)
+ mainModule := f.Module.Mod
+ mainModules = append(mainModules, mainModule)
+ indices = append(indices, indexModFile(data, f, mainModule, fixed))
+
+ if err := module.CheckImportPath(f.Module.Mod.Path); err != nil {
+ if pathErr, ok := err.(*module.InvalidPathError); ok {
+ pathErr.Kind = "module"
+ }
+ base.Fatalf("go: %v", err)
+ }
}
+ MainModules = makeMainModules(mainModules, modRoots, modFiles, indices, workFileGoVersion)
setDefaultBuildMod() // possibly enable automatic vendoring
- rs = requirementsFromModFile()
+ rs = requirementsFromModFiles(ctx, modFiles)
+
+ if inWorkspaceMode() {
+ // We don't need to do anything for vendor or update the mod file so
+ // return early.
+
+ return rs, true
+ }
+
+ mainModule := MainModules.mustGetSingleMainModule()
+
if cfg.BuildMod == "vendor" {
- readVendorList()
- checkVendorConsistency()
+ readVendorList(mainModule)
+ index := MainModules.Index(mainModule)
+ modFile := MainModules.ModFile(mainModule)
+ checkVendorConsistency(index, modFile)
rs.initVendor(vendorList)
}
+
if rs.hasRedundantRoot() {
// If any module path appears more than once in the roots, we know that the
// go.mod file needs to be updated even though we have not yet loaded any
// transitive dependencies.
+ var err error
rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
if err != nil {
base.Fatalf("go: %v", err)
}
}
- if index.goVersionV == "" {
+ if MainModules.Index(mainModule).goVersionV == "" {
// TODO(#45551): Do something more principled instead of checking
// cfg.CmdName directly here.
if cfg.BuildMod == "mod" && cfg.CmdName != "mod graph" && cfg.CmdName != "mod why" {
- addGoStmt(LatestGoVersion())
+ addGoStmt(MainModules.ModFile(mainModule), mainModule, LatestGoVersion())
if go117EnableLazyLoading {
// We need to add a 'go' version to the go.mod file, but we must assume
// that its existing contents match something between Go 1.11 and 1.16.
// Go 1.11 through 1.16 have eager requirements, but the latest Go
// version uses lazy requirements instead — so we need to cnvert the
// requirements to be lazy.
+ var err error
rs, err = convertDepth(ctx, rs, lazy)
if err != nil {
base.Fatalf("go: %v", err)
}
}
} else {
- rawGoVersion.Store(Target, modFileGoVersion())
+ rawGoVersion.Store(mainModule, modFileGoVersion(MainModules.ModFile(mainModule)))
}
}
@@ -500,9 +740,10 @@
// exactly the same as in the legacy configuration (for example, we can't get
// packages at multiple versions from the same module).
func CreateModFile(ctx context.Context, modPath string) {
- modRoot = base.Cwd()
+ modRoot := base.Cwd()
+ modRoots = []string{modRoot}
Init()
- modFilePath := ModFilePath()
+ modFilePath := modFilePath(modRoot)
if _, err := fsys.Stat(modFilePath); err == nil {
base.Fatalf("go: %s already exists", modFilePath)
}
@@ -533,12 +774,12 @@
}
fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", modPath)
- modFile = new(modfile.File)
+ modFile := new(modfile.File)
modFile.AddModuleStmt(modPath)
- initTarget(modFile.Module.Mod)
- addGoStmt(LatestGoVersion()) // Add the go directive before converted module requirements.
+ MainModules = makeMainModules([]module.Version{modFile.Module.Mod}, []string{modRoot}, []*modfile.File{modFile}, []*modFileIndex{nil}, "")
+ addGoStmt(modFile, modFile.Module.Mod, LatestGoVersion()) // Add the go directive before converted module requirements.
- convertedFrom, err := convertLegacyConfig(modPath)
+ convertedFrom, err := convertLegacyConfig(modFile, modRoot)
if convertedFrom != "" {
fmt.Fprintf(os.Stderr, "go: copying requirements from %s\n", base.ShortPath(convertedFrom))
}
@@ -546,12 +787,12 @@
base.Fatalf("go: %v", err)
}
- rs := requirementsFromModFile()
+ rs := requirementsFromModFiles(ctx, []*modfile.File{modFile})
rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
if err != nil {
base.Fatalf("go: %v", err)
}
- commitRequirements(ctx, modFileGoVersion(), rs)
+ commitRequirements(ctx, rs)
// Suggest running 'go mod tidy' unless the project is empty. Even if we
// imported all the correct requirements above, we're probably missing
@@ -577,6 +818,24 @@
}
}
+// CreateWorkFile initializes a new workspace by creating a go.work file.
+func CreateWorkFile(ctx context.Context, workFile string, modDirs []string) {
+ _ = TODOWorkspaces("Report an error if the file already exists.")
+
+ goV := LatestGoVersion() // Use current Go version by default
+ workF := new(modfile.WorkFile)
+ workF.Syntax = new(modfile.FileSyntax)
+ workF.AddGoStmt(goV)
+
+ for _, dir := range modDirs {
+ _ = TODOWorkspaces("Add the module path of the module.")
+ workF.AddDirectory(dir, "")
+ }
+
+ data := modfile.Format(workF.Syntax)
+ lockedfile.Write(workFile, bytes.NewReader(data), 0644)
+}
+
// fixVersion returns a modfile.VersionFixer implemented using the Query function.
//
// It resolves commit hashes and branch names to versions,
@@ -639,49 +898,88 @@
allowMissingModuleImports = true
}
-// initTarget sets Target and associated variables according to modFile,
-func initTarget(m module.Version) {
- Target = m
- targetPrefix = m.Path
-
- if rel := search.InDir(base.Cwd(), cfg.GOROOTsrc); rel != "" {
- targetInGorootSrc = true
- if m.Path == "std" {
- // The "std" module in GOROOT/src is the Go standard library. Unlike other
- // modules, the packages in the "std" module have no import-path prefix.
- //
- // Modules named "std" outside of GOROOT/src do not receive this special
- // treatment, so it is possible to run 'go test .' in other GOROOTs to
- // test individual packages using a combination of the modified package
- // and the ordinary standard library.
- // (See https://golang.org/issue/30756.)
- targetPrefix = ""
+// makeMainModules creates a MainModuleSet and associated variables according to
+// the given main modules.
+func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile.File, indices []*modFileIndex, workFileGoVersion string) *MainModuleSet {
+ for _, m := range ms {
+ if m.Version != "" {
+ panic("mainModulesCalled with module.Version with non empty Version field: " + fmt.Sprintf("%#v", m))
}
}
-}
+ modRootContainingCWD := findModuleRoot(base.Cwd())
+ mainModules := &MainModuleSet{
+ versions: ms[:len(ms):len(ms)],
+ inGorootSrc: map[module.Version]bool{},
+ pathPrefix: map[module.Version]string{},
+ modRoot: map[module.Version]string{},
+ modFiles: map[module.Version]*modfile.File{},
+ indices: map[module.Version]*modFileIndex{},
+ workFileGoVersion: workFileGoVersion,
+ }
+ for i, m := range ms {
+ mainModules.pathPrefix[m] = m.Path
+ mainModules.modRoot[m] = rootDirs[i]
+ mainModules.modFiles[m] = modFiles[i]
+ mainModules.indices[m] = indices[i]
-// requirementsFromModFile returns the set of non-excluded requirements from
-// the global modFile.
-func requirementsFromModFile() *Requirements {
- roots := make([]module.Version, 0, len(modFile.Require))
- direct := map[string]bool{}
- for _, r := range modFile.Require {
- if index != nil && index.exclude[r.Mod] {
- if cfg.BuildMod == "mod" {
- fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
- } else {
- fmt.Fprintf(os.Stderr, "go: ignoring requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
- }
- continue
+ if mainModules.modRoot[m] == modRootContainingCWD {
+ mainModules.modContainingCWD = m
}
- roots = append(roots, r.Mod)
- if !r.Indirect {
- direct[r.Mod.Path] = true
+ if rel := search.InDir(rootDirs[i], cfg.GOROOTsrc); rel != "" {
+ mainModules.inGorootSrc[m] = true
+ if m.Path == "std" {
+ // The "std" module in GOROOT/src is the Go standard library. Unlike other
+ // modules, the packages in the "std" module have no import-path prefix.
+ //
+ // Modules named "std" outside of GOROOT/src do not receive this special
+ // treatment, so it is possible to run 'go test .' in other GOROOTs to
+ // test individual packages using a combination of the modified package
+ // and the ordinary standard library.
+ // (See https://golang.org/issue/30756.)
+ mainModules.pathPrefix[m] = ""
+ }
+ }
+ }
+ return mainModules
+}
+
+// requirementsFromModFiles returns the set of non-excluded requirements from
+// the global modFile.
+func requirementsFromModFiles(ctx context.Context, modFiles []*modfile.File) *Requirements {
+ rootCap := 0
+ for i := range modFiles {
+ rootCap += len(modFiles[i].Require)
+ }
+ roots := make([]module.Version, 0, rootCap)
+ mPathCount := make(map[string]int)
+ for _, m := range MainModules.Versions() {
+ mPathCount[m.Path] = 1
+ }
+ direct := map[string]bool{}
+ for _, modFile := range modFiles {
+ requirement:
+ for _, r := range modFile.Require {
+ // TODO(#45713): Maybe join
+ for _, mainModule := range MainModules.Versions() {
+ if index := MainModules.Index(mainModule); index != nil && index.exclude[r.Mod] {
+ if cfg.BuildMod == "mod" {
+ fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
+ } else {
+ fmt.Fprintf(os.Stderr, "go: ignoring requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
+ }
+ continue requirement
+ }
+ }
+
+ roots = append(roots, r.Mod)
+ if !r.Indirect {
+ direct[r.Mod.Path] = true
+ }
}
}
module.Sort(roots)
- rs := newRequirements(modDepthFromGoVersion(modFileGoVersion()), roots, direct)
+ rs := newRequirements(modDepthFromGoVersion(MainModules.GoVersion()), roots, direct)
return rs
}
@@ -689,6 +987,11 @@
// wasn't provided. setDefaultBuildMod may be called multiple times.
func setDefaultBuildMod() {
if cfg.BuildModExplicit {
+ if inWorkspaceMode() && cfg.BuildMod != "readonly" {
+ base.Fatalf("go: -mod may only be set to readonly when in workspace mode." +
+ "\n\tRemove the -mod flag to use the default readonly value," +
+ "\n\tor set -workfile=off to disable workspace mode.")
+ }
// Don't override an explicit '-mod=' argument.
return
}
@@ -713,7 +1016,7 @@
cfg.BuildMod = "readonly"
return
}
- if modRoot == "" {
+ if modRoots == nil {
if allowMissingModuleImports {
cfg.BuildMod = "mod"
} else {
@@ -722,31 +1025,38 @@
return
}
- if fi, err := fsys.Stat(filepath.Join(modRoot, "vendor")); err == nil && fi.IsDir() {
- modGo := "unspecified"
- if index != nil && index.goVersionV != "" {
- if semver.Compare(index.goVersionV, "v1.14") >= 0 {
- // The Go version is at least 1.14, and a vendor directory exists.
- // Set -mod=vendor by default.
- cfg.BuildMod = "vendor"
- cfg.BuildModReason = "Go version in go.mod is at least 1.14 and vendor directory exists."
- return
- } else {
- modGo = index.goVersionV[1:]
+ if len(modRoots) == 1 {
+ index := MainModules.GetSingleIndexOrNil()
+ if fi, err := fsys.Stat(filepath.Join(modRoots[0], "vendor")); err == nil && fi.IsDir() {
+ modGo := "unspecified"
+ if index != nil && index.goVersionV != "" {
+ if semver.Compare(index.goVersionV, "v1.14") >= 0 {
+ // The Go version is at least 1.14, and a vendor directory exists.
+ // Set -mod=vendor by default.
+ cfg.BuildMod = "vendor"
+ cfg.BuildModReason = "Go version in go.mod is at least 1.14 and vendor directory exists."
+ return
+ } else {
+ modGo = index.goVersionV[1:]
+ }
}
- }
- // Since a vendor directory exists, we should record why we didn't use it.
- // This message won't normally be shown, but it may appear with import errors.
- cfg.BuildModReason = fmt.Sprintf("Go version in go.mod is %s, so vendor directory was not used.", modGo)
+ // Since a vendor directory exists, we should record why we didn't use it.
+ // This message won't normally be shown, but it may appear with import errors.
+ cfg.BuildModReason = fmt.Sprintf("Go version in go.mod is %s, so vendor directory was not used.", modGo)
+ }
}
cfg.BuildMod = "readonly"
}
+func mustHaveCompleteRequirements() bool {
+ return cfg.BuildMod != "mod" && !inWorkspaceMode()
+}
+
// convertLegacyConfig imports module requirements from a legacy vendoring
// configuration file, if one is present.
-func convertLegacyConfig(modPath string) (from string, err error) {
+func convertLegacyConfig(modFile *modfile.File, modRoot string) (from string, err error) {
noneSelected := func(path string) (version string) { return "none" }
queryPackage := func(path, rev string) (module.Version, error) {
pkgMods, modOnly, err := QueryPattern(context.Background(), path, rev, noneSelected, nil)
@@ -777,14 +1087,14 @@
// addGoStmt adds a go directive to the go.mod file if it does not already
// include one. The 'go' version added, if any, is the latest version supported
// by this toolchain.
-func addGoStmt(v string) {
+func addGoStmt(modFile *modfile.File, mod module.Version, v string) {
if modFile.Go != nil && modFile.Go.Version != "" {
return
}
if err := modFile.AddGoStmt(v); err != nil {
base.Fatalf("go: internal error: %v", err)
}
- rawGoVersion.Store(Target, v)
+ rawGoVersion.Store(mod, v)
}
// LatestGoVersion returns the latest version of the Go language supported by
@@ -835,7 +1145,7 @@
".git/config",
}
-func findModuleRoot(dir string) (root string) {
+func findModuleRoot(dir string) (roots string) {
if dir == "" {
panic("dir not set")
}
@@ -855,6 +1165,32 @@
return ""
}
+func findWorkspaceFile(dir string) (root string) {
+ if dir == "" {
+ panic("dir not set")
+ }
+ dir = filepath.Clean(dir)
+
+ // Look for enclosing go.mod.
+ for {
+ f := filepath.Join(dir, "go.work")
+ if fi, err := fsys.Stat(f); err == nil && !fi.IsDir() {
+ return f
+ }
+ d := filepath.Dir(dir)
+ if d == dir {
+ break
+ }
+ if d == cfg.GOROOT {
+ _ = TODOWorkspaces("If we end up checking in a go.work file to GOROOT/src," +
+ "remove this case.")
+ return "" // As a special case, don't cross GOROOT to find a go.work file.
+ }
+ dir = d
+ }
+ return ""
+}
+
func findAltConfig(dir string) (root, name string) {
if dir == "" {
panic("dir not set")
@@ -1000,12 +1336,13 @@
if !allowWriteGoMod {
panic("WriteGoMod called while disallowed")
}
- commitRequirements(ctx, modFileGoVersion(), LoadModFile(ctx))
+ commitRequirements(ctx, LoadModFile(ctx))
}
// commitRequirements writes sets the global requirements variable to rs and
// writes its contents back to the go.mod file on disk.
-func commitRequirements(ctx context.Context, goVersion string, rs *Requirements) {
+// goVersion, if non-empty, is used to set the version on the go.mod file.
+func commitRequirements(ctx context.Context, rs *Requirements) {
requirements = rs
if !allowWriteGoMod {
@@ -1013,10 +1350,22 @@
return
}
- if modRoot == "" {
+ if inWorkspaceMode() {
+ // go.mod files aren't updated in workspace mode, but we still want to
+ // update the go.work.sum file.
+ if err := modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
+ base.Fatalf("go: %v", err)
+ }
+ return
+ }
+
+ if MainModules.Len() != 1 || MainModules.ModRoot(MainModules.Versions()[0]) == "" {
// We aren't in a module, so we don't have anywhere to write a go.mod file.
return
}
+ mainModule := MainModules.mustGetSingleMainModule()
+ modFilePath := modFilePath(MainModules.ModRoot(mainModule))
+ modFile := MainModules.ModFile(mainModule)
var list []*modfile.Require
for _, m := range rs.rootModules {
@@ -1025,16 +1374,17 @@
Indirect: !rs.direct[m.Path],
})
}
- if goVersion != "" {
- modFile.AddGoStmt(goVersion)
+ if modFile.Go == nil || modFile.Go.Version == "" {
+ modFile.AddGoStmt(modFileGoVersion(modFile))
}
- if semver.Compare("v"+modFileGoVersion(), separateIndirectVersionV) < 0 {
+ if semver.Compare("v"+modFileGoVersion(modFile), separateIndirectVersionV) < 0 {
modFile.SetRequire(list)
} else {
modFile.SetRequireSeparateIndirect(list)
}
modFile.Cleanup()
+ index := MainModules.GetSingleIndexOrNil()
dirty := index.modFileIsDirty(modFile)
if dirty && cfg.BuildMod != "mod" {
// If we're about to fail due to -mod=readonly,
@@ -1048,12 +1398,13 @@
// Don't write go.mod, but write go.sum in case we added or trimmed sums.
// 'go mod init' shouldn't write go.sum, since it will be incomplete.
if cfg.CmdName != "mod init" {
- modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums))
+ if err := modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
+ base.Fatalf("go: %v", err)
+ }
}
return
}
- gomod := ModFilePath()
- if _, ok := fsys.OverlayPath(gomod); ok {
+ if _, ok := fsys.OverlayPath(modFilePath); ok {
if dirty {
base.Fatalf("go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay")
}
@@ -1066,12 +1417,14 @@
}
defer func() {
// At this point we have determined to make the go.mod file on disk equal to new.
- index = indexModFile(new, modFile, false)
+ MainModules.SetIndex(mainModule, indexModFile(new, modFile, mainModule, false))
// Update go.sum after releasing the side lock and refreshing the index.
// 'go mod init' shouldn't write go.sum, since it will be incomplete.
if cfg.CmdName != "mod init" {
- modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums))
+ if err := modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
+ base.Fatalf("go: %v", err)
+ }
}
}()
@@ -1083,7 +1436,7 @@
errNoChange := errors.New("no update needed")
- err = lockedfile.Transform(ModFilePath(), func(old []byte) ([]byte, error) {
+ err = lockedfile.Transform(modFilePath, func(old []byte) ([]byte, error) {
if bytes.Equal(old, new) {
// The go.mod file is already equal to new, possibly as the result of some
// other process.
@@ -1142,7 +1495,8 @@
for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) {
if v, ok := rs.rootSelected(prefix); ok && v != "none" {
m := module.Version{Path: prefix, Version: v}
- keep[resolveReplacement(m)] = true
+ r, _ := resolveReplacement(m)
+ keep[r] = true
}
}
continue
@@ -1153,7 +1507,8 @@
for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) {
if v := mg.Selected(prefix); v != "none" {
m := module.Version{Path: prefix, Version: v}
- keep[resolveReplacement(m)] = true
+ r, _ := resolveReplacement(m)
+ keep[r] = true
}
}
}
@@ -1165,7 +1520,7 @@
// Save sums for the root modules (or their replacements), but don't
// incur the cost of loading the graph just to find and retain the sums.
for _, m := range rs.rootModules {
- r := resolveReplacement(m)
+ r, _ := resolveReplacement(m)
keep[modkey(r)] = true
if which == addBuildListZipSums {
keep[r] = true
@@ -1178,13 +1533,15 @@
// The requirements from m's go.mod file are present in the module graph,
// so they are relevant to the MVS result regardless of whether m was
// actually selected.
- keep[modkey(resolveReplacement(m))] = true
+ r, _ := resolveReplacement(m)
+ keep[modkey(r)] = true
}
})
if which == addBuildListZipSums {
for _, m := range mg.BuildList() {
- keep[resolveReplacement(m)] = true
+ r, _ := resolveReplacement(m)
+ keep[r] = true
}
}
}
diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go
index ccdeb9b..9c5018f 100644
--- a/src/cmd/go/internal/modload/list.go
+++ b/src/cmd/go/internal/modload/list.go
@@ -72,14 +72,18 @@
}
if err == nil {
- commitRequirements(ctx, modFileGoVersion(), rs)
+ commitRequirements(ctx, rs)
}
return mods, err
}
func listModules(ctx context.Context, rs *Requirements, args []string, mode ListMode) (_ *Requirements, mods []*modinfo.ModulePublic, mgErr error) {
if len(args) == 0 {
- return rs, []*modinfo.ModulePublic{moduleInfo(ctx, rs, Target, mode)}, nil
+ var ms []*modinfo.ModulePublic
+ for _, m := range MainModules.Versions() {
+ ms = append(ms, moduleInfo(ctx, rs, m, mode))
+ }
+ return rs, ms, nil
}
needFullGraph := false
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index b54f670..efe6ad1 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -274,7 +274,9 @@
// If we're outside of a module, ensure that the failure mode
// indicates that.
- ModRoot()
+ if !HasModRoot() {
+ die()
+ }
if ld != nil {
m.AddError(err)
@@ -306,7 +308,7 @@
// The initial roots are the packages in the main module.
// loadFromRoots will expand that to "all".
m.Errs = m.Errs[:0]
- matchPackages(ctx, m, opts.Tags, omitStd, []module.Version{Target})
+ matchPackages(ctx, m, opts.Tags, omitStd, MainModules.Versions())
} else {
// Starting with the packages in the main module,
// enumerate the full list of "all".
@@ -401,13 +403,21 @@
// loaded.requirements, but here we may have also loaded (and want to
// preserve checksums for) additional entities from compatRS, which are
// only needed for compatibility with ld.TidyCompatibleVersion.
- modfetch.WriteGoSum(keep)
+ if err := modfetch.WriteGoSum(keep, mustHaveCompleteRequirements()); err != nil {
+ base.Fatalf("go: %v", err)
+ }
+ }
+
+ // Update the go.mod file's Go version if necessary.
+ modFile := MainModules.ModFile(MainModules.mustGetSingleMainModule())
+ if ld.GoVersion != "" {
+ modFile.AddGoStmt(ld.GoVersion)
}
}
// Success! Update go.mod and go.sum (if needed) and return the results.
loaded = ld
- commitRequirements(ctx, loaded.GoVersion, loaded.requirements)
+ commitRequirements(ctx, loaded.requirements)
for _, pkg := range ld.pkgs {
if !pkg.isTest() {
@@ -436,14 +446,23 @@
if !filepath.IsAbs(dir) {
absDir = filepath.Join(base.Cwd(), dir)
}
- if search.InDir(absDir, cfg.GOROOTsrc) == "" && search.InDir(absDir, ModRoot()) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
+
+ modRoot := findModuleRoot(absDir)
+ found := false
+ for _, mod := range MainModules.Versions() {
+ if MainModules.ModRoot(mod) == modRoot {
+ found = true
+ break
+ }
+ }
+ if !found && search.InDir(absDir, cfg.GOROOTsrc) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
m.Dirs = []string{}
m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir)))
return
}
}
- m.MatchDirs()
+ m.MatchDirs(modRoots)
}
// resolveLocalPackage resolves a filesystem path to a package path.
@@ -485,49 +504,69 @@
}
}
- if modRoot != "" && absDir == modRoot {
- if absDir == cfg.GOROOTsrc {
- return "", errPkgIsGorootSrc
+ for _, mod := range MainModules.Versions() {
+ modRoot := MainModules.ModRoot(mod)
+ if modRoot != "" && absDir == modRoot {
+ if absDir == cfg.GOROOTsrc {
+ return "", errPkgIsGorootSrc
+ }
+ return MainModules.PathPrefix(mod), nil
}
- return targetPrefix, nil
}
// Note: The checks for @ here are just to avoid misinterpreting
// the module cache directories (formerly GOPATH/src/mod/foo@v1.5.2/bar).
// It's not strictly necessary but helpful to keep the checks.
- if modRoot != "" && strings.HasPrefix(absDir, modRoot+string(filepath.Separator)) && !strings.Contains(absDir[len(modRoot):], "@") {
- suffix := filepath.ToSlash(absDir[len(modRoot):])
- if strings.HasPrefix(suffix, "/vendor/") {
- if cfg.BuildMod != "vendor" {
- return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
+ var pkgNotFoundErr error
+ pkgNotFoundLongestPrefix := ""
+ for _, mainModule := range MainModules.Versions() {
+ modRoot := MainModules.ModRoot(mainModule)
+ if modRoot != "" && strings.HasPrefix(absDir, modRoot+string(filepath.Separator)) && !strings.Contains(absDir[len(modRoot):], "@") {
+ suffix := filepath.ToSlash(absDir[len(modRoot):])
+ if strings.HasPrefix(suffix, "/vendor/") {
+ if cfg.BuildMod != "vendor" {
+ return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
+ }
+
+ readVendorList(mainModule)
+ pkg := strings.TrimPrefix(suffix, "/vendor/")
+ if _, ok := vendorPkgModule[pkg]; !ok {
+ return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
+ }
+ return pkg, nil
}
- readVendorList()
- pkg := strings.TrimPrefix(suffix, "/vendor/")
- if _, ok := vendorPkgModule[pkg]; !ok {
- return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
+ mainModulePrefix := MainModules.PathPrefix(mainModule)
+ if mainModulePrefix == "" {
+ pkg := strings.TrimPrefix(suffix, "/")
+ if pkg == "builtin" {
+ // "builtin" is a pseudo-package with a real source file.
+ // It's not included in "std", so it shouldn't resolve from "."
+ // within module "std" either.
+ return "", errPkgIsBuiltin
+ }
+ return pkg, nil
+ }
+
+ 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
}
-
- if targetPrefix == "" {
- pkg := strings.TrimPrefix(suffix, "/")
- if pkg == "builtin" {
- // "builtin" is a pseudo-package with a real source file.
- // It's not included in "std", so it shouldn't resolve from "."
- // within module "std" either.
- return "", errPkgIsBuiltin
- }
- return pkg, nil
- }
-
- pkg := targetPrefix + suffix
- if _, ok, err := dirInModule(pkg, targetPrefix, modRoot, true); err != nil {
- return "", err
- } else if !ok {
- return "", &PackageNotInModuleError{Mod: Target, Pattern: pkg}
- }
- return pkg, nil
+ }
+ if pkgNotFoundErr != nil {
+ return "", pkgNotFoundErr
}
if sub := search.InDir(absDir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") {
@@ -557,10 +596,10 @@
tryMod := func(m module.Version) (string, bool) {
var root string
var err error
- if repl := Replacement(m); repl.Path != "" && repl.Version == "" {
+ if repl, replModRoot := Replacement(m); repl.Path != "" && repl.Version == "" {
root = repl.Path
if !filepath.IsAbs(root) {
- root = filepath.Join(ModRoot(), root)
+ root = filepath.Join(replModRoot, root)
}
} else if repl.Path != "" {
root, err = modfetch.DownloadDir(repl)
@@ -645,14 +684,14 @@
return roots
},
})
- commitRequirements(ctx, loaded.GoVersion, loaded.requirements)
+ commitRequirements(ctx, loaded.requirements)
}
// DirImportPath returns the effective import path for dir,
-// provided it is within the main module, or else returns ".".
-func DirImportPath(ctx context.Context, dir string) string {
+// provided it is within a main module, or else returns ".".
+func (mms *MainModuleSet) DirImportPath(ctx context.Context, dir string) (path string, m module.Version) {
if !HasModRoot() {
- return "."
+ return ".", module.Version{}
}
LoadModFile(ctx) // Sets targetPrefix.
@@ -662,17 +701,32 @@
dir = filepath.Clean(dir)
}
- if dir == modRoot {
- return targetPrefix
- }
- if strings.HasPrefix(dir, modRoot+string(filepath.Separator)) {
- suffix := filepath.ToSlash(dir[len(modRoot):])
- if strings.HasPrefix(suffix, "/vendor/") {
- return strings.TrimPrefix(suffix, "/vendor/")
+ var longestPrefix string
+ var longestPrefixPath string
+ var longestPrefixVersion module.Version
+ for _, v := range mms.Versions() {
+ modRoot := mms.ModRoot(v)
+ if dir == modRoot {
+ return mms.PathPrefix(v), v
}
- return targetPrefix + suffix
+ 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 "."
+ if len(longestPrefix) > 0 {
+ return longestPrefixPath, longestPrefixVersion
+ }
+
+ return ".", module.Version{}
}
// ImportMap returns the actual package import path
@@ -894,10 +948,7 @@
if pkg.mod.Path == "" {
return false // loaded from the standard library, not a module
}
- if pkg.mod.Path == Target.Path {
- return false // loaded from the main module.
- }
- return true
+ return !MainModules.Contains(pkg.mod.Path)
}
var errMissing = errors.New("cannot find package")
@@ -915,7 +966,7 @@
}
if ld.GoVersion == "" {
- ld.GoVersion = modFileGoVersion()
+ ld.GoVersion = MainModules.GoVersion()
if ld.Tidy && semver.Compare("v"+ld.GoVersion, "v"+LatestGoVersion()) > 0 {
ld.errorf("go mod tidy: go.mod file indicates go %s, but maximum supported version is %s\n", ld.GoVersion, LatestGoVersion())
@@ -1168,7 +1219,7 @@
}
for _, pkg := range ld.pkgs {
- if pkg.mod != Target {
+ if pkg.mod.Version != "" || !MainModules.Contains(pkg.mod.Path) {
continue
}
for _, dep := range pkg.imports {
@@ -1327,6 +1378,15 @@
var err error
mod, err = queryImport(ctx, pkg.path, ld.requirements)
if err != nil {
+ var ime *ImportMissingError
+ if errors.As(err, &ime) {
+ for curstack := pkg.stack; curstack != nil; curstack = curstack.stack {
+ if MainModules.Contains(curstack.mod.Path) {
+ ime.ImportingMainModule = curstack.mod
+ break
+ }
+ }
+ }
// pkg.err was already non-nil, so we can reasonably attribute the error
// for pkg to either the original error or the one returned by
// queryImport. The existing error indicates only that we couldn't find
@@ -1425,7 +1485,7 @@
// so it's ok if we call it more than is strictly necessary.
wantTest := false
switch {
- case ld.allPatternIsRoot && pkg.mod == Target:
+ case ld.allPatternIsRoot && MainModules.Contains(pkg.mod.Path):
// We are loading the "all" pattern, which includes packages imported by
// tests in the main module. This package is in the main module, so we
// need to identify the imports of its test even if LoadTests is not set.
@@ -1446,7 +1506,7 @@
if wantTest {
var testFlags loadPkgFlags
- if pkg.mod == Target || (ld.allClosesOverTests && new.has(pkgInAll)) {
+ if MainModules.Contains(pkg.mod.Path) || (ld.allClosesOverTests && new.has(pkgInAll)) {
// Tests of packages in the main module are in "all", in the sense that
// they cause the packages they import to also be in "all". So are tests
// of packages in "all" if "all" closes over test dependencies.
@@ -1593,7 +1653,7 @@
if pkg.dir == "" {
return
}
- if pkg.mod == Target {
+ if MainModules.Contains(pkg.mod.Path) {
// Go ahead and mark pkg as in "all". This provides the invariant that a
// package that is *only* imported by other packages in "all" is always
// marked as such before loading its imports.
@@ -1698,13 +1758,14 @@
}
if str.HasPathPrefix(parentPath, "cmd") {
- if !ld.VendorModulesInGOROOTSrc || Target.Path != "cmd" {
+ if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("cmd") {
vendorPath := pathpkg.Join("cmd", "vendor", path)
+
if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
return vendorPath
}
}
- } else if !ld.VendorModulesInGOROOTSrc || Target.Path != "std" || str.HasPathPrefix(parentPath, "vendor") {
+ } else if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("std") || str.HasPathPrefix(parentPath, "vendor") {
// If we are outside of the 'std' module, resolve imports from within 'std'
// to the vendor directory.
//
@@ -1753,7 +1814,7 @@
firstPath := map[module.Version]string{}
for _, mod := range mods {
- src := resolveReplacement(mod)
+ src, _ := resolveReplacement(mod)
if prev, ok := firstPath[src]; !ok {
firstPath[src] = mod.Path
} else if prev != mod.Path {
@@ -1781,7 +1842,7 @@
fmt.Fprintln(os.Stderr)
goFlag := ""
- if ld.GoVersion != modFileGoVersion() {
+ if ld.GoVersion != MainModules.GoVersion() {
goFlag = " -go=" + ld.GoVersion
}
diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go
index 03e02e7..4638699 100644
--- a/src/cmd/go/internal/modload/modfile.go
+++ b/src/cmd/go/internal/modload/modfile.go
@@ -56,11 +56,9 @@
go117LazyTODO = false
)
-var modFile *modfile.File
-
// modFileGoVersion returns the (non-empty) Go version at which the requirements
-// in modFile are intepreted, or the latest Go version if modFile is nil.
-func modFileGoVersion() string {
+// in modFile are interpreted, or the latest Go version if modFile is nil.
+func modFileGoVersion(modFile *modfile.File) string {
if modFile == nil {
return LatestGoVersion()
}
@@ -92,9 +90,6 @@
exclude map[module.Version]bool
}
-// index is the index of the go.mod file as of when it was last read or written.
-var index *modFileIndex
-
type requireMeta struct {
indirect bool
}
@@ -137,8 +132,10 @@
// CheckExclusions returns an error equivalent to ErrDisallowed if module m is
// excluded by the main module's go.mod file.
func CheckExclusions(ctx context.Context, m module.Version) error {
- if index != nil && index.exclude[m] {
- return module.VersionError(m, errExcluded)
+ for _, mainModule := range MainModules.Versions() {
+ if index := MainModules.Index(mainModule); index != nil && index.exclude[m] {
+ return module.VersionError(m, errExcluded)
+ }
}
return nil
}
@@ -170,7 +167,7 @@
// Cannot be retracted.
return nil
}
- if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
+ if repl, _ := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
// All versions of the module were replaced.
// Don't load retractions, since we'd just load the replacement.
return nil
@@ -187,11 +184,11 @@
// We load the raw file here: the go.mod file may have a different module
// path that we expect if the module or its repository was renamed.
// We still want to apply retractions to other aliases of the module.
- rm, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
+ rm, replacedFrom, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
if err != nil {
return err
}
- summary, err := rawGoModSummary(rm)
+ summary, err := rawGoModSummary(rm, replacedFrom)
if err != nil {
return err
}
@@ -289,51 +286,72 @@
// Don't look up deprecation.
return "", nil
}
- if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
+ if repl, _ := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
// All versions of the module were replaced.
// We'll look up deprecation separately for the replacement.
return "", nil
}
- latest, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
+ latest, replacedFrom, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
if err != nil {
return "", err
}
- summary, err := rawGoModSummary(latest)
+ summary, err := rawGoModSummary(latest, replacedFrom)
if err != nil {
return "", err
}
return summary.deprecated, nil
}
-// Replacement returns the replacement for mod, if any, from go.mod.
+func replacement(mod module.Version, index *modFileIndex) (fromVersion string, to module.Version, ok bool) {
+ if r, ok := index.replace[mod]; ok {
+ return mod.Version, r, true
+ }
+ if r, ok := index.replace[module.Version{Path: mod.Path}]; ok {
+ return "", r, true
+ }
+ return "", module.Version{}, false
+}
+
+// Replacement returns the replacement for mod, if any, and and the module root
+// directory of the main module containing the replace directive.
// If there is no replacement for mod, Replacement returns
// a module.Version with Path == "".
-func Replacement(mod module.Version) module.Version {
- if index != nil {
- if r, ok := index.replace[mod]; ok {
- return r
- }
- if r, ok := index.replace[module.Version{Path: mod.Path}]; ok {
- return r
+func Replacement(mod module.Version) (module.Version, string) {
+ _ = TODOWorkspaces("Support replaces in the go.work file.")
+ foundFrom, found, foundModRoot := "", module.Version{}, ""
+ for _, v := range MainModules.Versions() {
+ if index := MainModules.Index(v); index != nil {
+ if from, r, ok := replacement(mod, index); ok {
+ modRoot := MainModules.ModRoot(v)
+ if foundModRoot != "" && foundFrom != from && found != r {
+ _ = TODOWorkspaces("once the go.work file supports replaces, recommend them as a way to override conflicts")
+ base.Errorf("conflicting replacements found for %v in workspace modules defined by %v and %v",
+ mod, modFilePath(foundModRoot), modFilePath(modRoot))
+ return found, foundModRoot
+ }
+ found, foundModRoot = r, modRoot
+ }
}
}
- return module.Version{}
+ return found, foundModRoot
}
// resolveReplacement returns the module actually used to load the source code
// for m: either m itself, or the replacement for m (iff m is replaced).
-func resolveReplacement(m module.Version) module.Version {
- if r := Replacement(m); r.Path != "" {
- return r
+// It also returns the modroot of the module providing the replacement if
+// one was found.
+func resolveReplacement(m module.Version) (module.Version, string) {
+ if r, replacedFrom := Replacement(m); r.Path != "" {
+ return r, replacedFrom
}
- return m
+ return m, ""
}
// indexModFile rebuilds the index of modFile.
// If modFile has been changed since it was first read,
// modFile.Cleanup must be called before indexModFile.
-func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileIndex {
+func indexModFile(data []byte, modFile *modfile.File, mod module.Version, needsFix bool) *modFileIndex {
i := new(modFileIndex)
i.data = data
i.dataNeedsFix = needsFix
@@ -345,12 +363,12 @@
i.goVersionV = ""
if modFile.Go == nil {
- rawGoVersion.Store(Target, "")
+ rawGoVersion.Store(mod, "")
} else {
// We're going to use the semver package to compare Go versions, so go ahead
// and add the "v" prefix it expects once instead of every time.
i.goVersionV = "v" + modFile.Go.Version
- rawGoVersion.Store(Target, modFile.Go.Version)
+ rawGoVersion.Store(mod, modFile.Go.Version)
}
i.require = make(map[module.Version]requireMeta, len(modFile.Require))
@@ -490,8 +508,8 @@
//
// The caller must not modify the returned summary.
func goModSummary(m module.Version) (*modFileSummary, error) {
- if m == Target {
- panic("internal error: goModSummary called on the Target module")
+ if m.Version == "" && MainModules.Contains(m.Path) {
+ panic("internal error: goModSummary called on a main module")
}
if cfg.BuildMod == "vendor" {
@@ -506,7 +524,7 @@
// For every module other than the target,
// return the full list of modules from modules.txt.
- readVendorList()
+ readVendorList(MainModules.mustGetSingleMainModule())
// We don't know what versions the vendored module actually relies on,
// so assume that it requires everything.
@@ -514,15 +532,15 @@
return summary, nil
}
- actual := resolveReplacement(m)
- if HasModRoot() && cfg.BuildMod == "readonly" && actual.Version != "" {
+ actual, replacedFrom := resolveReplacement(m)
+ if HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() && actual.Version != "" {
key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
if !modfetch.HaveSum(key) {
suggestion := fmt.Sprintf("; to add it:\n\tgo mod download %s", m.Path)
return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
}
}
- summary, err := rawGoModSummary(actual)
+ summary, err := rawGoModSummary(actual, replacedFrom)
if err != nil {
return nil, err
}
@@ -553,27 +571,29 @@
}
}
- if index != nil && len(index.exclude) > 0 {
- // Drop any requirements on excluded versions.
- // Don't modify the cached summary though, since we might need the raw
- // summary separately.
- haveExcludedReqs := false
- for _, r := range summary.require {
- if index.exclude[r] {
- haveExcludedReqs = true
- break
- }
- }
- if haveExcludedReqs {
- s := new(modFileSummary)
- *s = *summary
- s.require = make([]module.Version, 0, len(summary.require))
+ for _, mainModule := range MainModules.Versions() {
+ if index := MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 {
+ // Drop any requirements on excluded versions.
+ // Don't modify the cached summary though, since we might need the raw
+ // summary separately.
+ haveExcludedReqs := false
for _, r := range summary.require {
- if !index.exclude[r] {
- s.require = append(s.require, r)
+ if index.exclude[r] {
+ haveExcludedReqs = true
+ break
}
}
- 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
@@ -584,18 +604,23 @@
// its dependencies.
//
// rawGoModSummary cannot be used on the Target module.
-func rawGoModSummary(m module.Version) (*modFileSummary, error) {
- if m == Target {
+
+func rawGoModSummary(m module.Version, replacedFrom string) (*modFileSummary, error) {
+ if m.Path == "" && MainModules.Contains(m.Path) {
panic("internal error: rawGoModSummary called on the Target module")
}
+ type key struct {
+ m module.Version
+ replacedFrom string
+ }
type cached struct {
summary *modFileSummary
err error
}
- c := rawGoModSummaryCache.Do(m, func() interface{} {
+ c := rawGoModSummaryCache.Do(key{m, replacedFrom}, func() interface{} {
summary := new(modFileSummary)
- name, data, err := rawGoModData(m)
+ name, data, err := rawGoModData(m, replacedFrom)
if err != nil {
return cached{nil, err}
}
@@ -645,12 +670,15 @@
//
// Unlike rawGoModSummary, rawGoModData does not cache its results in memory.
// Use rawGoModSummary instead unless you specifically need these bytes.
-func rawGoModData(m module.Version) (name string, data []byte, err error) {
+func rawGoModData(m module.Version, replacedFrom string) (name string, data []byte, err error) {
if m.Version == "" {
// m is a replacement module with only a file path.
dir := m.Path
if !filepath.IsAbs(dir) {
- dir = filepath.Join(ModRoot(), dir)
+ if replacedFrom == "" {
+ panic(fmt.Errorf("missing module root of main module providing replacement with relative path: %v", dir))
+ }
+ dir = filepath.Join(replacedFrom, dir)
}
name = filepath.Join(dir, "go.mod")
if gomodActual, ok := fsys.OverlayPath(name); ok {
@@ -685,19 +713,20 @@
//
// If the queried latest version is replaced,
// queryLatestVersionIgnoringRetractions returns the replacement.
-func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, err error) {
+func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, replacedFrom string, err error) {
type entry struct {
- latest module.Version
- err error
+ latest module.Version
+ replacedFrom string // if latest is a replacement
+ err error
}
e := latestVersionIgnoringRetractionsCache.Do(path, func() interface{} {
ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
defer span.Done()
- if repl := Replacement(module.Version{Path: path}); repl.Path != "" {
+ if repl, replFrom := Replacement(module.Version{Path: path}); repl.Path != "" {
// All versions of the module were replaced.
// No need to query.
- return &entry{latest: repl}
+ return &entry{latest: repl, replacedFrom: replFrom}
}
// Find the latest version of the module.
@@ -709,12 +738,12 @@
return &entry{err: err}
}
latest := module.Version{Path: path, Version: rev.Version}
- if repl := resolveReplacement(latest); repl.Path != "" {
- latest = repl
+ if repl, replFrom := resolveReplacement(latest); repl.Path != "" {
+ latest, replacedFrom = repl, replFrom
}
- return &entry{latest: latest}
+ return &entry{latest: latest, replacedFrom: replacedFrom}
}).(*entry)
- return e.latest, e.err
+ return e.latest, e.replacedFrom, e.err
}
var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersionIgnoringRetractions result
diff --git a/src/cmd/go/internal/modload/mvs.go b/src/cmd/go/internal/modload/mvs.go
index 87619b4..40224d5 100644
--- a/src/cmd/go/internal/modload/mvs.go
+++ b/src/cmd/go/internal/modload/mvs.go
@@ -42,7 +42,7 @@
}
func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) {
- if mod == Target {
+ if MainModules.Contains(mod.Path) {
// Use the build list as it existed when r was constructed, not the current
// global build list.
return r.roots, nil
@@ -113,7 +113,7 @@
func previousVersion(m module.Version) (module.Version, error) {
// TODO(golang.org/issue/38714): thread tracing context through MVS.
- if m == Target {
+ if MainModules.Contains(m.Path) {
return module.Version{Path: m.Path, Version: "none"}, nil
}
diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go
index d4c906a..82979fb 100644
--- a/src/cmd/go/internal/modload/query.go
+++ b/src/cmd/go/internal/modload/query.go
@@ -110,11 +110,12 @@
allowed = func(context.Context, module.Version) error { return nil }
}
- if path == Target.Path && (query == "upgrade" || query == "patch") {
- if err := allowed(ctx, Target); err != nil {
+ if MainModules.Contains(path) && (query == "upgrade" || query == "patch") {
+ m := module.Version{Path: path}
+ if err := allowed(ctx, m); err != nil {
return nil, fmt.Errorf("internal error: main module version is not allowed: %w", err)
}
- return &modfetch.RevInfo{Version: Target.Version}, nil
+ return &modfetch.RevInfo{Version: m.Version}, nil
}
if path == "std" || path == "cmd" {
@@ -512,9 +513,10 @@
pkgMods, modOnly, err := QueryPattern(ctx, pattern, query, current, allowed)
if len(pkgMods) == 0 && err == nil {
+ replacement, _ := Replacement(modOnly.Mod)
return nil, &PackageNotInModuleError{
Mod: modOnly.Mod,
- Replacement: Replacement(modOnly.Mod),
+ Replacement: replacement,
Query: query,
Pattern: pattern,
}
@@ -551,7 +553,7 @@
return m.Errs[0]
}
- var match func(mod module.Version, root string, isLocal bool) *search.Match
+ var match func(mod module.Version, roots []string, isLocal bool) *search.Match
matchPattern := search.MatchPattern(pattern)
if i := strings.Index(pattern, "..."); i >= 0 {
@@ -559,30 +561,32 @@
if base == "." {
return nil, nil, &WildcardInFirstElementError{Pattern: pattern, Query: query}
}
- match = func(mod module.Version, root string, isLocal bool) *search.Match {
+ match = func(mod module.Version, roots []string, isLocal bool) *search.Match {
m := search.NewMatch(pattern)
matchPackages(ctx, m, imports.AnyTags(), omitStd, []module.Version{mod})
return m
}
} else {
- match = func(mod module.Version, root string, isLocal bool) *search.Match {
+ match = func(mod module.Version, roots []string, isLocal bool) *search.Match {
m := search.NewMatch(pattern)
prefix := mod.Path
- if mod == Target {
- prefix = targetPrefix
+ if MainModules.Contains(mod.Path) {
+ prefix = MainModules.PathPrefix(module.Version{Path: mod.Path})
}
- if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil {
- m.AddError(err)
- } else if ok {
- m.Pkgs = []string{pattern}
+ for _, root := range roots {
+ if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil {
+ m.AddError(err)
+ } else if ok {
+ m.Pkgs = []string{pattern}
+ }
}
return m
}
}
- var queryMatchesMainModule bool
- if HasModRoot() {
- m := match(Target, modRoot, true)
+ var mainModuleMatches []module.Version
+ for _, mainModule := range MainModules.Versions() {
+ m := match(mainModule, modRoots, true)
if len(m.Pkgs) > 0 {
if query != "upgrade" && query != "patch" {
return nil, nil, &QueryMatchesPackagesInMainModuleError{
@@ -591,12 +595,12 @@
Packages: m.Pkgs,
}
}
- if err := allowed(ctx, Target); err != nil {
- return nil, nil, fmt.Errorf("internal error: package %s is in the main module (%s), but version is not allowed: %w", pattern, Target.Path, err)
+ if err := allowed(ctx, mainModule); err != nil {
+ return nil, nil, fmt.Errorf("internal error: package %s is in the main module (%s), but version is not allowed: %w", pattern, mainModule.Path, err)
}
return []QueryResult{{
- Mod: Target,
- Rev: &modfetch.RevInfo{Version: Target.Version},
+ Mod: mainModule,
+ Rev: &modfetch.RevInfo{Version: mainModule.Version},
Packages: m.Pkgs,
}}, nil, nil
}
@@ -604,15 +608,17 @@
return nil, nil, err
}
- if matchPattern(Target.Path) {
- queryMatchesMainModule = true
+ var matchesMainModule bool
+ if matchPattern(mainModule.Path) {
+ mainModuleMatches = append(mainModuleMatches, mainModule)
+ matchesMainModule = true
}
- if (query == "upgrade" || query == "patch") && queryMatchesMainModule {
- if err := allowed(ctx, Target); err == nil {
+ if (query == "upgrade" || query == "patch") && matchesMainModule {
+ if err := allowed(ctx, mainModule); err == nil {
modOnly = &QueryResult{
- Mod: Target,
- Rev: &modfetch.RevInfo{Version: Target.Version},
+ Mod: mainModule,
+ Rev: &modfetch.RevInfo{Version: mainModule.Version},
}
}
}
@@ -625,16 +631,17 @@
if len(candidateModules) == 0 {
if modOnly != nil {
return nil, modOnly, nil
- } else if queryMatchesMainModule {
- return nil, nil, &QueryMatchesMainModuleError{
- Pattern: pattern,
- Query: query,
+ } else if len(mainModuleMatches) != 0 {
+ return nil, nil, &QueryMatchesMainModulesError{
+ MainModules: mainModuleMatches,
+ Pattern: pattern,
+ Query: query,
}
} else {
return nil, nil, &PackageNotInModuleError{
- Mod: Target,
- Query: query,
- Pattern: pattern,
+ MainModules: mainModuleMatches,
+ Query: query,
+ Pattern: pattern,
}
}
}
@@ -656,15 +663,16 @@
if err != nil {
return r, err
}
- m := match(r.Mod, root, isLocal)
+ m := match(r.Mod, []string{root}, isLocal)
r.Packages = m.Pkgs
if len(r.Packages) == 0 && !matchPattern(path) {
if err := firstError(m); err != nil {
return r, err
}
+ replacement, _ := Replacement(r.Mod)
return r, &PackageNotInModuleError{
Mod: r.Mod,
- Replacement: Replacement(r.Mod),
+ Replacement: replacement,
Query: query,
Pattern: pattern,
}
@@ -684,8 +692,8 @@
return err
})
- if queryMatchesMainModule && len(results) == 0 && modOnly == nil && errors.Is(err, fs.ErrNotExist) {
- return nil, nil, &QueryMatchesMainModuleError{
+ if len(mainModuleMatches) > 0 && len(results) == 0 && modOnly == nil && errors.Is(err, fs.ErrNotExist) {
+ return nil, nil, &QueryMatchesMainModulesError{
Pattern: pattern,
Query: query,
}
@@ -701,8 +709,13 @@
func modulePrefixesExcludingTarget(path string) []string {
prefixes := make([]string, 0, strings.Count(path, "/")+1)
+ mainModulePrefixes := make(map[string]bool)
+ for _, m := range MainModules.Versions() {
+ mainModulePrefixes[m.Path] = true
+ }
+
for {
- if path != targetPrefix {
+ if !mainModulePrefixes[path] {
if _, _, ok := module.SplitPathVersion(path); ok {
prefixes = append(prefixes, path)
}
@@ -759,7 +772,7 @@
case *PackageNotInModuleError:
// Given the option, prefer to attribute “package not in module”
// to modules other than the main one.
- if noPackage == nil || noPackage.Mod == Target {
+ if noPackage == nil || MainModules.Contains(noPackage.Mod.Path) {
noPackage = rErr
}
case *NoMatchingVersionError:
@@ -878,6 +891,7 @@
// code for the versions it knows about, and thus did not have the opportunity
// to return a non-400 status code to suppress fallback.
type PackageNotInModuleError struct {
+ MainModules []module.Version
Mod module.Version
Replacement module.Version
Query string
@@ -885,11 +899,15 @@
}
func (e *PackageNotInModuleError) Error() string {
- if e.Mod == Target {
- if strings.Contains(e.Pattern, "...") {
- return fmt.Sprintf("main module (%s) does not contain packages matching %s", Target.Path, e.Pattern)
+ if len(e.MainModules) > 0 {
+ prefix := "workspace modules do"
+ 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 := ""
@@ -951,7 +969,7 @@
// we don't need to verify it in go.sum. This makes 'go list -m -u' faster
// and simpler.
func versionHasGoMod(_ context.Context, m module.Version) (bool, error) {
- _, data, err := rawGoModData(m)
+ _, data, err := rawGoModData(m, "")
if err != nil {
return false, err
}
@@ -978,14 +996,18 @@
repo = emptyRepo{path: path, err: err}
}
- if index == nil {
- return repo, err
- }
- if _, ok := index.highestReplaced[path]; !ok {
- return repo, err
+ // TODO(#45713): Join all the highestReplaced fields into a single value.
+ for _, mm := range MainModules.Versions() {
+ index := MainModules.Index(mm)
+ if index == nil {
+ continue
+ }
+ if _, ok := index.highestReplaced[path]; ok {
+ return &replacementRepo{repo: repo}, nil
+ }
}
- return &replacementRepo{repo: repo}, nil
+ return repo, err
}
// An emptyRepo is a versionRepo that contains no versions.
@@ -1024,11 +1046,13 @@
}
versions := repoVersions
- if index != nil && len(index.replace) > 0 {
- path := rr.ModulePath()
- for m, _ := range index.replace {
- if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) {
- versions = append(versions, m.Version)
+ for _, mm := range MainModules.Versions() {
+ if index := MainModules.Index(mm); index != nil && len(index.replace) > 0 {
+ path := rr.ModulePath()
+ for m, _ := range index.replace {
+ if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) {
+ versions = append(versions, m.Version)
+ }
}
}
}
@@ -1046,7 +1070,16 @@
func (rr *replacementRepo) Stat(rev string) (*modfetch.RevInfo, error) {
info, err := rr.repo.Stat(rev)
- if err == nil || index == nil || len(index.replace) == 0 {
+ if err == nil {
+ return info, err
+ }
+ var hasReplacements bool
+ for _, v := range MainModules.Versions() {
+ if index := MainModules.Index(v); index != nil && len(index.replace) > 0 {
+ hasReplacements = true
+ }
+ }
+ if !hasReplacements {
return info, err
}
@@ -1065,7 +1098,7 @@
}
}
- if r := Replacement(module.Version{Path: path, Version: v}); r.Path == "" {
+ if r, _ := Replacement(module.Version{Path: path, Version: v}); r.Path == "" {
return info, err
}
return rr.replacementStat(v)
@@ -1073,27 +1106,42 @@
func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) {
info, err := rr.repo.Latest()
+ path := rr.ModulePath()
- if index != nil {
- path := rr.ModulePath()
- if v, ok := index.highestReplaced[path]; ok {
- if v == "" {
- // The only replacement is a wildcard that doesn't specify a version, so
- // synthesize a pseudo-version with an appropriate major version and a
- // timestamp below any real timestamp. That way, if the main module is
- // used from within some other module, the user will be able to upgrade
- // the requirement to any real version they choose.
- if _, pathMajor, ok := module.SplitPathVersion(path); ok && len(pathMajor) > 0 {
- v = module.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
- } else {
- v = module.PseudoVersion("v0", "", time.Time{}, "000000000000")
+ highestReplaced, found := "", false
+ for _, mm := range MainModules.Versions() {
+ if index := MainModules.Index(mm); index != nil {
+ if v, ok := index.highestReplaced[path]; ok {
+ if !found {
+ highestReplaced, found = v, true
+ continue
+ }
+ if semver.Compare(v, highestReplaced) > 0 {
+ highestReplaced = v
}
}
+ }
+ }
- if err != nil || semver.Compare(v, info.Version) > 0 {
- return rr.replacementStat(v)
+ if found {
+ v := highestReplaced
+
+ if v == "" {
+ // The only replacement is a wildcard that doesn't specify a version, so
+ // synthesize a pseudo-version with an appropriate major version and a
+ // timestamp below any real timestamp. That way, if the main module is
+ // used from within some other module, the user will be able to upgrade
+ // the requirement to any real version they choose.
+ if _, pathMajor, ok := module.SplitPathVersion(path); ok && len(pathMajor) > 0 {
+ v = module.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
+ } else {
+ v = module.PseudoVersion("v0", "", time.Time{}, "000000000000")
}
}
+
+ if err != nil || semver.Compare(v, info.Version) > 0 {
+ return rr.replacementStat(v)
+ }
}
return info, err
@@ -1108,20 +1156,46 @@
return rev, nil
}
-// A QueryMatchesMainModuleError indicates that a query requests
+// A QueryMatchesMainModulesError indicates that a query requests
// a version of the main module that cannot be satisfied.
// (The main module's version cannot be changed.)
-type QueryMatchesMainModuleError struct {
- Pattern string
- Query string
+type QueryMatchesMainModulesError struct {
+ MainModules []module.Version
+ Pattern string
+ Query string
}
-func (e *QueryMatchesMainModuleError) Error() string {
- if e.Pattern == Target.Path {
+func (e *QueryMatchesMainModulesError) Error() string {
+ if MainModules.Contains(e.Pattern) {
return fmt.Sprintf("can't request version %q of the main module (%s)", e.Query, e.Pattern)
}
- return fmt.Sprintf("can't request version %q of pattern %q that includes the main module (%s)", e.Query, e.Pattern, Target.Path)
+ plural := ""
+ mainModulePaths := make([]string, len(e.MainModules))
+ for i := range e.MainModules {
+ mainModulePaths[i] = e.MainModules[i].Path
+ }
+ if len(e.MainModules) > 1 {
+ plural = "s"
+ }
+ return fmt.Sprintf("can't request version %q of pattern %q that includes the main module%s (%s)", e.Query, e.Pattern, plural, strings.Join(mainModulePaths, ", "))
+}
+
+// A QueryUpgradesAllError indicates that a query requests
+// an upgrade on the all pattern.
+// (The main module's version cannot be changed.)
+type QueryUpgradesAllError struct {
+ MainModules []module.Version
+ Query string
+}
+
+func (e *QueryUpgradesAllError) Error() string {
+ var plural string = ""
+ if len(e.MainModules) != 1 {
+ plural = "s"
+ }
+
+ return fmt.Sprintf("can't request version %q of pattern \"all\" that includes the main module%s", e.Query, plural)
}
// A QueryMatchesPackagesInMainModuleError indicates that a query cannot be
diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go
index 658fc6f..799c48e 100644
--- a/src/cmd/go/internal/modload/search.go
+++ b/src/cmd/go/internal/modload/search.go
@@ -131,9 +131,10 @@
}
if cfg.BuildMod == "vendor" {
- if HasModRoot() {
- walkPkgs(ModRoot(), targetPrefix, pruneGoMod|pruneVendor)
- walkPkgs(filepath.Join(ModRoot(), "vendor"), "", pruneVendor)
+ mod := MainModules.mustGetSingleMainModule()
+ if modRoot := MainModules.ModRoot(mod); modRoot != "" {
+ walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
+ walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
}
return
}
@@ -147,12 +148,12 @@
root, modPrefix string
isLocal bool
)
- if mod == Target {
- if !HasModRoot() {
+ if MainModules.Contains(mod.Path) {
+ if MainModules.ModRoot(mod) == "" {
continue // If there is no main module, we can't search in it.
}
- root = ModRoot()
- modPrefix = targetPrefix
+ root = MainModules.ModRoot(mod)
+ modPrefix = MainModules.PathPrefix(mod)
isLocal = true
} else {
var err error
diff --git a/src/cmd/go/internal/modload/vendor.go b/src/cmd/go/internal/modload/vendor.go
index 80713b0..daa5888 100644
--- a/src/cmd/go/internal/modload/vendor.go
+++ b/src/cmd/go/internal/modload/vendor.go
@@ -15,6 +15,7 @@
"cmd/go/internal/base"
+ "golang.org/x/mod/modfile"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
)
@@ -35,13 +36,13 @@
}
// readVendorList reads the list of vendored modules from vendor/modules.txt.
-func readVendorList() {
+func readVendorList(mainModule module.Version) {
vendorOnce.Do(func() {
vendorList = nil
vendorPkgModule = make(map[string]module.Version)
vendorVersion = make(map[string]string)
vendorMeta = make(map[module.Version]vendorMetadata)
- data, err := os.ReadFile(filepath.Join(ModRoot(), "vendor/modules.txt"))
+ data, err := os.ReadFile(filepath.Join(MainModules.ModRoot(mainModule), "vendor/modules.txt"))
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
base.Fatalf("go: %s", err)
@@ -134,8 +135,8 @@
// checkVendorConsistency verifies that the vendor/modules.txt file matches (if
// go 1.14) or at least does not contradict (go 1.13 or earlier) the
// requirements and replacements listed in the main module's go.mod file.
-func checkVendorConsistency() {
- readVendorList()
+func checkVendorConsistency(index *modFileIndex, modFile *modfile.File) {
+ readVendorList(MainModules.mustGetSingleMainModule())
pre114 := false
if semver.Compare(index.goVersionV, "v1.14") < 0 {
@@ -208,7 +209,7 @@
}
for _, mod := range vendorReplaced {
- r := Replacement(mod)
+ r, _ := Replacement(mod)
if r == (module.Version{}) {
vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in go.mod")
continue
@@ -219,6 +220,7 @@
}
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)
}
}
diff --git a/src/cmd/go/internal/mvs/mvs.go b/src/cmd/go/internal/mvs/mvs.go
index 6969f90..566fa4b 100644
--- a/src/cmd/go/internal/mvs/mvs.go
+++ b/src/cmd/go/internal/mvs/mvs.go
@@ -8,6 +8,7 @@
import (
"fmt"
+ "reflect"
"sort"
"sync"
@@ -85,11 +86,11 @@
// of the list are sorted by path.
//
// See https://research.swtch.com/vgo-mvs for details.
-func BuildList(target module.Version, reqs Reqs) ([]module.Version, error) {
- return buildList(target, reqs, nil)
+func BuildList(targets []module.Version, reqs Reqs) ([]module.Version, error) {
+ 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 {
if reqs.Max(v1, v2) != v1 {
return -1
@@ -102,7 +103,7 @@
var (
mu sync.Mutex
- g = NewGraph(cmp, []module.Version{target})
+ g = NewGraph(cmp, targets)
upgrades = map[module.Version]module.Version{}
errs = map[module.Version]error{} // (non-nil errors only)
)
@@ -110,7 +111,9 @@
// Explore work graph in parallel in case reqs.Required
// does high-latency network operations.
var work par.Work
- work.Add(target)
+ for _, target := range targets {
+ work.Add(target)
+ }
work.Do(10, func(item interface{}) {
m := item.(module.Version)
@@ -168,12 +171,12 @@
// The final list is the minimum version of each module found in the graph.
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.
// "" denotes the main module, which has no version. However, MVS treats
// version strings as opaque, so "" is not a special value here.
// 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
}
@@ -181,8 +184,8 @@
// Req returns the minimal requirement list for the target module,
// with the constraint that all module paths listed in base must
// appear in the returned list.
-func Req(target module.Version, base []string, reqs Reqs) ([]module.Version, error) {
- list, err := BuildList(target, reqs)
+func Req(mainModule module.Version, base []string, reqs Reqs) ([]module.Version, error) {
+ list, err := BuildList([]module.Version{mainModule}, reqs)
if err != nil {
return nil, err
}
@@ -194,7 +197,8 @@
// Compute postorder, cache requirements.
var postorder []module.Version
reqCache := map[module.Version][]module.Version{}
- reqCache[target] = nil
+ reqCache[mainModule] = nil
+
var walk func(module.Version) error
walk = func(m module.Version) error {
_, ok := reqCache[m]
@@ -273,7 +277,7 @@
// UpgradeAll returns a build list for the target module
// in which every module is upgraded to its latest version.
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 {
return target, nil
}
@@ -308,7 +312,7 @@
}
}
- 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 {
return module.Version{Path: m.Path, Version: v}, nil
}
@@ -331,7 +335,7 @@
//
// In order to generate those new requirements, we need to identify versions
// 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 {
return nil, err
}
@@ -446,7 +450,7 @@
// list with the actual versions of the downgraded modules as selected by MVS,
// instead of our initial downgrades.
// (See the downhiddenartifact and downhiddencross test cases).
- actual, err := BuildList(target, &override{
+ actual, err := BuildList([]module.Version{target}, &override{
target: target,
list: downgraded,
Reqs: reqs,
@@ -466,7 +470,7 @@
}
}
- return BuildList(target, &override{
+ return BuildList([]module.Version{target}, &override{
target: target,
list: downgraded,
Reqs: reqs,
diff --git a/src/cmd/go/internal/mvs/mvs_test.go b/src/cmd/go/internal/mvs/mvs_test.go
index 598ed66..26d004f 100644
--- a/src/cmd/go/internal/mvs/mvs_test.go
+++ b/src/cmd/go/internal/mvs/mvs_test.go
@@ -507,7 +507,7 @@
t.Fatalf("build takes one argument: %q", line)
}
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)
})
continue
diff --git a/src/cmd/go/internal/run/run.go b/src/cmd/go/internal/run/run.go
index 9e9e49e..931fdce 100644
--- a/src/cmd/go/internal/run/run.go
+++ b/src/cmd/go/internal/run/run.go
@@ -65,6 +65,7 @@
CmdRun.Run = runRun // break init loop
work.AddBuildFlags(CmdRun, work.DefaultBuildFlags)
+ base.AddWorkfileFlag(&CmdRun.Flag)
CmdRun.Flag.Var((*base.StringsFlag)(&work.ExecCmd), "exec", "")
}
@@ -73,6 +74,8 @@
}
func runRun(ctx context.Context, cmd *base.Command, args []string) {
+ modload.InitWorkfile()
+
if shouldUseOutsideModuleMode(args) {
// Set global module flags for 'go run cmd@version'.
// This must be done before modload.Init, but we need to call work.BuildInit
diff --git a/src/cmd/go/internal/search/search.go b/src/cmd/go/internal/search/search.go
index a0c806a..ebd4990 100644
--- a/src/cmd/go/internal/search/search.go
+++ b/src/cmd/go/internal/search/search.go
@@ -202,12 +202,6 @@
}
}
-var modRoot string
-
-func SetModRoot(dir string) {
- modRoot = dir
-}
-
// 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
// path, or "./", or "../". On Windows, the pattern may use slash or backslash
@@ -215,7 +209,7 @@
//
// If any errors may have caused the set of directories to be incomplete,
// MatchDirs appends those errors to m.Errs.
-func (m *Match) MatchDirs() {
+func (m *Match) MatchDirs(modRoots []string) {
m.Dirs = []string{}
if !m.IsLocal() {
m.AddError(fmt.Errorf("internal error: MatchDirs: %s is not a valid filesystem pattern", m.pattern))
@@ -253,15 +247,24 @@
// We need to preserve the ./ for pattern matching
// and in the returned import paths.
- if modRoot != "" {
+ if len(modRoots) > 1 {
abs, err := filepath.Abs(dir)
if err != nil {
m.AddError(err)
return
}
- if !hasFilepathPrefix(abs, modRoot) {
- m.AddError(fmt.Errorf("directory %s is outside module root (%s)", abs, modRoot))
- return
+ var found bool
+ for _, modRoot := range modRoots {
+ 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 @@
// ImportPaths returns the matching paths to use for the given command line.
// It calls ImportPathsQuiet and then WarnUnmatched.
-func ImportPaths(patterns []string) []*Match {
- matches := ImportPathsQuiet(patterns)
+func ImportPaths(patterns, modRoots []string) []*Match {
+ matches := ImportPathsQuiet(patterns, modRoots)
WarnUnmatched(matches)
return 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
for _, a := range CleanPatterns(patterns) {
m := NewMatch(a)
if m.IsLocal() {
- m.MatchDirs()
+ m.MatchDirs(modRoots)
// 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
diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go
index efebc12..d65f54f 100644
--- a/src/cmd/go/internal/test/test.go
+++ b/src/cmd/go/internal/test/test.go
@@ -29,6 +29,7 @@
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/lockedfile"
+ "cmd/go/internal/modload"
"cmd/go/internal/search"
"cmd/go/internal/trace"
"cmd/go/internal/work"
@@ -582,6 +583,7 @@
}
func runTest(ctx context.Context, cmd *base.Command, args []string) {
+ modload.InitWorkfile()
pkgArgs, testArgs = testFlags(args)
if cfg.DebugTrace != "" {
diff --git a/src/cmd/go/internal/test/testflag.go b/src/cmd/go/internal/test/testflag.go
index 45118cb..97a9ef3 100644
--- a/src/cmd/go/internal/test/testflag.go
+++ b/src/cmd/go/internal/test/testflag.go
@@ -28,6 +28,7 @@
func init() {
work.AddBuildFlags(CmdTest, work.OmitVFlag)
+ base.AddWorkfileFlag(&CmdTest.Flag)
cf := CmdTest.Flag
cf.BoolVar(&testC, "c", false, "")
diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go
index 0ed2389..c51dd39 100644
--- a/src/cmd/go/internal/work/build.go
+++ b/src/cmd/go/internal/work/build.go
@@ -121,6 +121,14 @@
directory, but it is not accessed. When -modfile is specified, an
alternate go.sum file is also used: its path is derived from the
-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
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
@@ -201,6 +209,7 @@
AddBuildFlags(CmdBuild, DefaultBuildFlags)
AddBuildFlags(CmdInstall, DefaultBuildFlags)
+ base.AddWorkfileFlag(&CmdBuild.Flag)
}
// Note that flags consulted by other parts of the code
@@ -364,6 +373,7 @@
var runtimeVersion = runtime.Version()
func runBuild(ctx context.Context, cmd *base.Command, args []string) {
+ modload.InitWorkfile()
BuildInit()
var b Builder
b.Init()
diff --git a/src/cmd/go/testdata/script/mod_list_command_line_arguments.txt b/src/cmd/go/testdata/script/mod_list_command_line_arguments.txt
new file mode 100644
index 0000000..fd99ae8
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_list_command_line_arguments.txt
@@ -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
\ No newline at end of file
diff --git a/src/cmd/go/testdata/script/mod_outside.txt b/src/cmd/go/testdata/script/mod_outside.txt
index 33341f7..3b45594 100644
--- a/src/cmd/go/testdata/script/mod_outside.txt
+++ b/src/cmd/go/testdata/script/mod_outside.txt
@@ -251,7 +251,7 @@
# outside std.
go run ./stdonly/stdonly.go
stdout 'path is command-line-arguments$'
-stdout 'main is command-line-arguments \(devel\)'
+stdout 'main is $'
# 'go generate' should work with file arguments.
[exec:touch] go generate ./needmod/needmod.go
diff --git a/src/cmd/go/testdata/script/modfile_flag.txt b/src/cmd/go/testdata/script/modfile_flag.txt
index baf25d3..7cce581 100644
--- a/src/cmd/go/testdata/script/modfile_flag.txt
+++ b/src/cmd/go/testdata/script/modfile_flag.txt
@@ -78,7 +78,7 @@
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.
cp go.alt.mod goaltmod
! go mod tidy -modfile=goaltmod
diff --git a/src/cmd/go/testdata/script/version.txt b/src/cmd/go/testdata/script/version.txt
index 8615a4a..f3aa57e 100644
--- a/src/cmd/go/testdata/script/version.txt
+++ b/src/cmd/go/testdata/script/version.txt
@@ -28,6 +28,13 @@
stdout '^\tpath\trsc.io/fortune'
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.
[!buildmode:pie] stop
go build -buildmode=pie -o external.exe rsc.io/fortune
diff --git a/src/cmd/go/testdata/script/work.txt b/src/cmd/go/testdata/script/work.txt
new file mode 100644
index 0000000..529c1c0
--- /dev/null
+++ b/src/cmd/go/testdata/script/work.txt
@@ -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")
+}
diff --git a/src/cmd/go/testdata/script/work_edit.txt b/src/cmd/go/testdata/script/work_edit.txt
new file mode 100644
index 0000000..0de4069
--- /dev/null
+++ b/src/cmd/go/testdata/script/work_edit.txt
@@ -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
+)
\ No newline at end of file
diff --git a/src/cmd/go/testdata/script/work_sum.txt b/src/cmd/go/testdata/script/work_sum.txt
new file mode 100644
index 0000000..99f66a4
--- /dev/null
+++ b/src/cmd/go/testdata/script/work_sum.txt
@@ -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())
+}
\ No newline at end of file
diff --git a/src/cmd/vendor/golang.org/x/mod/modfile/rule.go b/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
index 78f83fa..d6a2d38 100644
--- a/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
+++ b/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
@@ -423,68 +423,12 @@
}
case "replace":
- arrow := 2
- if len(args) >= 2 && args[1] == "=>" {
- arrow = 1
- }
- 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)
+ replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
+ if wrappederr != nil {
+ *errs = append(*errs, *wrappederr)
return
}
- s, err := parseString(&args[0])
- 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,
- })
+ f.Replace = append(f.Replace, replace)
case "retract":
rationale := parseDirectiveComment(block, line)
@@ -515,6 +459,83 @@
}
}
+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
// to errs.
//
@@ -556,6 +577,63 @@
}
}
+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
// as a directory path. Just like on the go command line, relative paths
// and rooted paths are directory paths; the rest are module paths.
@@ -1165,6 +1243,10 @@
}
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
old := module.Version{Path: oldPath, Version: oldVers}
new := module.Version{Path: newPath, Version: newVers}
@@ -1178,12 +1260,12 @@
}
var hint *Line
- for _, r := range f.Replace {
+ for _, r := range *replace {
if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
if need {
// Found replacement for old; update to use new.
r.New = new
- f.Syntax.updateLine(r.Syntax, tokens...)
+ syntax.updateLine(r.Syntax, tokens...)
need = false
continue
}
@@ -1196,7 +1278,7 @@
}
}
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
}
@@ -1282,30 +1364,36 @@
// retract directives are not de-duplicated since comments are
// meaningful, and versions may be retracted multiple times.
func (f *File) removeDups() {
+ removeDups(f.Syntax, &f.Exclude, &f.Replace)
+}
+
+func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace) {
kill := make(map[*Line]bool)
// Remove duplicate excludes.
- haveExclude := make(map[module.Version]bool)
- for _, x := range f.Exclude {
- if haveExclude[x.Mod] {
- kill[x.Syntax] = true
- continue
+ if exclude != nil {
+ haveExclude := make(map[module.Version]bool)
+ for _, x := range *exclude {
+ if haveExclude[x.Mod] {
+ kill[x.Syntax] = true
+ continue
+ }
+ haveExclude[x.Mod] = true
}
- haveExclude[x.Mod] = true
- }
- var excl []*Exclude
- for _, x := range f.Exclude {
- if !kill[x.Syntax] {
- excl = append(excl, x)
+ var excl []*Exclude
+ for _, x := range *exclude {
+ if !kill[x.Syntax] {
+ excl = append(excl, x)
+ }
}
+ *exclude = excl
}
- f.Exclude = excl
// Remove duplicate replacements.
// Later replacements take priority over earlier ones.
haveReplace := make(map[module.Version]bool)
- for i := len(f.Replace) - 1; i >= 0; i-- {
- x := f.Replace[i]
+ for i := len(*replace) - 1; i >= 0; i-- {
+ x := (*replace)[i]
if haveReplace[x.Old] {
kill[x.Syntax] = true
continue
@@ -1313,18 +1401,18 @@
haveReplace[x.Old] = true
}
var repl []*Replace
- for _, x := range f.Replace {
+ for _, x := range *replace {
if !kill[x.Syntax] {
repl = append(repl, x)
}
}
- f.Replace = repl
+ *replace = repl
// Duplicate require and retract directives are not removed.
// Drop killed statements from the syntax tree.
var stmts []Expr
- for _, stmt := range f.Syntax.Stmt {
+ for _, stmt := range syntax.Stmt {
switch stmt := stmt.(type) {
case *Line:
if kill[stmt] {
@@ -1344,7 +1432,7 @@
}
stmts = append(stmts, stmt)
}
- f.Syntax.Stmt = stmts
+ syntax.Stmt = stmts
}
// lineLess returns whether li should be sorted before lj. It sorts
diff --git a/src/cmd/vendor/golang.org/x/mod/modfile/work.go b/src/cmd/vendor/golang.org/x/mod/modfile/work.go
new file mode 100644
index 0000000..b1fabff
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/mod/modfile/work.go
@@ -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)
+}
diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt
index 1765e3e..eed06a9 100644
--- a/src/cmd/vendor/modules.txt
+++ b/src/cmd/vendor/modules.txt
@@ -28,7 +28,7 @@
## explicit; go 1.17
golang.org/x/crypto/ed25519
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
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile