1
2
3
4
5 package modcmd
6
7 import (
8 "context"
9 "fmt"
10 "strings"
11
12 "cmd/go/internal/base"
13 "cmd/go/internal/imports"
14 "cmd/go/internal/modload"
15 )
16
17 var cmdWhy = &base.Command{
18 UsageLine: "go mod why [-m] [-vendor] packages...",
19 Short: "explain why packages or modules are needed",
20 Long: `
21 Why shows a shortest path in the import graph from the main module to
22 each of the listed packages. If the -m flag is given, why treats the
23 arguments as a list of modules and finds a path to any package in each
24 of the modules.
25
26 By default, why queries the graph of packages matched by "go list all",
27 which includes tests for reachable packages. The -vendor flag causes why
28 to exclude tests of dependencies.
29
30 The output is a sequence of stanzas, one for each package or module
31 name on the command line, separated by blank lines. Each stanza begins
32 with a comment line "# package" or "# module" giving the target
33 package or module. Subsequent lines give a path through the import
34 graph, one package per line. If the package or module is not
35 referenced from the main module, the stanza will display a single
36 parenthesized note indicating that fact.
37
38 For example:
39
40 $ go mod why golang.org/x/text/language golang.org/x/text/encoding
41 # golang.org/x/text/language
42 rsc.io/quote
43 rsc.io/sampler
44 golang.org/x/text/language
45
46 # golang.org/x/text/encoding
47 (main module does not need package golang.org/x/text/encoding)
48 $
49
50 See https://golang.org/ref/mod#go-mod-why for more about 'go mod why'.
51 `,
52 }
53
54 var (
55 whyM = cmdWhy.Flag.Bool("m", false, "")
56 whyVendor = cmdWhy.Flag.Bool("vendor", false, "")
57 )
58
59 func init() {
60 cmdWhy.Run = runWhy
61 base.AddChdirFlag(&cmdWhy.Flag)
62 base.AddModCommonFlags(&cmdWhy.Flag)
63 }
64
65 func runWhy(ctx context.Context, cmd *base.Command, args []string) {
66 moduleLoaderState := modload.NewState()
67 moduleLoaderState.InitWorkfile()
68 moduleLoaderState.ForceUseModules = true
69 moduleLoaderState.RootMode = modload.NeedRoot
70 modload.ExplicitWriteGoMod = true
71
72 loadOpts := modload.PackageOpts{
73 Tags: imports.AnyTags(),
74 VendorModulesInGOROOTSrc: true,
75 LoadTests: !*whyVendor,
76 SilencePackageErrors: true,
77 UseVendorAll: *whyVendor,
78 }
79
80 if *whyM {
81 for _, arg := range args {
82 if strings.Contains(arg, "@") {
83 base.Fatalf("go: %s: 'go mod why' requires a module path, not a version query", arg)
84 }
85 }
86
87 mods, err := modload.ListModules(moduleLoaderState, ctx, args, 0, "")
88 if err != nil {
89 base.Fatal(err)
90 }
91
92 byModule := make(map[string][]string)
93 _, pkgs := modload.LoadPackages(moduleLoaderState, ctx, loadOpts, "all")
94 for _, path := range pkgs {
95 m := modload.PackageModule(path)
96 if m.Path != "" {
97 byModule[m.Path] = append(byModule[m.Path], path)
98 }
99 }
100 sep := ""
101 for _, m := range mods {
102 best := ""
103 bestDepth := 1000000000
104 for _, path := range byModule[m.Path] {
105 d := modload.WhyDepth(path)
106 if d > 0 && d < bestDepth {
107 best = path
108 bestDepth = d
109 }
110 }
111 why := modload.Why(best)
112 if why == "" {
113 vendoring := ""
114 if *whyVendor {
115 vendoring = " to vendor"
116 }
117 why = "(main module does not need" + vendoring + " module " + m.Path + ")\n"
118 }
119 fmt.Printf("%s# %s\n%s", sep, m.Path, why)
120 sep = "\n"
121 }
122 } else {
123
124 matches, _ := modload.LoadPackages(moduleLoaderState, ctx, loadOpts, args...)
125
126 modload.LoadPackages(moduleLoaderState, ctx, loadOpts, "all")
127
128 sep := ""
129 for _, m := range matches {
130 for _, path := range m.Pkgs {
131 why := modload.Why(path)
132 if why == "" {
133 vendoring := ""
134 if *whyVendor {
135 vendoring = " to vendor"
136 }
137 why = "(main module does not need" + vendoring + " package " + path + ")\n"
138 }
139 fmt.Printf("%s# %s\n%s", sep, path, why)
140 sep = "\n"
141 }
142 }
143 }
144 }
145
View as plain text