Recently, while writing a small Golang program for setting reminders I came across a small confusion that I guess most newcomers to Golang will have - how to organise a package in a way that will enable it to cleanly contain two or more binaries.
This post is not aimed at experienced Golang programmers, it’s mostly aimed at beginners to understand how to compose more complex packages, beyond making the usual “one package one binary” ones. It’s essentially what I would like to have read (or understand) after spinning my wheels for a bit while building my first (more complex) package.
But, if you are one of the experienced folks I would be very grateful if you finish reading this article and call out any mistakes you might find. Also I, and probably other beginners, would be happy to find out any other alternative approaches to this problem.
That being said, let’s begin by seeing what an elementary package layout looks like, and then we can continue with building a more complex one.
Imagine we need to build a CLI program in Golang. One that needs to tell us our fortune, or provide advice on a subject that we are thinking about. Just like a Magic 8-Ball.
Let’s begin by fleshing out the
main file of this package:
The implementation is quite straightforward. We have a list of predefined
answers, blatantly ripped off from
Wikipedia’s article on Magic 8-ball. The
program takes the input from
STDIN, ignores it (obviously) and with the help of
rand package takes a random answer from the array, which is then printed
We can test this by running
go run eight_ball.go:
I guess I won’t be getting rich by wining the lottery. Now, let’s throw in another program in our package. This one, following the same theme, can be about fortune cookies.
If you are carefully reading the code, you can see the approach is basically the
same. We have a list of predefined quotes, and using the
we print out a random quote when the program is executed.
If we run the program, we would get the following output:
Nice and simple. The program works flawlessly.
So how can we build this now in a binary, and distrubite it for different operating systems and architectures. It’s really one of the things that Go really shines for, right?
If you have done any Go you would immediately say: using
go build. Let’s give
that a shot:
Whoops, what happenned? Well, the error is quite self-descriptive. Basically we
main function defined in both of these files. But, on the other hand
if we rename those functions they will not be run when we create binaries out
of the programs. Let’s try to rename the
main function as
eight_ball.go, and then try to run the program using
As you can see, then the binary cannot be built, since Go’s compiler complains
about the missing
main function. On the other hand, the missing
eight_ball.go will actually make
go build work.
Why is that? Well you see, in a package with a name
foo there can only be one
main function. When the package is built, that
main function will be the
entry point for the program (in the binary), so it’s mandatory the package has it.
eight_ball.go is missing it, and
cookie.go has it, the package
will have a single
main function (in
cookie.go), rendering the
Split the packages
Okay, so by now hopefully it’s clear that we cannot have two binaries with two
entry points in a single package. So, why don’t we split the files in two packages.
cookie.go can have a
cookie package, while
eight_ball package. Both of them will have a
main function and
everything should work smoothly, right?
Let’s do that and give it a shot. First, let’s rename the packages in the
eight_ball.go files respectively:
Let’s try to build these packages now:
Okay, cool. We ran
go build on the files and there were no problems. That means
that Go produced the binaries for each of these files and we could run them.
Nope. It didn’t.
If we see Go’s documentation
go build command, we will find this segment:
When compiling a single main package, build writes the resulting executable to an output file named after the first source file (‘go build ed.go rx.go’ writes ‘ed’ or ‘ed.exe’) or the source code directory (‘go build unix/sam’ writes ‘sam’ or ‘sam.exe’). The ‘.exe’ suffix is added when writing a Windows executable.
When compiling multiple packages or a single non-main package, build compiles the packages but discards the resulting object, serving only as a check that the packages can be built.
This means that, to produce a binary of a package, whose name will be derived
from the folder name it is stored in, we need to build a
main package. That
means that our
eight_ball.go files will have to be contained
in their own folders, while their package names have to stay
Reorganizing our files
What we need to do is quite simple actually. In our working directory let’s
introduce a folder called
cmd, which will store both of our commands.
Let’s run the following commands:
If we run
tree on our working directory, we will see the following structure:
If we try to build the packages now we won’t get far as well:
But, the restructuring of the packages allows us to now use the
go install ./...
command, which will install our packages in our
Whoa, so, what happenned? How did these programs just got installed?
What happenned is the following - since we moved the programs to their own
subfolders in our workspace, when built (and installed) they will inherit the
name of the folder they are in. Therefore,
cmd/cookie/main.go will compile
cookie binary, while
cmd/eight_ball/main.go will compile into a
eight_ball binary. Then, after buildilng, these binaries will be installed
into the Go binaries path (
If you would like to build the binaries for each of the two packages, without
installing them to
$GOPATH/bin, then you will need to
cd in to the paths
main.go files are, and run
go build there:
What if I want a library as part of the package?
This question makes sense - what if with the package one wants to ship some
additional code. Or even better, what if you would like to
import code from
the package in the CLI programs?
Let’s extract the
quotes variables from the CLI programs into a
new package, and try to import that one back in:
After extracting these two functions in a top level package, which will return
the content for the two commands, we can
import the package in the
eight_ball commands, which will utilise the content. By being able to
extract code in a common package, it allows us to easily reuse any
of the shared code between multiple commands.
For example, if we had a database driver that would write to a database
sqlite), we could easily
import this driver in both
eight_ball and use the code within the context of the program.
Let’s modify now the CLI programs to
import the top-level package:
It’s as simple as that. Here we purposely alias the package to
ft, so we don’t
have to type
fortune_teller every time we want to invoke these functions. To
test the commands manually, the simplest way is to install them using
go install ./..., which will install all of the packages that can be found in
the path (and all of it’s subpaths).
After trying this approach out, I noticed that some other popular packages use
a similar structure of building packages. For example, you can see that
places it’s command line program
cmd/bolt path. So does packr, and
other various libraries that I have noticed. So, I would safely assume that
this pattern of organising your packages is clean and safe to use.
Since you got to the end, I will assume that you would like some more reading resources around building and installing packages, and file structure organisation. I recommend you continue your journey in Go with the following links:
- How to Write Go Code
- Command go - Compile packages and dependencies
- Structuring Applications in Go - I think this is probably the origin of the structural pattern used in this article
If you would like to see the code used in this article, you can get it from my Github.