]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.cmdgo] cmd/go: add go mod editwork command
authorMichael Matloob <matloob@golang.org>
Wed, 16 Jun 2021 22:46:23 +0000 (18:46 -0400)
committerMichael Matloob <matloob@golang.org>
Sat, 31 Jul 2021 00:25:16 +0000 (00:25 +0000)
go mod editwork behaves similarly to go mod edit:
it has flags to change the go version, and add and remove
directory and replace directives.

For #45713
Change-Id: I1c795c122bfe461d6e87dd731692e0bf1bbe2bf7
Reviewed-on: https://go-review.googlesource.com/c/go/+/334938
Trust: Michael Matloob <matloob@golang.org>
Run-TryBot: Michael Matloob <matloob@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
src/cmd/go/alldocs.go
src/cmd/go/internal/modcmd/editwork.go [new file with mode: 0644]
src/cmd/go/internal/modcmd/mod.go
src/cmd/go/internal/modload/init.go
src/cmd/go/testdata/script/work_edit.txt [new file with mode: 0644]

index af1ee396841f87ba6277bf49750f38e02b905b32..e0973acbf4569bdfd02f79f5ee0cd5fbbd1e96ae 100644 (file)
 //
 //     download    download modules to local cache
 //     edit        edit go.mod from tools or scripts
+//     editwork    edit go.work from tools or scripts
 //     graph       print module requirement graph
 //     init        initialize new module in current directory
 //     initwork    initialize workspace file
 // See https://golang.org/ref/mod#go-mod-edit for more about 'go mod edit'.
 //
 //
+// Edit go.work from tools or scripts
+//
+// Usage:
+//
+//     go mod editwork [editing flags] [go.work]
+//
+// Editwork provides a command-line interface for editing go.work,
+// for use primarily by tools or scripts. It only reads go.work;
+// it does not look up information about the modules involved.
+// If no file is specified, editwork looks for a go.work file in the current
+// directory and its parent directories
+//
+// The editing flags specify a sequence of editing operations.
+//
+// The -fmt flag reformats the go.work 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 editwork -fmt'.
+//
+// The -directory=path and -dropdirectory=path flags
+// add and drop a directory from the go.work files set of module directories.
+//
+// The -replace=old[@v]=new[@v] flag adds a replacement of the given
+// module path and version pair. If the @v in old@v is omitted, a
+// replacement without a version on the left side is added, which applies
+// to all versions of 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 redundant replacements for old[@v],
+// so omitting @v will drop existing replacements for specific versions.
+//
+// The -dropreplace=old[@v] flag drops a replacement of the given
+// module path and version pair. If the @v is omitted, a replacement without
+// a version on the left side is dropped.
+//
+// The -directory, -dropdirectory, -replace, and -dropreplace,
+// editing flags may be repeated, and the changes are applied in the order given.
+//
+// The -go=version flag sets the expected Go language version.
+//
+// The -print flag prints the final go.work in its text format instead of
+// writing it back to go.mod.
+//
+// The -json flag prints the final go.work 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 GoWork struct {
+//             Go        string
+//             Directory []Directory
+//             Replace   []Replace
+//     }
+//
+//     type Directory struct {
+//             Path       string
+//             ModulePath string
+//     }
+//
+//     type Replace struct {
+//             Old Module
+//             New Module
+//     }
+//
+// See the workspaces design proposal at
+// https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
+// more information.
+//
+//
 // Print module requirement graph
 //
 // Usage:
