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
golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e // indirect
golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a
golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
golang.org/x/tools v0.1.6-0.20210809225032-337cebd2c151

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/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI=
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a h1:e8qnjKz4EE6OjRki9wTadWSIogINvq10sMcuBRORxMY=
golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61 h1:gQY3CVezomIImcWCpxp6Mhj+fXCOZ+gD8/88326LVqw=
golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View file

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

View file

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

View file

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

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.
func ExtraEnvVars() []cfg.EnvVar {
gomod := ""
modload.Init()
if modload.HasModRoot() {
gomod = filepath.Join(modload.ModRoot(), "go.mod")
gomod = modload.ModFilePath()
} else if modload.Enabled() {
gomod = os.DevNull
}

View file

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

View file

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

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

View file

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

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() {
cmdGraph.Flag.Var(&graphGo, "go", "")
base.AddModCommonFlags(&cmdGraph.Flag)
base.AddWorkfileFlag(&cmdGraph.Flag)
}
func runGraph(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
if len(args) > 0 {
base.Fatalf("go mod graph: graph takes no arguments")
}

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{
cmdDownload,
cmdEdit,
cmdEditwork,
cmdGraph,
cmdInit,
cmdInitwork,
cmdTidy,
cmdVendor,
cmdVerify,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

@ -78,7 +78,7 @@ cmp go.mod go.mod.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.
cp go.alt.mod goaltmod
! go mod tidy -modfile=goaltmod

View file

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

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":
arrow := 2
if len(args) >= 2 && args[1] == "=>" {
arrow = 1
}
if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb)
replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
if wrappederr != nil {
*errs = append(*errs, *wrappederr)
return
}
s, err := parseString(&args[0])
if err != nil {
errorf("invalid quoted string: %v", err)
return
}
pathMajor, err := modulePathMajor(s)
if err != nil {
wrapModPathError(s, err)
return
}
var v string
if arrow == 2 {
v, err = parseVersion(verb, s, &args[1], fix)
if err != nil {
wrapError(err)
return
}
if err := module.CheckPathMajor(v, pathMajor); err != nil {
wrapModPathError(s, err)
return
}
}
ns, err := parseString(&args[arrow+1])
if err != nil {
errorf("invalid quoted string: %v", err)
return
}
nv := ""
if len(args) == arrow+2 {
if !IsDirectoryPath(ns) {
errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)")
return
}
if filepath.Separator == '/' && strings.Contains(ns, `\`) {
errorf("replacement directory appears to be Windows path (on a non-windows system)")
return
}
}
if len(args) == arrow+3 {
nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
if err != nil {
wrapError(err)
return
}
if IsDirectoryPath(ns) {
errorf("replacement module directory path %q cannot have version", ns)
return
}
}
f.Replace = append(f.Replace, &Replace{
Old: module.Version{Path: s, Version: v},
New: module.Version{Path: ns, Version: nv},
Syntax: line,
})
f.Replace = append(f.Replace, replace)
case "retract":
rationale := parseDirectiveComment(block, line)
@ -515,6 +459,83 @@ func (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
// 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
// as a directory path. Just like on the go command line, relative paths
// and rooted paths are directory paths; the rest are module paths.
@ -1165,6 +1243,10 @@ func (f *File) DropExclude(path, vers 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
old := module.Version{Path: oldPath, Version: oldVers}
new := module.Version{Path: newPath, Version: newVers}
@ -1178,12 +1260,12 @@ func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
}
var hint *Line
for _, r := range f.Replace {
for _, r := range *replace {
if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
if need {
// Found replacement for old; update to use new.
r.New = new
f.Syntax.updateLine(r.Syntax, tokens...)
syntax.updateLine(r.Syntax, tokens...)
need = false
continue
}
@ -1196,7 +1278,7 @@ func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
}
}
if need {
f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)})
*replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)})
}
return nil
}
@ -1282,30 +1364,36 @@ func (f *File) SortBlocks() {
// retract directives are not de-duplicated since comments are
// meaningful, and versions may be retracted multiple times.
func (f *File) removeDups() {
removeDups(f.Syntax, &f.Exclude, &f.Replace)
}
func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace) {
kill := make(map[*Line]bool)
// Remove duplicate excludes.
haveExclude := make(map[module.Version]bool)
for _, x := range f.Exclude {
if haveExclude[x.Mod] {
kill[x.Syntax] = true
continue
if exclude != nil {
haveExclude := make(map[module.Version]bool)
for _, x := range *exclude {
if haveExclude[x.Mod] {
kill[x.Syntax] = true
continue
}
haveExclude[x.Mod] = true
}
haveExclude[x.Mod] = true
}
var excl []*Exclude
for _, x := range f.Exclude {
if !kill[x.Syntax] {
excl = append(excl, x)
var excl []*Exclude
for _, x := range *exclude {
if !kill[x.Syntax] {
excl = append(excl, x)
}
}
*exclude = excl
}
f.Exclude = excl
// Remove duplicate replacements.
// Later replacements take priority over earlier ones.
haveReplace := make(map[module.Version]bool)
for i := len(f.Replace) - 1; i >= 0; i-- {
x := f.Replace[i]
for i := len(*replace) - 1; i >= 0; i-- {
x := (*replace)[i]
if haveReplace[x.Old] {
kill[x.Syntax] = true
continue
@ -1313,18 +1401,18 @@ func (f *File) removeDups() {
haveReplace[x.Old] = true
}
var repl []*Replace
for _, x := range f.Replace {
for _, x := range *replace {
if !kill[x.Syntax] {
repl = append(repl, x)
}
}
f.Replace = repl
*replace = repl
// Duplicate require and retract directives are not removed.
// Drop killed statements from the syntax tree.
var stmts []Expr
for _, stmt := range f.Syntax.Stmt {
for _, stmt := range syntax.Stmt {
switch stmt := stmt.(type) {
case *Line:
if kill[stmt] {
@ -1344,7 +1432,7 @@ func (f *File) removeDups() {
}
stmts = append(stmts, stmt)
}
f.Syntax.Stmt = stmts
syntax.Stmt = stmts
}
// 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
golang.org/x/crypto/ed25519
golang.org/x/crypto/ed25519/internal/edwards25519
# golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a
# golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61
## explicit; go 1.17
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile