Skip to content
This repository has been archived by the owner on Sep 30, 2022. It is now read-only.

Latest commit

 

History

History
459 lines (332 loc) · 15 KB

README.md

File metadata and controls

459 lines (332 loc) · 15 KB

Using go list, go mod why and go mod graph

go list, go mod why and go mod graph can be used to analyse modules and packages. In this guide we introduce each command as we analyse the github.com/go-modules-by-example/listmodwhygraph module.

For any of the commands, go help provides more details; for example, go help list or go help mod graph. These help guides are also available on the Go website.

Walk-through

Clone our example repo ready for analysis:

$ git clone -q https://github.com/go-modules-by-example/listmodwhygraph /home/gopher/scratchpad/listmodwhygraph
$ cd /home/gopher/scratchpad/listmodwhygraph

Our main module is:

$ go list -m
github.com/go-modules-by-example/listmodwhygraph

The -m flag to go list causes list to list modules instead of packages.

The packages in the main module are:

$ go list github.com/go-modules-by-example/listmodwhygraph/...
github.com/go-modules-by-example/listmodwhygraph

That is to say, the module github.com/go-modules-by-example/listmodwhygraph consists of the single package github.com/go-modules-by-example/listmodwhygraph.

The main module has the following structure:

$ tree --noreport
.
|-- go.mod
|-- go.sum
`-- listmodwhygraph.go

Examine the single .go file in the single package in our main module:

$ cat /home/gopher/scratchpad/listmodwhygraph/listmodwhygraph.go
package listmodwhygraph

import (
	_ "github.com/go-modules-by-example/incomplete"
	_ "github.com/kr/pretty"
	_ "github.com/sirupsen/logrus"
)

Ensure the main module's go.mod (and go.sum) reflects its source code:

$ go mod tidy

Building on "Visually analysing module dependencies", use go mod graph to look at the module requirement graph. Remember, each of the nodes in this graph is a module, and an edge m1 -> m2 indicates that m1 requires m2. Click the image for a larger version.

Module Dependency Graph

See below for details on how this graph was generated.

The nodes of this graph can also be given by:

$ go list -m all
github.com/go-modules-by-example/listmodwhygraph
github.com/davecgh/go-spew v1.1.1
github.com/go-modules-by-example/incomplete v0.0.0-20190513150356-5cba17ec1a73
...

Looking at the graph and the output of go list, the first question that comes to mind is why the module golang.org/x/tools appears to be a dependency of the main module, because it is not imported by hello.go.

We can answer that question using go mod why which shows us the shortest path from a package in the main module to any package in the golang.org/x/tools module:

$ go mod why -m golang.org/x/tools
# golang.org/x/tools
github.com/go-modules-by-example/listmodwhygraph
github.com/go-modules-by-example/incomplete
golang.org/x/tools/go/packages

This seems to suggest that the dependency exists because of the requirements of github.com/go-modules-by-example-forks/incomplete. But the edge github.com/go-modules-by-example-forks/incomplete -> golang.org/x/tools is definitely not visible on our graph.

Use go list again to find out more information about golang.org/x/tools:

$ go list -m -json golang.org/x/tools
{
	"Path": "golang.org/x/tools",
	"Version": "v0.0.0-20190511041617-99f201b6807e",
	"Time": "2019-05-10T21:16:17-07:00",
	"Indirect": true,
	"Dir": "/home/gopher/pkg/mod/golang.org/x/tools@v0.0.0-20190511041617-99f201b6807e",
	"GoMod": "/home/gopher/pkg/mod/cache/download/golang.org/x/tools/@v/v0.0.0-20190511041617-99f201b6807e.mod"
}

This shows that golang.org/x/tools is an indirect dependency. Indirect requirements only arise when using modules that fail to state some of their own dependencies (this includes requirements that have not yet been converted to be modules) or when explicitly upgrading a module's dependencies ahead of its own stated requirements.

We can also see this from the // indirect annotation in the main module's go.mod:

$ cat /home/gopher/scratchpad/listmodwhygraph/go.mod
module github.com/go-modules-by-example/listmodwhygraph

go 1.12

require (
	github.com/go-modules-by-example/incomplete v0.0.0-20190513150356-5cba17ec1a73
	github.com/kr/pretty v0.1.0
	github.com/sirupsen/logrus v1.4.1
	golang.org/x/tools v0.0.0-20190511041617-99f201b6807e // indirect
)

In this case, the indirect dependency comes about because the go.mod for github.com/go-modules-by-example-forks/incomplete fails to completely state its dependencies; golang.org/x/tools is missing.

Returning to go list.

go list gives information about packages. By default it lists the import path of packages matching the package patterns provided. With no arguments go list applies to the package in the current directory:

$ go list
github.com/go-modules-by-example/listmodwhygraph

We can extend this to include the transitive dependencies of the named packages:

$ go list -deps
errors
internal/cpu
unsafe
internal/bytealg
...

Use the -f flag to exclude standard library dependencies:

$ go list -deps -f "{{if not .Standard}}{{.ImportPath}}{{end}}"
golang.org/x/sys/unix
github.com/sirupsen/logrus
golang.org/x/tools/go/internal/gcimporter
golang.org/x/tools/go/gcexportdata
golang.org/x/tools/go/internal/packagesdriver
golang.org/x/tools/internal/fastwalk
golang.org/x/tools/internal/gopathwalk
golang.org/x/tools/internal/semver
golang.org/x/tools/go/packages
github.com/go-modules-by-example/incomplete
github.com/kr/text
github.com/kr/pretty
github.com/go-modules-by-example/listmodwhygraph

Looking at the original module requirement graph, we might ask ourselves, why are there no packages from the modules github.com/davecgh/go-spew and github.com/kr/pty in the go list output? Use go mod why -m to find the shortest path from a package in the main module to a package in each module.

Firstly github.com/davecgh/go-spew:

$ go mod why -m github.com/davecgh/go-spew
# github.com/davecgh/go-spew
github.com/go-modules-by-example/listmodwhygraph
github.com/sirupsen/logrus
github.com/sirupsen/logrus.test
github.com/stretchr/testify/assert
github.com/davecgh/go-spew/spew

We see that the package github.com/davecgh/go-spew/spew in the module github.com/davecgh/go-spew is one such path. We can therefore expect an identical answer if we ask the question of the package itself (no -m this time):

$ go mod why github.com/davecgh/go-spew/spew
# github.com/davecgh/go-spew/spew
github.com/go-modules-by-example/listmodwhygraph
github.com/sirupsen/logrus
github.com/sirupsen/logrus.test
github.com/stretchr/testify/assert
github.com/davecgh/go-spew/spew

Secondly, github.com/kr/pty:

$ go mod why -m github.com/kr/pty
# github.com/kr/pty
(main module does not need module github.com/kr/pty)

This tells us that there is no package from the github.com/kr/pty module in the transitive import graph of of the main module (github.com/go-modules-by-example/listmodwhygraph).

So why does the github.com/kr/pty appear in the module requirement graph? Currently, no command can give us the answer (something that is being tracked in #27900 and will likely be fixed in Go 1.12).

But we can clearly see the answer by visually inspecting the requirement graph above. We see that github.com/kr/text requires github.com/kr/pty. So why does github.com/kr/text exist in the requirement graph?

$ go mod why -m github.com/kr/text
# github.com/kr/text
github.com/go-modules-by-example/listmodwhygraph
github.com/kr/pretty
github.com/kr/text

For this module we have a concrete answer. So we are now clear that github.com/kr/text is in our requirement graph because it is transitively used by an import of our main package. Furthermore, github.com/kr/pty is present because it is a requirement of github.com/kr/text and given the module requirement graph is conservative it reflects all such requirements.

Conclusion

go list, go mod graph and go mod why are very powerful tools when it comes to understanding your code and its dependencies. We have barely scratched the surface of go list in this simple guide; as with all tools, read the help for more information on each, for example. go help list.

Generate the module requirement graph:

$ go mod graph | sed -Ee 's/@[^[:blank:]]+//g' | sort | uniq >unver.txt
$ cat <<EOD >graph.dot
digraph {
	graph [overlap=false, size=14];
	root="$(go list -m)";
	node [  shape = plaintext, fontname = "Helvetica", fontsize=24];
	"$(go list -m)" [style = filled, fillcolor = "#E94762"];
EOD
$ cat unver.txt | awk '{print "\""$1"\" -> \""$2"\""};' >>graph.dot
$ echo "}" >>graph.dot
$ sed -i 's+\("github.com/[^/]*/\)\([^"]*"\)+\1\\n\2+g' graph.dot
$ sfdp -Tsvg -o graph.svg graph.dot

Version details

go version go1.12.5 linux/amd64