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.work 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 It takes a version like "1.26" or "1.26.2".
67 Using "none" as the version removes the go directive.
68
69 The -toolchain=name flag sets the Go toolchain to use.
70 It takes a toolchain name like "go1.26" or "go1.26.2".
71 Using "none" as the name removes the toolchain directive.
72
73 The -print flag prints the final go.work in its text format instead of
74 writing it back to go.work.
75
76 The -json flag prints the final go.work file in JSON format instead of
77 writing it back to go.work. The JSON output corresponds to these Go types:
78
79 type GoWork struct {
80 Go string
81 Toolchain string
82 Godebug []Godebug
83 Use []Use
84 Replace []Replace
85 }
86
87 type Godebug struct {
88 Key string
89 Value string
90 }
91
92 type Use struct {
93 DiskPath string
94 ModulePath string
95 }
96
97 type Replace struct {
98 Old Module
99 New Module
100 }
101
102 type Module struct {
103 Path string
104 Version string
105 }
106
107 See the workspaces reference at https://go.dev/ref/mod#workspaces
108 for more information.
109 `,
110 }
111
112 var (
113 editFmt = cmdEdit.Flag.Bool("fmt", false, "")
114 editGo = cmdEdit.Flag.String("go", "", "")
115 editToolchain = cmdEdit.Flag.String("toolchain", "", "")
116 editJSON = cmdEdit.Flag.Bool("json", false, "")
117 editPrint = cmdEdit.Flag.Bool("print", false, "")
118 workedits []func(file *modfile.WorkFile)
119 )
120
121 type flagFunc func(string)
122
123 func (f flagFunc) String() string { return "" }
124 func (f flagFunc) Set(s string) error { f(s); return nil }
125
126 func init() {
127 cmdEdit.Run = runEditwork
128
129 cmdEdit.Flag.Var(flagFunc(flagEditworkGodebug), "godebug", "")
130 cmdEdit.Flag.Var(flagFunc(flagEditworkDropGodebug), "dropgodebug", "")
131 cmdEdit.Flag.Var(flagFunc(flagEditworkUse), "use", "")
132 cmdEdit.Flag.Var(flagFunc(flagEditworkDropUse), "dropuse", "")
133 cmdEdit.Flag.Var(flagFunc(flagEditworkReplace), "replace", "")
134 cmdEdit.Flag.Var(flagFunc(flagEditworkDropReplace), "dropreplace", "")
135 base.AddChdirFlag(&cmdEdit.Flag)
136 }
137
138 func runEditwork(ctx context.Context, cmd *base.Command, args []string) {
139 moduleLoader := modload.NewLoader()
140 if *editJSON && *editPrint {
141 base.Fatalf("go: cannot use both -json and -print")
142 }
143
144 if len(args) > 1 {
145 base.Fatalf("go: 'go help work edit' accepts at most one argument")
146 }
147 var gowork string
148 if len(args) == 1 {
149 gowork = args[0]
150 } else {
151 moduleLoader.InitWorkfile()
152 gowork = modload.WorkFilePath(moduleLoader)
153 }
154 if gowork == "" {
155 base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
156 }
157
158 if *editGo != "" && *editGo != "none" {
159 if !modfile.GoVersionRE.MatchString(*editGo) {
160 base.Fatalf(`go work: invalid -go option; expecting something like "-go %s"`, gover.Local())
161 }
162 }
163 if *editToolchain != "" && *editToolchain != "none" {
164 if !modfile.ToolchainRE.MatchString(*editToolchain) {
165 base.Fatalf(`go work: invalid -toolchain option; expecting something like "-toolchain go%s"`, gover.Local())
166 }
167 }
168
169 anyFlags := *editGo != "" ||
170 *editToolchain != "" ||
171 *editJSON ||
172 *editPrint ||
173 *editFmt ||
174 len(workedits) > 0
175
176 if !anyFlags {
177 base.Fatalf("go: no flags specified (see 'go help work edit').")
178 }
179
180 workFile, err := modload.ReadWorkFile(gowork)
181 if err != nil {
182 base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gowork), err)
183 }
184
185 if *editGo == "none" {
186 workFile.DropGoStmt()
187 } else if *editGo != "" {
188 if err := workFile.AddGoStmt(*editGo); err != nil {
189 base.Fatalf("go: internal error: %v", err)
190 }
191 }
192 if *editToolchain == "none" {
193 workFile.DropToolchainStmt()
194 } else if *editToolchain != "" {
195 if err := workFile.AddToolchainStmt(*editToolchain); err != nil {
196 base.Fatalf("go: internal error: %v", err)
197 }
198 }
199
200 if len(workedits) > 0 {
201 for _, edit := range workedits {
202 edit(workFile)
203 }
204 }
205
206 workFile.SortBlocks()
207 workFile.Cleanup()
208
209
210
211
212
213
214
215 if *editJSON {
216 editPrintJSON(workFile)
217 return
218 }
219
220 if *editPrint {
221 os.Stdout.Write(modfile.Format(workFile.Syntax))
222 return
223 }
224
225 modload.WriteWorkFile(gowork, workFile)
226 }
227
228
229 func flagEditworkGodebug(arg string) {
230 key, value, ok := strings.Cut(arg, "=")
231 if !ok || strings.ContainsAny(arg, "\"`',") {
232 base.Fatalf("go: -godebug=%s: need key=value", arg)
233 }
234 workedits = append(workedits, func(f *modfile.WorkFile) {
235 if err := f.AddGodebug(key, value); err != nil {
236 base.Fatalf("go: -godebug=%s: %v", arg, err)
237 }
238 })
239 }
240
241
242 func flagEditworkDropGodebug(arg string) {
243 workedits = append(workedits, func(f *modfile.WorkFile) {
244 if err := f.DropGodebug(arg); err != nil {
245 base.Fatalf("go: -dropgodebug=%s: %v", arg, err)
246 }
247 })
248 }
249
250
251 func flagEditworkUse(arg string) {
252 workedits = append(workedits, func(f *modfile.WorkFile) {
253 _, mf, err := modload.ReadModFile(filepath.Join(arg, "go.mod"), nil)
254 modulePath := ""
255 if err == nil {
256 modulePath = mf.Module.Mod.Path
257 }
258 f.AddUse(modload.ToDirectoryPath(arg), modulePath)
259 if err := f.AddUse(modload.ToDirectoryPath(arg), ""); err != nil {
260 base.Fatalf("go: -use=%s: %v", arg, err)
261 }
262 })
263 }
264
265
266 func flagEditworkDropUse(arg string) {
267 workedits = append(workedits, func(f *modfile.WorkFile) {
268 if err := f.DropUse(modload.ToDirectoryPath(arg)); err != nil {
269 base.Fatalf("go: -dropuse=%s: %v", arg, err)
270 }
271 })
272 }
273
274
275
276
277
278
279 func allowedVersionArg(arg string) bool {
280 return !modfile.MustQuote(arg)
281 }
282
283
284
285 func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
286 before, after, found, err := modload.ParsePathVersion(arg)
287 if err != nil {
288 return "", "", err
289 }
290 if !found {
291 path = arg
292 } else {
293 path, version = strings.TrimSpace(before), strings.TrimSpace(after)
294 }
295 if err := module.CheckImportPath(path); err != nil {
296 if !allowDirPath || !modfile.IsDirectoryPath(path) {
297 return path, version, fmt.Errorf("invalid %s path: %v", adj, err)
298 }
299 }
300 if path != arg && !allowedVersionArg(version) {
301 return path, version, fmt.Errorf("invalid %s version: %q", adj, version)
302 }
303 return path, version, nil
304 }
305
306
307 func flagEditworkReplace(arg string) {
308 before, after, found := strings.Cut(arg, "=")
309 if !found {
310 base.Fatalf("go: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
311 }
312 old, new := strings.TrimSpace(before), strings.TrimSpace(after)
313 if strings.HasPrefix(new, ">") {
314 base.Fatalf("go: -replace=%s: separator between old and new is =, not =>", arg)
315 }
316 oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
317 if err != nil {
318 base.Fatalf("go: -replace=%s: %v", arg, err)
319 }
320 newPath, newVersion, err := parsePathVersionOptional("new", new, true)
321 if err != nil {
322 base.Fatalf("go: -replace=%s: %v", arg, err)
323 }
324 if newPath == new && !modfile.IsDirectoryPath(new) {
325 base.Fatalf("go: -replace=%s: unversioned new path must be local directory", arg)
326 }
327
328 workedits = append(workedits, func(f *modfile.WorkFile) {
329 if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
330 base.Fatalf("go: -replace=%s: %v", arg, err)
331 }
332 })
333 }
334
335
336 func flagEditworkDropReplace(arg string) {
337 path, version, err := parsePathVersionOptional("old", arg, true)
338 if err != nil {
339 base.Fatalf("go: -dropreplace=%s: %v", arg, err)
340 }
341 workedits = append(workedits, func(f *modfile.WorkFile) {
342 if err := f.DropReplace(path, version); err != nil {
343 base.Fatalf("go: -dropreplace=%s: %v", arg, err)
344 }
345 })
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 if workFile.Toolchain != nil {
355 f.Toolchain = workFile.Toolchain.Name
356 }
357 for _, d := range workFile.Godebug {
358 f.Godebug = append(f.Godebug, godebugJSON{Key: d.Key, Value: d.Value})
359 }
360 for _, d := range workFile.Use {
361 f.Use = append(f.Use, useJSON{DiskPath: d.Path, ModPath: d.ModulePath})
362 }
363 for _, r := range workFile.Replace {
364 f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
365 }
366 data, err := json.MarshalIndent(&f, "", "\t")
367 if err != nil {
368 base.Fatalf("go: internal error: %v", err)
369 }
370 data = append(data, '\n')
371 os.Stdout.Write(data)
372 }
373
374
375 type workfileJSON struct {
376 Go string `json:",omitempty"`
377 Toolchain string `json:",omitempty"`
378 Godebug []godebugJSON `json:",omitempty"`
379 Use []useJSON `json:",omitempty"`
380 Replace []replaceJSON `json:",omitempty"`
381 }
382
383 type godebugJSON struct {
384 Key string
385 Value string
386 }
387
388 type useJSON struct {
389 DiskPath string
390 ModPath string `json:",omitempty"`
391 }
392
393 type replaceJSON struct {
394 Old module.Version
395 New module.Version
396 }
397
View as plain text