This does not yet include support for the //go:embed directive added in this release. * Makefile.am (check-runtime): Don't create check-runtime-dir. (mostlyclean-local): Don't remove check-runtime-dir. (check-go-tool, check-vet): Copy in go.mod and modules.txt. (check-cgo-test, check-carchive-test): Add go.mod file. * Makefile.in: Regenerate. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/280172
507 lines
15 KiB
Go
507 lines
15 KiB
Go
// Copyright 2018 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// go mod edit
|
|
|
|
package modcmd
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"cmd/go/internal/base"
|
|
"cmd/go/internal/lockedfile"
|
|
"cmd/go/internal/modfetch"
|
|
"cmd/go/internal/modload"
|
|
|
|
"golang.org/x/mod/modfile"
|
|
"golang.org/x/mod/module"
|
|
)
|
|
|
|
var cmdEdit = &base.Command{
|
|
UsageLine: "go mod edit [editing flags] [go.mod]",
|
|
Short: "edit go.mod from tools or scripts",
|
|
Long: `
|
|
Edit provides a command-line interface for editing go.mod,
|
|
for use primarily by tools or scripts. It reads only go.mod;
|
|
it does not look up information about the modules involved.
|
|
By default, edit reads and writes the go.mod file of the main module,
|
|
but a different target file can be specified after the editing flags.
|
|
|
|
The editing flags specify a sequence of editing operations.
|
|
|
|
The -fmt flag reformats the go.mod file without making other changes.
|
|
This reformatting is also implied by any other modifications that use or
|
|
rewrite the go.mod file. The only time this flag is needed is if no other
|
|
flags are specified, as in 'go mod edit -fmt'.
|
|
|
|
The -module flag changes the module's path (the go.mod file's module line).
|
|
|
|
The -require=path@version and -droprequire=path flags
|
|
add and drop a requirement on the given module path and version.
|
|
Note that -require overrides any existing requirements on path.
|
|
These flags are mainly for tools that understand the module graph.
|
|
Users should prefer 'go get path@version' or 'go get path@none',
|
|
which make other go.mod adjustments as needed to satisfy
|
|
constraints imposed by other modules.
|
|
|
|
The -exclude=path@version and -dropexclude=path@version flags
|
|
add and drop an exclusion for the given module path and version.
|
|
Note that -exclude=path@version is a no-op if that exclusion already exists.
|
|
|
|
The -replace=old[@v]=new[@v] flag adds a replacement of the given
|
|
module path and version pair. If the @v in old@v is omitted, a
|
|
replacement without a version on the left side is added, which applies
|
|
to all versions of the old module path. If the @v in new@v is omitted,
|
|
the new path should be a local module root directory, not a module
|
|
path. Note that -replace overrides any redundant replacements for old[@v],
|
|
so omitting @v will drop existing replacements for specific versions.
|
|
|
|
The -dropreplace=old[@v] flag drops a replacement of the given
|
|
module path and version pair. If the @v is omitted, a replacement without
|
|
a version on the left side is dropped.
|
|
|
|
The -retract=version and -dropretract=version flags add and drop a
|
|
retraction on the given version. The version may be a single version
|
|
like "v1.2.3" or a closed interval like "[v1.1.0,v1.1.9]". Note that
|
|
-retract=version is a no-op if that retraction already exists.
|
|
|
|
The -require, -droprequire, -exclude, -dropexclude, -replace,
|
|
-dropreplace, -retract, and -dropretract editing flags may be repeated,
|
|
and the changes are applied in the order given.
|
|
|
|
The -go=version flag sets the expected Go language version.
|
|
|
|
The -print flag prints the final go.mod in its text format instead of
|
|
writing it back to go.mod.
|
|
|
|
The -json flag prints the final go.mod file in JSON format instead of
|
|
writing it back to go.mod. The JSON output corresponds to these Go types:
|
|
|
|
type Module struct {
|
|
Path string
|
|
Version string
|
|
}
|
|
|
|
type GoMod struct {
|
|
Module Module
|
|
Go string
|
|
Require []Require
|
|
Exclude []Module
|
|
Replace []Replace
|
|
}
|
|
|
|
type Require struct {
|
|
Path string
|
|
Version string
|
|
Indirect bool
|
|
}
|
|
|
|
type Replace struct {
|
|
Old Module
|
|
New Module
|
|
}
|
|
|
|
type Retract struct {
|
|
Low string
|
|
High string
|
|
Rationale string
|
|
}
|
|
|
|
Retract entries representing a single version (not an interval) will have
|
|
the "Low" and "High" fields set to the same value.
|
|
|
|
Note that this only describes the go.mod file itself, not other modules
|
|
referred to indirectly. For the full set of modules available to a build,
|
|
use 'go list -m -json all'.
|
|
|
|
For example, a tool can obtain the go.mod as a data structure by
|
|
parsing the output of 'go mod edit -json' and can then make changes
|
|
by invoking 'go mod edit' with -require, -exclude, and so on.
|
|
`,
|
|
}
|
|
|
|
var (
|
|
editFmt = cmdEdit.Flag.Bool("fmt", false, "")
|
|
editGo = cmdEdit.Flag.String("go", "", "")
|
|
editJSON = cmdEdit.Flag.Bool("json", false, "")
|
|
editPrint = cmdEdit.Flag.Bool("print", false, "")
|
|
editModule = cmdEdit.Flag.String("module", "", "")
|
|
edits []func(*modfile.File) // edits specified in flags
|
|
)
|
|
|
|
type flagFunc func(string)
|
|
|
|
func (f flagFunc) String() string { return "" }
|
|
func (f flagFunc) Set(s string) error { f(s); return nil }
|
|
|
|
func init() {
|
|
cmdEdit.Run = runEdit // break init cycle
|
|
|
|
cmdEdit.Flag.Var(flagFunc(flagRequire), "require", "")
|
|
cmdEdit.Flag.Var(flagFunc(flagDropRequire), "droprequire", "")
|
|
cmdEdit.Flag.Var(flagFunc(flagExclude), "exclude", "")
|
|
cmdEdit.Flag.Var(flagFunc(flagDropReplace), "dropreplace", "")
|
|
cmdEdit.Flag.Var(flagFunc(flagReplace), "replace", "")
|
|
cmdEdit.Flag.Var(flagFunc(flagDropExclude), "dropexclude", "")
|
|
cmdEdit.Flag.Var(flagFunc(flagRetract), "retract", "")
|
|
cmdEdit.Flag.Var(flagFunc(flagDropRetract), "dropretract", "")
|
|
|
|
base.AddModCommonFlags(&cmdEdit.Flag)
|
|
base.AddBuildFlagsNX(&cmdEdit.Flag)
|
|
}
|
|
|
|
func runEdit(ctx context.Context, cmd *base.Command, args []string) {
|
|
anyFlags :=
|
|
*editModule != "" ||
|
|
*editGo != "" ||
|
|
*editJSON ||
|
|
*editPrint ||
|
|
*editFmt ||
|
|
len(edits) > 0
|
|
|
|
if !anyFlags {
|
|
base.Fatalf("go mod edit: no flags specified (see 'go help mod edit').")
|
|
}
|
|
|
|
if *editJSON && *editPrint {
|
|
base.Fatalf("go mod edit: cannot use both -json and -print")
|
|
}
|
|
|
|
if len(args) > 1 {
|
|
base.Fatalf("go mod edit: too many arguments")
|
|
}
|
|
var gomod string
|
|
if len(args) == 1 {
|
|
gomod = args[0]
|
|
} else {
|
|
gomod = modload.ModFilePath()
|
|
}
|
|
|
|
if *editModule != "" {
|
|
if err := module.CheckImportPath(*editModule); err != nil {
|
|
base.Fatalf("go mod: invalid -module: %v", err)
|
|
}
|
|
}
|
|
|
|
if *editGo != "" {
|
|
if !modfile.GoVersionRE.MatchString(*editGo) {
|
|
base.Fatalf(`go mod: invalid -go option; expecting something like "-go 1.12"`)
|
|
}
|
|
}
|
|
|
|
data, err := lockedfile.Read(gomod)
|
|
if err != nil {
|
|
base.Fatalf("go: %v", err)
|
|
}
|
|
|
|
modFile, err := modfile.Parse(gomod, data, nil)
|
|
if err != nil {
|
|
base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gomod), err)
|
|
}
|
|
|
|
if *editModule != "" {
|
|
modFile.AddModuleStmt(*editModule)
|
|
}
|
|
|
|
if *editGo != "" {
|
|
if err := modFile.AddGoStmt(*editGo); err != nil {
|
|
base.Fatalf("go: internal error: %v", err)
|
|
}
|
|
}
|
|
|
|
if len(edits) > 0 {
|
|
for _, edit := range edits {
|
|
edit(modFile)
|
|
}
|
|
}
|
|
modFile.SortBlocks()
|
|
modFile.Cleanup() // clean file after edits
|
|
|
|
if *editJSON {
|
|
editPrintJSON(modFile)
|
|
return
|
|
}
|
|
|
|
out, err := modFile.Format()
|
|
if err != nil {
|
|
base.Fatalf("go: %v", err)
|
|
}
|
|
|
|
if *editPrint {
|
|
os.Stdout.Write(out)
|
|
return
|
|
}
|
|
|
|
// Make a best-effort attempt to acquire the side lock, only to exclude
|
|
// previous versions of the 'go' command from making simultaneous edits.
|
|
if unlock, err := modfetch.SideLock(); err == nil {
|
|
defer unlock()
|
|
}
|
|
|
|
err = lockedfile.Transform(gomod, func(lockedData []byte) ([]byte, error) {
|
|
if !bytes.Equal(lockedData, data) {
|
|
return nil, errors.New("go.mod changed during editing; not overwriting")
|
|
}
|
|
return out, nil
|
|
})
|
|
if err != nil {
|
|
base.Fatalf("go: %v", err)
|
|
}
|
|
}
|
|
|
|
// parsePathVersion parses -flag=arg expecting arg to be path@version.
|
|
func parsePathVersion(flag, arg string) (path, version string) {
|
|
i := strings.Index(arg, "@")
|
|
if i < 0 {
|
|
base.Fatalf("go mod: -%s=%s: need path@version", flag, arg)
|
|
}
|
|
path, version = strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
|
|
if err := module.CheckImportPath(path); err != nil {
|
|
base.Fatalf("go mod: -%s=%s: invalid path: %v", flag, arg, err)
|
|
}
|
|
|
|
if !allowedVersionArg(version) {
|
|
base.Fatalf("go mod: -%s=%s: invalid version %q", flag, arg, version)
|
|
}
|
|
|
|
return path, version
|
|
}
|
|
|
|
// parsePath parses -flag=arg expecting arg to be path (not path@version).
|
|
func parsePath(flag, arg string) (path string) {
|
|
if strings.Contains(arg, "@") {
|
|
base.Fatalf("go mod: -%s=%s: need just path, not path@version", flag, arg)
|
|
}
|
|
path = arg
|
|
if err := module.CheckImportPath(path); err != nil {
|
|
base.Fatalf("go mod: -%s=%s: invalid path: %v", flag, arg, err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
// parsePathVersionOptional parses path[@version], using adj to
|
|
// describe any errors.
|
|
func parsePathVersionOptional(adj, arg string, allowDirPath bool) (path, version string, err error) {
|
|
if i := strings.Index(arg, "@"); i < 0 {
|
|
path = arg
|
|
} else {
|
|
path, version = strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
|
|
}
|
|
if err := module.CheckImportPath(path); err != nil {
|
|
if !allowDirPath || !modfile.IsDirectoryPath(path) {
|
|
return path, version, fmt.Errorf("invalid %s path: %v", adj, err)
|
|
}
|
|
}
|
|
if path != arg && !allowedVersionArg(version) {
|
|
return path, version, fmt.Errorf("invalid %s version: %q", adj, version)
|
|
}
|
|
return path, version, nil
|
|
}
|
|
|
|
// parseVersionInterval parses a single version like "v1.2.3" or a closed
|
|
// interval like "[v1.2.3,v1.4.5]". Note that a single version has the same
|
|
// representation as an interval with equal upper and lower bounds: both
|
|
// Low and High are set.
|
|
func parseVersionInterval(arg string) (modfile.VersionInterval, error) {
|
|
if !strings.HasPrefix(arg, "[") {
|
|
if !allowedVersionArg(arg) {
|
|
return modfile.VersionInterval{}, fmt.Errorf("invalid version: %q", arg)
|
|
}
|
|
return modfile.VersionInterval{Low: arg, High: arg}, nil
|
|
}
|
|
if !strings.HasSuffix(arg, "]") {
|
|
return modfile.VersionInterval{}, fmt.Errorf("invalid version interval: %q", arg)
|
|
}
|
|
s := arg[1 : len(arg)-1]
|
|
i := strings.Index(s, ",")
|
|
if i < 0 {
|
|
return modfile.VersionInterval{}, fmt.Errorf("invalid version interval: %q", arg)
|
|
}
|
|
low := strings.TrimSpace(s[:i])
|
|
high := strings.TrimSpace(s[i+1:])
|
|
if !allowedVersionArg(low) || !allowedVersionArg(high) {
|
|
return modfile.VersionInterval{}, fmt.Errorf("invalid version interval: %q", arg)
|
|
}
|
|
return modfile.VersionInterval{Low: low, High: high}, nil
|
|
}
|
|
|
|
// allowedVersionArg returns whether a token may be used as a version in go.mod.
|
|
// We don't call modfile.CheckPathVersion, because that insists on versions
|
|
// being in semver form, but here we want to allow versions like "master" or
|
|
// "1234abcdef", which the go command will resolve the next time it runs (or
|
|
// during -fix). Even so, we need to make sure the version is a valid token.
|
|
func allowedVersionArg(arg string) bool {
|
|
return !modfile.MustQuote(arg)
|
|
}
|
|
|
|
// flagRequire implements the -require flag.
|
|
func flagRequire(arg string) {
|
|
path, version := parsePathVersion("require", arg)
|
|
edits = append(edits, func(f *modfile.File) {
|
|
if err := f.AddRequire(path, version); err != nil {
|
|
base.Fatalf("go mod: -require=%s: %v", arg, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// flagDropRequire implements the -droprequire flag.
|
|
func flagDropRequire(arg string) {
|
|
path := parsePath("droprequire", arg)
|
|
edits = append(edits, func(f *modfile.File) {
|
|
if err := f.DropRequire(path); err != nil {
|
|
base.Fatalf("go mod: -droprequire=%s: %v", arg, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// flagExclude implements the -exclude flag.
|
|
func flagExclude(arg string) {
|
|
path, version := parsePathVersion("exclude", arg)
|
|
edits = append(edits, func(f *modfile.File) {
|
|
if err := f.AddExclude(path, version); err != nil {
|
|
base.Fatalf("go mod: -exclude=%s: %v", arg, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// flagDropExclude implements the -dropexclude flag.
|
|
func flagDropExclude(arg string) {
|
|
path, version := parsePathVersion("dropexclude", arg)
|
|
edits = append(edits, func(f *modfile.File) {
|
|
if err := f.DropExclude(path, version); err != nil {
|
|
base.Fatalf("go mod: -dropexclude=%s: %v", arg, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// flagReplace implements the -replace flag.
|
|
func flagReplace(arg string) {
|
|
var i int
|
|
if i = strings.Index(arg, "="); i < 0 {
|
|
base.Fatalf("go mod: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
|
|
}
|
|
old, new := strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
|
|
if strings.HasPrefix(new, ">") {
|
|
base.Fatalf("go mod: -replace=%s: separator between old and new is =, not =>", arg)
|
|
}
|
|
oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
|
|
if err != nil {
|
|
base.Fatalf("go mod: -replace=%s: %v", arg, err)
|
|
}
|
|
newPath, newVersion, err := parsePathVersionOptional("new", new, true)
|
|
if err != nil {
|
|
base.Fatalf("go mod: -replace=%s: %v", arg, err)
|
|
}
|
|
if newPath == new && !modfile.IsDirectoryPath(new) {
|
|
base.Fatalf("go mod: -replace=%s: unversioned new path must be local directory", arg)
|
|
}
|
|
|
|
edits = append(edits, func(f *modfile.File) {
|
|
if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
|
|
base.Fatalf("go mod: -replace=%s: %v", arg, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// flagDropReplace implements the -dropreplace flag.
|
|
func flagDropReplace(arg string) {
|
|
path, version, err := parsePathVersionOptional("old", arg, true)
|
|
if err != nil {
|
|
base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
|
|
}
|
|
edits = append(edits, func(f *modfile.File) {
|
|
if err := f.DropReplace(path, version); err != nil {
|
|
base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// flagRetract implements the -retract flag.
|
|
func flagRetract(arg string) {
|
|
vi, err := parseVersionInterval(arg)
|
|
if err != nil {
|
|
base.Fatalf("go mod: -retract=%s: %v", arg, err)
|
|
}
|
|
edits = append(edits, func(f *modfile.File) {
|
|
if err := f.AddRetract(vi, ""); err != nil {
|
|
base.Fatalf("go mod: -retract=%s: %v", arg, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// flagDropRetract implements the -dropretract flag.
|
|
func flagDropRetract(arg string) {
|
|
vi, err := parseVersionInterval(arg)
|
|
if err != nil {
|
|
base.Fatalf("go mod: -dropretract=%s: %v", arg, err)
|
|
}
|
|
edits = append(edits, func(f *modfile.File) {
|
|
if err := f.DropRetract(vi); err != nil {
|
|
base.Fatalf("go mod: -dropretract=%s: %v", arg, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// fileJSON is the -json output data structure.
|
|
type fileJSON struct {
|
|
Module module.Version
|
|
Go string `json:",omitempty"`
|
|
Require []requireJSON
|
|
Exclude []module.Version
|
|
Replace []replaceJSON
|
|
Retract []retractJSON
|
|
}
|
|
|
|
type requireJSON struct {
|
|
Path string
|
|
Version string `json:",omitempty"`
|
|
Indirect bool `json:",omitempty"`
|
|
}
|
|
|
|
type replaceJSON struct {
|
|
Old module.Version
|
|
New module.Version
|
|
}
|
|
|
|
type retractJSON struct {
|
|
Low string `json:",omitempty"`
|
|
High string `json:",omitempty"`
|
|
Rationale string `json:",omitempty"`
|
|
}
|
|
|
|
// editPrintJSON prints the -json output.
|
|
func editPrintJSON(modFile *modfile.File) {
|
|
var f fileJSON
|
|
if modFile.Module != nil {
|
|
f.Module = modFile.Module.Mod
|
|
}
|
|
if modFile.Go != nil {
|
|
f.Go = modFile.Go.Version
|
|
}
|
|
for _, r := range modFile.Require {
|
|
f.Require = append(f.Require, requireJSON{Path: r.Mod.Path, Version: r.Mod.Version, Indirect: r.Indirect})
|
|
}
|
|
for _, x := range modFile.Exclude {
|
|
f.Exclude = append(f.Exclude, x.Mod)
|
|
}
|
|
for _, r := range modFile.Replace {
|
|
f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
|
|
}
|
|
for _, r := range modFile.Retract {
|
|
f.Retract = append(f.Retract, retractJSON{r.Low, r.High, r.Rationale})
|
|
}
|
|
data, err := json.MarshalIndent(&f, "", "\t")
|
|
if err != nil {
|
|
base.Fatalf("go: internal error: %v", err)
|
|
}
|
|
data = append(data, '\n')
|
|
os.Stdout.Write(data)
|
|
}
|