A Makefile for Golang
I created a GNU Makefile to facilitate building my Go projects. It is composed of two files, Makefile and golang.mk, which are available at the top of my Golearn project in Gitlab.
The Makefile has two core targets: all, the default, and tests. The all
target builds all source code in a project, and the tests target runs all
associated Go tests. There are two other targets, clean and cleanall, to
clean command binaries and all binaries (.a
libraries included) respectively,
that may have been created before.
The Makefile uses default values to capture the way the project is organized. They can be modified as required to accommodate different directory structures.
The Default Project Layout #
The basic structure of a project is illustrated in the following example:
$GOPATH
├── bin
├── pkg
└── src
└── gitlab.com
└── pirivan
├── project
│ ├── cmd
│ │ ├── prog1
│ │ ├── prog2
│ │ └── prog3
│ └── internal
│ └── pkg
│ ├── liba
│ ├── libb
│ └── libc
└── other_project
The top three directories underneath $GOPATH
, bin
, pkg
, and src
, host
the Go workspace binaries, libraries, and source code respectively. In this
example the Go project is at gitlab.com/pirivan/project
and has three programs
called prog{1,2,3}
under the cmd
directory. Each of these directories has
one or more *.go
and *_test.go
source files.
The project includes three internal libraries lib{a,b,c}
in the internal/pkg
directory. They are internal, meaning that they are not meant to be used by
other projects. Each of these library directories have *.go
and *._test.go
files as well.
This is how you use the Makefile with a project organized this way:
# cd to the project's directory
$ cd $GOPATH/src/gitlab.com/pirivan/project
# compile and build binaries
$ make
# run tests
$ make tests
# clean the prog{1,2,3} binaries
$ make clean
# or, clean the prog{1,2,3} binaries and the lib{a,b,c} .a files
$ make cleanall
The Makefile creates the project binaries in the $(GOPATH)/bin
directory, and
the .a
library files in
$GOPATH/linux_amd64/gitlab.com/pirivan/project/internal/pkg
, as usual.
Whenever you modify a source file under the cmd
directory, the corresponding
command program is rebuilt when you run make
again. Similarly, the libraries
are rebuilt when one of their source files is modified. More interestingly
however, command binaries are automatically rebuilt when one or more of the
libraries used by the commands are modified. Therefore, all dependencies are
taken care of.
Finally, whenever you modify a Go test program in any directory, the
corresponding tests are rerun when you run make tests
again.
The Makefile #
The Makefile is split into two files: Makefile, which you may want to customize, and golang.mk, which shouldn’t need to be modified. Here is the Makefile:
# Directory for binary files
# Default is GOBIN := $(GOPATH)/bin
# Directory for source code relative to current directory
# Default is CMDDIR := cmd
# Directory for internal libraries relative to current directory
# Default is INTERNALDIR := internal/pkg
include golang.mk
With the exception of the last line, which includes the golang.mk file, the
default Makefile is just comments! This means that you can use it immediately if
your project is organized as described above. There is nothing else to say, just
copy the Makefile
and golang.mk
files to the root of your Go project and you
can start building and testing your code right away, it couldn’t be easier!
Changing the Commands Directory #
You may want to organize the project inside sub-directories for convenience. For
example, client packages go under cmd/client
and server pakages go under
cmd/server
. The project structure would look like this:
$GOPATH
├── bin
├── pkg
└── src
└── gitlab.com
└── pirivan
└── project
├── bin
├── cmd
│ ├── client
│ │ └── cli
│ └── server
│ ├── prog1
│ └── prog2
└── internal
└── pkg
├── liba
├── libb
└── libc
└── other_project
As before, each of the directories client/cli
and server/prog{1,2}
contains
one or more source and test files implementing a Go package.
The Makefile as described above still works, there is no need for you to modify
anything. The Go tools recursively traverse the cmd directory scanning all
subdirectories and determining what Go packages are there that need to be
compiled. But, say that you want to compile the client programs only, maybe
because the server directory is in a messy state at the moment. You can do
this by modifying the Makefile
as follows:
# Directory for binary files
# Default is GOBIN := $(GOPATH)/bin
# Directory for source code relative to current directory
# Default is CMDDIR := cmd
CMDDIR := cmd/client
# Directory for internal libraries relative to current directory
# Default is INTERNALDIR := internal/pkg
include golang.mk
To compile the server programs instead, modify it accordingly with
CMDDIR := cmd/server
With these changes you limit the Makefile to process only one set of packages at a time. However, see below for a way to run the Makefile in a more dynamic way using command-line variables.
You can modify the INTERNALDIR
variable also if you need to, but I haven’t
found this to be necessary yet.
Changing the Binary Directory #
You may want the change the directory used to host the command binaries. For
example, you may want to avoid polluting the main $GOPATH/bin
directory with
project binaries that you are just testing.
Let’s say that you want to use a local bin
directory inside your project to
host the binaries. You create this directory as follows:
# cd to the project's directory
$ cd $GOPATH/src/gitlab.com/pirivan/project
# create the local bin directory
$ mkdir bin
Now you modify the Makefile to read like this:
# Directory for binary files
# Default is BINDIR := $(GOPATH)/bin
GOBIN = bin
# Directory for source code relative to current directory
# Default is CMDDIR := cmd
# Directory for internal libraries relative to current directory
# Default is INTERNALDIR := internal/pkg
include golang.mk
When you run the make
command again, the project binaries are installed now in
$GOPATH/src/gitlab.com/pirivan/project/bin
, which you can run as
bin/prog{1,2,3}
.
Of course you can keep changes to both the CMDDIR
and BINDIR
definitions
together if both need to be changed.
Using Command-Line Variables #
You may override the definitions of the variables from the command line. This avoids you having to modify the Makefile and eventually having to keep track of changes in your version control system.
The following example illustrates how to run the original, unmodified, Makefile for the client programs but still implementing the changes discussed above:
# cd to the project's directory
$ cd $GOPATH/src/gitlab.com/pirivan/project
# compile and build the client binaries
$ make CMDDIR=cmd/client GOBIN=bin
# compile and build the server binaries
$ make CMDDIR=cmd/server GOBIN=bin
# run all tests, no need to use GOBIN
$ make tests
# clean the local bin directory
$ make CMDDIR=cmd/client GOBIN=bin clean
Future Improvements #
Other targets such as format
, debug
and deploy
may come later. Handling of
versions and vendor branches are also some possibilities. But I found that the
current version of the Makefile simplifies the command line effort substantially
when building my projects. I hope you do find it useful too.
Makefile targets #
In summary, the Makefile supports the following targets:
Target | Description |
---|---|
Runs the libs and commands targets | |
libs | Compiles libraries from the INTERNALS directory |
commands | Compiles command binaries from the CMDDIR directory |
tests | Runs all project tests |
clean | Removes all binaries created by the commands target |
cleanall | Removes all binaries created by the commands and libs targets |