aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2018-08-07 15:52:04 -0400
committerRuss Cox <rsc@golang.org>2018-08-10 00:47:13 +0000
commit7aa9855704a766a1a16fbed79f9f6e34fc83bdf0 (patch)
treee115e2fde769f9785dc2082001a0c396bbb81895
parenta4749604dc6cfea517ce2590336596a4ae570e78 (diff)
downloadgo-7aa9855704a766a1a16fbed79f9f6e34fc83bdf0.tar.gz
go-7aa9855704a766a1a16fbed79f9f6e34fc83bdf0.zip
cmd/go: add go mod why
A very common question is "why is this package or module being kept by go mod vendor or go mod tidy?" go mod why answers that question. Fixes #26620. Change-Id: Iac3b6bbdf703b4784f5eed8e0f69d41325bc6d7f Reviewed-on: https://go-review.googlesource.com/128359 Reviewed-by: Bryan C. Mills <bcmills@google.com>
-rw-r--r--src/cmd/go/alldocs.go58
-rw-r--r--src/cmd/go/internal/modcmd/mod.go1
-rw-r--r--src/cmd/go/internal/modcmd/why.go119
-rw-r--r--src/cmd/go/internal/modload/load.go45
-rw-r--r--src/cmd/go/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt2
-rw-r--r--src/cmd/go/testdata/mod/golang.org_x_text_v0.3.0.txt2
-rw-r--r--src/cmd/go/testdata/script/mod_why.txt114
7 files changed, 341 insertions, 0 deletions
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go
index a4a66efcf5..aea77175e8 100644
--- a/src/cmd/go/alldocs.go
+++ b/src/cmd/go/alldocs.go
@@ -871,6 +871,7 @@
//
// The commands are:
//
+// download download modules to local cache
// edit edit go.mod from tools or scripts
// fix make go.mod semantically consistent
// graph print module requirement graph
@@ -878,9 +879,42 @@
// tidy add missing and remove unused modules
// vendor make vendored copy of dependencies
// verify verify dependencies have expected content
+// why explain why packages or modules are needed
//
// Use "go help mod <command>" for more information about a command.
//
+// Download modules to local cache
+//
+// Usage:
+//
+// go mod download [-dir] [-json] [modules]
+//
+// Download downloads the named modules, which can be module patterns selecting
+// dependencies of the main module or module queries of the form path@version.
+// With no arguments, download applies to all dependencies of the main module.
+//
+// The go command will automatically download modules as needed during ordinary
+// execution. The "go mod download" command is useful mainly for pre-filling
+// the local cache or to compute the answers for a Go module proxy.
+//
+// By default, download reports errors to standard error but is otherwise silent.
+// The -json flag causes download to print a sequence of JSON objects
+// to standard output, describing each downloaded module (or failure),
+// corresponding to this Go struct:
+//
+// type Module struct {
+// Path string // module path
+// Version string // module version
+// Error string // error loading module
+// Info string // absolute path to cached .info file
+// GoMod string // absolute path to cached .mod file
+// Zip string // absolute path to cached .zip file
+// Dir string // absolute path to cached source root directory
+// }
+//
+// See 'go help module' for more about module queries.
+//
+//
// Edit go.mod from tools or scripts
//
// Usage:
@@ -1079,6 +1113,30 @@
// non-zero status.
//
//
+// Explain why packages or modules are needed
+//
+// Usage:
+//
+// go mod why [-m] [-vendor] packages...
+//
+// Why shows a shortest path in the import graph from the main module to
+// each of the listed packages. If the -m flag is given, why treats the
+// arguments as a list of modules and finds a path to any package in each
+// of the modules.
+//
+// By default, why queries the graph of packages matched by "go list all",
+// which includes tests for reachable packages. The -vendor flag causes why
+// to exclude tests of dependencies.
+//
+// The output is a sequence of stanzas, one for each package or module
+// name on the command line, separated by blank lines. Each stanza begins
+// with a comment line "# package" or "# module" giving the target
+// package or module. Subsequent lines give a path through the import
+// graph, one package per line. If the package or module is not
+// referenced from the main module the stanza will be empty except for
+// the comment line.
+//
+//
// Compile and run Go program
//
// Usage:
diff --git a/src/cmd/go/internal/modcmd/mod.go b/src/cmd/go/internal/modcmd/mod.go
index 0f78cc3b41..c1d0c13a10 100644
--- a/src/cmd/go/internal/modcmd/mod.go
+++ b/src/cmd/go/internal/modcmd/mod.go
@@ -27,5 +27,6 @@ See 'go help modules' for an overview of module functionality.
cmdTidy,
cmdVendor,
cmdVerify,
+ cmdWhy,
},
}
diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go
new file mode 100644
index 0000000000..6923685599
--- /dev/null
+++ b/src/cmd/go/internal/modcmd/why.go
@@ -0,0 +1,119 @@
+// 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.
+
+package modcmd
+
+import (
+ "cmd/go/internal/base"
+ "cmd/go/internal/modload"
+ "cmd/go/internal/module"
+ "fmt"
+ "strings"
+)
+
+var cmdWhy = &base.Command{
+ UsageLine: "go mod why [-m] [-vendor] packages...",
+ Short: "explain why packages or modules are needed",
+ Long: `
+Why shows a shortest path in the import graph from the main module to
+each of the listed packages. If the -m flag is given, why treats the
+arguments as a list of modules and finds a path to any package in each
+of the modules.
+
+By default, why queries the graph of packages matched by "go list all",
+which includes tests for reachable packages. The -vendor flag causes why
+to exclude tests of dependencies.
+
+The output is a sequence of stanzas, one for each package or module
+name on the command line, separated by blank lines. Each stanza begins
+with a comment line "# package" or "# module" giving the target
+package or module. Subsequent lines give a path through the import
+graph, one package per line. If the package or module is not
+referenced from the main module, the stanza will display a single
+parenthesized note indicating that fact.
+
+For example:
+
+ $ go mod why golang.org/x/text/language golang.org/x/text/encoding
+ # golang.org/x/text/language
+ rsc.io/quote
+ rsc.io/sampler
+ golang.org/x/text/language
+
+ # golang.org/x/text/encoding
+ (main module does not need package golang.org/x/text/encoding)
+ $
+ `,
+}
+
+var (
+ whyM = cmdWhy.Flag.Bool("m", false, "")
+ whyVendor = cmdWhy.Flag.Bool("vendor", false, "")
+)
+
+func init() {
+ cmdWhy.Run = runWhy // break init cycle
+}
+
+func runWhy(cmd *base.Command, args []string) {
+ loadALL := modload.LoadALL
+ if *whyVendor {
+ loadALL = modload.LoadVendor
+ }
+ if *whyM {
+ listU := false
+ listVersions := false
+ for _, arg := range args {
+ if strings.Contains(arg, "@") {
+ base.Fatalf("go mod why: module query not allowed")
+ }
+ }
+ mods := modload.ListModules(args, listU, listVersions)
+ byModule := make(map[module.Version][]string)
+ for _, path := range loadALL() {
+ m := modload.PackageModule(path)
+ if m.Path != "" {
+ byModule[m] = append(byModule[m], path)
+ }
+ }
+ sep := ""
+ for _, m := range mods {
+ best := ""
+ bestDepth := 1000000000
+ for _, path := range byModule[module.Version{Path: m.Path, Version: m.Version}] {
+ d := modload.WhyDepth(path)
+ if d > 0 && d < bestDepth {
+ best = path
+ bestDepth = d
+ }
+ }
+ why := modload.Why(best)
+ if why == "" {
+ vendoring := ""
+ if *whyVendor {
+ vendoring = " to vendor"
+ }
+ why = "(main module does not need" + vendoring + " module " + m.Path + ")\n"
+ }
+ fmt.Printf("%s# %s\n%s", sep, m.Path, why)
+ sep = "\n"
+ }
+ } else {
+ pkgs := modload.ImportPaths(args) // resolve to packages
+ loadALL() // rebuild graph, from main module (not from named packages)
+ sep := ""
+ for _, path := range pkgs {
+ why := modload.Why(path)
+ if why == "" {
+ vendoring := ""
+ if *whyVendor {
+ vendoring = " to vendor"
+ }
+ why = "(main module does not need" + vendoring + " package " + path + ")\n"
+ }
+ fmt.Printf("%s# %s\n%s", sep, path, why)
+ sep = "\n"
+ }
+ }
+}
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index d15832bdea..b42d0d2e50 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -671,6 +671,51 @@ func (pkg *loadPkg) stackText() string {
return buf.String()
}
+// why returns the text to use in "go mod why" output about the given package.
+// It is less ornate than the stackText but conatins the same information.
+func (pkg *loadPkg) why() string {
+ var buf strings.Builder
+ var stack []*loadPkg
+ for p := pkg; p != nil; p = p.stack {
+ stack = append(stack, p)
+ }
+
+ for i := len(stack) - 1; i >= 0; i-- {
+ p := stack[i]
+ if p.testOf != nil {
+ fmt.Fprintf(&buf, "%s.test\n", p.testOf.path)
+ } else {
+ fmt.Fprintf(&buf, "%s\n", p.path)
+ }
+ }
+ return buf.String()
+}
+
+// Why returns the "go mod why" output stanza for the given package,
+// without the leading # comment.
+// The package graph must have been loaded already, usually by LoadALL.
+// If there is no reason for the package to be in the current build,
+// Why returns an empty string.
+func Why(path string) string {
+ pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+ if !ok {
+ return ""
+ }
+ return pkg.why()
+}
+
+// WhyDepth returns the number of steps in the Why listing.
+// If there is no reason for the package to be in the current build,
+// WhyDepth returns 0.
+func WhyDepth(path string) int {
+ n := 0
+ pkg, _ := loaded.pkgCache.Get(path).(*loadPkg)
+ for p := pkg; p != nil; p = p.stack {
+ n++
+ }
+ return n
+}
+
// Replacement returns the replacement for mod, if any, from go.mod.
// If there is no replacement for mod, Replacement returns
// a module.Version with Path == "".
diff --git a/src/cmd/go/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt b/src/cmd/go/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt
index e03b3ce081..f4f50cdedb 100644
--- a/src/cmd/go/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt
+++ b/src/cmd/go/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt
@@ -6,6 +6,8 @@ module golang.org/x/text
{"Version":"v0.0.0-20170915032832-14c0d48ead0c","Name":"v0.0.0-20170915032832-14c0d48ead0c","Short":"14c0d48ead0c","Time":"2017-09-15T03:28:32Z"}
-- go.mod --
module golang.org/x/text
+-- unused/unused.go --
+package unused
-- language/lang.go --
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
diff --git a/src/cmd/go/testdata/mod/golang.org_x_text_v0.3.0.txt b/src/cmd/go/testdata/mod/golang.org_x_text_v0.3.0.txt
index 6932642cd6..5561afae8e 100644
--- a/src/cmd/go/testdata/mod/golang.org_x_text_v0.3.0.txt
+++ b/src/cmd/go/testdata/mod/golang.org_x_text_v0.3.0.txt
@@ -6,6 +6,8 @@ module golang.org/x/text
{"Version":"v0.3.0","Name":"","Short":"","Time":"2017-09-16T03:28:32Z"}
-- go.mod --
module golang.org/x/text
+-- unused/unused.go --
+package unused
-- language/lang.go --
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
diff --git a/src/cmd/go/testdata/script/mod_why.txt b/src/cmd/go/testdata/script/mod_why.txt
new file mode 100644
index 0000000000..4d556fc73f
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_why.txt
@@ -0,0 +1,114 @@
+env GO111MODULE=on
+
+go list -test all
+stdout rsc.io/quote
+stdout golang.org/x/text/language
+
+# why a package?
+go mod why golang.org/x/text/language
+cmp stdout why-language.txt
+
+# why a module?
+go mod why -m golang.org...
+cmp stdout why-text-module.txt
+
+# why a package used only in tests?
+go mod why rsc.io/testonly
+cmp stdout why-testonly.txt
+
+# why a module used only in tests?
+go mod why -m rsc.io/testonly
+cmp stdout why-testonly.txt
+
+# test package not needed
+go mod why golang.org/x/text/unused
+cmp stdout why-unused.txt
+
+# vendor doesn't use packages used only in tests.
+go mod why -vendor rsc.io/testonly
+cmp stdout why-vendor.txt
+
+# vendor doesn't use modules used only in tests.
+go mod why -vendor -m rsc.io/testonly
+cmp stdout why-vendor-module.txt
+
+# test multiple packages
+go mod why golang.org/x/text/language golang.org/x/text/unused
+cmp stdout why-both.txt
+
+# test multiple modules
+go mod why -m rsc.io/quote rsc.io/sampler
+cmp stdout why-both-module.txt
+
+-- go.mod --
+module mymodule
+require rsc.io/quote v1.5.2
+
+-- x/x.go --
+package x
+import _ "mymodule/z"
+
+-- y/y.go --
+package y
+
+-- y/y_test.go --
+package y
+import _ "rsc.io/quote"
+
+-- z/z.go --
+package z
+import _ "mymodule/y"
+
+
+-- why-language.txt --
+# golang.org/x/text/language
+mymodule/y
+mymodule/y.test
+rsc.io/quote
+rsc.io/sampler
+golang.org/x/text/language
+-- why-unused.txt --
+# golang.org/x/text/unused
+(main module does not need package golang.org/x/text/unused)
+-- why-text-module.txt --
+# golang.org/x/text
+mymodule/y
+mymodule/y.test
+rsc.io/quote
+rsc.io/sampler
+golang.org/x/text/language
+-- why-testonly.txt --
+# rsc.io/testonly
+mymodule/y
+mymodule/y.test
+rsc.io/quote
+rsc.io/sampler
+rsc.io/sampler.test
+rsc.io/testonly
+-- why-vendor.txt --
+# rsc.io/testonly
+(main module does not need to vendor package rsc.io/testonly)
+-- why-vendor-module.txt --
+# rsc.io/testonly
+(main module does not need to vendor module rsc.io/testonly)
+-- why-both.txt --
+# golang.org/x/text/language
+mymodule/y
+mymodule/y.test
+rsc.io/quote
+rsc.io/sampler
+golang.org/x/text/language
+
+# golang.org/x/text/unused
+(main module does not need package golang.org/x/text/unused)
+-- why-both-module.txt --
+# rsc.io/quote
+mymodule/y
+mymodule/y.test
+rsc.io/quote
+
+# rsc.io/sampler
+mymodule/y
+mymodule/y.test
+rsc.io/quote
+rsc.io/sampler