cmd/go/internal/modcmd: add "vgo mod" command
The new "vgo mod" command in this CL provides a single command
for a handful of basic module maintenance and query operations,
instead of allowing them to explode the number of non-package-based
go subcommands.
In a followup CL, "vgo vendor" will become "vgo mod -vendor", and
"vgo verify" will become "vgo mod -verify".
In another followup CL, "vgo sync" will remove unused modules from go.mod.
See the help text in modcmd/mod.go for an overview of the command.
Change-Id: I114ce32f7f7c1b7725a9cab09e084c86fc99574b
Reviewed-on: https://go-review.googlesource.com/118317
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/modcmd/mod.go b/vendor/cmd/go/internal/modcmd/mod.go
new file mode 100644
index 0000000..f8b36d4
--- /dev/null
+++ b/vendor/cmd/go/internal/modcmd/mod.go
@@ -0,0 +1,427 @@
+// Copyright 2018 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 modcmd implements the ``go mod'' command.
+package modcmd
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "strings"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/modfile"
+ "cmd/go/internal/module"
+ "cmd/go/internal/vgo"
+)
+
+var CmdMod = &base.Command{
+ UsageLine: "mod [maintenance flags]",
+ Short: "module maintenance",
+ Long: `
+Mod performs module maintenance operations as specified by the
+following flags, which may be combined.
+
+The first group of operations provide low-level editing operations
+for manipulating go.mod from the command line or in scripts or
+other tools. They read only go.mod itself; they do not look up any
+information about the modules involved.
+
+The -init flag initializes and writes a new go.mod to the current directory,
+in effect creating a new module rooted at the current directory.
+The file go.mod must not already exist.
+If possible, mod will guess the module path from import comments
+(see 'go help importpath') or from version control configuration.
+To override this guess, use the -module flag.
+(Without -init, mod applies to the current module.)
+
+The -module flag changes (or, with -init, sets) the module's path
+(the go.mod file's module line).
+
+The -require=path@version and -droprequire=path flags
+add and drop a requirement on the given module path and version.
+Note that -require overrides any existing requirements on path.
+These flags are mainly for tools that understand the module graph.
+Users should prefer 'go get path@version' or 'go get path@none',
+which make other go.mod adjustments as needed to satisfy
+constraints imposed by other modules.
+
+The -exclude=path@version and -dropexclude=path@version flags
+add and drop an exclusion for the given module path and version.
+Note that -exclude=path@version is a no-op if that exclusion already exists.
+
+The -replace=old@v=>new@w and -dropreplace=old@v flags
+add and drop a replacement of the given module path and version pair.
+Note that -replace overrides any existing replacements for old@v.
+
+These editing flags (-require, -droprequire, -exclude, -dropexclude,
+-replace, and -dropreplace) may be repeated.
+
+The -fmt flag reformats the go.mod 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 -fmt'.
+
+The -json flag prints the go.mod file in JSON format corresponding to these
+Go types:
+
+ type Module struct {
+ Path string
+ Version string
+ }
+
+ type GoMod struct {
+ Module Module
+ Require []Module
+ Exclude []Module
+ Replace []struct{ Old, New Module }
+ }
+
+Note that this only describes the go.mod file itself, not other modules
+referred to indirectly. For the full set of modules available to a build,
+use 'go list -m -json all'.
+
+The next group of operations provide higher-level editing and maintenance
+of a module, beyond the go.mod file.
+
+The -packages flag prints a list of packages in the module.
+It only identifies directories containing Go source code;
+it does not check that those directories contain code that builds.
+
+The -fix flag updates go.mod to use canonical version identifiers and
+to be semantically consistent. For example, consider this go.mod file:
+
+ module M
+
+ require (
+ A v1
+ B v1.0.0
+ C v1.0.0
+ D v1.2.3
+ E dev
+ )
+
+ exclude D v1.2.3
+
+First, -fix rewrites non-canonical version identifiers to semver form, so
+A's v1 becomes v1.0.0 and E's dev becomes the pseudo-version for the latest
+commit on the dev branch, perhaps v0.0.0-20180523231146-b3f5c0f6e5f1.
+
+Next, -fix updates requirements to respect exclusions, so the requirement
+on the excluded D v1.2.3 is updated to use the next available version of D,
+perhaps D v1.2.4 or D v1.3.0.
+
+Finally, -fix removes redundant or misleading requirements.
+For example, if A v1.0.0 itself requires B v1.2.0 and C v1.0.0,
+then go.mod's requirement of B v1.0.0 is misleading (superseded
+by B's need for v1.2.0), and its requirement of C v1.0.0 is redundant
+(implied by B's need for the same version), so both will be removed.
+
+Although -fix runs the fix-up operation in isolation, the fix-up also
+runs automatically any time a go command uses the module graph,
+to update go.mod to reflect reality. For example, the -sync, -vendor,
+and -verify flags all effectively imply -fix. And because the module
+graph defines the meaning of import statements, any commands
+that load packages—'go build', 'go test', 'go list', and so on—also
+effectively imply 'go mod -fix'.
+
+The -sync flag synchronizes go.mod with the source code in the module.
+It adds any missing modules necessary to build the current module's
+packages and dependencies, and it removes unused modules that
+don't provide any relevant packages.
+
+The -vendor flag resets the module's vendor directory to include all
+packages needed to build and test all the module's packages and
+their dependencies.
+
+The -verify flag checks that the dependencies of the current module,
+which are stored in a local downloaded source cache, have not been
+modified since being downloaded. If all the modules are unmodified,
+-verify prints "all modules verified." Otherwise it reports which
+modules have been changed and causes 'go mod' to exit with a
+non-zero status.
+ `,
+}
+
+var (
+ modFmt = CmdMod.Flag.Bool("fmt", false, "")
+ modFix = CmdMod.Flag.Bool("fix", false, "")
+ modJSON = CmdMod.Flag.Bool("json", false, "")
+ modPackages = CmdMod.Flag.Bool("packages", false, "")
+ modSync = CmdMod.Flag.Bool("sync", false, "")
+ modVendor = CmdMod.Flag.Bool("vendor", false, "")
+ modVerify = CmdMod.Flag.Bool("verify", false, "")
+
+ modEdits []func(*modfile.File) // edits specified in flags
+)
+
+type flagFunc func(string)
+
+func (f flagFunc) String() string { return "" }
+func (f flagFunc) Set(s string) error { f(s); return nil }
+
+func init() {
+ CmdMod.Run = runMod // break init cycle
+
+ CmdMod.Flag.BoolVar(&vgo.CmdModInit, "init", vgo.CmdModInit, "")
+ CmdMod.Flag.StringVar(&vgo.CmdModModule, "module", vgo.CmdModModule, "")
+
+ CmdMod.Flag.Var(flagFunc(flagAddRequire), "addrequire", "")
+ CmdMod.Flag.Var(flagFunc(flagDropRequire), "droprequire", "")
+ CmdMod.Flag.Var(flagFunc(flagAddExclude), "addexclude", "")
+ CmdMod.Flag.Var(flagFunc(flagDropReplace), "dropreplace", "")
+ CmdMod.Flag.Var(flagFunc(flagAddReplace), "addreplace", "")
+ CmdMod.Flag.Var(flagFunc(flagDropExclude), "dropexclude", "")
+}
+
+func runMod(cmd *base.Command, args []string) {
+ if vgo.Init(); !vgo.Enabled() {
+ base.Fatalf("vgo mod: cannot use outside module")
+ }
+ if len(args) != 0 {
+ base.Fatalf("vgo mod: mod takes no arguments")
+ }
+
+ anyFlags :=
+ vgo.CmdModInit ||
+ vgo.CmdModModule != "" ||
+ *modVendor ||
+ *modVerify ||
+ *modJSON ||
+ *modFmt ||
+ *modFix ||
+ *modPackages ||
+ *modSync ||
+ len(modEdits) > 0
+
+ if !anyFlags {
+ base.Fatalf("vgo mod: no flags specified (see 'go help mod').")
+ }
+
+ if vgo.CmdModModule != "" {
+ if err := module.CheckPath(vgo.CmdModModule); err != nil {
+ base.Fatalf("vgo mod: invalid -module: %v", err)
+ }
+ }
+
+ if vgo.CmdModInit {
+ if _, err := os.Stat("go.mod"); err == nil {
+ base.Fatalf("vgo mod -init: go.mod already exists")
+ }
+ }
+ vgo.InitMod()
+
+ // Syntactic edits.
+
+ modFile := vgo.ModFile()
+ if vgo.CmdModModule != "" {
+ modFile.AddModuleStmt(vgo.CmdModModule)
+ }
+
+ if len(modEdits) > 0 {
+ for _, edit := range modEdits {
+ edit(modFile)
+ }
+ }
+ vgo.WriteGoMod() // write back syntactic changes
+
+ // Semantic edits.
+
+ needBuildList := *modFix
+
+ if *modSync || *modVendor || needBuildList {
+ if *modSync || *modVendor {
+ fmt.Println(vgo.ImportPaths([]string{"ALL"}))
+ } else {
+ vgo.LoadBuildList()
+ }
+ if *modSync {
+ // ImportPaths(ALL) already added missing modules.
+ // Remove unused modules.
+ panic("TODO sync unused")
+ }
+ vgo.WriteGoMod()
+ if *modVendor {
+ panic("TODO: move runVendor over")
+ }
+ }
+
+ // Read-only queries, processed only after updating go.mod.
+
+ if *modJSON {
+ modPrintJSON()
+ }
+
+ if *modPackages {
+ for _, pkg := range vgo.TargetPackages() {
+ fmt.Printf("%s\n", pkg)
+ }
+ }
+
+ if *modVerify {
+ panic("TODO: move runVerify over")
+ }
+}
+
+// parsePathVersion parses -flag=arg expecting arg to be path@version.
+func parsePathVersion(flag, arg string) (path, version string) {
+ i := strings.Index(arg, "@")
+ if i < 0 {
+ base.Fatalf("vgo mod: -%s=%s: need path@version", flag, arg)
+ }
+ path, version = strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
+ if err := module.CheckPath(path); err != nil {
+ base.Fatalf("vgo mod: -%s=%s: invalid path: %v", flag, arg, err)
+ }
+
+ // We don't call modfile.CheckPathVersion, because that insists
+ // on versions being in semver form, but here we want to allow
+ // versions like "master" or "1234abcdef", which vgo will resolve
+ // the next time it runs (or during -fix).
+ // Even so, we need to make sure the version is a valid token.
+ if modfile.MustQuote(version) {
+ base.Fatalf("vgo mod: -%s=%s: invalid version %q", flag, arg, version)
+ }
+
+ return path, version
+}
+
+// parsePath parses -flag=arg expecting arg to be path (not path@version).
+func parsePath(flag, arg string) (path string) {
+ if strings.Contains(arg, "@") {
+ base.Fatalf("vgo mod: -%s=%s: need just path, not path@version", flag, arg)
+ }
+ path = arg
+ if err := module.CheckPath(path); err != nil {
+ base.Fatalf("vgo mod: -%s=%s: invalid path: %v", flag, arg, err)
+ }
+ return path
+}
+
+// flagAddRequire implements the -addrequire flag.
+func flagAddRequire(arg string) {
+ path, version := parsePathVersion("addrequire", arg)
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.AddRequire(path, version); err != nil {
+ base.Fatalf("vgo mod: -addrequire=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagDropRequire implements the -droprequire flag.
+func flagDropRequire(arg string) {
+ path := parsePath("droprequire", arg)
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.DropRequire(path); err != nil {
+ base.Fatalf("vgo mod: -droprequire=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagAddExclude implements the -addexclude flag.
+func flagAddExclude(arg string) {
+ path, version := parsePathVersion("addexclude", arg)
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.AddExclude(path, version); err != nil {
+ base.Fatalf("vgo mod: -addexclude=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagDropExclude implements the -dropexclude flag.
+func flagDropExclude(arg string) {
+ path, version := parsePathVersion("dropexclude", arg)
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.DropExclude(path, version); err != nil {
+ base.Fatalf("vgo mod: -dropexclude=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagAddReplace implements the -addreplace flag.
+func flagAddReplace(arg string) {
+ var i int
+ if i = strings.Index(arg, "=>"); i < 0 {
+ base.Fatalf("vgo mod: -addreplace=%s: need old@v=>new[@v] (missing =>)", arg)
+ }
+ old, new := strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+2:])
+ if i = strings.Index(old, "@"); i < 0 {
+ base.Fatalf("vgo mod: -addreplace=%s: need old@v=>new[@v] (missing @ in old@v)", arg)
+ }
+ oldPath, oldVersion := strings.TrimSpace(old[:i]), strings.TrimSpace(old[i+1:])
+ if err := module.CheckPath(oldPath); err != nil {
+ base.Fatalf("vgo mod: -addreplace=%s: invalid old path: %v", arg, err)
+ }
+ if modfile.MustQuote(oldVersion) {
+ base.Fatalf("vgo mod: -addreplace=%s: invalid old version %q", arg, oldVersion)
+ }
+ var newPath, newVersion string
+ if i = strings.Index(new, "@"); i >= 0 {
+ newPath, newVersion = strings.TrimSpace(new[:i]), strings.TrimSpace(new[i+1:])
+ if err := module.CheckPath(newPath); err != nil {
+ base.Fatalf("vgo mod: -addreplace=%s: invalid new path: %v", arg, err)
+ }
+ if modfile.MustQuote(newVersion) {
+ base.Fatalf("vgo mod: -addreplace=%s: invalid new version %q", arg, newVersion)
+ }
+ } else {
+ if !modfile.IsDirectoryPath(new) {
+ base.Fatalf("vgo mod: -addreplace=%s: unversioned new path must be local directory", arg)
+ }
+ newPath = new
+ }
+
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
+ base.Fatalf("vgo mod: -addreplace=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagDropReplace implements the -dropreplace flag.
+func flagDropReplace(arg string) {
+ path, version := parsePathVersion("dropreplace", arg)
+ modEdits = append(modEdits, func(f *modfile.File) {
+ if err := f.DropReplace(path, version); err != nil {
+ base.Fatalf("vgo mod: -dropreplace=%s: %v", arg, err)
+ }
+ })
+}
+
+// fileJSON is the -json output data structure.
+type fileJSON struct {
+ Module module.Version
+ Require []module.Version
+ Exclude []module.Version
+ Replace []replaceJSON
+}
+
+type replaceJSON struct {
+ Old module.Version
+ New module.Version
+}
+
+// modPrintJSON prints the -json output.
+func modPrintJSON() {
+ modFile := vgo.ModFile()
+
+ var f fileJSON
+ f.Module = modFile.Module.Mod
+ for _, r := range modFile.Require {
+ f.Require = append(f.Require, r.Mod)
+ }
+ for _, x := range modFile.Exclude {
+ f.Exclude = append(f.Exclude, x.Mod)
+ }
+ for _, r := range modFile.Replace {
+ f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
+ }
+ data, err := json.MarshalIndent(&f, "", "\t")
+ if err != nil {
+ base.Fatalf("vgo mod -json: internal error: %v", err)
+ }
+ data = append(data, '\n')
+ os.Stdout.Write(data)
+}
diff --git a/vendor/cmd/go/internal/modfile/read.go b/vendor/cmd/go/internal/modfile/read.go
index f4a3926..8963fc2 100644
--- a/vendor/cmd/go/internal/modfile/read.go
+++ b/vendor/cmd/go/internal/modfile/read.go
@@ -89,6 +89,130 @@
return start, end
}
+func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line {
+ if hint == nil {
+ // If no hint given, add to the first statement of the given type.
+ Loop:
+ for _, stmt := range x.Stmt {
+ switch stmt := stmt.(type) {
+ case *Line:
+ if stmt.Token != nil && stmt.Token[0] == tokens[0] {
+ hint = stmt
+ break Loop
+ }
+ case *LineBlock:
+ if stmt.Token[0] == tokens[0] {
+ hint = stmt
+ break Loop
+ }
+ }
+ }
+ }
+
+ if hint != nil {
+ for i, stmt := range x.Stmt {
+ switch stmt := stmt.(type) {
+ case *Line:
+ if stmt == hint {
+ // Convert line to line block.
+ stmt.InBlock = true
+ block := &LineBlock{Token: stmt.Token[:1], Line: []*Line{stmt}}
+ stmt.Token = stmt.Token[1:]
+ x.Stmt[i] = block
+ new := &Line{Token: tokens[1:], InBlock: true}
+ block.Line = append(block.Line, new)
+ return new
+ }
+ case *LineBlock:
+ if stmt == hint {
+ new := &Line{Token: tokens[1:], InBlock: true}
+ stmt.Line = append(stmt.Line, new)
+ return new
+ }
+ for j, line := range stmt.Line {
+ if line == hint {
+ // Add new line after hint.
+ stmt.Line = append(stmt.Line, nil)
+ copy(stmt.Line[j+2:], stmt.Line[j+1:])
+ new := &Line{Token: tokens[1:], InBlock: true}
+ stmt.Line[j+1] = new
+ return new
+ }
+ }
+ }
+ }
+ }
+
+ new := &Line{Token: tokens}
+ x.Stmt = append(x.Stmt, new)
+ return new
+}
+
+func (x *FileSyntax) updateLine(line *Line, tokens ...string) {
+ if line.InBlock {
+ tokens = tokens[1:]
+ }
+ line.Token = tokens
+}
+
+func (x *FileSyntax) removeLine(line *Line) {
+ line.Token = nil
+}
+
+// Cleanup cleans up the file syntax x after any edit operations.
+// To avoid quadratic behavior, removeLine marks the line as dead
+// by setting line.Token = nil but does not remove it from the slice
+// in which it appears. After edits have all been indicated,
+// calling Cleanup cleans out the dead lines.
+func (x *FileSyntax) Cleanup() {
+ w := 0
+ for _, stmt := range x.Stmt {
+ switch stmt := stmt.(type) {
+ case *Line:
+ if stmt.Token == nil {
+ continue
+ }
+ case *LineBlock:
+ ww := 0
+ for _, line := range stmt.Line {
+ if line.Token != nil {
+ stmt.Line[ww] = line
+ ww++
+ }
+ }
+ if ww == 0 {
+ continue
+ }
+ if ww == 1 {
+ // Collapse block into single line.
+ line := &Line{
+ Comments: Comments{
+ Before: commentsAdd(stmt.Before, stmt.Line[0].Before),
+ Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix),
+ After: commentsAdd(stmt.Line[0].After, stmt.After),
+ },
+ Token: stringsAdd(stmt.Token, stmt.Line[0].Token),
+ }
+ x.Stmt[w] = line
+ w++
+ continue
+ }
+ stmt.Line = stmt.Line[:ww]
+ }
+ x.Stmt[w] = stmt
+ w++
+ }
+ x.Stmt = x.Stmt[:w]
+}
+
+func commentsAdd(x, y []Comment) []Comment {
+ return append(x[:len(x):len(x)], y...)
+}
+
+func stringsAdd(x, y []string) []string {
+ return append(x[:len(x):len(x)], y...)
+}
+
// A CommentBlock represents a top-level block of comments separate
// from any rule.
type CommentBlock struct {
@@ -103,9 +227,10 @@
// A Line is a single line of tokens.
type Line struct {
Comments
- Start Position
- Token []string
- End Position
+ Start Position
+ Token []string
+ InBlock bool
+ End Position
}
func (x *Line) Span() (start, end Position) {
@@ -680,9 +805,10 @@
switch tok {
case '\n', _EOF, _EOL:
return &Line{
- Start: start,
- Token: token,
- End: end,
+ Start: start,
+ Token: token,
+ End: end,
+ InBlock: true,
}
default:
token = append(token, sym.text)
diff --git a/vendor/cmd/go/internal/modfile/rule.go b/vendor/cmd/go/internal/modfile/rule.go
index 5a784a3..3170146 100644
--- a/vendor/cmd/go/internal/modfile/rule.go
+++ b/vendor/cmd/go/internal/modfile/rule.go
@@ -18,6 +18,7 @@
"cmd/go/internal/semver"
)
+// A File is the parsed, interpreted form of a go.mod file.
type File struct {
Module *Module
Require []*Require
@@ -27,38 +28,45 @@
Syntax *FileSyntax
}
+// A Module is the module statement.
type Module struct {
- Mod module.Version
- Major string
+ Mod module.Version
+ Syntax *Line
}
+// A Require is a single require statement.
type Require struct {
Mod module.Version
Syntax *Line
}
+// An Exclude is a single exclude statement.
type Exclude struct {
Mod module.Version
Syntax *Line
}
+// A Replace is a single replace statement.
type Replace struct {
- Old module.Version
- New module.Version
-
+ Old module.Version
+ New module.Version
Syntax *Line
}
-func (f *File) AddModuleStmt(path string) {
- f.Module = &Module{
- Mod: module.Version{Path: path},
- }
+func (f *File) AddModuleStmt(path string) error {
if f.Syntax == nil {
f.Syntax = new(FileSyntax)
}
- f.Syntax.Stmt = append(f.Syntax.Stmt, &Line{
- Token: []string{"module", AutoQuote(path)},
- })
+ if f.Module == nil {
+ f.Module = &Module{
+ Mod: module.Version{Path: path},
+ Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)),
+ }
+ } else {
+ f.Module.Mod.Path = path
+ f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path))
+ }
+ return nil
}
func (f *File) AddComment(text string) {
@@ -135,7 +143,7 @@
fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line)
return
}
- f.Module = new(Module)
+ f.Module = &Module{Syntax: line}
if len(args) != 1 {
fmt.Fprintf(errs, "%s:%d: usage: module module/path [version]\n", f.Syntax.Name, line.Start.Line)
@@ -215,7 +223,7 @@
}
nv := ""
if len(args) == 4 {
- if !isDirectoryPath(ns) {
+ if !IsDirectoryPath(ns) {
fmt.Fprintf(errs, "%s:%d: replacement module without version must be directory path (rooted or starting with ./ or ../)", f.Syntax.Name, line.Start.Line)
return
}
@@ -231,7 +239,7 @@
fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err)
return
}
- if isDirectoryPath(ns) {
+ if IsDirectoryPath(ns) {
fmt.Fprintf(errs, "%s:%d: replacement module directory path %q cannot have version", f.Syntax.Name, line.Start.Line, ns)
return
}
@@ -245,7 +253,10 @@
}
}
-func isDirectoryPath(ns string) bool {
+// 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.
+func IsDirectoryPath(ns string) bool {
// Because go.mod files can move from one system to another,
// we check all known path syntaxes, both Unix and Windows.
return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") ||
@@ -253,19 +264,21 @@
len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':'
}
-func mustQuote(t string) bool {
- for _, r := range t {
+// MustQuote reports whether s must be quoted in order to appear as
+// a single token in a go.mod line.
+func MustQuote(s string) bool {
+ for _, r := range s {
if !unicode.IsPrint(r) || r == ' ' || r == '"' || r == '\'' || r == '`' {
return true
}
}
- return t == "" || strings.Contains(t, "//") || strings.Contains(t, "/*")
+ return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*")
}
// AutoQuote returns s or, if quoting is required for s to appear in a go.mod,
// the quotation of s.
func AutoQuote(s string) string {
- if mustQuote(s) {
+ if MustQuote(s) {
return strconv.Quote(s)
}
return s
@@ -339,39 +352,64 @@
return Format(f.Syntax), nil
}
-func (x *File) AddRequire(path, vers string) {
- var syntax *Line
+// 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 *File) Cleanup() {
+ w := 0
+ for _, r := range f.Require {
+ if r.Mod.Path != "" {
+ f.Require[w] = r
+ w++
+ }
+ }
+ f.Require = f.Require[:w]
- for i, stmt := range x.Syntax.Stmt {
- switch stmt := stmt.(type) {
- case *LineBlock:
- if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
- syntax = &Line{Token: []string{AutoQuote(path), vers}}
- stmt.Line = append(stmt.Line, syntax)
- goto End
- }
- case *Line:
- if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
- stmt.Token = stmt.Token[1:]
- syntax = &Line{Token: []string{AutoQuote(path), vers}}
- x.Syntax.Stmt[i] = &LineBlock{
- Comments: stmt.Comments,
- Token: []string{"require"},
- Line: []*Line{
- stmt,
- syntax,
- },
- }
- goto End
+ w = 0
+ for _, x := range f.Exclude {
+ if x.Mod.Path != "" {
+ f.Exclude[w] = x
+ w++
+ }
+ }
+ f.Exclude = f.Exclude[: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 *File) AddRequire(path, vers string) error {
+ need := true
+ for _, r := range f.Require {
+ if r.Mod.Path == path {
+ if need {
+ r.Mod.Version = vers
+ f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers)
+ need = false
+ } else {
+ f.Syntax.removeLine(r.Syntax)
+ *r = Require{}
}
}
}
- syntax = &Line{Token: []string{"require", AutoQuote(path), vers}}
- x.Syntax.Stmt = append(x.Syntax.Stmt, syntax)
+ if need {
+ f.AddNewRequire(path, vers)
+ }
+ return nil
+}
-End:
- x.Require = append(x.Require, &Require{module.Version{Path: path, Version: vers}, syntax})
+func (f *File) AddNewRequire(path, vers string) {
+ f.Require = append(f.Require, &Require{module.Version{Path: path, Version: vers}, f.Syntax.addLine(nil, "require", AutoQuote(path), vers)})
}
func (f *File) SetRequire(req []module.Version) {
@@ -420,11 +458,89 @@
f.Syntax.Stmt = newStmts
for path, vers := range need {
- f.AddRequire(path, vers)
+ f.AddNewRequire(path, vers)
}
f.SortBlocks()
}
+func (f *File) DropRequire(path string) error {
+ for _, r := range f.Require {
+ if r.Mod.Path == path {
+ f.Syntax.removeLine(r.Syntax)
+ *r = Require{}
+ }
+ }
+ return nil
+}
+
+func (f *File) AddExclude(path, vers string) error {
+ var hint *Line
+ for _, x := range f.Exclude {
+ if x.Mod.Path == path && x.Mod.Version == vers {
+ return nil
+ }
+ if x.Mod.Path == path {
+ hint = x.Syntax
+ }
+ }
+
+ f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)})
+ return nil
+}
+
+func (f *File) DropExclude(path, vers string) error {
+ for _, x := range f.Exclude {
+ if x.Mod.Path == path && x.Mod.Version == vers {
+ f.Syntax.removeLine(x.Syntax)
+ *x = Exclude{}
+ }
+ }
+ return nil
+}
+
+func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
+ need := true
+ old := module.Version{Path: oldPath, Version: oldVers}
+ new := module.Version{Path: newPath, Version: newVers}
+ tokens := []string{"replace", AutoQuote(oldPath), oldVers, "=>", AutoQuote(newPath)}
+ if newVers != "" {
+ tokens = append(tokens, newVers)
+ }
+
+ var hint *Line
+ for _, r := range f.Replace {
+ if r.Old == old {
+ if need {
+ // Found replacement for old; update to use new.
+ r.New = new
+ f.Syntax.updateLine(r.Syntax, tokens...)
+ need = false
+ continue
+ }
+ // Already added; delete other replacements for same.
+ f.Syntax.removeLine(r.Syntax)
+ *r = Replace{}
+ }
+ if r.Old.Path == oldPath {
+ hint = r.Syntax
+ }
+ }
+ if need {
+ f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)})
+ }
+ return nil
+}
+
+func (f *File) DropReplace(oldPath, oldVers string) error {
+ for _, r := range f.Replace {
+ if r.Old.Path == oldPath && r.Old.Version == oldVers {
+ f.Syntax.removeLine(r.Syntax)
+ *r = Replace{}
+ }
+ }
+ return nil
+}
+
func (f *File) SortBlocks() {
f.removeDups() // otherwise sorting is unsafe
diff --git a/vendor/cmd/go/internal/vgo/get.go b/vendor/cmd/go/internal/vgo/get.go
index b61f7bc..988e50d 100644
--- a/vendor/cmd/go/internal/vgo/get.go
+++ b/vendor/cmd/go/internal/vgo/get.go
@@ -141,7 +141,7 @@
}
*/
}
- writeGoMod()
+ WriteGoMod()
if len(args) > 0 {
InstallHook(args)
diff --git a/vendor/cmd/go/internal/vgo/init.go b/vendor/cmd/go/internal/vgo/init.go
index 826dfd8..1b6beb4 100644
--- a/vendor/cmd/go/internal/vgo/init.go
+++ b/vendor/cmd/go/internal/vgo/init.go
@@ -41,8 +41,29 @@
gopath string
srcV string
+
+ CmdModInit bool // go mod -init flag
+ CmdModModule string // go mod -module flag
+
)
+// TargetPackages returns the list of packages in the target (top-level) module.
+func TargetPackages() []string {
+ return matchPackages("ALL", []module.Version{Target})
+}
+
+// ModFile returns the parsed go.mod file.
+//
+// Note that after calling ImportPaths or LoadBuildList,
+// the require statements in the modfile.File are no longer
+// the source of truth and will be ignored: edits made directly
+// will be lost at the next call to WriteGoMod.
+// To make permanent changes to the require statements
+// in go.mod, edit it before calling ImportPaths or LoadBuildList.
+func ModFile() *modfile.File {
+ return modFile
+}
+
func BinDir() string {
if !Enabled() {
panic("vgo.Bin")
@@ -110,17 +131,23 @@
base.Fatalf("go: %v", err)
}
- root, _ := FindModuleRoot(cwd, "", MustBeVgo)
- if root == "" {
- // If invoked as vgo, insist on a mod file.
- if MustBeVgo {
- base.Fatalf("cannot determine module root; please create a go.mod file there")
+ if CmdModInit {
+ // Running 'go mod -init': go.mod will be created in current directory.
+ ModRoot = cwd
+ } else {
+ root, _ := FindModuleRoot(cwd, "", MustBeVgo)
+ if root == "" {
+ // If invoked as vgo, insist on a mod file.
+ if MustBeVgo {
+ base.Fatalf("cannot determine module root; please create a go.mod file there")
+ }
+ return
}
- return
+ ModRoot = root
}
+
enabled = true
- ModRoot = root
- search.SetModRoot(root)
+ search.SetModRoot(ModRoot)
}
func Enabled() bool {
@@ -146,11 +173,21 @@
srcV = filepath.Join(list[0], "src/v")
codehost.WorkRoot = filepath.Join(srcV, "cache/vcswork")
+ if CmdModInit {
+ // Running go mod -init: do legacy module conversion
+ // (go.mod does not exist yet).
+ legacyModInit()
+ return
+ }
+
gomod := filepath.Join(ModRoot, "go.mod")
data, err := ioutil.ReadFile(gomod)
if err != nil {
- legacyModInit()
- return
+ if os.IsNotExist(err) {
+ legacyModInit()
+ return
+ }
+ base.Fatalf("vgo: %v", err)
}
f, err := modfile.Parse(gomod, data, fixVersion)
@@ -180,7 +217,7 @@
excluded[x.Mod] = true
}
Target = f.Module.Mod
- writeGoMod()
+ WriteGoMod()
}
func allowed(m module.Version) bool {
@@ -282,6 +319,10 @@
// Exported only for testing.
func FindModulePath(dir string) (string, error) {
+ if CmdModModule != "" {
+ // Running go mod -init -module=x/y/z; return x/y/z.
+ return CmdModModule, nil
+ }
for _, gpdir := range filepath.SplitList(cfg.BuildContext.GOPATH) {
src := filepath.Join(gpdir, "src") + string(filepath.Separator)
if strings.HasPrefix(dir, src) {
@@ -358,7 +399,8 @@
return path
}
-func writeGoMod() {
+// WriteGoMod writes the current build list back to go.mod.
+func WriteGoMod() {
writeModHash()
if buildList != nil {
@@ -371,6 +413,7 @@
file := filepath.Join(ModRoot, "go.mod")
old, _ := ioutil.ReadFile(file)
+ modFile.Cleanup() // clean file after edits
new, err := modFile.Format()
if err != nil {
base.Fatalf("vgo: %v", err)
diff --git a/vendor/cmd/go/internal/vgo/load.go b/vendor/cmd/go/internal/vgo/load.go
index d650bf2..c56838f 100644
--- a/vendor/cmd/go/internal/vgo/load.go
+++ b/vendor/cmd/go/internal/vgo/load.go
@@ -60,7 +60,26 @@
ld.importList(imports, levelBuild)
ld.importList(testImports, levelBuild)
})
- writeGoMod()
+ WriteGoMod()
+}
+
+// LoadBuildList loads the build list from go.mod.
+// The loading of the build list happens automatically in ImportPaths:
+// LoadBuildList need only be called if ImportPaths is not
+// (typically in commands that care about the module but
+// no particular package).
+func LoadBuildList() {
+ if Init(); !Enabled() {
+ base.Fatalf("vgo: LoadBuildList called but vgo not enabled")
+ }
+ InitMod()
+ iterate(func(*loader) {})
+ WriteGoMod()
+}
+
+// PkgMod returns a map from package import path to the module supplying that package.
+func PkgMod() map[string]module.Version {
+ return pkgmod
}
func ImportPaths(args []string) []string {
@@ -70,7 +89,7 @@
InitMod()
paths := importPaths(args)
- writeGoMod()
+ WriteGoMod()
return paths
}
diff --git a/vendor/cmd/go/internal/vgo/verify.go b/vendor/cmd/go/internal/vgo/verify.go
index d21c9d7..0938092 100644
--- a/vendor/cmd/go/internal/vgo/verify.go
+++ b/vendor/cmd/go/internal/vgo/verify.go
@@ -44,9 +44,7 @@
}
// Make go.mod consistent but don't load any packages.
- InitMod()
- iterate(func(*loader) {})
- writeGoMod()
+ LoadBuildList()
ok := true
for _, mod := range buildList[1:] {
diff --git a/vendor/cmd/go/main.go b/vendor/cmd/go/main.go
index 0604990..d8c6b22 100644
--- a/vendor/cmd/go/main.go
+++ b/vendor/cmd/go/main.go
@@ -27,6 +27,7 @@
"cmd/go/internal/get"
"cmd/go/internal/help"
"cmd/go/internal/list"
+ "cmd/go/internal/modcmd"
"cmd/go/internal/run"
"cmd/go/internal/test"
"cmd/go/internal/tool"
@@ -49,6 +50,7 @@
get.CmdGet,
work.CmdInstall,
list.CmdList,
+ modcmd.CmdMod,
run.CmdRun,
test.CmdTest,
tool.CmdTool,
@@ -124,9 +126,16 @@
os.Exit(2)
}
- vgo.Init()
- if !vgo.MustBeVgo {
- if vgo.Enabled() {
+ // Run vgo.Init so that each subcommand doesn't have to worry about it.
+ // Also install the vgo get command instead of the old go get command in vgo mode.
+ //
+ // If we should be vgo (if the command is named vgo or if invoked as go -vgo),
+ // and there is no go.mod file, vgo.Init will treat that as a fatal error.
+ // Normally that's fine, but if this is 'go mod -init' we need to give it a
+ // chance to create that go.mod file, so skip the init dance for 'go mod'.
+ if args[0] != "mod" {
+ vgo.Init()
+ if !vgo.MustBeVgo && vgo.Enabled() {
// Didn't do this above, so do it now.
*get.CmdGet = *vgo.CmdGet
}
diff --git a/vendor/cmd/go/vgo_test.go b/vendor/cmd/go/vgo_test.go
index 54ad36f..10e0d6b 100644
--- a/vendor/cmd/go/vgo_test.go
+++ b/vendor/cmd/go/vgo_test.go
@@ -5,8 +5,7 @@
package Main_test
import (
- "cmd/go/internal/modconv"
- "cmd/go/internal/vgo"
+ "bytes"
"internal/testenv"
"io/ioutil"
"os"
@@ -14,6 +13,9 @@
"runtime"
"sort"
"testing"
+
+ "cmd/go/internal/modconv"
+ "cmd/go/internal/vgo"
)
func TestVGOROOT(t *testing.T) {
@@ -80,6 +82,146 @@
}
}
+func TestModEdit(t *testing.T) {
+ // Test that local replacements work
+ // and that they can use a dummy name
+ // that isn't resolvable and need not even
+ // include a dot. See golang.org/issue/24100.
+ tg := testgo(t)
+ defer tg.cleanup()
+ tg.makeTempdir()
+ tg.cd(tg.path("."))
+ tg.must(os.MkdirAll(tg.path("w"), 0777))
+ tg.must(ioutil.WriteFile(tg.path("x.go"), []byte("package x\n"), 0666))
+ tg.must(ioutil.WriteFile(tg.path("w/w.go"), []byte("package w\n"), 0666))
+
+ mustHaveGoMod := func(text string) {
+ data, err := ioutil.ReadFile(tg.path("go.mod"))
+ tg.must(err)
+ if string(data) != text {
+ t.Fatalf("go.mod mismatch:\nhave:<<<\n%s>>>\nwant:<<<\n%s\n", string(data), text)
+ }
+ }
+
+ tg.runFail("-vgo", "mod", "-init")
+ tg.grepStderr(`cannot determine module path`, "")
+ _, err := os.Stat(tg.path("go.mod"))
+ if err == nil {
+ t.Fatalf("failed go mod -init created go.mod")
+ }
+
+ tg.run("-vgo", "mod", "-init", "-module", "x.x/y/z")
+ tg.grepStderr("creating new go.mod: module x.x/y/z", "")
+ mustHaveGoMod(`module x.x/y/z
+`)
+
+ tg.runFail("-vgo", "mod", "-init")
+ mustHaveGoMod(`module x.x/y/z
+`)
+
+ tg.run("-vgo", "mod",
+ "-droprequire=x.1",
+ "-addrequire=x.1@v1.0.0",
+ "-addrequire=x.2@v1.1.0",
+ "-droprequire=x.2",
+ "-addexclude=x.1 @ v1.2.0",
+ "-addexclude=x.1@v1.2.1",
+ "-addreplace=x.1@v1.3.0=>y.1@v1.4.0",
+ "-addreplace=x.1@v1.4.0 => ../z",
+ )
+ mustHaveGoMod(`module x.x/y/z
+
+require x.1 v1.0.0
+
+exclude (
+ x.1 v1.2.0
+ x.1 v1.2.1
+)
+
+replace (
+ x.1 v1.3.0 => y.1 v1.4.0
+ x.1 v1.4.0 => ../z
+)
+`)
+
+ tg.run("-vgo", "mod",
+ "-droprequire=x.1",
+ "-dropexclude=x.1@v1.2.1",
+ "-dropreplace=x.1@v1.3.0",
+ "-addrequire=x.3@v1.99.0",
+ )
+ mustHaveGoMod(`module x.x/y/z
+
+exclude x.1 v1.2.0
+
+replace x.1 v1.4.0 => ../z
+
+require x.3 v1.99.0
+`)
+
+ tg.run("-vgo", "mod", "-json")
+ want := `{
+ "Module": {
+ "Path": "x.x/y/z",
+ "Version": ""
+ },
+ "Require": [
+ {
+ "Path": "x.3",
+ "Version": "v1.99.0"
+ }
+ ],
+ "Exclude": [
+ {
+ "Path": "x.1",
+ "Version": "v1.2.0"
+ }
+ ],
+ "Replace": [
+ {
+ "Old": {
+ "Path": "x.1",
+ "Version": "v1.4.0"
+ },
+ "New": {
+ "Path": "../z",
+ "Version": ""
+ }
+ }
+ ]
+}
+`
+ if have := tg.getStdout(); have != want {
+ t.Fatalf("go mod -json mismatch:\nhave:<<<\n%s>>>\nwant:<<<\n%s\n", have, want)
+ }
+
+ tg.run("-vgo", "mod", "-packages")
+ want = `x.x/y/z
+x.x/y/z/w
+`
+ if have := tg.getStdout(); have != want {
+ t.Fatalf("go mod -packages mismatch:\nhave:<<<\n%s>>>\nwant:<<<\n%s\n", have, want)
+ }
+
+ data, err := ioutil.ReadFile(tg.path("go.mod"))
+ tg.must(err)
+ data = bytes.Replace(data, []byte("\n"), []byte("\r\n"), -1)
+ data = append(data, " \n"...)
+ tg.must(ioutil.WriteFile(tg.path("go.mod"), data, 0666))
+
+ tg.run("-vgo", "mod", "-fmt")
+ mustHaveGoMod(`module x.x/y/z
+
+exclude x.1 v1.2.0
+
+replace x.1 v1.4.0 => ../z
+
+require x.3 v1.99.0
+`)
+}
+
+// TODO(rsc): Test mod -sync, mod -fix (network required).
+
func TestLocalModule(t *testing.T) {
// Test that local replacements work
// and that they can use a dummy name