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 moduleLoaderState := modload.NewState()
136 if *editJSON && *editPrint {
137 base.Fatalf("go: cannot use both -json and -print")
138 }
139
140 if len(args) > 1 {
141 base.Fatalf("go: 'go help work edit' accepts at most one argument")
142 }
143 var gowork string
144 if len(args) == 1 {
145 gowork = args[0]
146 } else {
147 moduleLoaderState.InitWorkfile()
148 gowork = modload.WorkFilePath(moduleLoaderState)
149 }
150 if gowork == "" {
151 base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)")
152 }
153
154 if *editGo != "" && *editGo != "none" {
155 if !modfile.GoVersionRE.MatchString(*editGo) {
156 base.Fatalf(`go work: invalid -go option; expecting something like "-go %s"`, gover.Local())
157 }
158 }
159 if *editToolchain != "" && *editToolchain != "none" {
160 if !modfile.ToolchainRE.MatchString(*editToolchain) {
161 base.Fatalf(`go work: invalid -toolchain option; expecting something like "-toolchain go%s"`, gover.Local())
162 }
163 }
164
165 anyFlags := *editGo != "" ||
166 *editToolchain != "" ||
167 *editJSON ||
168 *editPrint ||
169 *editFmt ||
170 len(workedits) > 0
171
172 if !anyFlags {
173 base.Fatalf("go: no flags specified (see 'go help work edit').")
174 }
175
176 workFile, err := modload.ReadWorkFile(gowork)
177 if err != nil {
178 base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gowork), err)
179 }
180
181 if *editGo == "none" {
182 workFile.DropGoStmt()
183 } else if *editGo != "" {
184 if err := workFile.AddGoStmt(*editGo); err != nil {
185 base.Fatalf("go: internal error: %v", err)
186 }
187 }
188 if *editToolchain == "none" {
189 workFile.DropToolchainStmt()
190 } else if *editToolchain != "" {
191 if err := workFile.AddToolchainStmt(*editToolchain); err != nil {
192 base.Fatalf("go: internal error: %v", err)
193 }
194 }
195
196 if len(workedits) > 0 {
197 for _, edit := range workedits {
198 edit(workFile)
199 }
200 }
201
202 workFile.SortBlocks()
203 workFile.Cleanup()
204
205
206
207
208
209
210
211 if *editJSON {
212 editPrintJSON(workFile)
213 return
214 }
215
216 if *editPrint {
217 os.Stdout.Write(modfile.Format(workFile.Syntax))
218 return
219 }
220
221 modload.WriteWorkFile(gowork, workFile)
222 }
223
224
225 func flagEditworkGodebug(arg string) {
226 key, value, ok := strings.Cut(arg, "=")
227 if !ok || strings.ContainsAny(arg, "\"`',") {
228 base.Fatalf("go: -godebug=%s: need key=value", arg)
229 }
230 workedits = append(workedits, func(f *modfile.WorkFile) {
231 if err := f.AddGodebug(key, value); err != nil {
232 base.Fatalf("go: -godebug=%s: %v", arg, err)
233 }
234 })
235 }
236
237
238 func flagEditworkDropGodebug(arg string) {
239 workedits = append(workedits, func(f *modfile.WorkFile) {
240 if err := f.DropGodebug(arg); err != nil {
241 base.Fatalf("go: -dropgodebug=%s: %v", arg, err)
242 }
243 })
244 }
245
246
247 func flagEditworkUse(arg string) {
248 workedits = append(workedits, func(f *modfile.WorkFile) {
249 _, mf, err := modload.ReadModFile(filepath.Join(arg, "go.mod"), nil)
250 modulePath := ""
251 if err == nil {
252 modulePath = mf.Module.Mod.Path
253 }
254 f.AddUse(modload.ToDirectoryPath(arg), modulePath)
255 if err := f.AddUse(modload.ToDirectoryPath(arg), ""); err != nil {
256 base.Fatalf("go: -use=%s: %v", arg, err)
257 }
258 })
259 }
260
261
262 func flagEditworkDropUse(arg string) {
263 workedits = append(workedits, func(f *modfile.WorkFile) {
264 if err := f.DropUse(modload.ToDirectoryPath(arg)); err != nil {
265 base.Fatalf("go: -dropdirectory=%s: %v", arg, err)
266 }
267 })
268 }
269
270
271
272
273
274
275 func allowedVersionArg(arg string) bool {
276 return !modfile.MustQuote(arg)
277 }
278
279
280
281 func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
282 before, after, found, err := modload.ParsePathVersion(arg)
283 if err != nil {
284 return "", "", err
285 }
286 if !found {
287 path = arg
288 } else {
289 path, version = strings.TrimSpace(before), strings.TrimSpace(after)
290 }
291 if err := module.CheckImportPath(path); err != nil {
292 if !allowDirPath || !modfile.IsDirectoryPath(path) {
293 return path, version, fmt.Errorf("invalid %s path: %v", adj, err)
294 }
295 }
296 if path != arg && !allowedVersionArg(version) {
297 return path, version, fmt.Errorf("invalid %s version: %q", adj, version)
298 }
299 return path, version, nil
300 }
301
302
303 func flagEditworkReplace(arg string) {
304 before, after, found := strings.Cut(arg, "=")
305 if !found {
306 base.Fatalf("go: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
307 }
308 old, new := strings.TrimSpace(before), strings.TrimSpace(after)
309 if strings.HasPrefix(new, ">") {
310 base.Fatalf("go: -replace=%s: separator between old and new is =, not =>", arg)
311 }
312 oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
313 if err != nil {
314 base.Fatalf("go: -replace=%s: %v", arg, err)
315 }
316 newPath, newVersion, err := parsePathVersionOptional("new", new, true)
317 if err != nil {
318 base.Fatalf("go: -replace=%s: %v", arg, err)
319 }
320 if newPath == new && !modfile.IsDirectoryPath(new) {
321 base.Fatalf("go: -replace=%s: unversioned new path must be local directory", arg)
322 }
323
324 workedits = append(workedits, func(f *modfile.WorkFile) {
325 if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
326 base.Fatalf("go: -replace=%s: %v", arg, err)
327 }
328 })
329 }
330
331
332 func flagEditworkDropReplace(arg string) {
333 path, version, err := parsePathVersionOptional("old", arg, true)
334 if err != nil {
335 base.Fatalf("go: -dropreplace=%s: %v", arg, err)
336 }
337 workedits = append(workedits, func(f *modfile.WorkFile) {
338 if err := f.DropReplace(path, version); err != nil {
339 base.Fatalf("go: -dropreplace=%s: %v", arg, err)
340 }
341 })
342 }
343
344 type replaceJSON struct {
345 Old module.Version
346 New module.Version
347 }
348
349
350 func editPrintJSON(workFile *modfile.WorkFile) {
351 var f workfileJSON
352 if workFile.Go != nil {
353 f.Go = workFile.Go.Version
354 }
355 for _, d := range workFile.Use {
356 f.Use = append(f.Use, useJSON{DiskPath: d.Path, ModPath: d.ModulePath})
357 }
358
359 for _, r := range workFile.Replace {
360 f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
361 }
362 data, err := json.MarshalIndent(&f, "", "\t")
363 if err != nil {
364 base.Fatalf("go: internal error: %v", err)
365 }
366 data = append(data, '\n')
367 os.Stdout.Write(data)
368 }
369
370
371 type workfileJSON struct {
372 Go string `json:",omitempty"`
373 Use []useJSON
374 Replace []replaceJSON
375 }
376
377 type useJSON struct {
378 DiskPath string
379 ModPath string `json:",omitempty"`
380 }
381
View as plain text