Source file src/pkg/cmd/go/internal/modcmd/edit.go
1
2
3
4
5
6
7 package modcmd
8
9 import (
10 "bytes"
11 "encoding/json"
12 "fmt"
13 "io/ioutil"
14 "os"
15 "path/filepath"
16 "strings"
17
18 "cmd/go/internal/base"
19 "cmd/go/internal/modfetch"
20 "cmd/go/internal/modfile"
21 "cmd/go/internal/modload"
22 "cmd/go/internal/module"
23 )
24
25 var cmdEdit = &base.Command{
26 UsageLine: "go mod edit [editing flags] [go.mod]",
27 Short: "edit go.mod from tools or scripts",
28 Long: `
29 Edit provides a command-line interface for editing go.mod,
30 for use primarily by tools or scripts. It reads only go.mod;
31 it does not look up information about the modules involved.
32 By default, edit reads and writes the go.mod file of the main module,
33 but a different target file can be specified after the editing flags.
34
35 The editing flags specify a sequence of editing operations.
36
37 The -fmt flag reformats the go.mod file without making other changes.
38 This reformatting is also implied by any other modifications that use or
39 rewrite the go.mod file. The only time this flag is needed is if no other
40 flags are specified, as in 'go mod edit -fmt'.
41
42 The -module flag changes the module's path (the go.mod file's module line).
43
44 The -require=path@version and -droprequire=path flags
45 add and drop a requirement on the given module path and version.
46 Note that -require overrides any existing requirements on path.
47 These flags are mainly for tools that understand the module graph.
48 Users should prefer 'go get path@version' or 'go get path@none',
49 which make other go.mod adjustments as needed to satisfy
50 constraints imposed by other modules.
51
52 The -exclude=path@version and -dropexclude=path@version flags
53 add and drop an exclusion for the given module path and version.
54 Note that -exclude=path@version is a no-op if that exclusion already exists.
55
56 The -replace=old[@v]=new[@v] and -dropreplace=old[@v] flags
57 add and drop a replacement of the given module path and version pair.
58 If the @v in old@v is omitted, the replacement applies to all versions
59 with the old module path. If the @v in new@v is omitted, the new path
60 should be a local module root directory, not a module path.
61 Note that -replace overrides any existing replacements for old[@v].
62
63 The -require, -droprequire, -exclude, -dropexclude, -replace,
64 and -dropreplace editing flags may be repeated, and the changes
65 are applied in the order given.
66
67 The -go=version flag sets the expected Go language version.
68
69 The -print flag prints the final go.mod in its text format instead of
70 writing it back to go.mod.
71
72 The -json flag prints the final go.mod file in JSON format instead of
73 writing it back to go.mod. The JSON output corresponds to these Go types:
74
75 type Module struct {
76 Path string
77 Version string
78 }
79
80 type GoMod struct {
81 Module Module
82 Go string
83 Require []Require
84 Exclude []Module
85 Replace []Replace
86 }
87
88 type Require struct {
89 Path string
90 Version string
91 Indirect bool
92 }
93
94 type Replace struct {
95 Old Module
96 New Module
97 }
98
99 Note that this only describes the go.mod file itself, not other modules
100 referred to indirectly. For the full set of modules available to a build,
101 use 'go list -m -json all'.
102
103 For example, a tool can obtain the go.mod as a data structure by
104 parsing the output of 'go mod edit -json' and can then make changes
105 by invoking 'go mod edit' with -require, -exclude, and so on.
106 `,
107 }
108
109 var (
110 editFmt = cmdEdit.Flag.Bool("fmt", false, "")
111 editGo = cmdEdit.Flag.String("go", "", "")
112 editJSON = cmdEdit.Flag.Bool("json", false, "")
113 editPrint = cmdEdit.Flag.Bool("print", false, "")
114 editModule = cmdEdit.Flag.String("module", "", "")
115 edits []func(*modfile.File)
116 )
117
118 type flagFunc func(string)
119
120 func (f flagFunc) String() string { return "" }
121 func (f flagFunc) Set(s string) error { f(s); return nil }
122
123 func init() {
124 cmdEdit.Run = runEdit
125
126 cmdEdit.Flag.Var(flagFunc(flagRequire), "require", "")
127 cmdEdit.Flag.Var(flagFunc(flagDropRequire), "droprequire", "")
128 cmdEdit.Flag.Var(flagFunc(flagExclude), "exclude", "")
129 cmdEdit.Flag.Var(flagFunc(flagDropReplace), "dropreplace", "")
130 cmdEdit.Flag.Var(flagFunc(flagReplace), "replace", "")
131 cmdEdit.Flag.Var(flagFunc(flagDropExclude), "dropexclude", "")
132
133 base.AddBuildFlagsNX(&cmdEdit.Flag)
134 }
135
136 func runEdit(cmd *base.Command, args []string) {
137 anyFlags :=
138 *editModule != "" ||
139 *editGo != "" ||
140 *editJSON ||
141 *editPrint ||
142 *editFmt ||
143 len(edits) > 0
144
145 if !anyFlags {
146 base.Fatalf("go mod edit: no flags specified (see 'go help mod edit').")
147 }
148
149 if *editJSON && *editPrint {
150 base.Fatalf("go mod edit: cannot use both -json and -print")
151 }
152
153 if len(args) > 1 {
154 base.Fatalf("go mod edit: too many arguments")
155 }
156 var gomod string
157 if len(args) == 1 {
158 gomod = args[0]
159 } else {
160 gomod = filepath.Join(modload.ModRoot(), "go.mod")
161 }
162
163 if *editModule != "" {
164 if err := module.CheckPath(*editModule); err != nil {
165 base.Fatalf("go mod: invalid -module: %v", err)
166 }
167 }
168
169 if *editGo != "" {
170 if !modfile.GoVersionRE.MatchString(*editGo) {
171 base.Fatalf(`go mod: invalid -go option; expecting something like "-go 1.12"`)
172 }
173 }
174
175 data, err := ioutil.ReadFile(gomod)
176 if err != nil {
177 base.Fatalf("go: %v", err)
178 }
179
180 modFile, err := modfile.Parse(gomod, data, nil)
181 if err != nil {
182 base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gomod), err)
183 }
184
185 if *editModule != "" {
186 modFile.AddModuleStmt(*editModule)
187 }
188
189 if *editGo != "" {
190 if err := modFile.AddGoStmt(*editGo); err != nil {
191 base.Fatalf("go: internal error: %v", err)
192 }
193 }
194
195 if len(edits) > 0 {
196 for _, edit := range edits {
197 edit(modFile)
198 }
199 }
200 modFile.SortBlocks()
201 modFile.Cleanup()
202
203 if *editJSON {
204 editPrintJSON(modFile)
205 return
206 }
207
208 out, err := modFile.Format()
209 if err != nil {
210 base.Fatalf("go: %v", err)
211 }
212
213 if *editPrint {
214 os.Stdout.Write(out)
215 return
216 }
217
218 unlock := modfetch.SideLock()
219 defer unlock()
220 lockedData, err := ioutil.ReadFile(gomod)
221 if err == nil && !bytes.Equal(lockedData, data) {
222 base.Fatalf("go: go.mod changed during editing; not overwriting")
223 }
224 if err := ioutil.WriteFile(gomod, out, 0666); err != nil {
225 base.Fatalf("go: %v", err)
226 }
227 }
228
229
230 func parsePathVersion(flag, arg string) (path, version string) {
231 i := strings.Index(arg, "@")
232 if i < 0 {
233 base.Fatalf("go mod: -%s=%s: need path@version", flag, arg)
234 }
235 path, version = strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
236 if err := module.CheckPath(path); err != nil {
237 base.Fatalf("go mod: -%s=%s: invalid path: %v", flag, arg, err)
238 }
239
240
241
242
243
244
245 if modfile.MustQuote(version) {
246 base.Fatalf("go mod: -%s=%s: invalid version %q", flag, arg, version)
247 }
248
249 return path, version
250 }
251
252
253 func parsePath(flag, arg string) (path string) {
254 if strings.Contains(arg, "@") {
255 base.Fatalf("go mod: -%s=%s: need just path, not path@version", flag, arg)
256 }
257 path = arg
258 if err := module.CheckPath(path); err != nil {
259 base.Fatalf("go mod: -%s=%s: invalid path: %v", flag, arg, err)
260 }
261 return path
262 }
263
264
265
266 func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
267 if i := strings.Index(arg, "@"); i < 0 {
268 path = arg
269 } else {
270 path, version = strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
271 }
272 if err := module.CheckPath(path); err != nil {
273 if !allowDirPath || !modfile.IsDirectoryPath(path) {
274 return path, version, fmt.Errorf("invalid %s path: %v", adj, err)
275 }
276 }
277 if path != arg && modfile.MustQuote(version) {
278 return path, version, fmt.Errorf("invalid %s version: %q", adj, version)
279 }
280 return path, version, nil
281 }
282
283
284 func flagRequire(arg string) {
285 path, version := parsePathVersion("require", arg)
286 edits = append(edits, func(f *modfile.File) {
287 if err := f.AddRequire(path, version); err != nil {
288 base.Fatalf("go mod: -require=%s: %v", arg, err)
289 }
290 })
291 }
292
293
294 func flagDropRequire(arg string) {
295 path := parsePath("droprequire", arg)
296 edits = append(edits, func(f *modfile.File) {
297 if err := f.DropRequire(path); err != nil {
298 base.Fatalf("go mod: -droprequire=%s: %v", arg, err)
299 }
300 })
301 }
302
303
304 func flagExclude(arg string) {
305 path, version := parsePathVersion("exclude", arg)
306 edits = append(edits, func(f *modfile.File) {
307 if err := f.AddExclude(path, version); err != nil {
308 base.Fatalf("go mod: -exclude=%s: %v", arg, err)
309 }
310 })
311 }
312
313
314 func flagDropExclude(arg string) {
315 path, version := parsePathVersion("dropexclude", arg)
316 edits = append(edits, func(f *modfile.File) {
317 if err := f.DropExclude(path, version); err != nil {
318 base.Fatalf("go mod: -dropexclude=%s: %v", arg, err)
319 }
320 })
321 }
322
323
324 func flagReplace(arg string) {
325 var i int
326 if i = strings.Index(arg, "="); i < 0 {
327 base.Fatalf("go mod: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
328 }
329 old, new := strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
330 if strings.HasPrefix(new, ">") {
331 base.Fatalf("go mod: -replace=%s: separator between old and new is =, not =>", arg)
332 }
333 oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
334 if err != nil {
335 base.Fatalf("go mod: -replace=%s: %v", arg, err)
336 }
337 newPath, newVersion, err := parsePathVersionOptional("new", new, true)
338 if err != nil {
339 base.Fatalf("go mod: -replace=%s: %v", arg, err)
340 }
341 if newPath == new && !modfile.IsDirectoryPath(new) {
342 base.Fatalf("go mod: -replace=%s: unversioned new path must be local directory", arg)
343 }
344
345 edits = append(edits, func(f *modfile.File) {
346 if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
347 base.Fatalf("go mod: -replace=%s: %v", arg, err)
348 }
349 })
350 }
351
352
353 func flagDropReplace(arg string) {
354 path, version, err := parsePathVersionOptional("old", arg, true)
355 if err != nil {
356 base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
357 }
358 edits = append(edits, func(f *modfile.File) {
359 if err := f.DropReplace(path, version); err != nil {
360 base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
361 }
362 })
363 }
364
365
366 type fileJSON struct {
367 Module module.Version
368 Go string `json:",omitempty"`
369 Require []requireJSON
370 Exclude []module.Version
371 Replace []replaceJSON
372 }
373
374 type requireJSON struct {
375 Path string
376 Version string `json:",omitempty"`
377 Indirect bool `json:",omitempty"`
378 }
379
380 type replaceJSON struct {
381 Old module.Version
382 New module.Version
383 }
384
385
386 func editPrintJSON(modFile *modfile.File) {
387 var f fileJSON
388 if modFile.Module != nil {
389 f.Module = modFile.Module.Mod
390 }
391 if modFile.Go != nil {
392 f.Go = modFile.Go.Version
393 }
394 for _, r := range modFile.Require {
395 f.Require = append(f.Require, requireJSON{Path: r.Mod.Path, Version: r.Mod.Version, Indirect: r.Indirect})
396 }
397 for _, x := range modFile.Exclude {
398 f.Exclude = append(f.Exclude, x.Mod)
399 }
400 for _, r := range modFile.Replace {
401 f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
402 }
403 data, err := json.MarshalIndent(&f, "", "\t")
404 if err != nil {
405 base.Fatalf("go: internal error: %v", err)
406 }
407 data = append(data, '\n')
408 os.Stdout.Write(data)
409 }
410
View as plain text