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

@ -49,6 +49,8 @@ var (
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,11 +389,13 @@ 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 {
if pkg.Name == "main" && pkg.Module != nil {
if !modload.MainModules.Contains(pkg.Module.Path) {
haveExternalExe = true
break
}
}
}
if haveExternalExe {
fmt.Fprint(os.Stderr, "go get: installing executables with 'go get' in module mode is deprecated.")
var altMsg string
@ -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,9 +803,10 @@ 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{
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,8 +1759,9 @@ 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{
if modload.MainModules.Contains(m.Path) && m.Version != "" {
reportError(q, &modload.QueryMatchesMainModulesError{
MainModules: []module.Version{{Path: m.Path}},
Pattern: q.pattern,
Query: q.version,
})
@ -1775,7 +1791,7 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi
resolved := make([]module.Version, 0, len(r.resolvedVersion))
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,8 +192,8 @@ 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,
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))
}
}
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,12 +416,14 @@ func (mg *ModuleGraph) findError() error {
}
func (mg *ModuleGraph) allRootsSelected() bool {
roots, _ := mg.g.RequiredBy(Target)
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})
// 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
}
if rs.depth == eager && reflect.DeepEqual(min, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
roots = append(roots, min...)
}
if MainModules.Len() > 1 {
module.Sort(roots)
}
if rs.depth == eager && reflect.DeepEqual(roots, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
// The root set is unchanged and rs was already eager, so keep rs to
// 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 {
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 Target, dir, nil
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,8 +419,9 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) {
// To avoid spurious remote fetches, try the latest replacement for each
// module (golang.org/issue/26241).
if index != nil {
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
@ -439,6 +449,8 @@ func queryImport(ctx context.Context, path string, rs *Requirements) (module.Ver
}
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.
@ -467,12 +479,12 @@ func queryImport(ctx context.Context, path string, rs *Requirements) (module.Ver
// 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(mods[0]),
}
Replacement: replacement,
}
}
@ -638,14 +650,14 @@ func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFile
// The isLocal return value reports whether the replacement,
// 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{})
}

View file

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

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,16 +504,23 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
}
}
for _, mod := range MainModules.Versions() {
modRoot := MainModules.ModRoot(mod)
if modRoot != "" && absDir == modRoot {
if absDir == cfg.GOROOTsrc {
return "", errPkgIsGorootSrc
}
return targetPrefix, nil
return MainModules.PathPrefix(mod), 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.
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/") {
@ -502,7 +528,7 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
}
readVendorList()
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)
@ -510,7 +536,8 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
return pkg, nil
}
if targetPrefix == "" {
mainModulePrefix := MainModules.PathPrefix(mainModule)
if mainModulePrefix == "" {
pkg := strings.TrimPrefix(suffix, "/")
if pkg == "builtin" {
// "builtin" is a pseudo-package with a real source file.
@ -521,14 +548,26 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
return pkg, nil
}
pkg := targetPrefix + suffix
if _, ok, err := dirInModule(pkg, targetPrefix, modRoot, true); err != nil {
pkg := mainModulePrefix + suffix
if _, ok, err := dirInModule(pkg, mainModulePrefix, modRoot, true); err != nil {
return "", err
} else if !ok {
return "", &PackageNotInModuleError{Mod: Target, Pattern: pkg}
// This main module could contain the directory but doesn't. Other main
// modules might contain the directory, so wait till we finish the loop
// to see if another main module contains directory. But if not,
// return an error.
if len(mainModulePrefix) > len(pkgNotFoundLongestPrefix) {
pkgNotFoundLongestPrefix = mainModulePrefix
pkgNotFoundErr = &PackageNotInModuleError{MainModules: []module.Version{mainModule}, Pattern: pkg}
}
continue
}
return pkg, nil
}
}
if pkgNotFoundErr != nil {
return "", pkgNotFoundErr
}
if sub := search.InDir(absDir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") {
pkg := filepath.ToSlash(sub)
@ -557,10 +596,10 @@ func pathInModuleCache(ctx context.Context, dir string, rs *Requirements) string
tryMod := func(m module.Version) (string, bool) {
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)
}
var longestPrefix string
var longestPrefixPath string
var longestPrefixVersion module.Version
for _, v := range mms.Versions() {
modRoot := mms.ModRoot(v)
if dir == modRoot {
return targetPrefix
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/") {
return strings.TrimPrefix(suffix, "/vendor/")
longestPrefixPath = strings.TrimPrefix(suffix, "/vendor/")
}
return targetPrefix + suffix
longestPrefixPath = mms.PathPrefix(v) + suffix
}
return "."
}
}
if len(longestPrefix) > 0 {
return longestPrefixPath, longestPrefixVersion
}
return ".", module.Version{}
}
// ImportMap returns the actual package import path
@ -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,9 +132,11 @@ 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] {
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.
// If there is no replacement for mod, Replacement returns
// a module.Version with Path == "".
func Replacement(mod module.Version) module.Version {
if index != nil {
func replacement(mod module.Version, index *modFileIndex) (fromVersion string, to module.Version, ok bool) {
if r, ok := index.replace[mod]; ok {
return r
return mod.Version, r, true
}
if r, ok := index.replace[module.Version{Path: mod.Path}]; ok {
return r
return "", r, true
}
return "", module.Version{}, false
}
// Replacement returns the replacement for mod, if any, and and the module root
// directory of the main module containing the replace directive.
// If there is no replacement for mod, Replacement returns
// a module.Version with Path == "".
func Replacement(mod module.Version) (module.Version, string) {
_ = TODOWorkspaces("Support replaces in the go.work file.")
foundFrom, found, foundModRoot := "", module.Version{}, ""
for _, v := range MainModules.Versions() {
if index := MainModules.Index(v); index != nil {
if from, r, ok := replacement(mod, index); ok {
modRoot := MainModules.ModRoot(v)
if foundModRoot != "" && foundFrom != from && found != r {
_ = TODOWorkspaces("once the go.work file supports replaces, recommend them as a way to override conflicts")
base.Errorf("conflicting replacements found for %v in workspace modules defined by %v and %v",
mod, modFilePath(foundModRoot), modFilePath(modRoot))
return found, foundModRoot
}
found, foundModRoot = r, modRoot
}
}
return module.Version{}
}
return found, foundModRoot
}
// resolveReplacement returns the module actually used to load the source code
// 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,7 +571,8 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
}
}
if index != nil && len(index.exclude) > 0 {
for _, mainModule := range MainModules.Versions() {
if index := MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 {
// Drop any requirements on excluded versions.
// Don't modify the cached summary though, since we might need the raw
// summary separately.
@ -576,6 +595,7 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
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
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})
}
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,14 +631,15 @@ 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{
} else if len(mainModuleMatches) != 0 {
return nil, nil, &QueryMatchesMainModulesError{
MainModules: mainModuleMatches,
Pattern: pattern,
Query: query,
}
} else {
return nil, nil, &PackageNotInModuleError{
Mod: Target,
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}
}
// TODO(#45713): Join all the highestReplaced fields into a single value.
for _, mm := range MainModules.Versions() {
index := MainModules.Index(mm)
if index == nil {
return repo, err
continue
}
if _, ok := index.highestReplaced[path]; ok {
return &replacementRepo{repo: repo}, nil
}
if _, ok := index.highestReplaced[path]; !ok {
return repo, err
}
return &replacementRepo{repo: repo}, nil
return repo, err
}
// An emptyRepo is a versionRepo that contains no versions.
@ -1024,7 +1046,8 @@ func (rr *replacementRepo) Versions(prefix string) ([]string, error) {
}
versions := repoVersions
if index != nil && len(index.replace) > 0 {
for _, mm := range MainModules.Versions() {
if index := MainModules.Index(mm); index != nil && len(index.replace) > 0 {
path := rr.ModulePath()
for m, _ := range index.replace {
if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) {
@ -1032,6 +1055,7 @@ func (rr *replacementRepo) Versions(prefix string) ([]string, error) {
}
}
}
}
if len(versions) == len(repoVersions) { // No replacement versions added.
return versions, nil
@ -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,10 +1106,26 @@ func (rr *replacementRepo) Stat(rev string) (*modfetch.RevInfo, error) {
func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) {
info, err := rr.repo.Latest()
if index != nil {
path := rr.ModulePath()
highestReplaced, found := "", false
for _, mm := range MainModules.Versions() {
if index := MainModules.Index(mm); index != nil {
if v, ok := index.highestReplaced[path]; ok {
if !found {
highestReplaced, found = v, true
continue
}
if semver.Compare(v, highestReplaced) > 0 {
highestReplaced = 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
@ -1094,7 +1143,6 @@ func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) {
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 {
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
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,11 +1364,16 @@ 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.
if exclude != nil {
haveExclude := make(map[module.Version]bool)
for _, x := range f.Exclude {
for _, x := range *exclude {
if haveExclude[x.Mod] {
kill[x.Syntax] = true
continue
@ -1294,18 +1381,19 @@ func (f *File) removeDups() {
haveExclude[x.Mod] = true
}
var excl []*Exclude
for _, x := range f.Exclude {
for _, x := range *exclude {
if !kill[x.Syntax] {
excl = append(excl, x)
}
}
f.Exclude = excl
*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