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 "golang.org/x/mod/module"
17 )
18
19 var cmdWhy = &base.Command{
20 UsageLine: "go mod why [-m] [-vendor] packages...",
21 Short: "explain why packages or modules are needed",
22 Long: `
23 Why shows a shortest path in the import graph from the main module to
24 each of the listed packages. If the -m flag is given, why treats the
25 arguments as a list of modules and finds a path to any package in each
26 of the modules.
27
28 By default, why queries the graph of packages matched by "go list all",
29 which includes tests for reachable packages. The -vendor flag causes why
30 to exclude tests of dependencies.
31
32 The output is a sequence of stanzas, one for each package or module
33 name on the command line, separated by blank lines. Each stanza begins
34 with a comment line "# package" or "# module" giving the target
35 package or module. Subsequent lines give a path through the import
36 graph, one package per line. If the package or module is not
37 referenced from the main module, the stanza will display a single
38 parenthesized note indicating that fact.
39
40 For example:
41
42 $ go mod why golang.org/x/text/language golang.org/x/text/encoding
43 # golang.org/x/text/language
44 rsc.io/quote
45 rsc.io/sampler
46 golang.org/x/text/language
47
48 # golang.org/x/text/encoding
49 (main module does not need package golang.org/x/text/encoding)
50 $
51
52 See https://golang.org/ref/mod#go-mod-why for more about 'go mod why'.
53 `,
54 }
55
56 var (
57 whyM = cmdWhy.Flag.Bool("m", false, "")
58 whyVendor = cmdWhy.Flag.Bool("vendor", false, "")
59 )
60
61 func init() {
62 cmdWhy.Run = runWhy
63 base.AddModCommonFlags(&cmdWhy.Flag)
64 }
65
66 func runWhy(ctx context.Context, cmd *base.Command, args []string) {
67 modload.ForceUseModules = true
68 modload.RootMode = modload.NeedRoot
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 mod why: module query not allowed")
82 }
83 }
84
85 mods, err := modload.ListModules(ctx, args, 0)
86 if err != nil {
87 base.Fatalf("go mod why: %v", err)
88 }
89
90 byModule := make(map[module.Version][]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] = append(byModule[m], path)
96 }
97 }
98 sep := ""
99 for _, m := range mods {
100 best := ""
101 bestDepth := 1000000000
102 for _, path := range byModule[module.Version{Path: m.Path, Version: m.Version}] {
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