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