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 := strings.Cut(arg, "@")
282 if !found {
283 path = arg
284 } else {
285 path, version = strings.TrimSpace(before), strings.TrimSpace(after)
286 }
287 if err := module.CheckImportPath(path); err != nil {
288 if !allowDirPath || !modfile.IsDirectoryPath(path) {
289 return path, version, fmt.Errorf("invalid %s path: %v", adj, err)
290 }
291 }
292 if path != arg && !allowedVersionArg(version) {
293 return path, version, fmt.Errorf("invalid %s version: %q", adj, version)
294 }
295 return path, version, nil
296 }
297
298
299 func flagEditworkReplace(arg string) {
300 before, after, found := strings.Cut(arg, "=")
301 if !found {
302 base.Fatalf("go: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
303 }
304 old, new := strings.TrimSpace(before), strings.TrimSpace(after)
305 if strings.HasPrefix(new, ">") {
306 base.Fatalf("go: -replace=%s: separator between old and new is =, not =>", arg)
307 }
308 oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
309 if err != nil {
310 base.Fatalf("go: -replace=%s: %v", arg, err)
311 }
312 newPath, newVersion, err := parsePathVersionOptional("new", new, true)
313 if err != nil {
314 base.Fatalf("go: -replace=%s: %v", arg, err)
315 }
316 if newPath == new && !modfile.IsDirectoryPath(new) {
317 base.Fatalf("go: -replace=%s: unversioned new path must be local directory", arg)
318 }
319
320 workedits = append(workedits, func(f *modfile.WorkFile) {
321 if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
322 base.Fatalf("go: -replace=%s: %v", arg, err)
323 }
324 })
325 }
326
327
328 func flagEditworkDropReplace(arg string) {
329 path, version, err := parsePathVersionOptional("old", arg, true)
330 if err != nil {
331 base.Fatalf("go: -dropreplace=%s: %v", arg, err)
332 }
333 workedits = append(workedits, func(f *modfile.WorkFile) {
334 if err := f.DropReplace(path, version); err != nil {
335 base.Fatalf("go: -dropreplace=%s: %v", arg, err)
336 }
337 })
338 }
339
340 type replaceJSON struct {
341 Old module.Version
342 New module.Version
343 }
344
345
346 func editPrintJSON(workFile *modfile.WorkFile) {
347 var f workfileJSON
348 if workFile.Go != nil {
349 f.Go = workFile.Go.Version
350 }
351 for _, d := range workFile.Use {
352 f.Use = append(f.Use, useJSON{DiskPath: d.Path, ModPath: d.ModulePath})
353 }
354
355 for _, r := range workFile.Replace {
356 f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
357 }
358 data, err := json.MarshalIndent(&f, "", "\t")
359 if err != nil {
360 base.Fatalf("go: internal error: %v", err)
361 }
362 data = append(data, '\n')
363 os.Stdout.Write(data)
364 }
365
366
367 type workfileJSON struct {
368 Go string `json:",omitempty"`
369 Use []useJSON
370 Replace []replaceJSON
371 }
372
373 type useJSON struct {
374 DiskPath string
375 ModPath string `json:",omitempty"`
376 }
377
View as plain text