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