Skip to content

Commit 1bd5a20

Browse files
author
Bryan C. Mills
committed
cmd/go: add a -go flag to 'go mod graph'
For #46366 Change-Id: I8417e6e4dbb8cb56ff7afc16893a01b7bb938217 Reviewed-on: https://go-review.googlesource.com/c/go/+/329529 Trust: Bryan C. Mills <bcmills@google.com> Run-TryBot: Bryan C. Mills <bcmills@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Jay Conrod <jayconrod@google.com>
1 parent 761edf7 commit 1bd5a20

File tree

7 files changed

+154
-8
lines changed

7 files changed

+154
-8
lines changed

doc/go1.17.html

+7
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,13 @@ <h4 id="lazy-loading">Lazy module loading</h4>
187187
features.
188188
</p>
189189

190+
<p><!-- golang.org/issue/46366 -->
191+
The <code>go</code> <code>mod</code> <code>graph</code> subcommand also
192+
supports the <code>-go</code> flag, which causes it to report the graph as
193+
seen by the indicated Go version, showing dependencies that may otherwise be
194+
pruned out by lazy loading.
195+
</p>
196+
190197
<h4 id="module-deprecation-comments">Module deprecation comments</h4>
191198

192199
<p><!-- golang.org/issue/40357 -->

src/cmd/go/alldocs.go

+5-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/go/internal/modcmd/graph.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,29 @@ import (
1818
)
1919

2020
var cmdGraph = &base.Command{
21-
UsageLine: "go mod graph",
21+
UsageLine: "go mod graph [-go=version]",
2222
Short: "print module requirement graph",
2323
Long: `
2424
Graph prints the module requirement graph (with replacements applied)
2525
in text form. Each line in the output has two space-separated fields: a module
2626
and one of its requirements. Each module is identified as a string of the form
2727
path@version, except for the main module, which has no @version suffix.
2828
29+
The -go flag causes graph to report the module graph as loaded by by the
30+
given Go version, instead of the version indicated by the 'go' directive
31+
in the go.mod file.
32+
2933
See https://golang.org/ref/mod#go-mod-graph for more about 'go mod graph'.
3034
`,
3135
Run: runGraph,
3236
}
3337

38+
var (
39+
graphGo goVersionFlag
40+
)
41+
3442
func init() {
43+
cmdGraph.Flag.Var(&graphGo, "go", "")
3544
base.AddModCommonFlags(&cmdGraph.Flag)
3645
}
3746

@@ -41,7 +50,7 @@ func runGraph(ctx context.Context, cmd *base.Command, args []string) {
4150
}
4251
modload.ForceUseModules = true
4352
modload.RootMode = modload.NeedRoot
44-
mg := modload.LoadModGraph(ctx)
53+
mg := modload.LoadModGraph(ctx, graphGo.String())
4554

4655
w := bufio.NewWriter(os.Stdout)
4756
defer w.Flush()

