github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/workcmd/edit.go (about)

     1  // Copyright 2021 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 work edit
     6  
     7  package workcmd
     8  
     9  import (
    10  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/base"
    11  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/modload"
    12  	"context"
    13  	"encoding/json"
    14  	"fmt"
    15  	"os"
    16  	"path/filepath"
    17  	"strings"
    18  
    19  	"github.com/bir3/gocompiler/src/xvendor/golang.org/x/mod/module"
    20  
    21  	"github.com/bir3/gocompiler/src/xvendor/golang.org/x/mod/modfile"
    22  )
    23  
    24  var cmdEdit = &base.Command{
    25  	UsageLine: "go work edit [editing flags] [go.work]",
    26  	Short:     "edit go.work from tools or scripts",
    27  	Long: `Edit provides a command-line interface for editing go.work,
    28  for use primarily by tools or scripts. It only reads go.work;
    29  it does not look up information about the modules involved.
    30  If no file is specified, Edit looks for a go.work file in the current
    31  directory and its parent directories
    32  
    33  The editing flags specify a sequence of editing operations.
    34  
    35  The -fmt flag reformats the go.work 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 work edit -fmt'.
    39  
    40  The -use=path and -dropuse=path flags
    41  add and drop a use directive from the go.work file's set of module directories.
    42  
    43  The -replace=old[@v]=new[@v] flag adds a replacement of the given
    44  module path and version pair. If the @v in old@v is omitted, a
    45  replacement without a version on the left side is added, which applies
    46  to all versions of the old module path. If the @v in new@v is omitted,
    47  the new path should be a local module root directory, not a module
    48  path. Note that -replace overrides any redundant replacements for old[@v],
    49  so omitting @v will drop existing replacements for specific versions.
    50  
    51  The -dropreplace=old[@v] flag drops a replacement of the given
    52  module path and version pair. If the @v is omitted, a replacement without
    53  a version on the left side is dropped.
    54  
    55  The -use, -dropuse, -replace, and -dropreplace,
    56  editing flags may be repeated, and the changes are applied in the order given.
    57  
    58  The -go=version flag sets the expected Go language version.
    59  
    60  The -print flag prints the final go.work in its text format instead of
    61  writing it back to go.mod.
    62  
    63  The -json flag prints the final go.work file in JSON format instead of
    64  writing it back to go.mod. The JSON output corresponds to these Go types:
    65  
    66  	type GoWork struct {
    67  		Go      string
    68  		Use     []Use
    69  		Replace []Replace
    70  	}
    71  
    72  	type Use struct {
    73  		DiskPath   string
    74  		ModulePath string
    75  	}
    76  
    77  	type Replace struct {
    78  		Old Module
    79  		New Module
    80  	}
    81  
    82  	type Module struct {
    83  		Path    string
    84  		Version string
    85  	}
    86  
    87  See the workspaces reference at https://go.dev/ref/mod#workspaces
    88  for more information.
    89  `,
    90  }
    91  
    92  var (
    93  	editFmt   = cmdEdit.Flag.Bool("fmt", false, "")
    94  	editGo    = cmdEdit.Flag.String("go", "", "")
    95  	editJSON  = cmdEdit.Flag.Bool("json", false, "")
    96  	editPrint = cmdEdit.Flag.Bool("print", false, "")
    97  	workedits []func(file *modfile.WorkFile) // edits specified in flags
    98  )
    99  
   100  type flagFunc func(string)
   101  
   102  func (f flagFunc) String() string     { return "" }
   103  func (f flagFunc) Set(s string) error { f(s); return nil }
   104  
   105  func init() {
   106  	cmdEdit.Run = runEditwork // break init cycle
   107  
   108  	cmdEdit.Flag.Var(flagFunc(flagEditworkUse), "use", "")
   109  	cmdEdit.Flag.Var(flagFunc(flagEditworkDropUse), "dropuse", "")
   110  	cmdEdit.Flag.Var(flagFunc(flagEditworkReplace), "replace", "")
   111  	cmdEdit.Flag.Var(flagFunc(flagEditworkDropReplace), "dropreplace", "")
   112  	base.AddChdirFlag(&cmdEdit.Flag)
   113  }
   114  
   115  func runEditwork(ctx context.Context, cmd *base.Command, args []string) {
   116  	if *editJSON && *editPrint {
   117  		base.Fatalf("go: cannot use both -json and -print")
   118  	}
   119  
   120  	if len(args) > 1 {
   121  		base.Fatalf("go: 'go help work edit' accepts at most one argument")
   122  	}
   123  	var gowork string
   124  	if len(args) == 1 {
   125  		gowork = args[0]
   126  	} else {
   127  		modload.InitWorkfile()
   128  		gowork = modload.WorkFilePath()
   129  	}
   130  
   131  	if *editGo != "" {
   132  		if !modfile.GoVersionRE.MatchString(*editGo) {
   133  			base.Fatalf(`go mod: invalid -go option; expecting something like "-go %s"`, modload.LatestGoVersion())
   134  		}
   135  	}
   136  
   137  	if gowork == "" {
   138  		base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
   139  	}
   140  
   141  	anyFlags :=
   142  		*editGo != "" ||
   143  			*editJSON ||
   144  			*editPrint ||
   145  			*editFmt ||
   146  			len(workedits) > 0
   147  
   148  	if !anyFlags {
   149  		base.Fatalf("go: no flags specified (see 'go help work edit').")
   150  	}
   151  
   152  	workFile, err := modload.ReadWorkFile(gowork)
   153  	if err != nil {
   154  		base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gowork), err)
   155  	}
   156  
   157  	if *editGo != "" {
   158  		if err := workFile.AddGoStmt(*editGo); err != nil {
   159  			base.Fatalf("go: internal error: %v", err)
   160  		}
   161  	}
   162  
   163  	if len(workedits) > 0 {
   164  		for _, edit := range workedits {
   165  			edit(workFile)
   166  		}
   167  	}
   168  
   169  	modload.UpdateWorkFile(workFile)
   170  
   171  	workFile.SortBlocks()
   172  	workFile.Cleanup() // clean file after edits
   173  
   174  	if *editJSON {
   175  		editPrintJSON(workFile)
   176  		return
   177  	}
   178  
   179  	if *editPrint {
   180  		os.Stdout.Write(modfile.Format(workFile.Syntax))
   181  		return
   182  	}
   183  
   184  	modload.WriteWorkFile(gowork, workFile)
   185  }
   186  
   187  // flagEditworkUse implements the -use flag.
   188  func flagEditworkUse(arg string) {
   189  	workedits = append(workedits, func(f *modfile.WorkFile) {
   190  		_, mf, err := modload.ReadModFile(filepath.Join(arg, "go.mod"), nil)
   191  		modulePath := ""
   192  		if err == nil {
   193  			modulePath = mf.Module.Mod.Path
   194  		}
   195  		f.AddUse(modload.ToDirectoryPath(arg), modulePath)
   196  		if err := f.AddUse(modload.ToDirectoryPath(arg), ""); err != nil {
   197  			base.Fatalf("go: -use=%s: %v", arg, err)
   198  		}
   199  	})
   200  }
   201  
   202  // flagEditworkDropUse implements the -dropuse flag.
   203  func flagEditworkDropUse(arg string) {
   204  	workedits = append(workedits, func(f *modfile.WorkFile) {
   205  		if err := f.DropUse(modload.ToDirectoryPath(arg)); err != nil {
   206  			base.Fatalf("go: -dropdirectory=%s: %v", arg, err)
   207  		}
   208  	})
   209  }
   210  
   211  // allowedVersionArg returns whether a token may be used as a version in go.mod.
   212  // We don't call modfile.CheckPathVersion, because that insists on versions
   213  // being in semver form, but here we want to allow versions like "master" or
   214  // "1234abcdef", which the go command will resolve the next time it runs (or
   215  // during -fix).  Even so, we need to make sure the version is a valid token.
   216  func allowedVersionArg(arg string) bool {
   217  	return !modfile.MustQuote(arg)
   218  }
   219  
   220  // parsePathVersionOptional parses path[@version], using adj to
   221  // describe any errors.
   222  func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
   223  	before, after, found := strings.Cut(arg, "@")
   224  	if !found {
   225  		path = arg
   226  	} else {
   227  		path, version = strings.TrimSpace(before), strings.TrimSpace(after)
   228  	}
   229  	if err := module.CheckImportPath(path); err != nil {
   230  		if !allowDirPath || !modfile.IsDirectoryPath(path) {
   231  			return path, version, fmt.Errorf("invalid %s path: %v", adj, err)
   232  		}
   233  	}
   234  	if path != arg && !allowedVersionArg(version) {
   235  		return path, version, fmt.Errorf("invalid %s version: %q", adj, version)
   236  	}
   237  	return path, version, nil
   238  }
   239  
   240  // flagEditworkReplace implements the -replace flag.
   241  func flagEditworkReplace(arg string) {
   242  	before, after, found := strings.Cut(arg, "=")
   243  	if !found {
   244  		base.Fatalf("go: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
   245  	}
   246  	old, new := strings.TrimSpace(before), strings.TrimSpace(after)
   247  	if strings.HasPrefix(new, ">") {
   248  		base.Fatalf("go: -replace=%s: separator between old and new is =, not =>", arg)
   249  	}
   250  	oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
   251  	if err != nil {
   252  		base.Fatalf("go: -replace=%s: %v", arg, err)
   253  	}
   254  	newPath, newVersion, err := parsePathVersionOptional("new", new, true)
   255  	if err != nil {
   256  		base.Fatalf("go: -replace=%s: %v", arg, err)
   257  	}
   258  	if newPath == new && !modfile.IsDirectoryPath(new) {
   259  		base.Fatalf("go: -replace=%s: unversioned new path must be local directory", arg)
   260  	}
   261  
   262  	workedits = append(workedits, func(f *modfile.WorkFile) {
   263  		if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
   264  			base.Fatalf("go: -replace=%s: %v", arg, err)
   265  		}
   266  	})
   267  }
   268  
   269  // flagEditworkDropReplace implements the -dropreplace flag.
   270  func flagEditworkDropReplace(arg string) {
   271  	path, version, err := parsePathVersionOptional("old", arg, true)
   272  	if err != nil {
   273  		base.Fatalf("go: -dropreplace=%s: %v", arg, err)
   274  	}
   275  	workedits = append(workedits, func(f *modfile.WorkFile) {
   276  		if err := f.DropReplace(path, version); err != nil {
   277  			base.Fatalf("go: -dropreplace=%s: %v", arg, err)
   278  		}
   279  	})
   280  }
   281  
   282  type replaceJSON struct {
   283  	Old module.Version
   284  	New module.Version
   285  }
   286  
   287  // editPrintJSON prints the -json output.
   288  func editPrintJSON(workFile *modfile.WorkFile) {
   289  	var f workfileJSON
   290  	if workFile.Go != nil {
   291  		f.Go = workFile.Go.Version
   292  	}
   293  	for _, d := range workFile.Use {
   294  		f.Use = append(f.Use, useJSON{DiskPath: d.Path, ModPath: d.ModulePath})
   295  	}
   296  
   297  	for _, r := range workFile.Replace {
   298  		f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
   299  	}
   300  	data, err := json.MarshalIndent(&f, "", "\t")
   301  	if err != nil {
   302  		base.Fatalf("go: internal error: %v", err)
   303  	}
   304  	data = append(data, '\n')
   305  	os.Stdout.Write(data)
   306  }
   307  
   308  // workfileJSON is the -json output data structure.
   309  type workfileJSON struct {
   310  	Go      string `json:",omitempty"`
   311  	Use     []useJSON
   312  	Replace []replaceJSON
   313  }
   314  
   315  type useJSON struct {
   316  	DiskPath string
   317  	ModPath  string `json:",omitempty"`
   318  }