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	"os"
12	"sort"
13
14	"cmd/go/internal/base"
15	"cmd/go/internal/cfg"
16	"cmd/go/internal/modload"
17	"cmd/go/internal/par"
18	"cmd/go/internal/work"
19
20	"golang.org/x/mod/module"
21)
22
23var cmdGraph = &base.Command{
24	UsageLine: "go mod graph",
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	`,
32	Run: runGraph,
33}
34
35func init() {
36	work.AddModCommonFlags(cmdGraph)
37}
38
39func runGraph(cmd *base.Command, args []string) {
40	if len(args) > 0 {
41		base.Fatalf("go mod graph: graph takes no arguments")
42	}
43	// Checks go mod expected behavior
44	if !modload.Enabled() {
45		if cfg.Getenv("GO111MODULE") == "off" {
46			base.Fatalf("go: modules disabled by GO111MODULE=off; see 'go help modules'")
47		} else {
48			base.Fatalf("go: cannot find main module; see 'go help modules'")
49		}
50	}
51	modload.LoadBuildList()
52
53	reqs := modload.MinReqs()
54	format := func(m module.Version) string {
55		if m.Version == "" {
56			return m.Path
57		}
58		return m.Path + "@" + m.Version
59	}
60
61	// Note: using par.Work only to manage work queue.
62	// No parallelism here, so no locking.
63	var out []string
64	var deps int // index in out where deps start
65	var work par.Work
66	work.Add(modload.Target)
67	work.Do(1, func(item interface{}) {
68		m := item.(module.Version)
69		list, _ := reqs.Required(m)
70		for _, r := range list {
71			work.Add(r)
72			out = append(out, format(m)+" "+format(r)+"\n")
73		}
74		if m == modload.Target {
75			deps = len(out)
76		}
77	})
78
79	sort.Slice(out[deps:], func(i, j int) bool {
80		return out[deps+i][0] < out[deps+j][0]
81	})
82
83	w := bufio.NewWriter(os.Stdout)
84	for _, line := range out {
85		w.WriteString(line)
86	}
87	w.Flush()
88}
89