src/cmd/go/internal/modcmd/verify.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ func runVerify(ctx context.Context, cmd *base.Command, args []string) {
5454
sem := make(chan token, runtime.GOMAXPROCS(0))
5555

5656
// Use a slice of result channels, so that the output is deterministic.
57-
mods := modload.LoadModGraph(ctx).BuildList()[1:]
57+
const defaultGoVersion = ""
58+
mods := modload.LoadModGraph(ctx, defaultGoVersion).BuildList()[1:]
5859
errsChans := make([]<-chan []error, len(mods))
5960

6061
for i, mod := range mods {

src/cmd/go/internal/modget/get.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,8 @@ type versionReason struct {
506506
func newResolver(ctx context.Context, queries []*query) *resolver {
507507
// LoadModGraph also sets modload.Target, which is needed by various resolver
508508
// methods.
509-
mg := modload.LoadModGraph(ctx)
509+
const defaultGoVersion = ""
510+
mg := modload.LoadModGraph(ctx, defaultGoVersion)
510511

511512
buildList := mg.BuildList()
512513
initialVersion := make(map[string]string, len(buildList))
@@ -1803,7 +1804,8 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi
18031804
return false
18041805
}
18051806

1806-
r.buildList = modload.LoadModGraph(ctx).BuildList()
1807+
const defaultGoVersion = ""
1808+
r.buildList = modload.LoadModGraph(ctx, defaultGoVersion).BuildList()
18071809
r.buildListVersion = make(map[string]string, len(r.buildList))
18081810
for _, m := range r.buildList {
18091811
r.buildListVersion[m.Path] = m.Version

src/cmd/go/internal/modload/buildlist.go

+24-2
Original file line numberDiff line numberDiff line change
@@ -403,11 +403,33 @@ func (mg *ModuleGraph) allRootsSelected() bool {
403403
// LoadModGraph loads and returns the graph of module dependencies of the main module,
404404
// without loading any packages.
405405
//
406+
// If the goVersion string is non-empty, the returned graph is the graph
407+
// as interpreted by the given Go version (instead of the version indicated
408+
// in the go.mod file).
409+
//
406410
// Modules are loaded automatically (and lazily) in LoadPackages:
407411
// LoadModGraph need only be called if LoadPackages is not,
408412
// typically in commands that care about modules but no particular package.
409-
func LoadModGraph(ctx context.Context) *ModuleGraph {
410-
rs, mg, err := expandGraph(ctx, LoadModFile(ctx))
413+
func LoadModGraph(ctx context.Context, goVersion string) *ModuleGraph {
414+
rs := LoadModFile(ctx)
415+
416+
if goVersion != "" {
417+
depth := modDepthFromGoVersion(goVersion)
418+
if depth == eager && rs.depth != eager {
419+
// Use newRequirements instead of convertDepth because convertDepth
420+
// also updates roots; here, we want to report the unmodified roots
421+
// even though they may seem inconsistent.
422+
rs = newRequirements(eager, rs.rootModules, rs.direct)
423+
}
424+
425+
mg, err := rs.Graph(ctx)
426+
if err != nil {
427+
base.Fatalf("go: %v", err)
428+
}
429+
return mg
430+
}
431+
432+
rs, mg, err := expandGraph(ctx, rs)
411433
if err != nil {
412434
base.Fatalf("go: %v", err)
413435
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# For this module, Go 1.17 prunes out a (transitive and otherwise-irrelevant)
2+
# requirement on a retracted higher version of a dependency.
3+
# However, when Go 1.16 reads the same requirements from the go.mod file,
4+
# it does not prune out that requirement, and selects the retracted version.
5+
#
6+
# The Go 1.16 module graph looks like:
7+
#
8+
# m ---- lazy v0.1.0 ---- requireincompatible v0.1.0 ---- incompatible v2.0.0+incompatible
9+
# | |
10+
# + -------+------------- incompatible v1.0.0
11+
#
12+
# The Go 1.17 module graph is the same except that the dependencies of
13+
# requireincompatible are pruned out (because the module that requires
14+
# it — lazy v0.1.0 — specifies 'go 1.17', and it is not otherwise relevant to
15+
# the main module).
16+
17+
cp go.mod go.mod.orig
18+
19+
go mod graph
20+
cp stdout graph-1.17.txt
21+
stdout '^example\.com/m example\.com/retract/incompatible@v1\.0\.0$'
22+
stdout '^example\.net/lazy@v0\.1\.0 example\.com/retract/incompatible@v1\.0\.0$'
23+
! stdout 'example\.com/retract/incompatible@v2\.0\.0\+incompatible'
24+
25+
go mod graph -go=1.17
26+
cmp stdout graph-1.17.txt
27+
28+
cmp go.mod go.mod.orig
29+
30+
31+
# Setting -go=1.16 should report the graph as viewed by Go 1.16,
32+
# but should not edit the go.mod file.
33+
34+
go mod graph -go=1.16
35+
cp stdout graph-1.16.txt
36+
stdout '^example\.com/m example\.com/retract/incompatible@v1\.0\.0$'
37+
stdout '^example\.net/lazy@v0\.1\.0 example.com/retract/incompatible@v1\.0\.0$'
38+
stdout '^example.net/requireincompatible@v0.1.0 example.com/retract/incompatible@v2\.0\.0\+incompatible$'
39+
40+
cmp go.mod go.mod.orig
41+
42+
43+
# If we actually update the go.mod file to the requested go version,
44+
# we should get the same selected versions, but the roots of the graph
45+
# may be updated.
46+
#
47+
# TODO(#45551): The roots should not be updated.
48+
49+
go mod edit -go=1.16
50+
go mod graph
51+
! stdout '^example\.com/m example\.com/retract/incompatible@v1\.0\.0$'
52+
stdout '^example\.net/lazy@v0.1.0 example.com/retract/incompatible@v1\.0\.0$'
53+
stdout '^example.net/requireincompatible@v0.1.0 example.com/retract/incompatible@v2\.0\.0\+incompatible$'
54+
# TODO(#45551): cmp stdout graph-1.16.txt
55+
56+
57+
# Unsupported go versions should be rejected, since we don't know
58+
# what versions they would report.
59+
! go mod graph -go=1.99999999999
60+
stderr '^invalid value "1\.99999999999" for flag -go: maximum supported Go version is '$goversion'\nusage: go mod graph \[-go=version\]\nRun ''go help mod graph'' for details.$'
61+
62+
63+
-- go.mod --
64+
// Module m indirectly imports a package from
65+
// example.com/retract/incompatible. Its selected version of
66+
// that module is lower under Go 1.17 semantics than under Go 1.16.
67+
module example.com/m
68+
69+
go 1.17
70+
71+
replace (
72+
example.net/lazy v0.1.0 => ./lazy
73+
example.net/requireincompatible v0.1.0 => ./requireincompatible
74+
)
75+
76+
require (
77+
example.com/retract/incompatible v1.0.0 // indirect
78+
example.net/lazy v0.1.0
79+
)
80+
-- lazy/go.mod --
81+
// Module lazy requires example.com/retract/incompatible v1.0.0.
82+
//
83+
// When viewed from the outside it also has a transitive dependency
84+
// on v2.0.0+incompatible, but in lazy mode that transitive dependency
85+
// is pruned out.
86+
module example.net/lazy
87+
88+
go 1.17
89+
90+
exclude example.com/retract/incompatible v2.0.0+incompatible
91+
92+
require (
93+
example.com/retract/incompatible v1.0.0
94+
example.net/requireincompatible v0.1.0
95+
)
96+
-- requireincompatible/go.mod --
97+
module example.net/requireincompatible
98+
99+
go 1.15
100+
101+
require example.com/retract/incompatible v2.0.0+incompatible

0 commit comments

Comments
 (0)