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.AddModCommonFlags(&cmdWhy.Flag)
62 }
63
64 func runWhy(ctx context.Context, cmd *base.Command, args []string) {
65 modload.InitWorkfile()
66 modload.ForceUseModules = true
67 modload.RootMode = modload.NeedRoot
68 modload.ExplicitWriteGoMod = true
69
70 loadOpts := modload.PackageOpts{
71 Tags: imports.AnyTags(),
72 VendorModulesInGOROOTSrc: true,
73 LoadTests: !*whyVendor,
74 SilencePackageErrors: true,
75 UseVendorAll: *whyVendor,
76 }
77
78 if *whyM {
79 for _, arg := range args {
80 if strings.Contains(arg, "@") {
81 base.Fatalf("go: %s: 'go mod why' requires a module path, not a version query", arg)
82 }
83 }
84
85 mods, err := modload.ListModules(ctx, args, 0)
86 if err != nil {
87 base.Fatalf("go: %v", err)
88 }
89
90 byModule := make(map[string][]string)
91 _, pkgs := modload.LoadPackages(ctx, loadOpts, "all")
92 for _, path := range pkgs {
93 m := modload.PackageModule(path)
94 if m.Path != "" {
95 byModule[m.Path] = append(byModule[m.Path], path)
96 }
97 }
98 sep := ""
99 for _, m := range mods {
100 best := ""
101 bestDepth := 1000000000
102 for _, path := range byModule[m.Path] {
103 d := modload.WhyDepth(path)
104 if d > 0 && d < bestDepth {
105 best = path
106 bestDepth = d
107 }
108 }
109 why := modload.Why(best)
110 if why == "" {
111 vendoring := ""
112 if *whyVendor {
113 vendoring = " to vendor"
114 }
115 why = "(main module does not need" + vendoring + " module " + m.Path + ")\n"
116 }
117 fmt.Printf("%s# %s\n%s", sep, m.Path, why)
118 sep = "\n"
119 }
120 } else {
121
122 matches, _ := modload.LoadPackages(ctx, loadOpts, args...)
123
124 modload.LoadPackages(ctx, loadOpts, "all")
125
126 sep := ""
127 for _, m := range matches {
128 for _, path := range m.Pkgs {
129 why := modload.Why(path)
130 if why == "" {
131 vendoring := ""
132 if *whyVendor {
133 vendoring = " to vendor"
134 }
135 why = "(main module does not need" + vendoring + " package " + path + ")\n"
136 }
137 fmt.Printf("%s# %s\n%s", sep, path, why)
138 sep = "\n"
139 }
140 }
141 }
142 }
143
View as plain text