summaryrefslogtreecommitdiff
path: root/libgo/go/cmd/go/internal/modcmd/edit.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/cmd/go/internal/modcmd/edit.go')
-rw-r--r--libgo/go/cmd/go/internal/modcmd/edit.go382
1 files changed, 382 insertions, 0 deletions
diff --git a/libgo/go/cmd/go/internal/modcmd/edit.go b/libgo/go/cmd/go/internal/modcmd/edit.go
new file mode 100644
index 00000000000..5fea3e48e08
--- /dev/null
+++ b/libgo/go/cmd/go/internal/modcmd/edit.go
@@ -0,0 +1,382 @@
+// 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.
+
+// go mod edit
+
+package modcmd
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/modfile"
+ "cmd/go/internal/modload"
+ "cmd/go/internal/module"
+)
+
+var cmdEdit = &base.Command{
+ UsageLine: "go mod edit [editing flags] [go.mod]",
+ Short: "edit go.mod from tools or scripts",
+ Long: `
+Edit provides a command-line interface for editing go.mod,
+for use primarily by tools or scripts. It reads only go.mod;
+it does not look up information about the modules involved.
+By default, edit reads and writes the go.mod file of the main module,
+but a different target file can be specified after the editing flags.
+
+The editing flags specify a sequence of editing operations.
+
+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 edit -fmt'.
+
+The -module flag changes 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[@v] and -dropreplace=old[@v] flags
+add and drop a replacement of the given module path and version pair.
+If the @v in old@v is omitted, the replacement applies to all versions
+with 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 existing replacements for old[@v].
+
+The -require, -droprequire, -exclude, -dropexclude, -replace,
+and -dropreplace editing flags may be repeated, and the changes
+are applied in the order given.
+
+The -print flag prints the final go.mod in its text format instead of
+writing it back to go.mod.
+
+The -json flag prints the final go.mod 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 GoMod struct {
+ Module Module
+ Require []Require
+ Exclude []Module
+ Replace []Replace
+ }
+
+ type Require struct {
+ Path string
+ Version string
+ Indirect bool
+ }
+
+ type Replace struct {
+ Old Module
+ 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'.
+
+For example, a tool can obtain the go.mod as a data structure by
+parsing the output of 'go mod edit -json' and can then make changes
+by invoking 'go mod edit' with -require, -exclude, and so on.
+ `,
+}
+
+var (
+ editFmt = cmdEdit.Flag.Bool("fmt", false, "")
+ // editGo = cmdEdit.Flag.String("go", "", "")
+ editJSON = cmdEdit.Flag.Bool("json", false, "")
+ editPrint = cmdEdit.Flag.Bool("print", false, "")
+ editModule = cmdEdit.Flag.String("module", "", "")
+ edits []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() {
+ cmdEdit.Run = runEdit // break init cycle
+
+ cmdEdit.Flag.Var(flagFunc(flagRequire), "require", "")
+ cmdEdit.Flag.Var(flagFunc(flagDropRequire), "droprequire", "")
+ cmdEdit.Flag.Var(flagFunc(flagExclude), "exclude", "")
+ cmdEdit.Flag.Var(flagFunc(flagDropReplace), "dropreplace", "")
+ cmdEdit.Flag.Var(flagFunc(flagReplace), "replace", "")
+ cmdEdit.Flag.Var(flagFunc(flagDropExclude), "dropexclude", "")
+
+ base.AddBuildFlagsNX(&cmdEdit.Flag)
+}
+
+func runEdit(cmd *base.Command, args []string) {
+ anyFlags :=
+ *editModule != "" ||
+ *editJSON ||
+ *editPrint ||
+ *editFmt ||
+ len(edits) > 0
+
+ if !anyFlags {
+ base.Fatalf("go mod edit: no flags specified (see 'go help mod edit').")
+ }
+
+ if *editJSON && *editPrint {
+ base.Fatalf("go mod edit: cannot use both -json and -print")
+ }
+
+ if len(args) > 1 {
+ base.Fatalf("go mod edit: too many arguments")
+ }
+ var gomod string
+ if len(args) == 1 {
+ gomod = args[0]
+ } else {
+ modload.MustInit()
+ gomod = filepath.Join(modload.ModRoot, "go.mod")
+ }
+
+ if *editModule != "" {
+ if err := module.CheckPath(*editModule); err != nil {
+ base.Fatalf("go mod: invalid -module: %v", err)
+ }
+ }
+
+ // TODO(rsc): Implement -go= once we start advertising it.
+
+ data, err := ioutil.ReadFile(gomod)
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
+
+ modFile, err := modfile.Parse(gomod, data, nil)
+ if err != nil {
+ base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gomod), err)
+ }
+
+ if *editModule != "" {
+ modFile.AddModuleStmt(modload.CmdModModule)
+ }
+
+ if len(edits) > 0 {
+ for _, edit := range edits {
+ edit(modFile)
+ }
+ }
+ modFile.SortBlocks()
+ modFile.Cleanup() // clean file after edits
+
+ if *editJSON {
+ editPrintJSON(modFile)
+ return
+ }
+
+ data, err = modFile.Format()
+ if err != nil {
+ base.Fatalf("go: %v", err)
+ }
+
+ if *editPrint {
+ os.Stdout.Write(data)
+ return
+ }
+
+ if err := ioutil.WriteFile(gomod, data, 0666); err != nil {
+ base.Fatalf("go: %v", err)
+ }
+}
+
+// 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("go 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("go 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 the go command 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("go 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("go mod: -%s=%s: need just path, not path@version", flag, arg)
+ }
+ path = arg
+ if err := module.CheckPath(path); err != nil {
+ base.Fatalf("go mod: -%s=%s: invalid path: %v", flag, arg, err)
+ }
+ return path
+}
+
+// parsePathVersionOptional parses path[@version], using adj to
+// describe any errors.
+func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
+ if i := strings.Index(arg, "@"); i < 0 {
+ path = arg
+ } else {
+ path, version = strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
+ }
+ if err := module.CheckPath(path); err != nil {
+ if !allowDirPath || !modfile.IsDirectoryPath(path) {
+ return path, version, fmt.Errorf("invalid %s path: %v", adj, err)
+ }
+ }
+ if path != arg && modfile.MustQuote(version) {
+ return path, version, fmt.Errorf("invalid %s version: %q", adj, version)
+ }
+ return path, version, nil
+}
+
+// flagRequire implements the -require flag.
+func flagRequire(arg string) {
+ path, version := parsePathVersion("require", arg)
+ edits = append(edits, func(f *modfile.File) {
+ if err := f.AddRequire(path, version); err != nil {
+ base.Fatalf("go mod: -require=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagDropRequire implements the -droprequire flag.
+func flagDropRequire(arg string) {
+ path := parsePath("droprequire", arg)
+ edits = append(edits, func(f *modfile.File) {
+ if err := f.DropRequire(path); err != nil {
+ base.Fatalf("go mod: -droprequire=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagExclude implements the -exclude flag.
+func flagExclude(arg string) {
+ path, version := parsePathVersion("exclude", arg)
+ edits = append(edits, func(f *modfile.File) {
+ if err := f.AddExclude(path, version); err != nil {
+ base.Fatalf("go mod: -exclude=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagDropExclude implements the -dropexclude flag.
+func flagDropExclude(arg string) {
+ path, version := parsePathVersion("dropexclude", arg)
+ edits = append(edits, func(f *modfile.File) {
+ if err := f.DropExclude(path, version); err != nil {
+ base.Fatalf("go mod: -dropexclude=%s: %v", arg, err)
+ }
+ })
+}
+
+// flagReplace implements the -replace flag.
+func flagReplace(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)
+ }
+
+ edits = append(edits, func(f *modfile.File) {
+ 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 flagDropReplace(arg string) {
+ path, version, err := parsePathVersionOptional("old", arg, true)
+ if err != nil {
+ base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
+ }
+ edits = append(edits, func(f *modfile.File) {
+ if err := f.DropReplace(path, version); err != nil {
+ base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
+ }
+ })
+}
+
+// fileJSON is the -json output data structure.
+type fileJSON struct {
+ Module module.Version
+ Require []requireJSON
+ Exclude []module.Version
+ Replace []replaceJSON
+}
+
+type requireJSON struct {
+ Path string
+ Version string `json:",omitempty"`
+ Indirect bool `json:",omitempty"`
+}
+
+type replaceJSON struct {
+ Old module.Version
+ New module.Version
+}
+
+// editPrintJSON prints the -json output.
+func editPrintJSON(modFile *modfile.File) {
+ var f fileJSON
+ f.Module = modFile.Module.Mod
+ for _, r := range modFile.Require {
+ f.Require = append(f.Require, requireJSON{Path: r.Mod.Path, Version: r.Mod.Version, Indirect: r.Indirect})
+ }
+ 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("go: internal error: %v", err)
+ }
+ data = append(data, '\n')
+ os.Stdout.Write(data)
+}