1
2
3
4
5
6
7 package workcmd
8
9 import (
10 "cmd/go/internal/base"
11 "cmd/go/internal/gover"
12 "cmd/go/internal/modload"
13 "context"
14 "encoding/json"
15 "fmt"
16 "os"
17 "path/filepath"
18 "strings"
19
20 "golang.org/x/mod/module"
21
22 "golang.org/x/mod/modfile"
23 )
24
25 var cmdEdit = &base.Command{
26 UsageLine: "go work edit [editing flags] [go.work]",
27 Short: "edit go.work from tools or scripts",
28 Long: `Edit provides a command-line interface for editing go.work,
29 for use primarily by tools or scripts. It only reads go.work;
30 it does not look up information about the modules involved.
31 If no file is specified, Edit looks for a go.work file in the current
32 directory and its parent directories
33
34 The editing flags specify a sequence of editing operations.
35
36 The -fmt flag reformats the go.work file without making other changes.
37 This reformatting is also implied by any other modifications that use or
38 rewrite the go.mod file. The only time this flag is needed is if no other
39 flags are specified, as in 'go work edit -fmt'.
40
41 The -godebug=key=value flag adds a godebug key=value line,
42 replacing any existing godebug lines with the given key.
43
44 The -dropgodebug=key flag drops any existing godebug lines
45 with the given key.
46
47 The -use=path and -dropuse=path flags
48 add and drop a use directive from the go.work file's set of module directories.
49
50 The -replace=old[@v]=new[@v] flag adds a replacement of the given
51 module path and version pair. If the @v in old@v is omitted, a
52 replacement without a version on the left side is added, which applies
53 to all versions of the old module path. If the @v in new@v is omitted,
54 the new path should be a local module root directory, not a module
55 path. Note that -replace overrides any redundant replacements for old[@v],
56 so omitting @v will drop existing replacements for specific versions.
57
58 The -dropreplace=old[@v] flag drops a replacement of the given
59 module path and version pair. If the @v is omitted, a replacement without
60 a version on the left side is dropped.
61
62 The -use, -dropuse, -replace, and -dropreplace,
63 editing flags may be repeated, and the changes are applied in the order given.
64
65 The -go=version flag sets the expected Go language version.
66
67 The -toolchain=name flag sets the Go toolchain to use.
68
69 The -print flag prints the final go.work in its text format instead of
70 writing it back to go.mod.
71
72 The -json flag prints the final go.work file in JSON format instead of
73 writing it back to go.mod. The JSON output corresponds to these Go types:
74
75 type GoWork struct {
76 Go string
77 Toolchain string
78 Godebug []Godebug
79 Use []Use
80 Replace []Replace
81 }
82
83 type Godebug struct {
84 Key string
85 Value string
86 }
87
88 type Use struct {
89 DiskPath string
90 ModulePath string
91 }
92
93 type Replace struct {
94 Old Module
95 New Module
96 }
97
98 type Module struct {
99 Path string
100 Version string
101 }
102
103 See the workspaces reference at https://go.dev/ref/mod#workspaces
104 for more information.
105 `,
106 }
107
108 var (
109 editFmt = cmdEdit.Flag.Bool("fmt", false, "")
110 editGo = cmdEdit.Flag.String("go", "", "")
111 editToolchain = cmdEdit.Flag.String("toolchain", "", "")
112 editJSON = cmdEdit.Flag.Bool("json", false, "")
113 editPrint = cmdEdit.Flag.Bool("print", false, "")
114 workedits []func(file *modfile.WorkFile)
115 )
116
117 type flagFunc func(string)
118
119 func (f flagFunc) String() string { return "" }
120 func (f flagFunc) Set(s string) error { f(s); return nil }
121
122 func init() {
123 cmdEdit.Run = runEditwork
124
125 cmdEdit.Flag.Var(flagFunc(flagEditworkGodebug), "godebug", "")
126 cmdEdit.Flag.Var(flagFunc(flagEditworkDropGodebug), "dropgodebug", "")
127 cmdEdit.Flag.Var(flagFunc(flagEditworkUse), "use", "")
128 cmdEdit.Flag.Var(flagFunc(flagEditworkDropUse), "dropuse", "")
129 cmdEdit.Flag.Var(flagFunc(flagEditworkReplace), "replace", "")
130 cmdEdit.Flag.Var(flagFunc(flagEditworkDropReplace), "dropreplace", "")
131 base.AddChdirFlag(&cmdEdit.Flag)
132 }
133
134 func runEditwork(ctx context.Context, cmd *base.Command, args []string) {
135 if *editJSON && *editPrint {
136 base.Fatalf("go: cannot use both -json and -print")
137 }
138
139 if len(args) > 1 {
140 base.Fatalf("go: 'go help work edit' accepts at most one argument")
141 }
142 var gowork string
143 if len(args) == 1 {
144 gowork = args[0]
145 } else {
146 modload.InitWorkfile()
147 gowork = modload.WorkFilePath()
148 }
149 if gowork == "" {
150 base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
151 }
152
153 if *editGo != "" && *editGo != "none" {
154 if !modfile.GoVersionRE.MatchString(*editGo) {
155 base.Fatalf(`go work: invalid -go option; expecting something like "-go %s"`, gover.Local())
156 }
157 }
158 if *editToolchain != "" && *editToolchain != "none" {
159 if !modfile.ToolchainRE.MatchString(*editToolchain) {
160 base.Fatalf(`go work: invalid -toolchain option; expecting something like "-toolchain go%s"`, gover.Local())
161 }
162 }
163
164 anyFlags := *editGo != "" ||
165 *editToolchain != "" ||
166 *editJSON ||
167 *editPrint ||
168 *editFmt ||
169 len(workedits) > 0
170
171 if !anyFlags {
172 base.Fatalf("go: no flags specified (see 'go help work edit').")
173 }
174
175 workFile, err := modload.ReadWorkFile(gowork)
176 if err != nil {
177 base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gowork), err)
178 }
179
180 if *editGo == "none" {
181 workFile.DropGoStmt()
182 } else if *editGo != "" {
183 if err := workFile.AddGoStmt(*editGo); err != nil {
184 base.Fatalf("go: internal error: %v", err)
185 }
186 }
187 if *editToolchain == "none" {
188 workFile.DropToolchainStmt()
189 } else if *editToolchain != "" {
190 if err := workFile.AddToolchainStmt(*editToolchain); err != nil {
191 base.Fatalf("go: internal error: %v", err)
192 }
193 }
194
195 if len(workedits) > 0 {
196 for _, edit := range workedits {
197 edit(workFile)
198 }
199 }
200
201 workFile.SortBlocks()
202 workFile.Cleanup()
203
204
205
206
207
208
209
210 if *editJSON {
211 editPrintJSON(workFile)
212 return
213 }
214
215 if *editPrint {
216 os.Stdout.Write(modfile.Format(workFile.Syntax))
217 return
218 }
219
220 modload.WriteWorkFile(gowork, workFile)
221 }
222
223
224 func flagEditworkGodebug(arg string) {
225 key, value, ok := strings.Cut(arg, "=")
226 if !ok || strings.ContainsAny(arg, "\"`',") {
227 base.Fatalf("go: -godebug=%s: need key=value", arg)
228 }
229 workedits = append(workedits, func(f *modfile.WorkFile) {
230 if err := f.AddGodebug(key, value); err != nil {
231 base.Fatalf("go: -godebug=%s: %v", arg, err)
232 }
233 })
234 }
235
236
237 func flagEditworkDropGodebug(arg string) {
238 workedits = append(workedits, func(f *modfile.WorkFile) {
239 if err := f.DropGodebug(arg); err != nil {
240 base.Fatalf("go: -dropgodebug=%s: %v", arg, err)
241 }
242 })
243 }
244
245
246 func flagEditworkUse(arg string) {
247 workedits = append(workedits, func(f *modfile.WorkFile) {
248 _, mf, err := modload.ReadModFile(filepath.Join(arg, "go.mod"), nil)
249 modulePath := ""
250 if err == nil {
251 modulePath = mf.Module.Mod.Path
252 }
253 f.AddUse(modload.ToDirectoryPath(arg), modulePath)
254 if err := f.AddUse(modload.ToDirectoryPath(arg), ""); err != nil {
255 base.Fatalf("go: -use=%s: %v", arg, err)
256 }
257 })
258 }
259
260
261 func flagEditworkDropUse(arg string) {
262 workedits = append(workedits, func(f *modfile.WorkFile) {
263 if err := f.DropUse(modload.ToDirectoryPath(arg)); err != nil {
264 base.Fatalf("go: -dropdirectory=%s: %v", arg, err)
265 }
266 })
267 }
268
269
270
271
272
273
274 func allowedVersionArg(arg string) bool {
275 return !modfile.MustQuote(arg)
276 }
277
278
279
280 func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
281 before, after, found, err := modload.ParsePathVersion(arg)
282 if err != nil {
283 return "", "", err
284 }
285 if !found {
286 path = arg
287 } else {
288 path, version = strings.TrimSpace(before), strings.TrimSpace(after)
289 }
290 if err := module.CheckImportPath(path); err != nil {
291 if !allowDirPath || !modfile.IsDirectoryPath(path) {
292 return path, version, fmt.Errorf("invalid %s path: %v", adj, err)
293 }
294 }
295 if path != arg && !allowedVersionArg(version) {
296 return path, version, fmt.Errorf("invalid %s version: %q", adj, version)
297 }
298 return path, version, nil
299 }
300
301
302 func flagEditworkReplace(arg string) {
303 before, after, found := strings.Cut(arg, "=")
304 if !found {
305 base.Fatalf("go: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
306 }
307 old, new := strings.TrimSpace(before), strings.TrimSpace(after)
308 if strings.HasPrefix(new, ">") {
309 base.Fatalf("go: -replace=%s: separator between old and new is =, not =>", arg)
310 }
311 oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
312 if err != nil {
313 base.Fatalf("go: -replace=%s: %v", arg, err)
314 }
315 newPath, newVersion, err := parsePathVersionOptional("new", new, true)
316 if err != nil {
317 base.Fatalf("go: -replace=%s: %v", arg, err)
318 }
319 if newPath == new && !modfile.IsDirectoryPath(new) {
320 base.Fatalf("go: -replace=%s: unversioned new path must be local directory", arg)
321 }
322
323 workedits = append(workedits, func(f *modfile.WorkFile) {
324 if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
325 base.Fatalf("go: -replace=%s: %v", arg, err)
326 }
327 })
328 }
329
330
331 func flagEditworkDropReplace(arg string) {
332 path, version, err := parsePathVersionOptional("old", arg, true)
333 if err != nil {
334 base.Fatalf("go: -dropreplace=%s: %v", arg, err)
335 }
336 workedits = append(workedits, func(f *modfile.WorkFile) {
337 if err := f.DropReplace(path, version); err != nil {
338 base.Fatalf("go: -dropreplace=%s: %v", arg, err)
339 }
340 })
341 }
342
343 type replaceJSON struct {
344 Old module.Version
345 New module.Version
346 }
347
348
349 func editPrintJSON(workFile *modfile.WorkFile) {
350 var f workfileJSON
351 if workFile.Go != nil {
352 f.Go = workFile.Go.Version
353 }
354 for _, d := range workFile.Use {
355 f.Use = append(f.Use, useJSON{DiskPath: d.Path, ModPath: d.ModulePath})
356 }
357
358 for _, r := range workFile.Replace {
359 f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
360 }
361 data, err := json.MarshalIndent(&f, "", "\t")
362 if err != nil {
363 base.Fatalf("go: internal error: %v", err)
364 }
365 data = append(data, '\n')
366 os.Stdout.Write(data)
367 }
368
369
370 type workfileJSON struct {
371 Go string `json:",omitempty"`
372 Use []useJSON
373 Replace []replaceJSON
374 }
375
376 type useJSON struct {
377 DiskPath string
378 ModPath string `json:",omitempty"`
379 }
380
View as plain text