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