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

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

Merge List:

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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -49,6 +49,8 @@ var (
ModCacheRW bool // -modcacherw flag ModCacheRW bool // -modcacherw flag
ModFile string // -modfile flag ModFile string // -modfile flag
WorkFile string // -workfile flag
WorkFileExplicit bool // whether -workfile was set explicitly
CmdName string // "build", "install", "list", "mod tidy", etc. CmdName string // "build", "install", "list", "mod tidy", etc.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -40,7 +40,7 @@ type Requirements struct {
depth modDepth depth modDepth
// rootModules is the set of module versions explicitly required by the main // rootModules is the set of module versions explicitly required by the main
// module, sorted and capped to length. It may contain duplicates, and may // modules, sorted and capped to length. It may contain duplicates, and may
// contain multiple versions for a given module path. // contain multiple versions for a given module path.
rootModules []module.Version rootModules []module.Version
maxRootVersion map[string]string maxRootVersion map[string]string
@ -99,8 +99,8 @@ var requirements *Requirements
// *Requirements before any other method. // *Requirements before any other method.
func newRequirements(depth modDepth, rootModules []module.Version, direct map[string]bool) *Requirements { func newRequirements(depth modDepth, rootModules []module.Version, direct map[string]bool) *Requirements {
for i, m := range rootModules { for i, m := range rootModules {
if m == Target { if m.Version == "" && MainModules.Contains(m.Path) {
panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is Target", i)) panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is a main module", i))
} }
if m.Path == "" || m.Version == "" { if m.Path == "" || m.Version == "" {
panic(fmt.Sprintf("bad requirement: rootModules[%v] = %v", i, m)) panic(fmt.Sprintf("bad requirement: rootModules[%v] = %v", i, m))
@ -135,9 +135,14 @@ func newRequirements(depth modDepth, rootModules []module.Version, direct map[st
func (rs *Requirements) initVendor(vendorList []module.Version) { func (rs *Requirements) initVendor(vendorList []module.Version) {
rs.graphOnce.Do(func() { rs.graphOnce.Do(func() {
mg := &ModuleGraph{ mg := &ModuleGraph{
g: mvs.NewGraph(cmpVersion, []module.Version{Target}), g: mvs.NewGraph(cmpVersion, MainModules.Versions()),
} }
if MainModules.Len() != 1 {
panic("There should be exactly one main module in Vendor mode.")
}
mainModule := MainModules.Versions()[0]
if rs.depth == lazy { if rs.depth == lazy {
// The roots of a lazy module should already include every module in the // The roots of a lazy module should already include every module in the
// vendor list, because the vendored modules are the same as those // vendor list, because the vendored modules are the same as those
@ -158,7 +163,7 @@ func (rs *Requirements) initVendor(vendorList []module.Version) {
// Now we can treat the rest of the module graph as effectively “pruned // Now we can treat the rest of the module graph as effectively “pruned
// out”, like a more aggressive version of lazy loading: in vendor mode, // out”, like a more aggressive version of lazy loading: in vendor mode,
// the root requirements *are* the complete module graph. // the root requirements *are* the complete module graph.
mg.g.Require(Target, rs.rootModules) mg.g.Require(mainModule, rs.rootModules)
} else { } else {
// The transitive requirements of the main module are not in general available // The transitive requirements of the main module are not in general available
// from the vendor directory, and we don't actually know how we got from // from the vendor directory, and we don't actually know how we got from
@ -170,7 +175,7 @@ func (rs *Requirements) initVendor(vendorList []module.Version) {
// graph, but still distinguishes between direct and indirect // graph, but still distinguishes between direct and indirect
// dependencies. // dependencies.
vendorMod := module.Version{Path: "vendor/modules.txt", Version: ""} vendorMod := module.Version{Path: "vendor/modules.txt", Version: ""}
mg.g.Require(Target, append(rs.rootModules, vendorMod)) mg.g.Require(mainModule, append(rs.rootModules, vendorMod))
mg.g.Require(vendorMod, vendorList) mg.g.Require(vendorMod, vendorList)
} }
@ -182,8 +187,8 @@ func (rs *Requirements) initVendor(vendorList []module.Version) {
// path, or the zero module.Version and ok=false if the module is not a root // path, or the zero module.Version and ok=false if the module is not a root
// dependency. // dependency.
func (rs *Requirements) rootSelected(path string) (version string, ok bool) { func (rs *Requirements) rootSelected(path string) (version string, ok bool) {
if path == Target.Path { if MainModules.Contains(path) {
return Target.Version, true return "", true
} }
if v, ok := rs.maxRootVersion[path]; ok { if v, ok := rs.maxRootVersion[path]; ok {
return v, true return v, true
@ -197,7 +202,7 @@ func (rs *Requirements) rootSelected(path string) (version string, ok bool) {
// selection. // selection.
func (rs *Requirements) hasRedundantRoot() bool { func (rs *Requirements) hasRedundantRoot() bool {
for i, m := range rs.rootModules { for i, m := range rs.rootModules {
if m.Path == Target.Path || (i > 0 && m.Path == rs.rootModules[i-1].Path) { if MainModules.Contains(m.Path) || (i > 0 && m.Path == rs.rootModules[i-1].Path) {
return true return true
} }
} }
@ -274,10 +279,17 @@ func readModGraph(ctx context.Context, depth modDepth, roots []module.Version) (
mu sync.Mutex // guards mg.g and hasError during loading mu sync.Mutex // guards mg.g and hasError during loading
hasError bool hasError bool
mg = &ModuleGraph{ mg = &ModuleGraph{
g: mvs.NewGraph(cmpVersion, []module.Version{Target}), g: mvs.NewGraph(cmpVersion, MainModules.Versions()),
} }
) )
mg.g.Require(Target, roots) for _, m := range MainModules.Versions() {
// Require all roots from all main modules.
_ = TODOWorkspaces("This flattens a level of the module graph, adding the dependencies " +
"of all main modules to a single requirements struct, and losing the information of which " +
"main module required which requirement. Rework the requirements struct and change this" +
"to reflect the structure of the main modules.")
mg.g.Require(m, roots)
}
var ( var (
loadQueue = par.NewQueue(runtime.GOMAXPROCS(0)) loadQueue = par.NewQueue(runtime.GOMAXPROCS(0))
@ -404,12 +416,14 @@ func (mg *ModuleGraph) findError() error {
} }
func (mg *ModuleGraph) allRootsSelected() bool { func (mg *ModuleGraph) allRootsSelected() bool {
roots, _ := mg.g.RequiredBy(Target) for _, mm := range MainModules.Versions() {
roots, _ := mg.g.RequiredBy(mm)
for _, m := range roots { for _, m := range roots {
if mg.Selected(m.Path) != m.Version { if mg.Selected(m.Path) != m.Version {
return false return false
} }
} }
}
return true return true
} }
@ -447,7 +461,7 @@ func LoadModGraph(ctx context.Context, goVersion string) *ModuleGraph {
base.Fatalf("go: %v", err) base.Fatalf("go: %v", err)
} }
commitRequirements(ctx, modFileGoVersion(), rs) commitRequirements(ctx, rs)
return mg return mg
} }
@ -513,7 +527,7 @@ func EditBuildList(ctx context.Context, add, mustSelect []module.Version) (chang
if err != nil { if err != nil {
return false, err return false, err
} }
commitRequirements(ctx, modFileGoVersion(), rs) commitRequirements(ctx, rs)
return changed, err return changed, err
} }
@ -546,10 +560,11 @@ type Conflict struct {
// both retain the same versions of all packages in pkgs and satisfy the // both retain the same versions of all packages in pkgs and satisfy the
// lazy loading invariants (if applicable). // lazy loading invariants (if applicable).
func tidyRoots(ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) { func tidyRoots(ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) {
mainModule := MainModules.mustGetSingleMainModule()
if rs.depth == eager { if rs.depth == eager {
return tidyEagerRoots(ctx, rs.direct, pkgs) return tidyEagerRoots(ctx, mainModule, rs.direct, pkgs)
} }
return tidyLazyRoots(ctx, rs.direct, pkgs) return tidyLazyRoots(ctx, mainModule, rs.direct, pkgs)
} }
func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) { func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) {
@ -574,10 +589,10 @@ func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements,
// To ensure that the loading process eventually converges, the caller should // To ensure that the loading process eventually converges, the caller should
// add any needed roots from the tidy root set (without removing existing untidy // add any needed roots from the tidy root set (without removing existing untidy
// roots) until the set of roots has converged. // roots) until the set of roots has converged.
func tidyLazyRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) { func tidyLazyRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
var ( var (
roots []module.Version roots []module.Version
pathIncluded = map[string]bool{Target.Path: true} pathIncluded = map[string]bool{mainModule.Path: true}
) )
// We start by adding roots for every package in "all". // We start by adding roots for every package in "all".
// //
@ -807,7 +822,7 @@ func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requiremen
// We've added or upgraded one or more roots, so load the full module // We've added or upgraded one or more roots, so load the full module
// graph so that we can update those roots to be consistent with other // graph so that we can update those roots to be consistent with other
// requirements. // requirements.
if cfg.BuildMod != "mod" { if mustHaveCompleteRequirements() {
// Our changes to the roots may have moved dependencies into or out of // Our changes to the roots may have moved dependencies into or out of
// the lazy-loading horizon, which could in turn change the selected // the lazy-loading horizon, which could in turn change the selected
// versions of other modules. (Unlike for eager modules, for lazy // versions of other modules. (Unlike for eager modules, for lazy
@ -855,7 +870,9 @@ func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requiremen
roots = make([]module.Version, 0, len(rs.rootModules)) roots = make([]module.Version, 0, len(rs.rootModules))
rootsUpgraded = false rootsUpgraded = false
inRootPaths := make(map[string]bool, len(rs.rootModules)+1) inRootPaths := make(map[string]bool, len(rs.rootModules)+1)
inRootPaths[Target.Path] = true for _, mm := range MainModules.Versions() {
inRootPaths[mm.Path] = true
}
for _, m := range rs.rootModules { for _, m := range rs.rootModules {
if inRootPaths[m.Path] { if inRootPaths[m.Path] {
// This root specifies a redundant path. We already retained the // This root specifies a redundant path. We already retained the
@ -958,7 +975,7 @@ func spotCheckRoots(ctx context.Context, rs *Requirements, mods map[module.Versi
// tidyEagerRoots returns a minimal set of root requirements that maintains the // tidyEagerRoots returns a minimal set of root requirements that maintains the
// selected version of every module that provided a package in pkgs, and // selected version of every module that provided a package in pkgs, and
// includes the selected version of every such module in direct as a root. // includes the selected version of every such module in direct as a root.
func tidyEagerRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) { func tidyEagerRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
var ( var (
keep []module.Version keep []module.Version
keptPath = map[string]bool{} keptPath = map[string]bool{}
@ -981,7 +998,7 @@ func tidyEagerRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg
} }
} }
min, err := mvs.Req(Target, rootPaths, &mvsReqs{roots: keep}) min, err := mvs.Req(mainModule, rootPaths, &mvsReqs{roots: keep})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1011,7 +1028,7 @@ func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requireme
return rs, err return rs, err
} }
if cfg.BuildMod != "mod" { if mustHaveCompleteRequirements() {
// Instead of actually updating the requirements, just check that no updates // Instead of actually updating the requirements, just check that no updates
// are needed. // are needed.
if rs == nil { if rs == nil {
@ -1070,7 +1087,7 @@ func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requireme
// This is only for convenience and clarity for end users: in an eager module, // This is only for convenience and clarity for end users: in an eager module,
// the choice of explicit vs. implicit dependency has no impact on MVS // the choice of explicit vs. implicit dependency has no impact on MVS
// selection (for itself or any other module). // selection (for itself or any other module).
keep := append(mg.BuildList()[1:], add...) keep := append(mg.BuildList()[MainModules.Len():], add...)
for _, m := range keep { for _, m := range keep {
if direct[m.Path] && !inRootPaths[m.Path] { if direct[m.Path] && !inRootPaths[m.Path] {
rootPaths = append(rootPaths, m.Path) rootPaths = append(rootPaths, m.Path)
@ -1078,16 +1095,25 @@ func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requireme
} }
} }
min, err := mvs.Req(Target, rootPaths, &mvsReqs{roots: keep}) // TODO(matloob): Make roots into a map.
var roots []module.Version
for _, mainModule := range MainModules.Versions() {
min, err := mvs.Req(mainModule, rootPaths, &mvsReqs{roots: keep})
if err != nil { if err != nil {
return rs, err return rs, err
} }
if rs.depth == eager && reflect.DeepEqual(min, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) { roots = append(roots, min...)
}
if MainModules.Len() > 1 {
module.Sort(roots)
}
if rs.depth == eager && reflect.DeepEqual(roots, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
// The root set is unchanged and rs was already eager, so keep rs to // The root set is unchanged and rs was already eager, so keep rs to
// preserve its cached ModuleGraph (if any). // preserve its cached ModuleGraph (if any).
return rs, nil return rs, nil
} }
return newRequirements(eager, min, direct), nil
return newRequirements(eager, roots, direct), nil
} }
// convertDepth returns a version of rs with the given depth. // convertDepth returns a version of rs with the given depth.
@ -1117,5 +1143,5 @@ func convertDepth(ctx context.Context, rs *Requirements, depth modDepth) (*Requi
if err != nil { if err != nil {
return rs, err return rs, err
} }
return newRequirements(lazy, mg.BuildList()[1:], rs.direct), nil return newRequirements(lazy, mg.BuildList()[MainModules.Len():], rs.direct), nil
} }

View file

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

View file

@ -32,6 +32,8 @@ type ImportMissingError struct {
Module module.Version Module module.Version
QueryErr error QueryErr error
ImportingMainModule module.Version
// isStd indicates whether we would expect to find the package in the standard // isStd indicates whether we would expect to find the package in the standard
// library. This is normally true for all dotless import paths, but replace // library. This is normally true for all dotless import paths, but replace
// directives can cause us to treat the replaced paths as also being in // directives can cause us to treat the replaced paths as also being in
@ -71,6 +73,9 @@ func (e *ImportMissingError) Error() string {
if e.QueryErr != nil { if e.QueryErr != nil {
return fmt.Sprintf("%s: %v", message, e.QueryErr) return fmt.Sprintf("%s: %v", message, e.QueryErr)
} }
if e.ImportingMainModule.Path != "" && e.ImportingMainModule != MainModules.ModContainingCWD() {
return fmt.Sprintf("%s; to add it:\n\tcd %s\n\tgo get %s", message, MainModules.ModRoot(e.ImportingMainModule), e.Path)
}
return fmt.Sprintf("%s; to add it:\n\tgo get %s", message, e.Path) return fmt.Sprintf("%s; to add it:\n\tgo get %s", message, e.Path)
} }
@ -257,11 +262,13 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
// Is the package in the standard library? // Is the package in the standard library?
pathIsStd := search.IsStandardImportPath(path) pathIsStd := search.IsStandardImportPath(path)
if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) { if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
if targetInGorootSrc { for _, mainModule := range MainModules.Versions() {
if dir, ok, err := dirInModule(path, targetPrefix, ModRoot(), true); err != nil { if MainModules.InGorootSrc(mainModule) {
if dir, ok, err := dirInModule(path, MainModules.PathPrefix(mainModule), MainModules.ModRoot(mainModule), true); err != nil {
return module.Version{}, dir, err return module.Version{}, dir, err
} else if ok { } else if ok {
return Target, dir, nil return mainModule, dir, nil
}
} }
} }
dir := filepath.Join(cfg.GOROOT, "src", path) dir := filepath.Join(cfg.GOROOT, "src", path)
@ -271,8 +278,10 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
// -mod=vendor is special. // -mod=vendor is special.
// Everything must be in the main module or the main module's vendor directory. // Everything must be in the main module or the main module's vendor directory.
if cfg.BuildMod == "vendor" { if cfg.BuildMod == "vendor" {
mainDir, mainOK, mainErr := dirInModule(path, targetPrefix, ModRoot(), true) mainModule := MainModules.mustGetSingleMainModule()
vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(ModRoot(), "vendor"), false) modRoot := MainModules.ModRoot(mainModule)
mainDir, mainOK, mainErr := dirInModule(path, MainModules.PathPrefix(mainModule), modRoot, true)
vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(modRoot, "vendor"), false)
if mainOK && vendorOK { if mainOK && vendorOK {
return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: []string{mainDir, vendorDir}} return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: []string{mainDir, vendorDir}}
} }
@ -280,12 +289,12 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
// Note that we're not checking that the package exists. // Note that we're not checking that the package exists.
// We'll leave that for load. // We'll leave that for load.
if !vendorOK && mainDir != "" { if !vendorOK && mainDir != "" {
return Target, mainDir, nil return mainModule, mainDir, nil
} }
if mainErr != nil { if mainErr != nil {
return module.Version{}, "", mainErr return module.Version{}, "", mainErr
} }
readVendorList() readVendorList(mainModule)
return vendorPkgModule[path], vendorDir, nil return vendorPkgModule[path], vendorDir, nil
} }
@ -410,8 +419,9 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) { func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) {
// To avoid spurious remote fetches, try the latest replacement for each // To avoid spurious remote fetches, try the latest replacement for each
// module (golang.org/issue/26241). // module (golang.org/issue/26241).
if index != nil {
var mods []module.Version var mods []module.Version
for _, v := range MainModules.Versions() {
if index := MainModules.Index(v); index != nil {
for mp, mv := range index.highestReplaced { for mp, mv := range index.highestReplaced {
if !maybeInModule(path, mp) { if !maybeInModule(path, mp) {
continue continue
@ -439,6 +449,8 @@ func queryImport(ctx context.Context, path string, rs *Requirements) (module.Ver
} }
mods = append(mods, module.Version{Path: mp, Version: mv}) mods = append(mods, module.Version{Path: mp, Version: mv})
} }
}
}
// Every module path in mods is a prefix of the import path. // Every module path in mods is a prefix of the import path.
// As in QueryPattern, prefer the longest prefix that satisfies the import. // As in QueryPattern, prefer the longest prefix that satisfies the import.
@ -467,12 +479,12 @@ func queryImport(ctx context.Context, path string, rs *Requirements) (module.Ver
// The package path is not valid to fetch remotely, // The package path is not valid to fetch remotely,
// so it can only exist in a replaced module, // so it can only exist in a replaced module,
// and we know from the above loop that it is not. // and we know from the above loop that it is not.
replacement, _ := Replacement(mods[0])
return module.Version{}, &PackageNotInModuleError{ return module.Version{}, &PackageNotInModuleError{
Mod: mods[0], Mod: mods[0],
Query: "latest", Query: "latest",
Pattern: path, Pattern: path,
Replacement: Replacement(mods[0]), Replacement: replacement,
}
} }
} }
@ -638,14 +650,14 @@ func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFile
// The isLocal return value reports whether the replacement, // The isLocal return value reports whether the replacement,
// if any, is local to the filesystem. // if any, is local to the filesystem.
func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, isLocal bool, err error) { func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, isLocal bool, err error) {
if mod == Target { if modRoot := MainModules.ModRoot(mod); modRoot != "" {
return ModRoot(), true, nil return modRoot, true, nil
} }
if r := Replacement(mod); r.Path != "" { if r, replacedFrom := Replacement(mod); r.Path != "" {
if r.Version == "" { if r.Version == "" {
dir = r.Path dir = r.Path
if !filepath.IsAbs(dir) { if !filepath.IsAbs(dir) {
dir = filepath.Join(ModRoot(), dir) dir = filepath.Join(replacedFrom, dir)
} }
// Ensure that the replacement directory actually exists: // Ensure that the replacement directory actually exists:
// dirInModule does not report errors for missing modules, // dirInModule does not report errors for missing modules,
@ -667,7 +679,7 @@ func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, i
mod = r mod = r
} }
if HasModRoot() && cfg.BuildMod == "readonly" && needSum && !modfetch.HaveSum(mod) { if HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() && needSum && !modfetch.HaveSum(mod) {
return "", false, module.VersionError(mod, &sumMissingError{}) return "", false, module.VersionError(mod, &sumMissingError{})
} }

View file

@ -17,6 +17,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync"
"cmd/go/internal/base" "cmd/go/internal/base"
"cmd/go/internal/cfg" "cmd/go/internal/cfg"
@ -45,27 +46,169 @@ var (
allowMissingModuleImports bool allowMissingModuleImports bool
) )
func TODOWorkspaces(s string) error {
return fmt.Errorf("need to support this for workspaces: %s", s)
}
// Variables set in Init. // Variables set in Init.
var ( var (
initialized bool initialized bool
modRoot 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 gopath string
workFileGoVersion string
) )
// Variables set in initTarget (during {Load,Create}ModFile). // Variable set in InitWorkfile
var ( var (
Target module.Version // Set to the path to the go.work file, or "" if workspace mode is disabled.
workFilePath string
// 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
) )
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 type Root int
const ( const (
@ -94,6 +237,7 @@ const (
// in go.mod, edit it before loading. // in go.mod, edit it before loading.
func ModFile() *modfile.File { func ModFile() *modfile.File {
Init() Init()
modFile := MainModules.ModFile(MainModules.mustGetSingleMainModule())
if modFile == nil { if modFile == nil {
die() die()
} }
@ -105,6 +249,26 @@ func BinDir() string {
return filepath.Join(gopath, "bin") 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 // Init determines whether module mode is enabled, locates the root of the
// current module (if any), sets environment variables for Git subprocesses, and // current module (if any), sets environment variables for Git subprocesses, and
// configures the cfg, codehost, load, modfetch, and search packages for use // configures the cfg, codehost, load, modfetch, and search packages for use
@ -169,18 +333,18 @@ func Init() {
if os.Getenv("GCM_INTERACTIVE") == "" { if os.Getenv("GCM_INTERACTIVE") == "" {
os.Setenv("GCM_INTERACTIVE", "never") os.Setenv("GCM_INTERACTIVE", "never")
} }
if modRoots != nil {
if modRoot != "" {
// modRoot set before Init was called ("go mod init" does this). // modRoot set before Init was called ("go mod init" does this).
// No need to search for go.mod. // No need to search for go.mod.
} else if RootMode == NoRoot { } else if RootMode == NoRoot {
if cfg.ModFile != "" && !base.InGOFLAGS("-modfile") { if cfg.ModFile != "" && !base.InGOFLAGS("-modfile") {
base.Fatalf("go: -modfile cannot be used with commands that ignore the current module") 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 { } else {
modRoot = findModuleRoot(base.Cwd()) if modRoot := findModuleRoot(base.Cwd()); modRoot == "" {
if modRoot == "" {
if cfg.ModFile != "" { 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.") base.Fatalf("go: cannot find main module, but -modfile was set.\n\t-modfile cannot be used to set the module root directory.")
} }
@ -198,11 +362,12 @@ func Init() {
// will find it and get modules when they're not expecting them. // 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 // It's a bit of a peculiar thing to disallow but quite mysterious
// when it happens. See golang.org/issue/26708. // 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()) fmt.Fprintf(os.Stderr, "go: warning: ignoring go.mod in system temp root %v\n", os.TempDir())
if !mustUseModules { if !mustUseModules {
return return
} }
} else {
modRoots = []string{modRoot}
} }
} }
if cfg.ModFile != "" && !strings.HasSuffix(cfg.ModFile, ".mod") { if cfg.ModFile != "" && !strings.HasSuffix(cfg.ModFile, ".mod") {
@ -212,6 +377,9 @@ func Init() {
// We're in module mode. Set any global variables that need to be set. // We're in module mode. Set any global variables that need to be set.
cfg.ModulesEnabled = true cfg.ModulesEnabled = true
setDefaultBuildMod() 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) list := filepath.SplitList(cfg.BuildContext.GOPATH)
if len(list) == 0 || list[0] == "" { if len(list) == 0 || list[0] == "" {
base.Fatalf("missing $GOPATH") base.Fatalf("missing $GOPATH")
@ -221,7 +389,16 @@ func Init() {
base.Fatalf("$GOPATH/go.mod exists but should not") 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. // We're in module mode, but not inside a module.
// //
// Commands like 'go build', 'go run', 'go list' have no go.mod file to // Commands like 'go build', 'go run', 'go list' have no go.mod file to
@ -240,8 +417,7 @@ func Init() {
// //
// See golang.org/issue/32027. // See golang.org/issue/32027.
} else { } else {
modfetch.GoSumFile = strings.TrimSuffix(ModFilePath(), ".mod") + ".sum" modfetch.GoSumFile = strings.TrimSuffix(modFilePath(modRoots[0]), ".mod") + ".sum"
search.SetModRoot(modRoot)
} }
} }
@ -255,7 +431,7 @@ func Init() {
// be called until the command is installed and flags are parsed. Instead of // be called until the command is installed and flags are parsed. Instead of
// calling Init and Enabled, the main package can call this function. // calling Init and Enabled, the main package can call this function.
func WillBeEnabled() bool { func WillBeEnabled() bool {
if modRoot != "" || cfg.ModulesEnabled { if modRoots != nil || cfg.ModulesEnabled {
// Already enabled. // Already enabled.
return true return true
} }
@ -297,16 +473,18 @@ func WillBeEnabled() bool {
// (usually through MustModRoot). // (usually through MustModRoot).
func Enabled() bool { func Enabled() bool {
Init() Init()
return modRoot != "" || cfg.ModulesEnabled return modRoots != nil || cfg.ModulesEnabled
} }
// ModRoot returns the root of the main module. func VendorDir() string {
// It calls base.Fatalf if there is no main module. return filepath.Join(MainModules.ModRoot(MainModules.mustGetSingleMainModule()), "vendor")
func ModRoot() string { }
if !HasModRoot() {
die() func inWorkspaceMode() bool {
if !initialized {
panic("inWorkspaceMode called before modload.Init called")
} }
return modRoot return workFilePath != ""
} }
// HasModRoot reports whether a main module is present. // HasModRoot reports whether a main module is present.
@ -314,17 +492,27 @@ func ModRoot() string {
// does not require a main module. // does not require a main module.
func HasModRoot() bool { func HasModRoot() bool {
Init() Init()
return modRoot != "" return modRoots != nil
} }
// ModFilePath returns the effective path of the go.mod file. Normally, this // MustHaveModRoot checks that a main module or main modules are present,
// "go.mod" in the directory returned by ModRoot, but the -modfile flag may // and calls base.Fatalf if there are no main modules.
// change its location. ModFilePath calls base.Fatalf if there is no main func MustHaveModRoot() {
// module, even if -modfile is set. Init()
func ModFilePath() string {
if !HasModRoot() { if !HasModRoot() {
die() 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 != "" { if cfg.ModFile != "" {
return cfg.ModFile return cfg.ModFile
} }
@ -365,6 +553,35 @@ func (goModDirtyError) Error() string {
var errGoModDirty error = goModDirtyError{} 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 // LoadModFile sets Target and, if there is a main module, parses the initial
// build list from its go.mod file. // build list from its go.mod file.
// //
@ -385,7 +602,7 @@ var errGoModDirty error = goModDirtyError{}
func LoadModFile(ctx context.Context) *Requirements { func LoadModFile(ctx context.Context) *Requirements {
rs, needCommit := loadModFile(ctx) rs, needCommit := loadModFile(ctx)
if needCommit { if needCommit {
commitRequirements(ctx, modFileGoVersion(), rs) commitRequirements(ctx, rs)
} }
return rs return rs
} }
@ -402,16 +619,21 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
} }
Init() Init()
if modRoot == "" { if len(modRoots) == 0 {
Target = module.Version{Path: "command-line-arguments"} _ = 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.")
targetPrefix = "command-line-arguments" mainModule := module.Version{Path: "command-line-arguments"}
MainModules = makeMainModules([]module.Version{mainModule}, []string{""}, []*modfile.File{nil}, []*modFileIndex{nil}, "")
goVersion := LatestGoVersion() goVersion := LatestGoVersion()
rawGoVersion.Store(Target, goVersion) rawGoVersion.Store(mainModule, goVersion)
requirements = newRequirements(modDepthFromGoVersion(goVersion), nil, nil) requirements = newRequirements(modDepthFromGoVersion(goVersion), nil, nil)
return requirements, false return requirements, false
} }
gomod := ModFilePath() var modFiles []*modfile.File
var mainModules []module.Version
var indices []*modFileIndex
for _, modroot := range modRoots {
gomod := modFilePath(modroot)
var data []byte var data []byte
var err error var err error
if gomodActual, ok := fsys.OverlayPath(gomod); ok { if gomodActual, ok := fsys.OverlayPath(gomod); ok {
@ -437,9 +659,10 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod") base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
} }
modFile = f modFiles = append(modFiles, f)
initTarget(f.Module.Mod) mainModule := f.Module.Mod
index = indexModFile(data, f, fixed) mainModules = append(mainModules, mainModule)
indices = append(indices, indexModFile(data, f, mainModule, fixed))
if err := module.CheckImportPath(f.Module.Mod.Path); err != nil { if err := module.CheckImportPath(f.Module.Mod.Path); err != nil {
if pathErr, ok := err.(*module.InvalidPathError); ok { if pathErr, ok := err.(*module.InvalidPathError); ok {
@ -447,42 +670,59 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
} }
base.Fatalf("go: %v", err) base.Fatalf("go: %v", err)
} }
}
MainModules = makeMainModules(mainModules, modRoots, modFiles, indices, workFileGoVersion)
setDefaultBuildMod() // possibly enable automatic vendoring 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" { if cfg.BuildMod == "vendor" {
readVendorList() readVendorList(mainModule)
checkVendorConsistency() index := MainModules.Index(mainModule)
modFile := MainModules.ModFile(mainModule)
checkVendorConsistency(index, modFile)
rs.initVendor(vendorList) rs.initVendor(vendorList)
} }
if rs.hasRedundantRoot() { if rs.hasRedundantRoot() {
// If any module path appears more than once in the roots, we know that the // 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 // go.mod file needs to be updated even though we have not yet loaded any
// transitive dependencies. // transitive dependencies.
var err error
rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false) rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
if err != nil { if err != nil {
base.Fatalf("go: %v", err) base.Fatalf("go: %v", err)
} }
} }
if index.goVersionV == "" { if MainModules.Index(mainModule).goVersionV == "" {
// TODO(#45551): Do something more principled instead of checking // TODO(#45551): Do something more principled instead of checking
// cfg.CmdName directly here. // cfg.CmdName directly here.
if cfg.BuildMod == "mod" && cfg.CmdName != "mod graph" && cfg.CmdName != "mod why" { if cfg.BuildMod == "mod" && cfg.CmdName != "mod graph" && cfg.CmdName != "mod why" {
addGoStmt(LatestGoVersion()) addGoStmt(MainModules.ModFile(mainModule), mainModule, LatestGoVersion())
if go117EnableLazyLoading { if go117EnableLazyLoading {
// We need to add a 'go' version to the go.mod file, but we must assume // 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. // 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 // Go 1.11 through 1.16 have eager requirements, but the latest Go
// version uses lazy requirements instead — so we need to cnvert the // version uses lazy requirements instead — so we need to cnvert the
// requirements to be lazy. // requirements to be lazy.
var err error
rs, err = convertDepth(ctx, rs, lazy) rs, err = convertDepth(ctx, rs, lazy)
if err != nil { if err != nil {
base.Fatalf("go: %v", err) base.Fatalf("go: %v", err)
} }
} }
} else { } else {
rawGoVersion.Store(Target, modFileGoVersion()) rawGoVersion.Store(mainModule, modFileGoVersion(MainModules.ModFile(mainModule)))
} }
} }
@ -500,9 +740,10 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
// exactly the same as in the legacy configuration (for example, we can't get // exactly the same as in the legacy configuration (for example, we can't get
// packages at multiple versions from the same module). // packages at multiple versions from the same module).
func CreateModFile(ctx context.Context, modPath string) { func CreateModFile(ctx context.Context, modPath string) {
modRoot = base.Cwd() modRoot := base.Cwd()
modRoots = []string{modRoot}
Init() Init()
modFilePath := ModFilePath() modFilePath := modFilePath(modRoot)
if _, err := fsys.Stat(modFilePath); err == nil { if _, err := fsys.Stat(modFilePath); err == nil {
base.Fatalf("go: %s already exists", modFilePath) base.Fatalf("go: %s already exists", modFilePath)
} }
@ -533,12 +774,12 @@ func CreateModFile(ctx context.Context, modPath string) {
} }
fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", modPath) fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", modPath)
modFile = new(modfile.File) modFile := new(modfile.File)
modFile.AddModuleStmt(modPath) modFile.AddModuleStmt(modPath)
initTarget(modFile.Module.Mod) MainModules = makeMainModules([]module.Version{modFile.Module.Mod}, []string{modRoot}, []*modfile.File{modFile}, []*modFileIndex{nil}, "")
addGoStmt(LatestGoVersion()) // Add the go directive before converted module requirements. 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 != "" { if convertedFrom != "" {
fmt.Fprintf(os.Stderr, "go: copying requirements from %s\n", base.ShortPath(convertedFrom)) fmt.Fprintf(os.Stderr, "go: copying requirements from %s\n", base.ShortPath(convertedFrom))
} }
@ -546,12 +787,12 @@ func CreateModFile(ctx context.Context, modPath string) {
base.Fatalf("go: %v", err) base.Fatalf("go: %v", err)
} }
rs := requirementsFromModFile() rs := requirementsFromModFiles(ctx, []*modfile.File{modFile})
rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false) rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
if err != nil { if err != nil {
base.Fatalf("go: %v", err) 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 // Suggest running 'go mod tidy' unless the project is empty. Even if we
// imported all the correct requirements above, we're probably missing // imported all the correct requirements above, we're probably missing
@ -577,6 +818,24 @@ func CreateModFile(ctx context.Context, modPath string) {
} }
} }
// CreateWorkFile initializes a new workspace by creating a go.work file.
func CreateWorkFile(ctx context.Context, workFile string, modDirs []string) {
_ = TODOWorkspaces("Report an error if the file already exists.")
goV := LatestGoVersion() // Use current Go version by default
workF := new(modfile.WorkFile)
workF.Syntax = new(modfile.FileSyntax)
workF.AddGoStmt(goV)
for _, dir := range modDirs {
_ = TODOWorkspaces("Add the module path of the module.")
workF.AddDirectory(dir, "")
}
data := modfile.Format(workF.Syntax)
lockedfile.Write(workFile, bytes.NewReader(data), 0644)
}
// fixVersion returns a modfile.VersionFixer implemented using the Query function. // fixVersion returns a modfile.VersionFixer implemented using the Query function.
// //
// It resolves commit hashes and branch names to versions, // It resolves commit hashes and branch names to versions,
@ -639,13 +898,36 @@ func AllowMissingModuleImports() {
allowMissingModuleImports = true allowMissingModuleImports = true
} }
// initTarget sets Target and associated variables according to modFile, // makeMainModules creates a MainModuleSet and associated variables according to
func initTarget(m module.Version) { // the given main modules.
Target = m func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile.File, indices []*modFileIndex, workFileGoVersion string) *MainModuleSet {
targetPrefix = m.Path for _, m := range ms {
if m.Version != "" {
panic("mainModulesCalled with module.Version with non empty Version field: " + fmt.Sprintf("%#v", m))
}
}
modRootContainingCWD := findModuleRoot(base.Cwd())
mainModules := &MainModuleSet{
versions: ms[:len(ms):len(ms)],
inGorootSrc: map[module.Version]bool{},
pathPrefix: map[module.Version]string{},
modRoot: map[module.Version]string{},
modFiles: map[module.Version]*modfile.File{},
indices: map[module.Version]*modFileIndex{},
workFileGoVersion: workFileGoVersion,
}
for i, m := range ms {
mainModules.pathPrefix[m] = m.Path
mainModules.modRoot[m] = rootDirs[i]
mainModules.modFiles[m] = modFiles[i]
mainModules.indices[m] = indices[i]
if rel := search.InDir(base.Cwd(), cfg.GOROOTsrc); rel != "" { if mainModules.modRoot[m] == modRootContainingCWD {
targetInGorootSrc = true mainModules.modContainingCWD = m
}
if rel := search.InDir(rootDirs[i], cfg.GOROOTsrc); rel != "" {
mainModules.inGorootSrc[m] = true
if m.Path == "std" { if m.Path == "std" {
// The "std" module in GOROOT/src is the Go standard library. Unlike other // 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, the packages in the "std" module have no import-path prefix.
@ -655,24 +937,39 @@ func initTarget(m module.Version) {
// test individual packages using a combination of the modified package // test individual packages using a combination of the modified package
// and the ordinary standard library. // and the ordinary standard library.
// (See https://golang.org/issue/30756.) // (See https://golang.org/issue/30756.)
targetPrefix = "" mainModules.pathPrefix[m] = ""
} }
} }
}
return mainModules
} }
// requirementsFromModFile returns the set of non-excluded requirements from // requirementsFromModFiles returns the set of non-excluded requirements from
// the global modFile. // the global modFile.
func requirementsFromModFile() *Requirements { func requirementsFromModFiles(ctx context.Context, modFiles []*modfile.File) *Requirements {
roots := make([]module.Version, 0, len(modFile.Require)) 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{} direct := map[string]bool{}
for _, modFile := range modFiles {
requirement:
for _, r := range modFile.Require { for _, r := range modFile.Require {
if index != nil && index.exclude[r.Mod] { // TODO(#45713): Maybe join
for _, mainModule := range MainModules.Versions() {
if index := MainModules.Index(mainModule); index != nil && index.exclude[r.Mod] {
if cfg.BuildMod == "mod" { if cfg.BuildMod == "mod" {
fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version) fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
} else { } else {
fmt.Fprintf(os.Stderr, "go: ignoring requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version) fmt.Fprintf(os.Stderr, "go: ignoring requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
} }
continue continue requirement
}
} }
roots = append(roots, r.Mod) roots = append(roots, r.Mod)
@ -680,8 +977,9 @@ func requirementsFromModFile() *Requirements {
direct[r.Mod.Path] = true direct[r.Mod.Path] = true
} }
} }
}
module.Sort(roots) module.Sort(roots)
rs := newRequirements(modDepthFromGoVersion(modFileGoVersion()), roots, direct) rs := newRequirements(modDepthFromGoVersion(MainModules.GoVersion()), roots, direct)
return rs return rs
} }
@ -689,6 +987,11 @@ func requirementsFromModFile() *Requirements {
// wasn't provided. setDefaultBuildMod may be called multiple times. // wasn't provided. setDefaultBuildMod may be called multiple times.
func setDefaultBuildMod() { func setDefaultBuildMod() {
if cfg.BuildModExplicit { 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. // Don't override an explicit '-mod=' argument.
return return
} }
@ -713,7 +1016,7 @@ func setDefaultBuildMod() {
cfg.BuildMod = "readonly" cfg.BuildMod = "readonly"
return return
} }
if modRoot == "" { if modRoots == nil {
if allowMissingModuleImports { if allowMissingModuleImports {
cfg.BuildMod = "mod" cfg.BuildMod = "mod"
} else { } else {
@ -722,7 +1025,9 @@ func setDefaultBuildMod() {
return return
} }
if fi, err := fsys.Stat(filepath.Join(modRoot, "vendor")); err == nil && fi.IsDir() { if len(modRoots) == 1 {
index := MainModules.GetSingleIndexOrNil()
if fi, err := fsys.Stat(filepath.Join(modRoots[0], "vendor")); err == nil && fi.IsDir() {
modGo := "unspecified" modGo := "unspecified"
if index != nil && index.goVersionV != "" { if index != nil && index.goVersionV != "" {
if semver.Compare(index.goVersionV, "v1.14") >= 0 { if semver.Compare(index.goVersionV, "v1.14") >= 0 {
@ -740,13 +1045,18 @@ func setDefaultBuildMod() {
// This message won't normally be shown, but it may appear with import errors. // 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.BuildModReason = fmt.Sprintf("Go version in go.mod is %s, so vendor directory was not used.", modGo)
} }
}
cfg.BuildMod = "readonly" cfg.BuildMod = "readonly"
} }
func mustHaveCompleteRequirements() bool {
return cfg.BuildMod != "mod" && !inWorkspaceMode()
}
// convertLegacyConfig imports module requirements from a legacy vendoring // convertLegacyConfig imports module requirements from a legacy vendoring
// configuration file, if one is present. // 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" } noneSelected := func(path string) (version string) { return "none" }
queryPackage := func(path, rev string) (module.Version, error) { queryPackage := func(path, rev string) (module.Version, error) {
pkgMods, modOnly, err := QueryPattern(context.Background(), path, rev, noneSelected, nil) pkgMods, modOnly, err := QueryPattern(context.Background(), path, rev, noneSelected, nil)
@ -777,14 +1087,14 @@ func convertLegacyConfig(modPath string) (from string, err error) {
// addGoStmt adds a go directive to the go.mod file if it does not already // 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 // include one. The 'go' version added, if any, is the latest version supported
// by this toolchain. // by this toolchain.
func addGoStmt(v string) { func addGoStmt(modFile *modfile.File, mod module.Version, v string) {
if modFile.Go != nil && modFile.Go.Version != "" { if modFile.Go != nil && modFile.Go.Version != "" {
return return
} }
if err := modFile.AddGoStmt(v); err != nil { if err := modFile.AddGoStmt(v); err != nil {
base.Fatalf("go: internal error: %v", err) 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 // LatestGoVersion returns the latest version of the Go language supported by
@ -835,7 +1145,7 @@ var altConfigs = []string{
".git/config", ".git/config",
} }
func findModuleRoot(dir string) (root string) { func findModuleRoot(dir string) (roots string) {
if dir == "" { if dir == "" {
panic("dir not set") panic("dir not set")
} }
@ -855,6 +1165,32 @@ func findModuleRoot(dir string) (root string) {
return "" 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) { func findAltConfig(dir string) (root, name string) {
if dir == "" { if dir == "" {
panic("dir not set") panic("dir not set")
@ -1000,12 +1336,13 @@ func WriteGoMod(ctx context.Context) {
if !allowWriteGoMod { if !allowWriteGoMod {
panic("WriteGoMod called while disallowed") panic("WriteGoMod called while disallowed")
} }
commitRequirements(ctx, modFileGoVersion(), LoadModFile(ctx)) commitRequirements(ctx, LoadModFile(ctx))
} }
// commitRequirements writes sets the global requirements variable to rs and // commitRequirements writes sets the global requirements variable to rs and
// writes its contents back to the go.mod file on disk. // 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 requirements = rs
if !allowWriteGoMod { if !allowWriteGoMod {
@ -1013,10 +1350,22 @@ func commitRequirements(ctx context.Context, goVersion string, rs *Requirements)
return 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. // We aren't in a module, so we don't have anywhere to write a go.mod file.
return return
} }
mainModule := MainModules.mustGetSingleMainModule()
modFilePath := modFilePath(MainModules.ModRoot(mainModule))
modFile := MainModules.ModFile(mainModule)
var list []*modfile.Require var list []*modfile.Require
for _, m := range rs.rootModules { for _, m := range rs.rootModules {
@ -1025,16 +1374,17 @@ func commitRequirements(ctx context.Context, goVersion string, rs *Requirements)
Indirect: !rs.direct[m.Path], Indirect: !rs.direct[m.Path],
}) })
} }
if goVersion != "" { if modFile.Go == nil || modFile.Go.Version == "" {
modFile.AddGoStmt(goVersion) modFile.AddGoStmt(modFileGoVersion(modFile))
} }
if semver.Compare("v"+modFileGoVersion(), separateIndirectVersionV) < 0 { if semver.Compare("v"+modFileGoVersion(modFile), separateIndirectVersionV) < 0 {
modFile.SetRequire(list) modFile.SetRequire(list)
} else { } else {
modFile.SetRequireSeparateIndirect(list) modFile.SetRequireSeparateIndirect(list)
} }
modFile.Cleanup() modFile.Cleanup()
index := MainModules.GetSingleIndexOrNil()
dirty := index.modFileIsDirty(modFile) dirty := index.modFileIsDirty(modFile)
if dirty && cfg.BuildMod != "mod" { if dirty && cfg.BuildMod != "mod" {
// If we're about to fail due to -mod=readonly, // If we're about to fail due to -mod=readonly,
@ -1048,12 +1398,13 @@ func commitRequirements(ctx context.Context, goVersion string, rs *Requirements)
// Don't write go.mod, but write go.sum in case we added or trimmed sums. // 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. // 'go mod init' shouldn't write go.sum, since it will be incomplete.
if cfg.CmdName != "mod init" { 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 return
} }
gomod := ModFilePath() if _, ok := fsys.OverlayPath(modFilePath); ok {
if _, ok := fsys.OverlayPath(gomod); ok {
if dirty { if dirty {
base.Fatalf("go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay") base.Fatalf("go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay")
} }
@ -1066,12 +1417,14 @@ func commitRequirements(ctx context.Context, goVersion string, rs *Requirements)
} }
defer func() { defer func() {
// At this point we have determined to make the go.mod file on disk equal to new. // 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. // 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. // 'go mod init' shouldn't write go.sum, since it will be incomplete.
if cfg.CmdName != "mod init" { if cfg.CmdName != "mod init" {
modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums)) if err := modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
base.Fatalf("go: %v", err)
}
} }
}() }()
@ -1083,7 +1436,7 @@ func commitRequirements(ctx context.Context, goVersion string, rs *Requirements)
errNoChange := errors.New("no update needed") 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) { if bytes.Equal(old, new) {
// The go.mod file is already equal to new, possibly as the result of some // The go.mod file is already equal to new, possibly as the result of some
// other process. // other process.
@ -1142,7 +1495,8 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) { for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) {
if v, ok := rs.rootSelected(prefix); ok && v != "none" { if v, ok := rs.rootSelected(prefix); ok && v != "none" {
m := module.Version{Path: prefix, Version: v} m := module.Version{Path: prefix, Version: v}
keep[resolveReplacement(m)] = true r, _ := resolveReplacement(m)
keep[r] = true
} }
} }
continue continue
@ -1153,7 +1507,8 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) { for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) {
if v := mg.Selected(prefix); v != "none" { if v := mg.Selected(prefix); v != "none" {
m := module.Version{Path: prefix, Version: v} m := module.Version{Path: prefix, Version: v}
keep[resolveReplacement(m)] = true r, _ := resolveReplacement(m)
keep[r] = true
} }
} }
} }
@ -1165,7 +1520,7 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
// Save sums for the root modules (or their replacements), but don't // 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. // incur the cost of loading the graph just to find and retain the sums.
for _, m := range rs.rootModules { for _, m := range rs.rootModules {
r := resolveReplacement(m) r, _ := resolveReplacement(m)
keep[modkey(r)] = true keep[modkey(r)] = true
if which == addBuildListZipSums { if which == addBuildListZipSums {
keep[r] = true keep[r] = true
@ -1178,13 +1533,15 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
// The requirements from m's go.mod file are present in the module graph, // 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 // so they are relevant to the MVS result regardless of whether m was
// actually selected. // actually selected.
keep[modkey(resolveReplacement(m))] = true r, _ := resolveReplacement(m)
keep[modkey(r)] = true
} }
}) })
if which == addBuildListZipSums { if which == addBuildListZipSums {
for _, m := range mg.BuildList() { for _, m := range mg.BuildList() {
keep[resolveReplacement(m)] = true r, _ := resolveReplacement(m)
keep[r] = true
} }
} }
} }

View file

@ -72,14 +72,18 @@ func ListModules(ctx context.Context, args []string, mode ListMode) ([]*modinfo.
} }
if err == nil { if err == nil {
commitRequirements(ctx, modFileGoVersion(), rs) commitRequirements(ctx, rs)
} }
return mods, err return mods, err
} }
func listModules(ctx context.Context, rs *Requirements, args []string, mode ListMode) (_ *Requirements, mods []*modinfo.ModulePublic, mgErr error) { func listModules(ctx context.Context, rs *Requirements, args []string, mode ListMode) (_ *Requirements, mods []*modinfo.ModulePublic, mgErr error) {
if len(args) == 0 { if len(args) == 0 {
return rs, []*modinfo.ModulePublic{moduleInfo(ctx, rs, Target, mode)}, nil var ms []*modinfo.ModulePublic
for _, m := range MainModules.Versions() {
ms = append(ms, moduleInfo(ctx, rs, m, mode))
}
return rs, ms, nil
} }
needFullGraph := false needFullGraph := false

View file

@ -274,7 +274,9 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
// If we're outside of a module, ensure that the failure mode // If we're outside of a module, ensure that the failure mode
// indicates that. // indicates that.
ModRoot() if !HasModRoot() {
die()
}
if ld != nil { if ld != nil {
m.AddError(err) m.AddError(err)
@ -306,7 +308,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
// The initial roots are the packages in the main module. // The initial roots are the packages in the main module.
// loadFromRoots will expand that to "all". // loadFromRoots will expand that to "all".
m.Errs = m.Errs[:0] m.Errs = m.Errs[:0]
matchPackages(ctx, m, opts.Tags, omitStd, []module.Version{Target}) matchPackages(ctx, m, opts.Tags, omitStd, MainModules.Versions())
} else { } else {
// Starting with the packages in the main module, // Starting with the packages in the main module,
// enumerate the full list of "all". // enumerate the full list of "all".
@ -401,13 +403,21 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
// loaded.requirements, but here we may have also loaded (and want to // loaded.requirements, but here we may have also loaded (and want to
// preserve checksums for) additional entities from compatRS, which are // preserve checksums for) additional entities from compatRS, which are
// only needed for compatibility with ld.TidyCompatibleVersion. // only needed for compatibility with ld.TidyCompatibleVersion.
modfetch.WriteGoSum(keep) if err := modfetch.WriteGoSum(keep, mustHaveCompleteRequirements()); err != nil {
base.Fatalf("go: %v", err)
}
}
// Update the go.mod file's Go version if necessary.
modFile := MainModules.ModFile(MainModules.mustGetSingleMainModule())
if ld.GoVersion != "" {
modFile.AddGoStmt(ld.GoVersion)
} }
} }
// Success! Update go.mod and go.sum (if needed) and return the results. // Success! Update go.mod and go.sum (if needed) and return the results.
loaded = ld loaded = ld
commitRequirements(ctx, loaded.GoVersion, loaded.requirements) commitRequirements(ctx, loaded.requirements)
for _, pkg := range ld.pkgs { for _, pkg := range ld.pkgs {
if !pkg.isTest() { if !pkg.isTest() {
@ -436,14 +446,23 @@ func matchLocalDirs(ctx context.Context, m *search.Match, rs *Requirements) {
if !filepath.IsAbs(dir) { if !filepath.IsAbs(dir) {
absDir = filepath.Join(base.Cwd(), dir) absDir = filepath.Join(base.Cwd(), dir)
} }
if search.InDir(absDir, cfg.GOROOTsrc) == "" && search.InDir(absDir, ModRoot()) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
modRoot := findModuleRoot(absDir)
found := false
for _, mod := range MainModules.Versions() {
if MainModules.ModRoot(mod) == modRoot {
found = true
break
}
}
if !found && search.InDir(absDir, cfg.GOROOTsrc) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
m.Dirs = []string{} m.Dirs = []string{}
m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir))) m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir)))
return return
} }
} }
m.MatchDirs() m.MatchDirs(modRoots)
} }
// resolveLocalPackage resolves a filesystem path to a package path. // resolveLocalPackage resolves a filesystem path to a package path.
@ -485,16 +504,23 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
} }
} }
for _, mod := range MainModules.Versions() {
modRoot := MainModules.ModRoot(mod)
if modRoot != "" && absDir == modRoot { if modRoot != "" && absDir == modRoot {
if absDir == cfg.GOROOTsrc { if absDir == cfg.GOROOTsrc {
return "", errPkgIsGorootSrc return "", errPkgIsGorootSrc
} }
return targetPrefix, nil return MainModules.PathPrefix(mod), nil
}
} }
// Note: The checks for @ here are just to avoid misinterpreting // Note: The checks for @ here are just to avoid misinterpreting
// the module cache directories (formerly GOPATH/src/mod/foo@v1.5.2/bar). // the module cache directories (formerly GOPATH/src/mod/foo@v1.5.2/bar).
// It's not strictly necessary but helpful to keep the checks. // It's not strictly necessary but helpful to keep the checks.
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):], "@") { if modRoot != "" && strings.HasPrefix(absDir, modRoot+string(filepath.Separator)) && !strings.Contains(absDir[len(modRoot):], "@") {
suffix := filepath.ToSlash(absDir[len(modRoot):]) suffix := filepath.ToSlash(absDir[len(modRoot):])
if strings.HasPrefix(suffix, "/vendor/") { if strings.HasPrefix(suffix, "/vendor/") {
@ -502,7 +528,7 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir) return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
} }
readVendorList() readVendorList(mainModule)
pkg := strings.TrimPrefix(suffix, "/vendor/") pkg := strings.TrimPrefix(suffix, "/vendor/")
if _, ok := vendorPkgModule[pkg]; !ok { if _, ok := vendorPkgModule[pkg]; !ok {
return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir) return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
@ -510,7 +536,8 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
return pkg, nil return pkg, nil
} }
if targetPrefix == "" { mainModulePrefix := MainModules.PathPrefix(mainModule)
if mainModulePrefix == "" {
pkg := strings.TrimPrefix(suffix, "/") pkg := strings.TrimPrefix(suffix, "/")
if pkg == "builtin" { if pkg == "builtin" {
// "builtin" is a pseudo-package with a real source file. // "builtin" is a pseudo-package with a real source file.
@ -521,14 +548,26 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
return pkg, nil return pkg, nil
} }
pkg := targetPrefix + suffix pkg := mainModulePrefix + suffix
if _, ok, err := dirInModule(pkg, targetPrefix, modRoot, true); err != nil { if _, ok, err := dirInModule(pkg, mainModulePrefix, modRoot, true); err != nil {
return "", err return "", err
} else if !ok { } else if !ok {
return "", &PackageNotInModuleError{Mod: Target, Pattern: pkg} // This main module could contain the directory but doesn't. Other main
// modules might contain the directory, so wait till we finish the loop
// to see if another main module contains directory. But if not,
// return an error.
if len(mainModulePrefix) > len(pkgNotFoundLongestPrefix) {
pkgNotFoundLongestPrefix = mainModulePrefix
pkgNotFoundErr = &PackageNotInModuleError{MainModules: []module.Version{mainModule}, Pattern: pkg}
}
continue
} }
return pkg, nil return pkg, nil
} }
}
if pkgNotFoundErr != nil {
return "", pkgNotFoundErr
}
if sub := search.InDir(absDir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") { if sub := search.InDir(absDir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") {
pkg := filepath.ToSlash(sub) pkg := filepath.ToSlash(sub)
@ -557,10 +596,10 @@ func pathInModuleCache(ctx context.Context, dir string, rs *Requirements) string
tryMod := func(m module.Version) (string, bool) { tryMod := func(m module.Version) (string, bool) {
var root string var root string
var err error var err error
if repl := Replacement(m); repl.Path != "" && repl.Version == "" { if repl, replModRoot := Replacement(m); repl.Path != "" && repl.Version == "" {
root = repl.Path root = repl.Path
if !filepath.IsAbs(root) { if !filepath.IsAbs(root) {
root = filepath.Join(ModRoot(), root) root = filepath.Join(replModRoot, root)
} }
} else if repl.Path != "" { } else if repl.Path != "" {
root, err = modfetch.DownloadDir(repl) root, err = modfetch.DownloadDir(repl)
@ -645,14 +684,14 @@ func ImportFromFiles(ctx context.Context, gofiles []string) {
return roots return roots
}, },
}) })
commitRequirements(ctx, loaded.GoVersion, loaded.requirements) commitRequirements(ctx, loaded.requirements)
} }
// DirImportPath returns the effective import path for dir, // DirImportPath returns the effective import path for dir,
// provided it is within the main module, or else returns ".". // provided it is within a main module, or else returns ".".
func DirImportPath(ctx context.Context, dir string) string { func (mms *MainModuleSet) DirImportPath(ctx context.Context, dir string) (path string, m module.Version) {
if !HasModRoot() { if !HasModRoot() {
return "." return ".", module.Version{}
} }
LoadModFile(ctx) // Sets targetPrefix. LoadModFile(ctx) // Sets targetPrefix.
@ -662,17 +701,32 @@ func DirImportPath(ctx context.Context, dir string) string {
dir = filepath.Clean(dir) dir = filepath.Clean(dir)
} }
var longestPrefix string
var longestPrefixPath string
var longestPrefixVersion module.Version
for _, v := range mms.Versions() {
modRoot := mms.ModRoot(v)
if dir == modRoot { if dir == modRoot {
return targetPrefix return mms.PathPrefix(v), v
} }
if strings.HasPrefix(dir, modRoot+string(filepath.Separator)) { 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):]) suffix := filepath.ToSlash(dir[len(modRoot):])
if strings.HasPrefix(suffix, "/vendor/") { if strings.HasPrefix(suffix, "/vendor/") {
return strings.TrimPrefix(suffix, "/vendor/") longestPrefixPath = strings.TrimPrefix(suffix, "/vendor/")
} }
return targetPrefix + suffix longestPrefixPath = mms.PathPrefix(v) + suffix
} }
return "." }
}
if len(longestPrefix) > 0 {
return longestPrefixPath, longestPrefixVersion
}
return ".", module.Version{}
} }
// ImportMap returns the actual package import path // ImportMap returns the actual package import path
@ -894,10 +948,7 @@ func (pkg *loadPkg) fromExternalModule() bool {
if pkg.mod.Path == "" { if pkg.mod.Path == "" {
return false // loaded from the standard library, not a module return false // loaded from the standard library, not a module
} }
if pkg.mod.Path == Target.Path { return !MainModules.Contains(pkg.mod.Path)
return false // loaded from the main module.
}
return true
} }
var errMissing = errors.New("cannot find package") var errMissing = errors.New("cannot find package")
@ -915,7 +966,7 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
} }
if ld.GoVersion == "" { if ld.GoVersion == "" {
ld.GoVersion = modFileGoVersion() ld.GoVersion = MainModules.GoVersion()
if ld.Tidy && semver.Compare("v"+ld.GoVersion, "v"+LatestGoVersion()) > 0 { if ld.Tidy && semver.Compare("v"+ld.GoVersion, "v"+LatestGoVersion()) > 0 {
ld.errorf("go mod tidy: go.mod file indicates go %s, but maximum supported version is %s\n", ld.GoVersion, LatestGoVersion()) ld.errorf("go mod tidy: go.mod file indicates go %s, but maximum supported version is %s\n", ld.GoVersion, LatestGoVersion())
@ -1168,7 +1219,7 @@ func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err err
} }
for _, pkg := range ld.pkgs { for _, pkg := range ld.pkgs {
if pkg.mod != Target { if pkg.mod.Version != "" || !MainModules.Contains(pkg.mod.Path) {
continue continue
} }
for _, dep := range pkg.imports { for _, dep := range pkg.imports {
@ -1327,6 +1378,15 @@ func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[mod
var err error var err error
mod, err = queryImport(ctx, pkg.path, ld.requirements) mod, err = queryImport(ctx, pkg.path, ld.requirements)
if err != nil { if err != nil {
var ime *ImportMissingError
if errors.As(err, &ime) {
for curstack := pkg.stack; curstack != nil; curstack = curstack.stack {
if MainModules.Contains(curstack.mod.Path) {
ime.ImportingMainModule = curstack.mod
break
}
}
}
// pkg.err was already non-nil, so we can reasonably attribute the error // pkg.err was already non-nil, so we can reasonably attribute the error
// for pkg to either the original error or the one returned by // for pkg to either the original error or the one returned by
// queryImport. The existing error indicates only that we couldn't find // queryImport. The existing error indicates only that we couldn't find
@ -1425,7 +1485,7 @@ func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkg
// so it's ok if we call it more than is strictly necessary. // so it's ok if we call it more than is strictly necessary.
wantTest := false wantTest := false
switch { switch {
case ld.allPatternIsRoot && pkg.mod == Target: case ld.allPatternIsRoot && MainModules.Contains(pkg.mod.Path):
// We are loading the "all" pattern, which includes packages imported by // We are loading the "all" pattern, which includes packages imported by
// tests in the main module. This package is in the main module, so we // tests in the main module. This package is in the main module, so we
// need to identify the imports of its test even if LoadTests is not set. // need to identify the imports of its test even if LoadTests is not set.
@ -1446,7 +1506,7 @@ func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkg
if wantTest { if wantTest {
var testFlags loadPkgFlags var testFlags loadPkgFlags
if pkg.mod == Target || (ld.allClosesOverTests && new.has(pkgInAll)) { if MainModules.Contains(pkg.mod.Path) || (ld.allClosesOverTests && new.has(pkgInAll)) {
// Tests of packages in the main module are in "all", in the sense that // Tests of packages in the main module are in "all", in the sense that
// they cause the packages they import to also be in "all". So are tests // they cause the packages they import to also be in "all". So are tests
// of packages in "all" if "all" closes over test dependencies. // of packages in "all" if "all" closes over test dependencies.
@ -1593,7 +1653,7 @@ func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
if pkg.dir == "" { if pkg.dir == "" {
return return
} }
if pkg.mod == Target { if MainModules.Contains(pkg.mod.Path) {
// Go ahead and mark pkg as in "all". This provides the invariant that a // Go ahead and mark pkg as in "all". This provides the invariant that a
// package that is *only* imported by other packages in "all" is always // package that is *only* imported by other packages in "all" is always
// marked as such before loading its imports. // marked as such before loading its imports.
@ -1698,13 +1758,14 @@ func (ld *loader) stdVendor(parentPath, path string) string {
} }
if str.HasPathPrefix(parentPath, "cmd") { if str.HasPathPrefix(parentPath, "cmd") {
if !ld.VendorModulesInGOROOTSrc || Target.Path != "cmd" { if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("cmd") {
vendorPath := pathpkg.Join("cmd", "vendor", path) vendorPath := pathpkg.Join("cmd", "vendor", path)
if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil { if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
return vendorPath return vendorPath
} }
} }
} else if !ld.VendorModulesInGOROOTSrc || Target.Path != "std" || str.HasPathPrefix(parentPath, "vendor") { } else if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("std") || str.HasPathPrefix(parentPath, "vendor") {
// If we are outside of the 'std' module, resolve imports from within 'std' // If we are outside of the 'std' module, resolve imports from within 'std'
// to the vendor directory. // to the vendor directory.
// //
@ -1753,7 +1814,7 @@ func (ld *loader) checkMultiplePaths() {
firstPath := map[module.Version]string{} firstPath := map[module.Version]string{}
for _, mod := range mods { for _, mod := range mods {
src := resolveReplacement(mod) src, _ := resolveReplacement(mod)
if prev, ok := firstPath[src]; !ok { if prev, ok := firstPath[src]; !ok {
firstPath[src] = mod.Path firstPath[src] = mod.Path
} else if prev != mod.Path { } else if prev != mod.Path {
@ -1781,7 +1842,7 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr)
goFlag := "" goFlag := ""
if ld.GoVersion != modFileGoVersion() { if ld.GoVersion != MainModules.GoVersion() {
goFlag = " -go=" + ld.GoVersion goFlag = " -go=" + ld.GoVersion
} }

View file

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

View file

@ -42,7 +42,7 @@ type mvsReqs struct {
} }
func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) { func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) {
if mod == Target { if MainModules.Contains(mod.Path) {
// Use the build list as it existed when r was constructed, not the current // Use the build list as it existed when r was constructed, not the current
// global build list. // global build list.
return r.roots, nil return r.roots, nil
@ -113,7 +113,7 @@ func versions(ctx context.Context, path string, allowed AllowedFunc) ([]string,
func previousVersion(m module.Version) (module.Version, error) { func previousVersion(m module.Version) (module.Version, error) {
// TODO(golang.org/issue/38714): thread tracing context through MVS. // TODO(golang.org/issue/38714): thread tracing context through MVS.
if m == Target { if MainModules.Contains(m.Path) {
return module.Version{Path: m.Path, Version: "none"}, nil return module.Version{Path: m.Path, Version: "none"}, nil
} }

View file

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

View file

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

View file

@ -15,6 +15,7 @@ import (
"cmd/go/internal/base" "cmd/go/internal/base"
"golang.org/x/mod/modfile"
"golang.org/x/mod/module" "golang.org/x/mod/module"
"golang.org/x/mod/semver" "golang.org/x/mod/semver"
) )
@ -35,13 +36,13 @@ type vendorMetadata struct {
} }
// readVendorList reads the list of vendored modules from vendor/modules.txt. // readVendorList reads the list of vendored modules from vendor/modules.txt.
func readVendorList() { func readVendorList(mainModule module.Version) {
vendorOnce.Do(func() { vendorOnce.Do(func() {
vendorList = nil vendorList = nil
vendorPkgModule = make(map[string]module.Version) vendorPkgModule = make(map[string]module.Version)
vendorVersion = make(map[string]string) vendorVersion = make(map[string]string)
vendorMeta = make(map[module.Version]vendorMetadata) vendorMeta = make(map[module.Version]vendorMetadata)
data, err := os.ReadFile(filepath.Join(ModRoot(), "vendor/modules.txt")) data, err := os.ReadFile(filepath.Join(MainModules.ModRoot(mainModule), "vendor/modules.txt"))
if err != nil { if err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
base.Fatalf("go: %s", err) base.Fatalf("go: %s", err)
@ -134,8 +135,8 @@ func readVendorList() {
// checkVendorConsistency verifies that the vendor/modules.txt file matches (if // checkVendorConsistency verifies that the vendor/modules.txt file matches (if
// go 1.14) or at least does not contradict (go 1.13 or earlier) the // go 1.14) or at least does not contradict (go 1.13 or earlier) the
// requirements and replacements listed in the main module's go.mod file. // requirements and replacements listed in the main module's go.mod file.
func checkVendorConsistency() { func checkVendorConsistency(index *modFileIndex, modFile *modfile.File) {
readVendorList() readVendorList(MainModules.mustGetSingleMainModule())
pre114 := false pre114 := false
if semver.Compare(index.goVersionV, "v1.14") < 0 { if semver.Compare(index.goVersionV, "v1.14") < 0 {
@ -208,7 +209,7 @@ func checkVendorConsistency() {
} }
for _, mod := range vendorReplaced { for _, mod := range vendorReplaced {
r := Replacement(mod) r, _ := Replacement(mod)
if r == (module.Version{}) { if r == (module.Version{}) {
vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in go.mod") vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in go.mod")
continue continue
@ -219,6 +220,7 @@ func checkVendorConsistency() {
} }
if vendErrors.Len() > 0 { if vendErrors.Len() > 0 {
modRoot := MainModules.ModRoot(MainModules.mustGetSingleMainModule())
base.Fatalf("go: inconsistent vendoring in %s:%s\n\n\tTo ignore the vendor directory, use -mod=readonly or -mod=mod.\n\tTo sync the vendor directory, run:\n\t\tgo mod vendor", modRoot, vendErrors) base.Fatalf("go: inconsistent vendoring in %s:%s\n\n\tTo ignore the vendor directory, use -mod=readonly or -mod=mod.\n\tTo sync the vendor directory, run:\n\t\tgo mod vendor", modRoot, vendErrors)
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

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

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

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

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

View file

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

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

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

View file

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