Visualizing your golang project's package structure

gopher is confused by thousands of packages

What I want to make

I use a pretty simple command line tool called godepgraph to generate a graph of package structure for a Golang project; But the problem with it is that it prints all the dependency packages which it makes a messy and unreadable graph. So I use a script to generate a graph from packages in a project and it will look like this.

sample output of the package structure

prerequirements

You need to have graphviz and godepgraph installed.

go install github.com/kisielk/godepgraph@latest

brew install graphviz    # check the graphviz website  if you're not using mac

This works if you’re using go modules. You just need to replace ./cmd/app-name in the first line with the relative path to the folder where your code’s main function is.

1MAIN_DIR=./cmd/app-name  # <--- here
2REOP_OWNER=$(go list -m | grep -oE '^[^/]+/[^/]+')
3REPO=$(go list -m)
4PACKAGES_GRAPH="$(go mod graph | sed 's/ /\n/g' | grep -oE '^[^@]+')"
5PACKAGES_DEPS="$(go list -deps $MAIN_DIR)"
6ALL_DEPENDENCIES="${PACKAGES_GRAPH}${PACKAGES_DEPS:+ }${PACKAGES_DEPS}"
7EXCLUDED_PACKAGES=$(echo $ALL_DEPENDENCIES | sed 's/ /\n/g' | grep -oE '^[^/]+/[^/]+' | uniq | grep -v $REOP_OWNER | tr '\n' ' ' | sed 's/ $//' | sed 's/ /,/g' | tr '\n' ' ')
8godepgraph -s -ignoreprefixes "$EXCLUDED_PACKAGES" $MAIN_DIR | sed "s|\"$REPO|\"|g" | dot -Tpng -o godepgraph.png

The smarter way to do it is to put it in a bash script file, and put $1 as the value of MAIN_DIR. Then you can call your script like script_name ./cmd/app-name. But I leave it to you if you wanna do it or not.

Why did I make it so complicated?

Well, I was interested to only have the packages I own to be listed in the graph. So I used following command which is supposed to only include packages with the given prefixes in the chart but it just returned an empty image.

godepgraph -onlyprefixes github.com/mdaliyan ./cmd/app-name | dot -Tpng -o godepgraph.png

So, since I just wanted to get to my graph, I went the other way around and gave it the packages I didn’t want to include. Because there are many of them and they are different from project to project, I used go command line to get all the dependencies in the project and make a list from them to give it to the -ignoreprefixes flag. And here is the same code we had above with detailed explanation.

 1# the directory where the main function is
 2# e.g. `.` or `./cmd/app-name`
 3MAIN_DIR=./cmd/app-name
 4
 5# the module address of the project, written in `go.mod` file.
 6# e.g. `github.com/mdaliyan/app-name`
 7REPO=$(go list -m) # github.com/mdaliyan/app-name
 8 
 9# The address of the project owner, written in `go.mod` file.
10# e.g. `github.com/mdaliyan`
11REOP_OWNER=$(go list -m | grep -oE '^[^/]+/[^/]+')
12
13# Collect all the direct and indirect depencencies
14# The command itself prints each of them in a new line
15# but we will get them space separated in one line in
16# our environment variable.
17PACKAGES_GRAPH="$(go mod graph | sed 's/ /\n/g' | grep -oE '^[^@]+')"
18PACKAGES_DEPS="$(go list -deps $MAIN_DIR)"
19
20# Concat all depencencies with a space in between. 
21# This list contains many duplications.
22ALL_DEPENDENCIES="${PACKAGES_GRAPH}${PACKAGES_DEPS:+ }${PACKAGES_DEPS}"
23
24# This command contains multiple pipes:
25#  1. replacing space with new line
26#  2. keeping the repository owner's addresses in each line
27#  3. get a unique list out of them
28#  4. remove your own repo owner address from the list
29#  ... the rest is to replace new lines with commas and 
30#      remove the last comma from the end
31EXCLUDED_PACKAGES=$(echo $ALL_DEPENDENCIES | \
32                  sed 's/ /\n/g' | \
33                  grep -oE '^[^/]+/[^/]+' | \
34                  uniq | \
35                  grep -v $REOP_OWNER | \
36                  tr '\n' ' ' | sed 's/ $//' | sed 's/ /,/g' | tr '\n' ' ')
37
38# This command contains multiple pipes:
39#  1. generates a graph's dot file
40#  2. remove your repository owner address from the links
41#     from the dot fileso the nodes in the charts become 
42#     short and nicer to read
43#  4. generate the graph in png format 
44godepgraph -s -ignoreprefixes "$EXCLUDED_PACKAGES" $MAIN_DIR | \
45                                      sed "s|\"$MODULE|\"|g" | \
46                                      dot -Tpng -o godepgraph.png