aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/go/internal/modcmd/why.go
blob: 3b14b27c8c780d954a133cd46dc4bee2f875e442 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// 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 (
	"context"
	"fmt"
	"strings"

	"cmd/go/internal/base"
	"cmd/go/internal/imports"
	"cmd/go/internal/modload"

	"golang.org/x/mod/module"
)

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)
	$

See https://golang.org/ref/mod#go-mod-why for more about 'go mod why'.
	`,
}

var (
	whyM      = cmdWhy.Flag.Bool("m", false, "")
	whyVendor = cmdWhy.Flag.Bool("vendor", false, "")
)

func init() {
	cmdWhy.Run = runWhy // break init cycle
	base.AddModCommonFlags(&cmdWhy.Flag)
}

func runWhy(ctx context.Context, cmd *base.Command, args []string) {
	modload.ForceUseModules = true
	modload.RootMode = modload.NeedRoot

	loadOpts := modload.PackageOpts{
		Tags:                     imports.AnyTags(),
		VendorModulesInGOROOTSrc: true,
		LoadTests:                !*whyVendor,
		SilencePackageErrors:     true,
		UseVendorAll:             *whyVendor,
	}

	if *whyM {
		for _, arg := range args {
			if strings.Contains(arg, "@") {
				base.Fatalf("go mod why: module query not allowed")
			}
		}

		mods, err := modload.ListModules(ctx, args, 0)
		if err != nil {
			base.Fatalf("go mod why: %v", err)
		}

		byModule := make(map[module.Version][]string)
		_, pkgs := modload.LoadPackages(ctx, loadOpts, "all")
		for _, path := range pkgs {
			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 {
		// Resolve to packages.
		matches, _ := modload.LoadPackages(ctx, loadOpts, args...)

		modload.LoadPackages(ctx, loadOpts, "all") // rebuild graph, from main module (not from named packages)

		sep := ""
		for _, m := range matches {
			for _, path := range m.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"
			}
		}
	}
}