]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: add go mod why
authorRuss Cox <rsc@golang.org>
Tue, 7 Aug 2018 19:52:04 +0000 (15:52 -0400)
committerRuss Cox <rsc@golang.org>
Fri, 10 Aug 2018 00:47:13 +0000 (00:47 +0000)
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>
src/cmd/go/alldocs.go
src/cmd/go/internal/modcmd/mod.go
src/cmd/go/internal/modcmd/why.go [new file with mode: 0644]
src/cmd/go/internal/modload/load.go
src/cmd/go/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt
src/cmd/go/testdata/mod/golang.org_x_text_v0.3.0.txt
src/cmd/go/testdata/script/mod_why.txt [new file with mode: 0644]

index a4a66efcf50134f75057aae360cd27544958aff4..aea77175e89c03489e54e7279a4ace957fe10344 100644 (file)
 //
 // 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
 //     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:
 // 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:
index 0f78cc3b419e7bf338a1434eea26106a1b58486c..c1d0c13a108503b14dc60cabbf63d1508dd89533 100644 (file)
@@ -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 (file)
index 0000000..6923685
--- /dev/null
@@ -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"
+               }
+       }
+}
index d15832bdeac95d36897df2724152de8fe607a8d5..b42d0d2e50fccac6a08188409fcf61f770a4264f 100644 (file)
@@ -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 == "".
index e03b3ce081de333cb6d2584b2d9183cac4049574..f4f50cdedb616ebb822ce89a152f2c09f168fe0a 100644 (file)
@@ -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
index 6932642cd66f0c4b9f25937e9f708d44fc8423ef..5561afae8ed2fd52ee24afb4c3b8777cb62e0aba 100644 (file)
@@ -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 (file)
index 0000000..4d556fc
--- /dev/null
@@ -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