diff --git a/src/cmd/doc/main.go b/src/cmd/doc/main.go index 3c45dd76df..ae1b7575e8 100644 --- a/src/cmd/doc/main.go +++ b/src/cmd/doc/main.go @@ -57,12 +57,13 @@ import ( ) var ( - unexported bool // -u flag - matchCase bool // -c flag - showAll bool // -all flag - showCmd bool // -cmd flag - showSrc bool // -src flag - short bool // -short flag + unexported bool // -u flag + matchCase bool // -c flag + chdir string // -C flag + showAll bool // -all flag + showCmd bool // -cmd flag + showSrc bool // -src flag + short bool // -short flag ) // usage is a replacement usage function for the flags package. @@ -96,6 +97,7 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { flagSet.Usage = usage unexported = false matchCase = false + flagSet.StringVar(&chdir, "C", "", "change to `dir` before running command") flagSet.BoolVar(&unexported, "u", false, "show unexported symbols as well as exported") flagSet.BoolVar(&matchCase, "c", false, "symbol matching honors case (paths not affected)") flagSet.BoolVar(&showAll, "all", false, "show all documentation for package") @@ -103,6 +105,11 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { flagSet.BoolVar(&showSrc, "src", false, "show source code for symbol") flagSet.BoolVar(&short, "short", false, "one-line representation for each symbol") flagSet.Parse(args) + if chdir != "" { + if err := os.Chdir(chdir); err != nil { + return err + } + } var paths []string var symbol, method string // Loop until something is printed. diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index a8206c475c..051cf25996 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -100,6 +100,10 @@ // The build flags are shared by the build, clean, get, install, list, run, // and test commands: // +// -C dir +// Change to dir before running the command. +// Any files named on the command line are interpreted after +// changing directories. // -a // force rebuilding of packages that are already up-to-date. // -n @@ -1233,6 +1237,8 @@ // referred to indirectly. For the full set of modules available to a build, // use 'go list -m -json all'. // +// Edit also provides the -C, -n, and -x build flags. +// // See https://golang.org/ref/mod#go-mod-edit for more about 'go mod edit'. // // # Print module requirement graph @@ -1797,7 +1803,7 @@ // // Usage: // -// go vet [-n] [-x] [-vettool prog] [build flags] [vet flags] [packages] +// go vet [-C dir] [-n] [-x] [-vettool prog] [build flags] [vet flags] [packages] // // Vet runs the Go vet command on the packages named by the import paths. // @@ -1806,6 +1812,7 @@ // For a list of checkers and their flags, see 'go tool vet help'. // For details of a specific checker such as 'printf', see 'go tool vet help printf'. // +// The -C flag changes to dir before running the 'go vet' command. // The -n flag prints commands that would be executed. // The -x flag prints commands as they are executed. // diff --git a/src/cmd/go/chdir_test.go b/src/cmd/go/chdir_test.go new file mode 100644 index 0000000000..44cbb9c3f7 --- /dev/null +++ b/src/cmd/go/chdir_test.go @@ -0,0 +1,49 @@ +// Copyright 2022 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. + +package main + +import ( + "cmd/go/internal/base" + "os" + "strings" + "testing" +) + +func TestChdir(t *testing.T) { + // We want -C to apply to every go subcommand. + // Test that every command either has a -C flag registered + // or has CustomFlags set. In the latter case, the command + // must be explicitly tested in TestScript/chdir. + script, err := os.ReadFile("testdata/script/chdir.txt") + if err != nil { + t.Fatal(err) + } + + var walk func(string, *base.Command) + walk = func(name string, cmd *base.Command) { + if len(cmd.Commands) > 0 { + for _, sub := range cmd.Commands { + walk(name+" "+sub.Name(), sub) + } + return + } + if !cmd.Runnable() { + return + } + if cmd.CustomFlags { + if !strings.Contains(string(script), "# "+name+"\n") { + t.Errorf("%s has custom flags, not tested in testdata/script/chdir.txt", name) + } + return + } + f := cmd.Flag.Lookup("C") + if f == nil { + t.Errorf("%s has no -C flag", name) + } else if f.Usage != "AddChdirFlag" { + t.Errorf("%s has -C flag but not from AddChdirFlag", name) + } + } + walk("go", base.Go) +} diff --git a/src/cmd/go/internal/base/flag.go b/src/cmd/go/internal/base/flag.go index 120420a126..9d8d1c0c8d 100644 --- a/src/cmd/go/internal/base/flag.go +++ b/src/cmd/go/internal/base/flag.go @@ -6,6 +6,7 @@ package base import ( "flag" + "os" "cmd/go/internal/cfg" "cmd/go/internal/fsys" @@ -57,6 +58,13 @@ func AddBuildFlagsNX(flags *flag.FlagSet) { flags.BoolVar(&cfg.BuildX, "x", false, "") } +// AddChdirFlag adds the -C flag to the flag set. +func AddChdirFlag(flags *flag.FlagSet) { + // The usage message is never printed, but it's used in chdir_test.go + // to identify that the -C flag is from AddChdirFlag. + flags.Func("C", "AddChdirFlag", os.Chdir) +} + // AddModFlag adds the -mod build flag to the flag set. func AddModFlag(flags *flag.FlagSet) { flags.Var(explicitStringFlag{value: &cfg.BuildMod, explicit: &cfg.BuildModExplicit}, "mod", "") diff --git a/src/cmd/go/internal/bug/bug.go b/src/cmd/go/internal/bug/bug.go index e667012fbb..ed1813605e 100644 --- a/src/cmd/go/internal/bug/bug.go +++ b/src/cmd/go/internal/bug/bug.go @@ -37,6 +37,7 @@ The report includes useful system information. func init() { CmdBug.Flag.BoolVar(&cfg.BuildV, "v", false, "") + base.AddChdirFlag(&CmdBug.Flag) } func runBug(ctx context.Context, cmd *base.Command, args []string) { diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go index f7f065529d..10499c2d3e 100644 --- a/src/cmd/go/internal/envcmd/env.go +++ b/src/cmd/go/internal/envcmd/env.go @@ -57,6 +57,7 @@ For more about environment variables, see 'go help environment'. func init() { CmdEnv.Run = runEnv // break init cycle + base.AddChdirFlag(&CmdEnv.Flag) } var ( diff --git a/src/cmd/go/internal/fmtcmd/fmt.go b/src/cmd/go/internal/fmtcmd/fmt.go index f6a8d207cd..62b22f6bcf 100644 --- a/src/cmd/go/internal/fmtcmd/fmt.go +++ b/src/cmd/go/internal/fmtcmd/fmt.go @@ -21,6 +21,7 @@ import ( func init() { base.AddBuildFlagsNX(&CmdFmt.Flag) + base.AddChdirFlag(&CmdFmt.Flag) base.AddModFlag(&CmdFmt.Flag) base.AddModCommonFlags(&CmdFmt.Flag) } diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go index 0b50afb668..f0b62e8b4b 100644 --- a/src/cmd/go/internal/modcmd/download.go +++ b/src/cmd/go/internal/modcmd/download.go @@ -84,6 +84,7 @@ func init() { // TODO(jayconrod): https://golang.org/issue/35849 Apply -x to other 'go mod' commands. cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "") + base.AddChdirFlag(&cmdDownload.Flag) base.AddModCommonFlags(&cmdDownload.Flag) } diff --git a/src/cmd/go/internal/modcmd/edit.go b/src/cmd/go/internal/modcmd/edit.go index edc1b19877..5fd13f2627 100644 --- a/src/cmd/go/internal/modcmd/edit.go +++ b/src/cmd/go/internal/modcmd/edit.go @@ -127,6 +127,8 @@ 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'. +Edit also provides the -C, -n, and -x build flags. + See https://golang.org/ref/mod#go-mod-edit for more about 'go mod edit'. `, } @@ -157,8 +159,9 @@ func init() { cmdEdit.Flag.Var(flagFunc(flagRetract), "retract", "") cmdEdit.Flag.Var(flagFunc(flagDropRetract), "dropretract", "") - base.AddModCommonFlags(&cmdEdit.Flag) base.AddBuildFlagsNX(&cmdEdit.Flag) + base.AddChdirFlag(&cmdEdit.Flag) + base.AddModCommonFlags(&cmdEdit.Flag) } func runEdit(ctx context.Context, cmd *base.Command, args []string) { diff --git a/src/cmd/go/internal/modcmd/graph.go b/src/cmd/go/internal/modcmd/graph.go index 9568c65740..feed6a0005 100644 --- a/src/cmd/go/internal/modcmd/graph.go +++ b/src/cmd/go/internal/modcmd/graph.go @@ -41,6 +41,7 @@ var ( func init() { cmdGraph.Flag.Var(&graphGo, "go", "") + base.AddChdirFlag(&cmdGraph.Flag) base.AddModCommonFlags(&cmdGraph.Flag) } diff --git a/src/cmd/go/internal/modcmd/init.go b/src/cmd/go/internal/modcmd/init.go index bc4620a2a8..e4be73fab0 100644 --- a/src/cmd/go/internal/modcmd/init.go +++ b/src/cmd/go/internal/modcmd/init.go @@ -34,6 +34,7 @@ See https://golang.org/ref/mod#go-mod-init for more about 'go mod init'. } func init() { + base.AddChdirFlag(&cmdInit.Flag) base.AddModCommonFlags(&cmdInit.Flag) } diff --git a/src/cmd/go/internal/modcmd/tidy.go b/src/cmd/go/internal/modcmd/tidy.go index d35476eb53..27889941c7 100644 --- a/src/cmd/go/internal/modcmd/tidy.go +++ b/src/cmd/go/internal/modcmd/tidy.go @@ -64,6 +64,7 @@ func init() { cmdTidy.Flag.BoolVar(&tidyE, "e", false, "") cmdTidy.Flag.Var(&tidyGo, "go", "") cmdTidy.Flag.Var(&tidyCompat, "compat", "") + base.AddChdirFlag(&cmdTidy.Flag) base.AddModCommonFlags(&cmdTidy.Flag) } diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go index a93c52dbb3..4f820eb13e 100644 --- a/src/cmd/go/internal/modcmd/vendor.go +++ b/src/cmd/go/internal/modcmd/vendor.go @@ -61,6 +61,7 @@ func init() { cmdVendor.Flag.BoolVar(&cfg.BuildV, "v", false, "") cmdVendor.Flag.BoolVar(&vendorE, "e", false, "") cmdVendor.Flag.StringVar(&vendorO, "o", "", "") + base.AddChdirFlag(&cmdVendor.Flag) base.AddModCommonFlags(&cmdVendor.Flag) } diff --git a/src/cmd/go/internal/modcmd/verify.go b/src/cmd/go/internal/modcmd/verify.go index 459bf5d070..a5f7f24563 100644 --- a/src/cmd/go/internal/modcmd/verify.go +++ b/src/cmd/go/internal/modcmd/verify.go @@ -38,6 +38,7 @@ See https://golang.org/ref/mod#go-mod-verify for more about 'go mod verify'. } func init() { + base.AddChdirFlag(&cmdVerify.Flag) base.AddModCommonFlags(&cmdVerify.Flag) } diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go index 8e929a0001..729c88f3f1 100644 --- a/src/cmd/go/internal/modcmd/why.go +++ b/src/cmd/go/internal/modcmd/why.go @@ -58,6 +58,7 @@ var ( func init() { cmdWhy.Run = runWhy // break init cycle + base.AddChdirFlag(&cmdWhy.Flag) base.AddModCommonFlags(&cmdWhy.Flag) } diff --git a/src/cmd/go/internal/tool/tool.go b/src/cmd/go/internal/tool/tool.go index afa3ac404f..069968b1b6 100644 --- a/src/cmd/go/internal/tool/tool.go +++ b/src/cmd/go/internal/tool/tool.go @@ -48,6 +48,7 @@ func isGccgoTool(tool string) bool { } func init() { + base.AddChdirFlag(&CmdTool.Flag) CmdTool.Flag.BoolVar(&toolN, "n", false, "") } diff --git a/src/cmd/go/internal/version/version.go b/src/cmd/go/internal/version/version.go index 6bbd48c6e6..a0f6123149 100644 --- a/src/cmd/go/internal/version/version.go +++ b/src/cmd/go/internal/version/version.go @@ -44,6 +44,7 @@ See also: go doc runtime/debug.BuildInfo. } func init() { + base.AddChdirFlag(&CmdVersion.Flag) CmdVersion.Run = runVersion // break init cycle } diff --git a/src/cmd/go/internal/vet/vet.go b/src/cmd/go/internal/vet/vet.go index e5f8af1c37..c73fa5b424 100644 --- a/src/cmd/go/internal/vet/vet.go +++ b/src/cmd/go/internal/vet/vet.go @@ -25,7 +25,7 @@ func init() { var CmdVet = &base.Command{ CustomFlags: true, - UsageLine: "go vet [-n] [-x] [-vettool prog] [build flags] [vet flags] [packages]", + UsageLine: "go vet [-C dir] [-n] [-x] [-vettool prog] [build flags] [vet flags] [packages]", Short: "report likely mistakes in packages", Long: ` Vet runs the Go vet command on the packages named by the import paths. @@ -35,6 +35,7 @@ For more about specifying packages, see 'go help packages'. For a list of checkers and their flags, see 'go tool vet help'. For details of a specific checker such as 'printf', see 'go tool vet help printf'. +The -C flag changes to dir before running the 'go vet' command. The -n flag prints commands that would be executed. The -x flag prints commands as they are executed. diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go index d8b7848071..553cd66ef3 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -58,6 +58,10 @@ will be written to that directory. The build flags are shared by the build, clean, get, install, list, run, and test commands: + -C dir + Change to dir before running the command. + Any files named on the command line are interpreted after + changing directories. -a force rebuilding of packages that are already up-to-date. -n @@ -282,6 +286,7 @@ const ( // install, list, run, and test commands. func AddBuildFlags(cmd *base.Command, mask BuildFlagMask) { base.AddBuildFlagsNX(&cmd.Flag) + base.AddChdirFlag(&cmd.Flag) cmd.Flag.BoolVar(&cfg.BuildA, "a", false, "") cmd.Flag.IntVar(&cfg.BuildP, "p", cfg.BuildP, "") if mask&OmitVFlag == 0 { diff --git a/src/cmd/go/internal/workcmd/edit.go b/src/cmd/go/internal/workcmd/edit.go index 8d1d38318b..f5e3304025 100644 --- a/src/cmd/go/internal/workcmd/edit.go +++ b/src/cmd/go/internal/workcmd/edit.go @@ -109,6 +109,7 @@ func init() { cmdEdit.Flag.Var(flagFunc(flagEditworkDropUse), "dropuse", "") cmdEdit.Flag.Var(flagFunc(flagEditworkReplace), "replace", "") cmdEdit.Flag.Var(flagFunc(flagEditworkDropReplace), "dropreplace", "") + base.AddChdirFlag(&cmdEdit.Flag) } func runEditwork(ctx context.Context, cmd *base.Command, args []string) { diff --git a/src/cmd/go/internal/workcmd/init.go b/src/cmd/go/internal/workcmd/init.go index c2513bac35..6fb033ee29 100644 --- a/src/cmd/go/internal/workcmd/init.go +++ b/src/cmd/go/internal/workcmd/init.go @@ -34,6 +34,7 @@ for more information. } func init() { + base.AddChdirFlag(&cmdInit.Flag) base.AddModCommonFlags(&cmdInit.Flag) } diff --git a/src/cmd/go/internal/workcmd/sync.go b/src/cmd/go/internal/workcmd/sync.go index 7712eb6b6b..9f9962709b 100644 --- a/src/cmd/go/internal/workcmd/sync.go +++ b/src/cmd/go/internal/workcmd/sync.go @@ -41,6 +41,7 @@ for more information. } func init() { + base.AddChdirFlag(&cmdSync.Flag) base.AddModCommonFlags(&cmdSync.Flag) } diff --git a/src/cmd/go/internal/workcmd/use.go b/src/cmd/go/internal/workcmd/use.go index fcb4e9e5f1..be90989ddd 100644 --- a/src/cmd/go/internal/workcmd/use.go +++ b/src/cmd/go/internal/workcmd/use.go @@ -43,6 +43,7 @@ var useR = cmdUse.Flag.Bool("r", false, "") func init() { cmdUse.Run = runUse // break init cycle + base.AddChdirFlag(&cmdUse.Flag) base.AddModCommonFlags(&cmdUse.Flag) } diff --git a/src/cmd/go/testdata/script/chdir.txt b/src/cmd/go/testdata/script/chdir.txt new file mode 100644 index 0000000000..8952d18a72 --- /dev/null +++ b/src/cmd/go/testdata/script/chdir.txt @@ -0,0 +1,31 @@ +env OLD=$PWD + +# basic -C functionality +cd $GOROOT/src/math +go list -C ../strings +stdout strings +! go list -C ../nonexist +stderr 'chdir.*nonexist' + +# check for -C in subcommands with custom flag parsing +# cmd/go/chdir_test.go handles the normal ones more directly. + +# go doc +go doc -C ../strings HasPrefix + +# go env +go env -C $OLD/custom GOMOD +stdout 'custom[\\/]go.mod' +! go env -C ../nonexist +stderr '^invalid value "../nonexist" for flag -C: chdir ../nonexist:.*$' + +# go test +go test -n -C ../strings +stderr 'strings\.test' + +# go vet +go vet -n -C ../strings +stderr strings_test + +-- custom/go.mod -- +module m