//
// Usage:
//
-// go get [-t] [-u] [-v] [build flags] [packages]
+// go get [-t] [-u] [-v] [-tool] [build flags] [packages]
//
// Get resolves its command-line arguments to packages at specific module versions,
// updates go.mod to require those versions, and downloads source code into the
// When the -t and -u flags are used together, get will update
// test dependencies as well.
//
+// The -tool flag instructs go to add a matching tool line to go.mod for each
+// listed package. If -tool is used with @none, the line will be removed.
+//
// The -x flag prints commands as they are executed. This is useful for
// debugging version control commands when a module is downloaded directly
// from a repository.
var CmdGet = &base.Command{
// Note: flags below are listed explicitly because they're the most common.
// Do not send CLs removing them because they're covered by [get flags].
- UsageLine: "go get [-t] [-u] [-v] [build flags] [packages]",
+ UsageLine: "go get [-t] [-u] [-v] [-tool] [build flags] [packages]",
Short: "add dependencies to current module and install them",
Long: `
Get resolves its command-line arguments to packages at specific module versions,
When the -t and -u flags are used together, get will update
test dependencies as well.
+The -tool flag instructs go to add a matching tool line to go.mod for each
+listed package. If -tool is used with @none, the line will be removed.
+
The -x flag prints commands as they are executed. This is useful for
debugging version control commands when a module is downloaded directly
from a repository.
getM = CmdGet.Flag.Bool("m", false, "")
getT = CmdGet.Flag.Bool("t", false, "")
getU upgradeFlag
+ getTool = CmdGet.Flag.Bool("tool", false, "")
getInsecure = CmdGet.Flag.Bool("insecure", false, "")
// -v is cfg.BuildV
)
}
r.checkPackageProblems(ctx, pkgPatterns)
+ if *getTool {
+ updateTools(ctx, queries, &opts)
+ }
+
// Everything succeeded. Update go.mod.
oldReqs := reqsFromGoMod(modload.ModFile())
}
}
+func updateTools(ctx context.Context, queries []*query, opts *modload.WriteOpts) {
+ pkgOpts := modload.PackageOpts{
+ VendorModulesInGOROOTSrc: true,
+ LoadTests: *getT,
+ ResolveMissingImports: false,
+ AllowErrors: true,
+ SilenceNoGoErrors: true,
+ }
+ patterns := []string{}
+ for _, q := range queries {
+ if search.IsMetaPackage(q.pattern) || q.pattern == "toolchain" {
+ base.Fatalf("go: go get -tool does not work with \"%s\".", q.pattern)
+ }
+ patterns = append(patterns, q.pattern)
+ }
+
+ matches, _ := modload.LoadPackages(ctx, pkgOpts, patterns...)
+ for i, m := range matches {
+ if queries[i].version == "none" {
+ opts.DropTools = append(opts.DropTools, m.Pkgs...)
+ } else {
+ opts.AddTools = append(opts.DropTools, m.Pkgs...)
+ }
+ }
+}
+
// parseArgs parses command-line arguments and reports errors.
//
// The command-line arguments are of the form path@version or simply path, with
DropToolchain bool // go get toolchain@none
ExplicitToolchain bool // go get has set explicit toolchain version
+ AddTools []string // go get -tool example.com/m1
+ DropTools []string // go get -tool example.com/m1@none
+
// TODO(bcmills): Make 'go mod tidy' update the go version in the Requirements
// instead of writing directly to the modfile.File
TidyWroteGo bool // Go.Version field already updated by 'go mod tidy'
modFile.AddToolchainStmt(toolchain)
}
+ for _, path := range opts.AddTools {
+ modFile.AddTool(path)
+ }
+
+ for _, path := range opts.DropTools {
+ modFile.DropTool(path)
+ }
+
// Update require blocks.
if gover.Compare(goVersion, gover.SeparateIndirectVersion) < 0 {
modFile.SetRequire(list)
}
index := MainModules.GetSingleIndexOrNil()
- dirty := index.modFileIsDirty(modFile)
+ dirty := index.modFileIsDirty(modFile) || len(opts.DropTools) > 0 || len(opts.AddTools) > 0
if dirty && cfg.BuildMod != "mod" {
// If we're about to fail due to -mod=readonly,
// prefer to report a dirty go.mod over a dirty go.sum
--- /dev/null
+# test go get -tool
+go get -tool example.com/tools/cmd/hello
+cmp go.mod go.mod.want
+
+# test -tool with @none
+go get -tool example.com/tools/cmd/hello@none
+cmp go.mod go.mod.gone
+
+go mod tidy
+cmp go.mod go.mod.empty
+
+# test -tool with wildcards
+go get -tool ./cmd/...
+cmp go.mod go.mod.wildcard
+! go get -tool ./cmd/...@none
+stderr 'can''t request explicit version "none" of path "./cmd/..." in main module'
+
+# test -tool with all
+! go get -tool all
+stderr 'go get -tool does not work with "all"'
+
+-- main.go --
+package main
+
+func main() {}
+
+-- go.mod --
+module example.com/foo
+go 1.24
+
+-- go.mod.want --
+module example.com/foo
+
+go 1.24
+
+tool example.com/tools/cmd/hello
+
+require example.com/tools v1.0.0 // indirect
+-- go.mod.gone --
+module example.com/foo
+
+go 1.24
+
+require example.com/tools v1.0.0 // indirect
+-- go.mod.empty --
+module example.com/foo
+
+go 1.24
+-- go.mod.wildcard --
+module example.com/foo
+
+go 1.24
+
+tool (
+ example.com/foo/cmd/a
+ example.com/foo/cmd/b
+)
+-- cmd/a/a.go --
+package a
+
+func main() {}
+
+-- cmd/b/b.go --
+package b
+
+func main() {}