mirror of
https://github.com/golang/go
synced 2024-11-23 19:40:08 -07:00
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>
This commit is contained in:
parent
a4749604dc
commit
7aa9855704
@ -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:
|
||||
|
@ -27,5 +27,6 @@ See 'go help modules' for an overview of module functionality.
|
||||
cmdTidy,
|
||||
cmdVendor,
|
||||
cmdVerify,
|
||||
cmdWhy,
|
||||
},
|
||||
}
|
||||
|
119
src/cmd/go/internal/modcmd/why.go
Normal file
119
src/cmd/go/internal/modcmd/why.go
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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 == "".
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
114
src/cmd/go/testdata/script/mod_why.txt
vendored
Normal file
114
src/cmd/go/testdata/script/mod_why.txt
vendored
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user