1
2
3
4
5
6
7 package workcmd
8
9 import (
10 "cmd/go/internal/base"
11 "cmd/go/internal/modload"
12 "context"
13 "encoding/json"
14 "fmt"
15 "os"
16 "path/filepath"
17 "strings"
18
19 "golang.org/x/mod/module"
20
21 "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)
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
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 }
113
114 func runEditwork(ctx context.Context, cmd *base.Command, args []string) {
115 if *editJSON && *editPrint {
116 base.Fatalf("go: cannot use both -json and -print")
117 }
118
119 if len(args) > 1 {
120 base.Fatalf("go: 'go help work edit' accepts at most one argument")
121 }
122 var gowork string
123 if len(args) == 1 {
124 gowork = args[0]
125 } else {
126 modload.InitWorkfile()
127 gowork = modload.WorkFilePath()
128 }
129
130 if *editGo != "" {
131 if !modfile.GoVersionRE.MatchString(*editGo) {
132 base.Fatalf(`go mod: invalid -go option; expecting something like "-go %s"`, modload.LatestGoVersion())
133 }
134 }
135
136 if gowork == "" {
137 base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
138 }
139
140 anyFlags :=
141 *editGo != "" ||
142 *editJSON ||
143 *editPrint ||
144 *editFmt ||
145 len(workedits) > 0
146
147 if !anyFlags {
148 base.Fatalf("go: no flags specified (see 'go help work edit').")
149 }
150
151 workFile, err := modload.ReadWorkFile(gowork)
152 if err != nil {
153 base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gowork), err)
154 }
155
156 if *editGo != "" {
157 if err := workFile.AddGoStmt(*editGo); err != nil {
158 base.Fatalf("go: internal error: %v", err)
159 }
160 }
161
162 if len(workedits) > 0 {
163 for _, edit := range workedits {
164 edit(workFile)
165 }
166 }
167
168 modload.UpdateWorkFile(workFile)
169
170 workFile.SortBlocks()
171 workFile.Cleanup()
172
173 if *editJSON {
174 editPrintJSON(workFile)
175 return
176 }
177
178 if *editPrint {
179 os.Stdout.Write(modfile.Format(workFile.Syntax))
180 return
181 }
182
183 modload.WriteWorkFile(gowork, workFile)
184 }
185
186
187 func flagEditworkUse(arg string) {
188 workedits = append(workedits, func(f *modfile.WorkFile) {
189 _, mf, err := modload.ReadModFile(filepath.Join(arg, "go.mod"), nil)
190 modulePath := ""
191 if err == nil {
192 modulePath = mf.Module.Mod.Path
193 }
194 f.AddUse(modload.ToDirectoryPath(arg), modulePath)
195 if err := f.AddUse(modload.ToDirectoryPath(arg), ""); err != nil {
196 base.Fatalf("go: -use=%s: %v", arg, err)
197 }
198 })
199 }
200
201
202 func flagEditworkDropUse(arg string) {
203 workedits = append(workedits, func(f *modfile.WorkFile) {
204 if err := f.DropUse(modload.ToDirectoryPath(arg)); err != nil {
205 base.Fatalf("go: -dropdirectory=%s: %v", arg, err)
206 }
207 })
208 }
209
210
211
212
213
214
215 func allowedVersionArg(arg string) bool {
216 return !modfile.MustQuote(arg)
217 }
218
219
220
221 func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
222 if i := strings.Index(arg, "@"); i < 0 {
223 path = arg
224 } else {
225 path, version = strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
226 }
227 if err := module.CheckImportPath(path); err != nil {
228 if !allowDirPath || !modfile.IsDirectoryPath(path) {
229 return path, version, fmt.Errorf("invalid %s path: %v", adj, err)
230 }
231 }
232 if path != arg && !allowedVersionArg(version) {
233 return path, version, fmt.Errorf("invalid %s version: %q", adj, version)
234 }
235 return path, version, nil
236 }
237
238
239 func flagEditworkReplace(arg string) {
240 var i int
241 if i = strings.Index(arg, "="); i < 0 {
242 base.Fatalf("go: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
243 }
244 old, new := strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
245 if strings.HasPrefix(new, ">") {
246 base.Fatalf("go: -replace=%s: separator between old and new is =, not =>", arg)
247 }
248 oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
249 if err != nil {
250 base.Fatalf("go: -replace=%s: %v", arg, err)
251 }
252 newPath, newVersion, err := parsePathVersionOptional("new", new, true)
253 if err != nil {
254 base.Fatalf("go: -replace=%s: %v", arg, err)
255 }
256 if newPath == new && !modfile.IsDirectoryPath(new) {
257 base.Fatalf("go: -replace=%s: unversioned new path must be local directory", arg)
258 }
259
260 workedits = append(workedits, func(f *modfile.WorkFile) {
261 if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
262 base.Fatalf("go: -replace=%s: %v", arg, err)
263 }
264 })
265 }
266
267
268 func flagEditworkDropReplace(arg string) {
269 path, version, err := parsePathVersionOptional("old", arg, true)
270 if err != nil {
271 base.Fatalf("go: -dropreplace=%s: %v", arg, err)
272 }
273 workedits = append(workedits, func(f *modfile.WorkFile) {
274 if err := f.DropReplace(path, version); err != nil {
275 base.Fatalf("go: -dropreplace=%s: %v", arg, err)
276 }
277 })
278 }
279
280 type replaceJSON struct {
281 Old module.Version
282 New module.Version
283 }
284
285
286 func editPrintJSON(workFile *modfile.WorkFile) {
287 var f workfileJSON
288 if workFile.Go != nil {
289 f.Go = workFile.Go.Version
290 }
291 for _, d := range workFile.Use {
292 f.Use = append(f.Use, useJSON{DiskPath: d.Path, ModPath: d.ModulePath})
293 }
294
295 for _, r := range workFile.Replace {
296 f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
297 }
298 data, err := json.MarshalIndent(&f, "", "\t")
299 if err != nil {
300 base.Fatalf("go: internal error: %v", err)
301 }
302 data = append(data, '\n')
303 os.Stdout.Write(data)
304 }
305
306
307 type workfileJSON struct {
308 Go string `json:",omitempty"`
309 Use []useJSON
310 Replace []replaceJSON
311 }
312
313 type useJSON struct {
314 DiskPath string
315 ModPath string `json:",omitempty"`
316 }
317
View as plain text