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