diff --git a/src/cmd/go/internal/modcmd/editwork.go b/src/cmd/go/internal/modcmd/editwork.go
new file mode 100644 (file)
index 0000000..f05d924
--- /dev/null
@@ -0,0 +1,282 @@
+// Copyright 2021 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 editwork
+
+package modcmd
+
+import (
+       "bytes"
+       "cmd/go/internal/base"
+       "cmd/go/internal/lockedfile"
+       "cmd/go/internal/modload"
+       "context"
+       "encoding/json"
+       "errors"
+       "os"
+       "strings"
+
+       "golang.org/x/mod/modfile"
+)
+
+var cmdEditwork = &base.Command{
+       UsageLine: "go mod editwork [editing flags] [go.work]",
+       Short:     "edit go.work from tools or scripts",
+       Long: `Editwork provides a command-line interface for editing go.work,
+for use primarily by tools or scripts. It only reads go.work;
+it does not look up information about the modules involved.
+If no file is specified, editwork looks for a go.work file in the current
+directory and its parent directories
+
+The editing flags specify a sequence of editing operations.
+
+The -fmt flag reformats the go.work 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 editwork -fmt'.
+
+The -directory=path and -dropdirectory=path flags
+add and drop a directory from the go.work files set of module directories.
+
+The -replace=old[@v]=new[@v] flag adds a replacement of the given
+module path and version pair. If the @v in old@v is omitted, a
+replacement without a version on the left side is added, which applies
+to all versions of 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 redundant replacements for old[@v],
+so omitting @v will drop existing replacements for specific versions.
+
+The -dropreplace=old[@v] flag drops a replacement of the given
+module path and version pair. If the @v is omitted, a replacement without
+a version on the left side is dropped.
+
+The -directory, -dropdirectory, -replace, and -dropreplace,
+editing flags may be repeated, and the changes are applied in the order given.
+
+The -go=version flag sets the expected Go language version.
+
+The -print flag prints the final go.work in its text format instead of
+writing it back to go.mod.
+
+The -json flag prints the final go.work 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 GoWork struct {
+               Go        string
+               Directory []Directory
+               Replace   []Replace
+       }
+
+       type Directory struct {
+               Path       string
+               ModulePath string
+       }
+
+       type Replace struct {
+               Old Module
+               New Module
+       }
+
+See the workspaces design proposal at
+https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
+more information.
+`,
+}
+
+var (
+       editworkFmt   = cmdEditwork.Flag.Bool("fmt", false, "")
+       editworkGo    = cmdEditwork.Flag.String("go", "", "")
+       editworkJSON  = cmdEditwork.Flag.Bool("json", false, "")
+       editworkPrint = cmdEditwork.Flag.Bool("print", false, "")
+       workedits     []func(file *modfile.WorkFile) // edits specified in flags
+)
+
+func init() {
+       cmdEditwork.Run = runEditwork // break init cycle
+
+       cmdEditwork.Flag.Var(flagFunc(flagEditworkDirectory), "directory", "")
+       cmdEditwork.Flag.Var(flagFunc(flagEditworkDropDirectory), "dropdirectory", "")
+       cmdEditwork.Flag.Var(flagFunc(flagEditworkReplace), "replace", "")
+       cmdEditwork.Flag.Var(flagFunc(flagEditworkDropReplace), "dropreplace", "")
+
+       base.AddWorkfileFlag(&cmdEditwork.Flag)
+}
+
+func runEditwork(ctx context.Context, cmd *base.Command, args []string) {
+       anyFlags :=
+               *editworkGo != "" ||
+                       *editworkJSON ||
+                       *editworkPrint ||
+                       *editworkFmt ||
+                       len(workedits) > 0
+
+       if !anyFlags {
+               base.Fatalf("go mod edit: no flags specified (see 'go help mod edit').")
+       }
+
+       if *editworkJSON && *editworkPrint {
+               base.Fatalf("go mod edit: cannot use both -json and -print")
+       }
+
+       if len(args) > 1 {
+               base.Fatalf("go mod edit: too many arguments")
+       }
+       var gowork string
+       if len(args) == 1 {
+               gowork = args[0]
+       } else {
+               modload.InitWorkfile()
+               gowork = modload.WorkFilePath()
+       }
+
+       if *editworkGo != "" {
+               if !modfile.GoVersionRE.MatchString(*editworkGo) {
+                       base.Fatalf(`go mod: invalid -go option; expecting something like "-go %s"`, modload.LatestGoVersion())
+               }
+       }
+
+       data, err := lockedfile.Read(gowork)
+       if err != nil {
+               base.Fatalf("go: %v", err)
+       }
+
+       workFile, err := modfile.ParseWork(gowork, data, nil)
+       if err != nil {
+               base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gowork), err)
+       }
+
+       if *editworkGo != "" {
+               if err := workFile.AddGoStmt(*editworkGo); err != nil {
+                       base.Fatalf("go: internal error: %v", err)
+               }
+       }
+
+       if len(workedits) > 0 {
+               for _, edit := range workedits {
+                       edit(workFile)
+               }
+       }
+       workFile.SortBlocks()
+       workFile.Cleanup() // clean file after edits
+
+       if *editworkJSON {
+               editworkPrintJSON(workFile)
+               return
+       }
+
+       out := modfile.Format(workFile.Syntax)
+
+       if *editworkPrint {
+               os.Stdout.Write(out)
+               return
+       }
+
+       err = lockedfile.Transform(gowork, func(lockedData []byte) ([]byte, error) {
+               if !bytes.Equal(lockedData, data) {
+                       return nil, errors.New("go.work changed during editing; not overwriting")
+               }
+               return out, nil
+       })
+       if err != nil {
+               base.Fatalf("go: %v", err)
+       }
+}
+
+// flagEditworkDirectory implements the -directory flag.
+func flagEditworkDirectory(arg string) {
+       workedits = append(workedits, func(f *modfile.WorkFile) {
+               if err := f.AddDirectory(arg, ""); err != nil {
+                       base.Fatalf("go mod: -directory=%s: %v", arg, err)
+               }
+       })
+}
+
+// flagEditworkDropDirectory implements the -dropdirectory flag.
+func flagEditworkDropDirectory(arg string) {
+       workedits = append(workedits, func(f *modfile.WorkFile) {
+               if err := f.DropDirectory(arg); err != nil {
+                       base.Fatalf("go mod: -dropdirectory=%s: %v", arg, err)
+               }
+       })
+}
+
+// flagReplace implements the -replace flag.
+func flagEditworkReplace(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)
+       }
+
+       workedits = append(workedits, func(f *modfile.WorkFile) {
+               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 flagEditworkDropReplace(arg string) {
+       path, version, err := parsePathVersionOptional("old", arg, true)
+       if err != nil {
+               base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
+       }
+       workedits = append(workedits, func(f *modfile.WorkFile) {
+               if err := f.DropReplace(path, version); err != nil {
+                       base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
+               }
+       })
+}
+
+// editPrintJSON prints the -json output.
+func editworkPrintJSON(workFile *modfile.WorkFile) {
+       var f workfileJSON
+       if workFile.Go != nil {
+               f.Go = workFile.Go.Version
+       }
+       for _, d := range workFile.Directory {
+               f.Directory = append(f.Directory, directoryJSON{DiskPath: d.Path, ModPath: d.ModulePath})
+       }
+
+       for _, r := range workFile.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)
+}
+
+// workfileJSON is the -json output data structure.
+type workfileJSON struct {
+       Go        string `json:",omitempty"`
+       Directory []directoryJSON
+       Replace   []replaceJSON
+}
+
+type directoryJSON struct {
+       DiskPath string
+       ModPath  string `json:",omitempty"`
+}
index 3586b44c1adf40de3414547c070708b768cee680..29aad5832407b05637aefb15281da953ecea6ac2 100644 (file)
@@ -23,6 +23,7 @@ See 'go help modules' for an overview of module functionality.
        Commands: []*base.Command{
                cmdDownload,
                cmdEdit,
+               cmdEditwork,
                cmdGraph,
                cmdInit,
                cmdInitwork,
index de8adbafc44293ccf4d76e1af77974f9be35ea3d..5dd946215bf9547c9d19905180ba4496aa25bad6 100644 (file)
@@ -249,6 +249,12 @@ func InitWorkfile() {
        }
 }
 
+// WorkFilePath returns the path of the go.work file, or "" if not in
+// workspace mode. WorkFilePath must be called after InitWorkfile.
+func WorkFilePath() string {
+       return workFilePath
+}
+
 // Init determines whether module mode is enabled, locates the root of the
 // current module (if any), sets environment variables for Git subprocesses, and
 // configures the cfg, codehost, load, modfetch, and search packages for use
diff --git a/src/cmd/go/testdata/script/work_edit.txt b/src/cmd/go/testdata/script/work_edit.txt
new file mode 100644 (file)
index 0000000..0717086
--- /dev/null
@@ -0,0 +1,157 @@
+# Test editing go.work files.
+
+go mod initwork m
+cmp go.work go.work.want_initial
+
+go mod editwork -directory n
+cmp go.work go.work.want_directory_n
+
+go mod editwork -go 1.18
+cmp go.work go.work.want_go_118
+
+go mod editwork -dropdirectory m
+cmp go.work go.work.want_dropdirectory_m
+
+go mod editwork -replace=x.1@v1.3.0=y.1@v1.4.0 -replace='x.1@v1.4.0 = ../z'
+cmp go.work go.work.want_add_replaces
+
+go mod editwork -directory n -directory ../a -directory /b -directory c -directory c
+cmp go.work go.work.want_multidirectory
+
+go mod editwork -dropdirectory /b -dropdirectory n
+cmp go.work go.work.want_multidropdirectory
+
+go mod editwork -dropreplace='x.1@v1.4.0'
+cmp go.work go.work.want_dropreplace
+
+go mod editwork -print -go 1.19 -directory b -dropdirectory c -replace 'x.1@v1.4.0 = ../z' -dropreplace x.1 -dropreplace x.1@v1.3.0
+cmp stdout go.work.want_print
+
+go mod editwork -json -go 1.19 -directory b -dropdirectory c -replace 'x.1@v1.4.0 = ../z' -dropreplace x.1 -dropreplace x.1@v1.3.0
+cmp stdout go.work.want_json
+
+go mod editwork -print -fmt -workfile unformatted
+cmp stdout formatted
+
+-- go.work.want_initial --
+go 1.17
+
+directory m
+-- go.work.want_directory_n --
+go 1.17
+
+directory (
+       m
+       n
+)
+-- go.work.want_go_118 --
+go 1.18
+
+directory (
+       m
+       n
+)
+-- go.work.want_dropdirectory_m --
+go 1.18
+
+directory n
+-- go.work.want_add_replaces --
+go 1.18
+
+directory n
+
+replace (
+       x.1 v1.3.0 => y.1 v1.4.0
+       x.1 v1.4.0 => ../z
+)
+-- go.work.want_multidirectory --
+go 1.18
+
+directory (
+       ../a
+       /b
+       c
+       n
+)
+
+replace (
+       x.1 v1.3.0 => y.1 v1.4.0
+       x.1 v1.4.0 => ../z
+)
+-- go.work.want_multidropdirectory --
+go 1.18
+
+directory (
+       ../a
+       c
+)
+
+replace (
+       x.1 v1.3.0 => y.1 v1.4.0
+       x.1 v1.4.0 => ../z
+)
+-- go.work.want_dropreplace --
+go 1.18
+
+directory (
+       ../a
+       c
+)
+
+replace x.1 v1.3.0 => y.1 v1.4.0
+-- go.work.want_print --
+go 1.19
+
+directory (
+       ../a
+       b
+)
+
+replace x.1 v1.4.0 => ../z
+-- go.work.want_json --
+{
+       "Go": "1.19",
+       "Directory": [
+               {
+                       "DiskPath": "../a"
+               },
+               {
+                       "DiskPath": "b"
+               }
+       ],
+       "Replace": [
+               {
+                       "Old": {
+                               "Path": "x.1",
+                               "Version": "v1.4.0"
+                       },
+                       "New": {
+                               "Path": "../z"
+                       }
+               }
+       ]
+}
+-- unformatted --
+go 1.17
+ directory (
+ a
+  b
+  c
+  )
+  replace (
+  x.1 v1.3.0 => y.1 v1.4.0
+                            x.1 v1.4.0 => ../z
+                            )
+-- formatted --
+go 1.17
+
+directory (
+       a
+       b
+       c
+)
+
+replace (
+       x.1 v1.3.0 => y.1 v1.4.0
+       x.1 v1.4.0 => ../z
+)
\ No newline at end of file