1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// go mod graph
6
7package modcmd
8
9import (
10	"bufio"
11	"context"
12	"os"
13
14	"cmd/go/internal/base"
15	"cmd/go/internal/cfg"
16	"cmd/go/internal/gover"
17	"cmd/go/internal/modload"
18	"cmd/go/internal/toolchain"
19
20	"golang.org/x/mod/module"
21)
22
23var cmdGraph = &base.Command{
24	UsageLine: "go mod graph [-go=version] [-x]",
25	Short:     "print module requirement graph",
26	Long: `
27Graph prints the module requirement graph (with replacements applied)
28in text form. Each line in the output has two space-separated fields: a module
29and one of its requirements. Each module is identified as a string of the form
30path@version, except for the main module, which has no @version suffix.
31
32The -go flag causes graph to report the module graph as loaded by the
33given Go version, instead of the version indicated by the 'go' directive
34in the go.mod file.
35
36The -x flag causes graph to print the commands graph executes.
37
38See https://golang.org/ref/mod#go-mod-graph for more about 'go mod graph'.
39	`,
40	Run: runGraph,
41}
42
43var (
44	graphGo goVersionFlag
45)
46
47func init() {
48	cmdGraph.Flag.Var(&graphGo, "go", "")
49	cmdGraph.Flag.BoolVar(&cfg.BuildX, "x", false, "")
50	base.AddChdirFlag(&cmdGraph.Flag)
51	base.AddModCommonFlags(&cmdGraph.Flag)
52}
53
54func runGraph(ctx context.Context, cmd *base.Command, args []string) {
55	modload.InitWorkfile()
56
57	if len(args) > 0 {
58		base.Fatalf("go: 'go mod graph' accepts no arguments")
59	}
60	modload.ForceUseModules = true
61	modload.RootMode = modload.NeedRoot
62
63	goVersion := graphGo.String()
64	if goVersion != "" && gover.Compare(gover.Local(), goVersion) < 0 {
65		toolchain.SwitchOrFatal(ctx, &gover.TooNewError{
66			What:      "-go flag",
67			GoVersion: goVersion,
68		})
69	}
70
71	mg, err := modload.LoadModGraph(ctx, goVersion)
72	if err != nil {
73		base.Fatal(err)
74	}
75
76	w := bufio.NewWriter(os.Stdout)
77	defer w.Flush()
78
79	format := func(m module.Version) {
80		w.WriteString(m.Path)
81		if m.Version != "" {
82			w.WriteString("@")
83			w.WriteString(m.Version)
84		}
85	}
86
87	mg.WalkBreadthFirst(func(m module.Version) {
88		reqs, _ := mg.RequiredBy(m)
89		for _, r := range reqs {
90			format(m)
91			w.WriteByte(' ')
92			format(r)
93			w.WriteByte('\n')
94		}
95	})
96}
97