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