github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/cmd/go/internal/modcmd/edit.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // go mod edit
     6  
     7  package modcmd
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"os"
    14  	"path/filepath"
    15  	"strings"
    16  
    17  	"cmd/go/internal/base"
    18  	"cmd/go/internal/modfile"
    19  	"cmd/go/internal/modload"
    20  	"cmd/go/internal/module"
    21  )
    22  
    23  var cmdEdit = &base.Command{
    24  	UsageLine: "go mod edit [editing flags] [go.mod]",
    25  	Short:     "edit go.mod from tools or scripts",
    26  	Long: `
    27  Edit provides a command-line interface for editing go.mod,
    28  for use primarily by tools or scripts. It reads only go.mod;
    29  it does not look up information about the modules involved.
    30  By default, edit reads and writes the go.mod file of the main module,
    31  but a different target file can be specified after the editing flags.
    32  
    33  The editing flags specify a sequence of editing operations.
    34  
    35  The -fmt flag reformats the go.mod file without making other changes.
    36  This reformatting is also implied by any other modifications that use or
    37  rewrite the go.mod file. The only time this flag is needed is if no other
    38  flags are specified, as in 'go mod edit -fmt'.
    39  
    40  The -module flag changes the module's path (the go.mod file's module line).
    41  
    42  The -require=path@version and -droprequire=path flags
    43  add and drop a requirement on the given module path and version.
    44  Note that -require overrides any existing requirements on path.
    45  These flags are mainly for tools that understand the module graph.
    46  Users should prefer 'go get path@version' or 'go get path@none',
    47  which make other go.mod adjustments as needed to satisfy
    48  constraints imposed by other modules.
    49  
    50  The -exclude=path@version and -dropexclude=path@version flags
    51  add and drop an exclusion for the given module path and version.
    52  Note that -exclude=path@version is a no-op if that exclusion already exists.
    53  
    54  The -replace=old[@v]=new[@v] and -dropreplace=old[@v] flags
    55  add and drop a replacement of the given module path and version pair.
    56  If the @v in old@v is omitted, the replacement applies to all versions
    57  with the old module path. If the @v in new@v is omitted, the new path
    58  should be a local module root directory, not a module path.
    59  Note that -replace overrides any existing replacements for old[@v].
    60  
    61  The -require, -droprequire, -exclude, -dropexclude, -replace,
    62  and -dropreplace editing flags may be repeated, and the changes
    63  are applied in the order given.
    64  
    65  The -print flag prints the final go.mod in its text format instead of
    66  writing it back to go.mod.
    67  
    68  The -json flag prints the final go.mod file in JSON format instead of
    69  writing it back to go.mod. The JSON output corresponds to these Go types:
    70  
    71  	type Module struct {
    72  		Path string
    73  		Version string
    74  	}
    75  
    76  	type GoMod struct {
    77  		Module Module
    78  		Require []Require
    79  		Exclude []Module
    80  		Replace []Replace
    81  	}
    82  
    83  	type Require struct {
    84  		Path string
    85  		Version string
    86  		Indirect bool
    87  	}
    88  
    89  	type Replace struct {
    90  		Old Module
    91  		New Module
    92  	}
    93  
    94  Note that this only describes the go.mod file itself, not other modules
    95  referred to indirectly. For the full set of modules available to a build,
    96  use 'go list -m -json all'.
    97  
    98  For example, a tool can obtain the go.mod as a data structure by
    99  parsing the output of 'go mod edit -json' and can then make changes
   100  by invoking 'go mod edit' with -require, -exclude, and so on.
   101  	`,
   102  }
   103  
   104  var (
   105  	editFmt = cmdEdit.Flag.Bool("fmt", false, "")
   106  	// editGo     = cmdEdit.Flag.String("go", "", "")
   107  	editJSON   = cmdEdit.Flag.Bool("json", false, "")
   108  	editPrint  = cmdEdit.Flag.Bool("print", false, "")
   109  	editModule = cmdEdit.Flag.String("module", "", "")
   110  	edits      []func(*modfile.File) // edits specified in flags
   111  )
   112  
   113  type flagFunc func(string)
   114  
   115  func (f flagFunc) String() string     { return "" }
   116  func (f flagFunc) Set(s string) error { f(s); return nil }
   117  
   118  func init() {
   119  	cmdEdit.Run = runEdit // break init cycle
   120  
   121  	cmdEdit.Flag.Var(flagFunc(flagRequire), "require", "")
   122  	cmdEdit.Flag.Var(flagFunc(flagDropRequire), "droprequire", "")
   123  	cmdEdit.Flag.Var(flagFunc(flagExclude), "exclude", "")
   124  	cmdEdit.Flag.Var(flagFunc(flagDropReplace), "dropreplace", "")
   125  	cmdEdit.Flag.Var(flagFunc(flagReplace), "replace", "")
   126  	cmdEdit.Flag.Var(flagFunc(flagDropExclude), "dropexclude", "")
   127  
   128  	base.AddBuildFlagsNX(&cmdEdit.Flag)
   129  }
   130  
   131  func runEdit(cmd *base.Command, args []string) {
   132  	anyFlags :=
   133  		*editModule != "" ||
   134  			*editJSON ||
   135  			*editPrint ||
   136  			*editFmt ||
   137  			len(edits) > 0
   138  
   139  	if !anyFlags {
   140  		base.Fatalf("go mod edit: no flags specified (see 'go help mod edit').")
   141  	}
   142  
   143  	if *editJSON && *editPrint {
   144  		base.Fatalf("go mod edit: cannot use both -json and -print")
   145  	}
   146  
   147  	if len(args) > 1 {
   148  		base.Fatalf("go mod edit: too many arguments")
   149  	}
   150  	var gomod string
   151  	if len(args) == 1 {
   152  		gomod = args[0]
   153  	} else {
   154  		modload.MustInit()
   155  		gomod = filepath.Join(modload.ModRoot, "go.mod")
   156  	}
   157  
   158  	if *editModule != "" {
   159  		if err := module.CheckPath(*editModule); err != nil {
   160  			base.Fatalf("go mod: invalid -module: %v", err)
   161  		}
   162  	}
   163  
   164  	// TODO(rsc): Implement -go= once we start advertising it.
   165  
   166  	data, err := ioutil.ReadFile(gomod)
   167  	if err != nil {
   168  		base.Fatalf("go: %v", err)
   169  	}
   170  
   171  	modFile, err := modfile.Parse(gomod, data, nil)
   172  	if err != nil {
   173  		base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gomod), err)
   174  	}
   175  
   176  	if *editModule != "" {
   177  		modFile.AddModuleStmt(modload.CmdModModule)
   178  	}
   179  
   180  	if len(edits) > 0 {
   181  		for _, edit := range edits {
   182  			edit(modFile)
   183  		}
   184  	}
   185  	modFile.SortBlocks()
   186  	modFile.Cleanup() // clean file after edits
   187  
   188  	if *editJSON {
   189  		editPrintJSON(modFile)
   190  		return
   191  	}
   192  
   193  	data, err = modFile.Format()
   194  	if err != nil {
   195  		base.Fatalf("go: %v", err)
   196  	}
   197  
   198  	if *editPrint {
   199  		os.Stdout.Write(data)
   200  		return
   201  	}
   202  
   203  	if err := ioutil.WriteFile(gomod, data, 0666); err != nil {
   204  		base.Fatalf("go: %v", err)
   205  	}
   206  }
   207  
   208  // parsePathVersion parses -flag=arg expecting arg to be path@version.
   209  func parsePathVersion(flag, arg string) (path, version string) {
   210  	i := strings.Index(arg, "@")
   211  	if i < 0 {
   212  		base.Fatalf("go mod: -%s=%s: need path@version", flag, arg)
   213  	}
   214  	path, version = strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
   215  	if err := module.CheckPath(path); err != nil {
   216  		base.Fatalf("go mod: -%s=%s: invalid path: %v", flag, arg, err)
   217  	}
   218  
   219  	// We don't call modfile.CheckPathVersion, because that insists
   220  	// on versions being in semver form, but here we want to allow
   221  	// versions like "master" or "1234abcdef", which the go command will resolve
   222  	// the next time it runs (or during -fix).
   223  	// Even so, we need to make sure the version is a valid token.
   224  	if modfile.MustQuote(version) {
   225  		base.Fatalf("go mod: -%s=%s: invalid version %q", flag, arg, version)
   226  	}
   227  
   228  	return path, version
   229  }
   230  
   231  // parsePath parses -flag=arg expecting arg to be path (not path@version).
   232  func parsePath(flag, arg string) (path string) {
   233  	if strings.Contains(arg, "@") {
   234  		base.Fatalf("go mod: -%s=%s: need just path, not path@version", flag, arg)
   235  	}
   236  	path = arg
   237  	if err := module.CheckPath(path); err != nil {
   238  		base.Fatalf("go mod: -%s=%s: invalid path: %v", flag, arg, err)
   239  	}
   240  	return path
   241  }
   242  
   243  // parsePathVersionOptional parses path[@version], using adj to
   244  // describe any errors.
   245  func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
   246  	if i := strings.Index(arg, "@"); i < 0 {
   247  		path = arg
   248  	} else {
   249  		path, version = strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
   250  	}
   251  	if err := module.CheckPath(path); err != nil {
   252  		if !allowDirPath || !modfile.IsDirectoryPath(path) {
   253  			return path, version, fmt.Errorf("invalid %s path: %v", adj, err)
   254  		}
   255  	}
   256  	if path != arg && modfile.MustQuote(version) {
   257  		return path, version, fmt.Errorf("invalid %s version: %q", adj, version)
   258  	}
   259  	return path, version, nil
   260  }
   261  
   262  // flagRequire implements the -require flag.
   263  func flagRequire(arg string) {
   264  	path, version := parsePathVersion("require", arg)
   265  	edits = append(edits, func(f *modfile.File) {
   266  		if err := f.AddRequire(path, version); err != nil {
   267  			base.Fatalf("go mod: -require=%s: %v", arg, err)
   268  		}
   269  	})
   270  }
   271  
   272  // flagDropRequire implements the -droprequire flag.
   273  func flagDropRequire(arg string) {
   274  	path := parsePath("droprequire", arg)
   275  	edits = append(edits, func(f *modfile.File) {
   276  		if err := f.DropRequire(path); err != nil {
   277  			base.Fatalf("go mod: -droprequire=%s: %v", arg, err)
   278  		}
   279  	})
   280  }
   281  
   282  // flagExclude implements the -exclude flag.
   283  func flagExclude(arg string) {
   284  	path, version := parsePathVersion("exclude", arg)
   285  	edits = append(edits, func(f *modfile.File) {
   286  		if err := f.AddExclude(path, version); err != nil {
   287  			base.Fatalf("go mod: -exclude=%s: %v", arg, err)
   288  		}
   289  	})
   290  }
   291  
   292  // flagDropExclude implements the -dropexclude flag.
   293  func flagDropExclude(arg string) {
   294  	path, version := parsePathVersion("dropexclude", arg)
   295  	edits = append(edits, func(f *modfile.File) {
   296  		if err := f.DropExclude(path, version); err != nil {
   297  			base.Fatalf("go mod: -dropexclude=%s: %v", arg, err)
   298  		}
   299  	})
   300  }
   301  
   302  // flagReplace implements the -replace flag.
   303  func flagReplace(arg string) {
   304  	var i int
   305  	if i = strings.Index(arg, "="); i < 0 {
   306  		base.Fatalf("go mod: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
   307  	}
   308  	old, new := strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
   309  	if strings.HasPrefix(new, ">") {
   310  		base.Fatalf("go mod: -replace=%s: separator between old and new is =, not =>", arg)
   311  	}
   312  	oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
   313  	if err != nil {
   314  		base.Fatalf("go mod: -replace=%s: %v", arg, err)
   315  	}
   316  	newPath, newVersion, err := parsePathVersionOptional("new", new, true)
   317  	if err != nil {
   318  		base.Fatalf("go mod: -replace=%s: %v", arg, err)
   319  	}
   320  	if newPath == new && !modfile.IsDirectoryPath(new) {
   321  		base.Fatalf("go mod: -replace=%s: unversioned new path must be local directory", arg)
   322  	}
   323  
   324  	edits = append(edits, func(f *modfile.File) {
   325  		if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
   326  			base.Fatalf("go mod: -replace=%s: %v", arg, err)
   327  		}
   328  	})
   329  }
   330  
   331  // flagDropReplace implements the -dropreplace flag.
   332  func flagDropReplace(arg string) {
   333  	path, version, err := parsePathVersionOptional("old", arg, true)
   334  	if err != nil {
   335  		base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
   336  	}
   337  	edits = append(edits, func(f *modfile.File) {
   338  		if err := f.DropReplace(path, version); err != nil {
   339  			base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
   340  		}
   341  	})
   342  }
   343  
   344  // fileJSON is the -json output data structure.
   345  type fileJSON struct {
   346  	Module  module.Version
   347  	Require []requireJSON
   348  	Exclude []module.Version
   349  	Replace []replaceJSON
   350  }
   351  
   352  type requireJSON struct {
   353  	Path     string
   354  	Version  string `json:",omitempty"`
   355  	Indirect bool   `json:",omitempty"`
   356  }
   357  
   358  type replaceJSON struct {
   359  	Old module.Version
   360  	New module.Version
   361  }
   362  
   363  // editPrintJSON prints the -json output.
   364  func editPrintJSON(modFile *modfile.File) {
   365  	var f fileJSON
   366  	f.Module = modFile.Module.Mod
   367  	for _, r := range modFile.Require {
   368  		f.Require = append(f.Require, requireJSON{Path: r.Mod.Path, Version: r.Mod.Version, Indirect: r.Indirect})
   369  	}
   370  	for _, x := range modFile.Exclude {
   371  		f.Exclude = append(f.Exclude, x.Mod)
   372  	}
   373  	for _, r := range modFile.Replace {
   374  		f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
   375  	}
   376  	data, err := json.MarshalIndent(&f, "", "\t")
   377  	if err != nil {
   378  		base.Fatalf("go: internal error: %v", err)
   379  	}
   380  	data = append(data, '\n')
   381  	os.Stdout.Write(data)
   382  }