Source file src/cmd/go/internal/modcmd/edit.go

     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  	"context"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"os"
    16  	"strings"
    17  
    18  	"cmd/go/internal/base"
    19  	"cmd/go/internal/gover"
    20  	"cmd/go/internal/lockedfile"
    21  	"cmd/go/internal/modfetch"
    22  	"cmd/go/internal/modload"
    23  
    24  	"golang.org/x/mod/modfile"
    25  	"golang.org/x/mod/module"
    26  )
    27  
    28  var cmdEdit = &base.Command{
    29  	UsageLine: "go mod edit [editing flags] [-fmt|-print|-json] [go.mod]",
    30  	Short:     "edit go.mod from tools or scripts",
    31  	Long: `
    32  Edit provides a command-line interface for editing go.mod,
    33  for use primarily by tools or scripts. It reads only go.mod;
    34  it does not look up information about the modules involved.
    35  By default, edit reads and writes the go.mod file of the main module,
    36  but a different target file can be specified after the editing flags.
    37  
    38  The editing flags specify a sequence of editing operations.
    39  
    40  The -fmt flag reformats the go.mod file without making other changes.
    41  This reformatting is also implied by any other modifications that use or
    42  rewrite the go.mod file. The only time this flag is needed is if no other
    43  flags are specified, as in 'go mod edit -fmt'.
    44  
    45  The -module flag changes the module's path (the go.mod file's module line).
    46  
    47  The -godebug=key=value flag adds a godebug key=value line,
    48  replacing any existing godebug lines with the given key.
    49  
    50  The -dropgodebug=key flag drops any existing godebug lines
    51  with the given key.
    52  
    53  The -require=path@version and -droprequire=path flags
    54  add and drop a requirement on the given module path and version.
    55  Note that -require overrides any existing requirements on path.
    56  These flags are mainly for tools that understand the module graph.
    57  Users should prefer 'go get path@version' or 'go get path@none',
    58  which make other go.mod adjustments as needed to satisfy
    59  constraints imposed by other modules.
    60  
    61  The -go=version flag sets the expected Go language version.
    62  This flag is mainly for tools that understand Go version dependencies.
    63  It takes a version like "1.26" or "1.26.2".
    64  Using the version "none" removes the go directive.
    65  Users should prefer 'go get go@version'.
    66  
    67  The -toolchain=name flag sets the Go toolchain to use.
    68  This flag is mainly for tools that understand Go version dependencies.
    69  It takes a toolchain name like "go1.26" or "go1.26.2".
    70  Using the name "none" removes the toolchain directive.
    71  Users should prefer 'go get toolchain@version'.
    72  
    73  The -exclude=path@version and -dropexclude=path@version flags
    74  add and drop an exclusion for the given module path and version.
    75  Note that -exclude=path@version is a no-op if that exclusion already exists.
    76  
    77  The -replace=old[@v]=new[@v] flag adds a replacement of the given
    78  module path and version pair. If the @v in old@v is omitted, a
    79  replacement without a version on the left side is added, which applies
    80  to all versions of the old module path. If the @v in new@v is omitted,
    81  the new path should be a local module root directory, not a module
    82  path. Note that -replace overrides any redundant replacements for old[@v],
    83  so omitting @v will drop existing replacements for specific versions.
    84  
    85  The -dropreplace=old[@v] flag drops a replacement of the given
    86  module path and version pair. If the @v is omitted, a replacement without
    87  a version on the left side is dropped.
    88  
    89  The -retract=version and -dropretract=version flags add and drop a
    90  retraction on the given version. The version may be a single version
    91  like "v1.2.3" or a closed interval like "[v1.1.0,v1.1.9]". Note that
    92  -retract=version is a no-op if that retraction already exists.
    93  
    94  The -tool=path and -droptool=path flags add and drop a tool declaration
    95  for the given path.
    96  
    97  The -ignore=path and -dropignore=path flags add and drop a ignore declaration
    98  for the given path.
    99  
   100  The -godebug, -dropgodebug, -require, -droprequire, -exclude, -dropexclude,
   101  -replace, -dropreplace, -retract, -dropretract, -tool, -droptool, -ignore,
   102  and -dropignore editing flags may be repeated, and the changes are applied
   103  in the order given.
   104  
   105  The -print flag prints the final go.mod in its text format instead of
   106  writing it back to go.mod.
   107  
   108  The -json flag prints the final go.mod file in JSON format instead of
   109  writing it back to go.mod. The JSON output corresponds to these Go types:
   110  
   111  	type GoMod struct {
   112  		Module    ModPath
   113  		Go        string
   114  		Toolchain string
   115  		Godebug   []Godebug
   116  		Require   []Require
   117  		Exclude   []Module
   118  		Replace   []Replace
   119  		Retract   []Retract
   120  		Tool      []Tool
   121  		Ignore    []Ignore
   122  	}
   123  
   124  	type Module struct {
   125  		Path    string
   126  		Version string
   127  	}
   128  
   129  	type ModPath struct {
   130  		Path       string
   131  		Deprecated string
   132  	}
   133  
   134  	type Godebug struct {
   135  		Key   string
   136  		Value string
   137  	}
   138  
   139  	type Require struct {
   140  		Path     string
   141  		Version  string
   142  		Indirect bool
   143  	}
   144  
   145  	type Replace struct {
   146  		Old Module
   147  		New Module
   148  	}
   149  
   150  	type Retract struct {
   151  		Low       string
   152  		High      string
   153  		Rationale string
   154  	}
   155  
   156  	type Tool struct {
   157  		Path string
   158  	}
   159  
   160  	type Ignore struct {
   161  		Path string
   162  	}
   163  
   164  Retract entries representing a single version (not an interval) will have
   165  the "Low" and "High" fields set to the same value.
   166  
   167  Note that this only describes the go.mod file itself, not other modules
   168  referred to indirectly. For the full set of modules available to a build,
   169  use 'go list -m -json all'.
   170  
   171  Edit also provides the -C, -n, and -x build flags.
   172  
   173  See https://go.dev/ref/mod#go-mod-edit for more about 'go mod edit'.
   174  	`,
   175  }
   176  
   177  var (
   178  	editFmt       = cmdEdit.Flag.Bool("fmt", false, "")
   179  	editGo        = cmdEdit.Flag.String("go", "", "")
   180  	editToolchain = cmdEdit.Flag.String("toolchain", "", "")
   181  	editJSON      = cmdEdit.Flag.Bool("json", false, "")
   182  	editPrint     = cmdEdit.Flag.Bool("print", false, "")
   183  	editModule    = cmdEdit.Flag.String("module", "", "")
   184  	edits         []func(*modfile.File) // edits specified in flags
   185  )
   186  
   187  type flagFunc func(string)
   188  
   189  func (f flagFunc) String() string     { return "" }
   190  func (f flagFunc) Set(s string) error { f(s); return nil }
   191  
   192  func init() {
   193  	cmdEdit.Run = runEdit // break init cycle
   194  
   195  	cmdEdit.Flag.Var(flagFunc(flagGodebug), "godebug", "")
   196  	cmdEdit.Flag.Var(flagFunc(flagDropGodebug), "dropgodebug", "")
   197  	cmdEdit.Flag.Var(flagFunc(flagRequire), "require", "")
   198  	cmdEdit.Flag.Var(flagFunc(flagDropRequire), "droprequire", "")
   199  	cmdEdit.Flag.Var(flagFunc(flagExclude), "exclude", "")
   200  	cmdEdit.Flag.Var(flagFunc(flagDropExclude), "dropexclude", "")
   201  	cmdEdit.Flag.Var(flagFunc(flagReplace), "replace", "")
   202  	cmdEdit.Flag.Var(flagFunc(flagDropReplace), "dropreplace", "")
   203  	cmdEdit.Flag.Var(flagFunc(flagRetract), "retract", "")
   204  	cmdEdit.Flag.Var(flagFunc(flagDropRetract), "dropretract", "")
   205  	cmdEdit.Flag.Var(flagFunc(flagTool), "tool", "")
   206  	cmdEdit.Flag.Var(flagFunc(flagDropTool), "droptool", "")
   207  	cmdEdit.Flag.Var(flagFunc(flagIgnore), "ignore", "")
   208  	cmdEdit.Flag.Var(flagFunc(flagDropIgnore), "dropignore", "")
   209  
   210  	base.AddBuildFlagsNX(&cmdEdit.Flag)
   211  	base.AddChdirFlag(&cmdEdit.Flag)
   212  	base.AddModCommonFlags(&cmdEdit.Flag)
   213  }
   214  
   215  func runEdit(ctx context.Context, cmd *base.Command, args []string) {
   216  	moduleLoader := modload.NewLoader()
   217  	anyFlags := *editModule != "" ||
   218  		*editGo != "" ||
   219  		*editToolchain != "" ||
   220  		*editJSON ||
   221  		*editPrint ||
   222  		*editFmt ||
   223  		len(edits) > 0
   224  
   225  	if !anyFlags {
   226  		base.Fatalf("go: no flags specified (see 'go help mod edit').")
   227  	}
   228  
   229  	if *editJSON && *editPrint {
   230  		base.Fatalf("go: cannot use both -json and -print")
   231  	}
   232  
   233  	if len(args) > 1 {
   234  		base.Fatalf("go: too many arguments")
   235  	}
   236  	var gomod string
   237  	if len(args) == 1 {
   238  		gomod = args[0]
   239  	} else {
   240  		gomod = moduleLoader.ModFilePath()
   241  	}
   242  
   243  	if *editModule != "" {
   244  		err := module.CheckImportPath(*editModule)
   245  		if err == nil {
   246  			err = modload.CheckReservedModulePath(*editModule)
   247  		}
   248  		if err != nil {
   249  			base.Fatalf("go: invalid -module: %v", err)
   250  		}
   251  	}
   252  
   253  	if *editGo != "" && *editGo != "none" {
   254  		if !modfile.GoVersionRE.MatchString(*editGo) {
   255  			base.Fatalf(`go mod: invalid -go option; expecting something like "-go %s"`, gover.Local())
   256  		}
   257  	}
   258  	if *editToolchain != "" && *editToolchain != "none" {
   259  		if !modfile.ToolchainRE.MatchString(*editToolchain) {
   260  			base.Fatalf(`go mod: invalid -toolchain option; expecting something like "-toolchain go%s"`, gover.Local())
   261  		}
   262  	}
   263  
   264  	data, err := lockedfile.Read(gomod)
   265  	if err != nil {
   266  		base.Fatal(err)
   267  	}
   268  
   269  	modFile, err := modfile.Parse(gomod, data, nil)
   270  	if err != nil {
   271  		base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gomod), err)
   272  	}
   273  
   274  	if *editModule != "" {
   275  		modFile.AddModuleStmt(*editModule)
   276  	}
   277  
   278  	if *editGo == "none" {
   279  		modFile.DropGoStmt()
   280  	} else if *editGo != "" {
   281  		if err := modFile.AddGoStmt(*editGo); err != nil {
   282  			base.Fatalf("go: internal error: %v", err)
   283  		}
   284  	}
   285  	if *editToolchain == "none" {
   286  		modFile.DropToolchainStmt()
   287  	} else if *editToolchain != "" {
   288  		if err := modFile.AddToolchainStmt(*editToolchain); err != nil {
   289  			base.Fatalf("go: internal error: %v", err)
   290  		}
   291  	}
   292  
   293  	if len(edits) > 0 {
   294  		for _, edit := range edits {
   295  			edit(modFile)
   296  		}
   297  	}
   298  	modFile.SortBlocks()
   299  	modFile.Cleanup() // clean file after edits
   300  
   301  	if *editJSON {
   302  		editPrintJSON(modFile)
   303  		return
   304  	}
   305  
   306  	out, err := modFile.Format()
   307  	if err != nil {
   308  		base.Fatal(err)
   309  	}
   310  
   311  	if *editPrint {
   312  		os.Stdout.Write(out)
   313  		return
   314  	}
   315  
   316  	// Make a best-effort attempt to acquire the side lock, only to exclude
   317  	// previous versions of the 'go' command from making simultaneous edits.
   318  	if unlock, err := modfetch.SideLock(ctx); err == nil {
   319  		defer unlock()
   320  	}
   321  
   322  	err = lockedfile.Transform(gomod, func(lockedData []byte) ([]byte, error) {
   323  		if !bytes.Equal(lockedData, data) {
   324  			return nil, errors.New("go.mod changed during editing; not overwriting")
   325  		}
   326  		return out, nil
   327  	})
   328  	if err != nil {
   329  		base.Fatal(err)
   330  	}
   331  }
   332  
   333  // parsePathVersion parses -flag=arg expecting arg to be path@version.
   334  func parsePathVersion(flag, arg string) (path, version string) {
   335  	before, after, found, err := modload.ParsePathVersion(arg)
   336  	if err != nil {
   337  		base.Fatalf("go: -%s=%s: %v", flag, arg, err)
   338  	}
   339  	if !found {
   340  		base.Fatalf("go: -%s=%s: need path@version", flag, arg)
   341  	}
   342  	path, version = strings.TrimSpace(before), strings.TrimSpace(after)
   343  	if err := module.CheckImportPath(path); err != nil {
   344  		base.Fatalf("go: -%s=%s: invalid path: %v", flag, arg, err)
   345  	}
   346  
   347  	if !allowedVersionArg(version) {
   348  		base.Fatalf("go: -%s=%s: invalid version %q", flag, arg, version)
   349  	}
   350  
   351  	return path, version
   352  }
   353  
   354  // parsePath parses -flag=arg expecting arg to be path (not path@version).
   355  func parsePath(flag, arg string) (path string) {
   356  	if strings.Contains(arg, "@") {
   357  		base.Fatalf("go: -%s=%s: need just path, not path@version", flag, arg)
   358  	}
   359  	path = arg
   360  	if err := module.CheckImportPath(path); err != nil {
   361  		base.Fatalf("go: -%s=%s: invalid path: %v", flag, arg, err)
   362  	}
   363  	return path
   364  }
   365  
   366  // parsePathVersionOptional parses path[@version], using adj to
   367  // describe any errors.
   368  func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
   369  	if allowDirPath && modfile.IsDirectoryPath(arg) {
   370  		return arg, "", nil
   371  	}
   372  	before, after, found, err := modload.ParsePathVersion(arg)
   373  	if err != nil {
   374  		return "", "", err
   375  	}
   376  	if !found {
   377  		path = arg
   378  	} else {
   379  		path, version = strings.TrimSpace(before), strings.TrimSpace(after)
   380  	}
   381  	if err := module.CheckImportPath(path); err != nil {
   382  		return path, version, fmt.Errorf("invalid %s path: %v", adj, err)
   383  	}
   384  	if path != arg && !allowedVersionArg(version) {
   385  		return path, version, fmt.Errorf("invalid %s version: %q", adj, version)
   386  	}
   387  	return path, version, nil
   388  }
   389  
   390  // parseVersionInterval parses a single version like "v1.2.3" or a closed
   391  // interval like "[v1.2.3,v1.4.5]". Note that a single version has the same
   392  // representation as an interval with equal upper and lower bounds: both
   393  // Low and High are set.
   394  func parseVersionInterval(arg string) (modfile.VersionInterval, error) {
   395  	if !strings.HasPrefix(arg, "[") {
   396  		if !allowedVersionArg(arg) {
   397  			return modfile.VersionInterval{}, fmt.Errorf("invalid version: %q", arg)
   398  		}
   399  		return modfile.VersionInterval{Low: arg, High: arg}, nil
   400  	}
   401  	if !strings.HasSuffix(arg, "]") {
   402  		return modfile.VersionInterval{}, fmt.Errorf("invalid version interval: %q", arg)
   403  	}
   404  	s := arg[1 : len(arg)-1]
   405  	before, after, found := strings.Cut(s, ",")
   406  	if !found {
   407  		return modfile.VersionInterval{}, fmt.Errorf("invalid version interval: %q", arg)
   408  	}
   409  	low := strings.TrimSpace(before)
   410  	high := strings.TrimSpace(after)
   411  	if !allowedVersionArg(low) || !allowedVersionArg(high) {
   412  		return modfile.VersionInterval{}, fmt.Errorf("invalid version interval: %q", arg)
   413  	}
   414  	return modfile.VersionInterval{Low: low, High: high}, nil
   415  }
   416  
   417  // allowedVersionArg returns whether a token may be used as a version in go.mod.
   418  // We don't call modfile.CheckPathVersion, because that insists on versions
   419  // being in semver form, but here we want to allow versions like "master" or
   420  // "1234abcdef", which the go command will resolve the next time it runs (or
   421  // during -fix).  Even so, we need to make sure the version is a valid token.
   422  func allowedVersionArg(arg string) bool {
   423  	return !modfile.MustQuote(arg)
   424  }
   425  
   426  // flagGodebug implements the -godebug flag.
   427  func flagGodebug(arg string) {
   428  	key, value, ok := strings.Cut(arg, "=")
   429  	if !ok || strings.ContainsAny(arg, "\"`',") {
   430  		base.Fatalf("go: -godebug=%s: need key=value", arg)
   431  	}
   432  	edits = append(edits, func(f *modfile.File) {
   433  		if err := f.AddGodebug(key, value); err != nil {
   434  			base.Fatalf("go: -godebug=%s: %v", arg, err)
   435  		}
   436  	})
   437  }
   438  
   439  // flagDropGodebug implements the -dropgodebug flag.
   440  func flagDropGodebug(arg string) {
   441  	edits = append(edits, func(f *modfile.File) {
   442  		if err := f.DropGodebug(arg); err != nil {
   443  			base.Fatalf("go: -dropgodebug=%s: %v", arg, err)
   444  		}
   445  	})
   446  }
   447  
   448  // flagRequire implements the -require flag.
   449  func flagRequire(arg string) {
   450  	path, version := parsePathVersion("require", arg)
   451  	edits = append(edits, func(f *modfile.File) {
   452  		if err := f.AddRequire(path, version); err != nil {
   453  			base.Fatalf("go: -require=%s: %v", arg, err)
   454  		}
   455  	})
   456  }
   457  
   458  // flagDropRequire implements the -droprequire flag.
   459  func flagDropRequire(arg string) {
   460  	path := parsePath("droprequire", arg)
   461  	edits = append(edits, func(f *modfile.File) {
   462  		if err := f.DropRequire(path); err != nil {
   463  			base.Fatalf("go: -droprequire=%s: %v", arg, err)
   464  		}
   465  	})
   466  }
   467  
   468  // flagExclude implements the -exclude flag.
   469  func flagExclude(arg string) {
   470  	path, version := parsePathVersion("exclude", arg)
   471  	edits = append(edits, func(f *modfile.File) {
   472  		if err := f.AddExclude(path, version); err != nil {
   473  			base.Fatalf("go: -exclude=%s: %v", arg, err)
   474  		}
   475  	})
   476  }
   477  
   478  // flagDropExclude implements the -dropexclude flag.
   479  func flagDropExclude(arg string) {
   480  	path, version := parsePathVersion("dropexclude", arg)
   481  	edits = append(edits, func(f *modfile.File) {
   482  		if err := f.DropExclude(path, version); err != nil {
   483  			base.Fatalf("go: -dropexclude=%s: %v", arg, err)
   484  		}
   485  	})
   486  }
   487  
   488  // flagReplace implements the -replace flag.
   489  func flagReplace(arg string) {
   490  	before, after, found := strings.Cut(arg, "=")
   491  	if !found {
   492  		base.Fatalf("go: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
   493  	}
   494  	old, new := strings.TrimSpace(before), strings.TrimSpace(after)
   495  	if strings.HasPrefix(new, ">") {
   496  		base.Fatalf("go: -replace=%s: separator between old and new is =, not =>", arg)
   497  	}
   498  	oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
   499  	if err != nil {
   500  		base.Fatalf("go: -replace=%s: %v", arg, err)
   501  	}
   502  	newPath, newVersion, err := parsePathVersionOptional("new", new, true)
   503  	if err != nil {
   504  		base.Fatalf("go: -replace=%s: %v", arg, err)
   505  	}
   506  	if newPath == new && !modfile.IsDirectoryPath(new) {
   507  		base.Fatalf("go: -replace=%s: unversioned new path must be local directory", arg)
   508  	}
   509  
   510  	edits = append(edits, func(f *modfile.File) {
   511  		if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
   512  			base.Fatalf("go: -replace=%s: %v", arg, err)
   513  		}
   514  	})
   515  }
   516  
   517  // flagDropReplace implements the -dropreplace flag.
   518  func flagDropReplace(arg string) {
   519  	path, version, err := parsePathVersionOptional("old", arg, true)
   520  	if err != nil {
   521  		base.Fatalf("go: -dropreplace=%s: %v", arg, err)
   522  	}
   523  	edits = append(edits, func(f *modfile.File) {
   524  		if err := f.DropReplace(path, version); err != nil {
   525  			base.Fatalf("go: -dropreplace=%s: %v", arg, err)
   526  		}
   527  	})
   528  }
   529  
   530  // flagRetract implements the -retract flag.
   531  func flagRetract(arg string) {
   532  	vi, err := parseVersionInterval(arg)
   533  	if err != nil {
   534  		base.Fatalf("go: -retract=%s: %v", arg, err)
   535  	}
   536  	edits = append(edits, func(f *modfile.File) {
   537  		if err := f.AddRetract(vi, ""); err != nil {
   538  			base.Fatalf("go: -retract=%s: %v", arg, err)
   539  		}
   540  	})
   541  }
   542  
   543  // flagDropRetract implements the -dropretract flag.
   544  func flagDropRetract(arg string) {
   545  	vi, err := parseVersionInterval(arg)
   546  	if err != nil {
   547  		base.Fatalf("go: -dropretract=%s: %v", arg, err)
   548  	}
   549  	edits = append(edits, func(f *modfile.File) {
   550  		if err := f.DropRetract(vi); err != nil {
   551  			base.Fatalf("go: -dropretract=%s: %v", arg, err)
   552  		}
   553  	})
   554  }
   555  
   556  // flagTool implements the -tool flag.
   557  func flagTool(arg string) {
   558  	path := parsePath("tool", arg)
   559  	edits = append(edits, func(f *modfile.File) {
   560  		if err := f.AddTool(path); err != nil {
   561  			base.Fatalf("go: -tool=%s: %v", arg, err)
   562  		}
   563  	})
   564  }
   565  
   566  // flagDropTool implements the -droptool flag.
   567  func flagDropTool(arg string) {
   568  	path := parsePath("droptool", arg)
   569  	edits = append(edits, func(f *modfile.File) {
   570  		if err := f.DropTool(path); err != nil {
   571  			base.Fatalf("go: -droptool=%s: %v", arg, err)
   572  		}
   573  	})
   574  }
   575  
   576  // flagIgnore implements the -ignore flag.
   577  func flagIgnore(arg string) {
   578  	edits = append(edits, func(f *modfile.File) {
   579  		if err := f.AddIgnore(arg); err != nil {
   580  			base.Fatalf("go: -ignore=%s: %v", arg, err)
   581  		}
   582  	})
   583  }
   584  
   585  // flagDropIgnore implements the -dropignore flag.
   586  func flagDropIgnore(arg string) {
   587  	edits = append(edits, func(f *modfile.File) {
   588  		if err := f.DropIgnore(arg); err != nil {
   589  			base.Fatalf("go: -dropignore=%s: %v", arg, err)
   590  		}
   591  	})
   592  }
   593  
   594  // fileJSON is the -json output data structure.
   595  type fileJSON struct {
   596  	Module    editModuleJSON
   597  	Go        string           `json:",omitempty"`
   598  	Toolchain string           `json:",omitempty"`
   599  	GoDebug   []debugJSON      `json:",omitempty"`
   600  	Require   []requireJSON    `json:",omitempty"`
   601  	Exclude   []module.Version `json:",omitempty"`
   602  	Replace   []replaceJSON    `json:",omitempty"`
   603  	Retract   []retractJSON    `json:",omitempty"`
   604  	Tool      []toolJSON       `json:",omitempty"`
   605  	Ignore    []ignoreJSON     `json:",omitempty"`
   606  }
   607  
   608  type editModuleJSON struct {
   609  	Path       string
   610  	Deprecated string `json:",omitempty"`
   611  }
   612  
   613  type debugJSON struct {
   614  	Key   string
   615  	Value string
   616  }
   617  
   618  type requireJSON struct {
   619  	Path     string
   620  	Version  string `json:",omitempty"`
   621  	Indirect bool   `json:",omitempty"`
   622  }
   623  
   624  type replaceJSON struct {
   625  	Old module.Version
   626  	New module.Version
   627  }
   628  
   629  type retractJSON struct {
   630  	Low       string `json:",omitempty"`
   631  	High      string `json:",omitempty"`
   632  	Rationale string `json:",omitempty"`
   633  }
   634  
   635  type toolJSON struct {
   636  	Path string
   637  }
   638  
   639  type ignoreJSON struct {
   640  	Path string
   641  }
   642  
   643  // editPrintJSON prints the -json output.
   644  func editPrintJSON(modFile *modfile.File) {
   645  	var f fileJSON
   646  	if modFile.Module != nil {
   647  		f.Module = editModuleJSON{
   648  			Path:       modFile.Module.Mod.Path,
   649  			Deprecated: modFile.Module.Deprecated,
   650  		}
   651  	}
   652  	if modFile.Go != nil {
   653  		f.Go = modFile.Go.Version
   654  	}
   655  	if modFile.Toolchain != nil {
   656  		f.Toolchain = modFile.Toolchain.Name
   657  	}
   658  	for _, r := range modFile.Require {
   659  		f.Require = append(f.Require, requireJSON{Path: r.Mod.Path, Version: r.Mod.Version, Indirect: r.Indirect})
   660  	}
   661  	for _, x := range modFile.Exclude {
   662  		f.Exclude = append(f.Exclude, x.Mod)
   663  	}
   664  	for _, r := range modFile.Replace {
   665  		f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
   666  	}
   667  	for _, r := range modFile.Retract {
   668  		f.Retract = append(f.Retract, retractJSON{r.Low, r.High, r.Rationale})
   669  	}
   670  	for _, t := range modFile.Tool {
   671  		f.Tool = append(f.Tool, toolJSON{t.Path})
   672  	}
   673  	for _, i := range modFile.Ignore {
   674  		f.Ignore = append(f.Ignore, ignoreJSON{i.Path})
   675  	}
   676  	for _, d := range modFile.Godebug {
   677  		f.GoDebug = append(f.GoDebug, debugJSON{d.Key, d.Value})
   678  	}
   679  	data, err := json.MarshalIndent(&f, "", "\t")
   680  	if err != nil {
   681  		base.Fatalf("go: internal error: %v", err)
   682  	}
   683  	data = append(data, '\n')
   684  	os.Stdout.Write(data)
   685  }
   686  

View as plain text