github.com/gernest/nezuko@v0.1.2/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 // z mod edit 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 "github.com/gernest/nezuko/internal/base" 19 "github.com/gernest/nezuko/internal/modfetch" 20 "github.com/gernest/nezuko/internal/modfile" 21 "github.com/gernest/nezuko/internal/modload" 22 "github.com/gernest/nezuko/internal/module" 23 ) 24 25 var cmdEdit = &base.Command{ 26 UsageLine: "z mod edit [editing flags] [z.mod]", 27 Short: "edit z.mod from tools or scripts", 28 Long: ` 29 Edit provides a command-line interface for editing z.mod, 30 for use primarily by tools or scripts. It reads only z.mod; 31 it does not look up information about the modules involved. 32 By default, edit reads and writes the z.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 z.mod file without making other changes. 38 This reformatting is also implied by any other modifications that use or 39 rewrite the z.mod file. The only time this flag is needed is if no other 40 flags are specified, as in 'z mod edit -fmt'. 41 42 The -module flag changes the module's path (the z.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 z.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 z.mod in its text format instead of 70 writing it back to z.mod. 71 72 The -json flag prints the final z.mod file in JSON format instead of 73 writing it back to z.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 z.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 z.mod as a data structure by 104 parsing the output of 'z mod edit -json' and can then make changes 105 by invoking 'z mod edit' with -require, -exclude, and so on. 106 `, 107 } 108 109 var ( 110 editFmt = cmdEdit.Flag.Bool("fmt", false, "") 111 editExports = cmdEdit.Flag.String("exports", "", "") 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) // edits specified in flags 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 // break init cycle 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 *editExports != "" || 140 *editJSON || 141 *editPrint || 142 *editFmt || 143 len(edits) > 0 144 145 if !anyFlags { 146 base.Fatalf("z mod edit: no flags specified (see 'go help mod edit').") 147 } 148 149 if *editJSON && *editPrint { 150 base.Fatalf("z mod edit: cannot use both -json and -print") 151 } 152 153 if len(args) > 1 { 154 base.Fatalf("z 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(), "z.mod") 161 } 162 163 if *editModule != "" { 164 if err := module.CheckPath(*editModule); err != nil { 165 base.Fatalf("z mod: invalid -module: %v", err) 166 } 167 } 168 169 if *editExports != "" { 170 if !modfile.IsValidExport(*editExports) { 171 base.Fatalf(`z mod: invalid -exports option; expecting something like "-exports foo"`) 172 } 173 } 174 175 data, err := ioutil.ReadFile(gomod) 176 if err != nil { 177 base.Fatalf("z: %v", err) 178 } 179 180 modFile, err := modfile.Parse(gomod, data, nil) 181 if err != nil { 182 base.Fatalf("z: errors parsing %s:\n%s", base.ShortPath(gomod), err) 183 } 184 185 if *editModule != "" { 186 modFile.AddModuleStmt(*editModule) 187 } 188 189 if *editExports != "" { 190 if err := modFile.AddExportsStmt(*editExports); err != nil { 191 base.Fatalf("z: 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() // clean file after edits 202 203 if *editJSON { 204 editPrintJSON(modFile) 205 return 206 } 207 208 out, err := modFile.Format() 209 if err != nil { 210 base.Fatalf("z: %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("z: z.mod changed during editing; not overwriting") 223 } 224 if err := ioutil.WriteFile(gomod, out, 0666); err != nil { 225 base.Fatalf("z: %v", err) 226 } 227 } 228 229 // parsePathVersion parses -flag=arg expecting arg to be path@version. 230 func parsePathVersion(flag, arg string) (path, version string) { 231 i := strings.Index(arg, "@") 232 if i < 0 { 233 base.Fatalf("z 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("z mod: -%s=%s: invalid path: %v", flag, arg, err) 238 } 239 240 // We don't call modfile.CheckPathVersion, because that insists 241 // on versions being in semver form, but here we want to allow 242 // versions like "master" or "1234abcdef", which the go command will resolve 243 // the next time it runs (or during -fix). 244 // Even so, we need to make sure the version is a valid token. 245 if modfile.MustQuote(version) { 246 base.Fatalf("z mod: -%s=%s: invalid version %q", flag, arg, version) 247 } 248 249 return path, version 250 } 251 252 // parsePath parses -flag=arg expecting arg to be path (not path@version). 253 func parsePath(flag, arg string) (path string) { 254 if strings.Contains(arg, "@") { 255 base.Fatalf("z 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("z mod: -%s=%s: invalid path: %v", flag, arg, err) 260 } 261 return path 262 } 263 264 // parsePathVersionOptional parses path[@version], using adj to 265 // describe any errors. 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 // flagRequire implements the -require flag. 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("z mod: -require=%s: %v", arg, err) 289 } 290 }) 291 } 292 293 // flagDropRequire implements the -droprequire flag. 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("z mod: -droprequire=%s: %v", arg, err) 299 } 300 }) 301 } 302 303 // flagExclude implements the -exclude flag. 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("z mod: -exclude=%s: %v", arg, err) 309 } 310 }) 311 } 312 313 // flagDropExclude implements the -dropexclude flag. 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("z mod: -dropexclude=%s: %v", arg, err) 319 } 320 }) 321 } 322 323 // flagReplace implements the -replace flag. 324 func flagReplace(arg string) { 325 var i int 326 if i = strings.Index(arg, "="); i < 0 { 327 base.Fatalf("z 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("z 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("z mod: -replace=%s: %v", arg, err) 336 } 337 newPath, newVersion, err := parsePathVersionOptional("new", new, true) 338 if err != nil { 339 base.Fatalf("z mod: -replace=%s: %v", arg, err) 340 } 341 if newPath == new && !modfile.IsDirectoryPath(new) { 342 base.Fatalf("z 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("z mod: -replace=%s: %v", arg, err) 348 } 349 }) 350 } 351 352 // flagDropReplace implements the -dropreplace flag. 353 func flagDropReplace(arg string) { 354 path, version, err := parsePathVersionOptional("old", arg, true) 355 if err != nil { 356 base.Fatalf("z 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("z mod: -dropreplace=%s: %v", arg, err) 361 } 362 }) 363 } 364 365 // fileJSON is the -json output data structure. 366 type fileJSON struct { 367 Module module.Version 368 Exports 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 // editPrintJSON prints the -json output. 386 func editPrintJSON(modFile *modfile.File) { 387 var f fileJSON 388 f.Module = modFile.Module.Mod 389 if modFile.Exports != nil { 390 f.Exports = modFile.Exports.Name 391 } 392 for _, r := range modFile.Require { 393 f.Require = append(f.Require, requireJSON{Path: r.Mod.Path, Version: r.Mod.Version, Indirect: r.Indirect}) 394 } 395 for _, x := range modFile.Exclude { 396 f.Exclude = append(f.Exclude, x.Mod) 397 } 398 for _, r := range modFile.Replace { 399 f.Replace = append(f.Replace, replaceJSON{r.Old, r.New}) 400 } 401 data, err := json.MarshalIndent(&f, "", "\t") 402 if err != nil { 403 base.Fatalf("z: internal error: %v", err) 404 } 405 data = append(data, '\n') 406 os.Stdout.Write(data) 407 }