Go project structure, building commands and packages

In the last blog, we saw how to create a simple Go script and run it to get the desired outcome. We also learnt about few useful editors and setup Visual Studio Code for working in Golang. Let’s take a step deeper and understand how to structure our Go project. We will also build a sample command binary and a package object.

golang project structure

Go projects are laid out in a very specific way. When you’re developing code in Python or NodeJS, the regular practice is to have a separate folder structure for every project which is managed as version control (Git or SVN) repository. Golang works with the concept of single workspace.

Here are few things that might put things into perspective:

  1. You should have a Go workspace to start writing your own Go source code or to use 3rd party packages
  2. Go workspace contains multiple Git (or any version controlled) repositories. Each repository contains one or more packages and each package contains Go source files
  3. Essentially Go workspace is a directory hierarchy that contains Go source code, packages and commands
  4. Package objects and command binaries are generated when the Go source code is compiled

Go workspace

A workspace in Go will typically contain three folders:

  1. src – Contains Go source code files resides (includes 3rd party fetched packages)
  2. bin – Hosts all the command binaries generated as result of compilation
  3. pkg – Lists all package objects created from source files

Let’s get practical and build our own command and library

Building a command binary

First let’s create a Go workspace, or do we have it already? Remember, we created go-work folder in the first blog, we also set the Environment variable GOPATH to point to go-work folder and added the GOPATH to our PATH environment variable. Here go-work is our workspace, cool!

Now let’s create 3 directories under this workspace named – src, bin and pkg

src folder will contain multiple Git repositories that track development of different packages, we can also call these repos as namespaces

Now browse to the src folder and create a namespace, say github.com/cjgiridhar. We will develop a command and a package under this repo, so add two folders namely, addition (command) and mathematics (package). This is how the tree structure will look like:

ubuntu:~/go-work$ tree
.
├── bin
├── pkg
└── src
    └── github.com
        └── cjgiridhar
            ├── addition
            └── mathematics

7 directories, 0 files

Create a new Go source file addition.go under addition folder and add the following code snippet to it. Note that code in addition.go belongs to the package main, this is the convention used by Golang to generate a command binary rather than a package object

We now use the go tool (go install) to build and install the Go code, if there is no output that means the build is successful and you should see a binary file created in the bin folder by the name addition

ubuntu:~/go-work/src/github.com/cjgiridhar/addition$ go install
ubuntu:~/go-work/src/github.com/cjgiridhar/addition$ ls ~/go-work/bin/
addition

You can now execute this binary file from ~/go-work/bin/addition. Or you could also update PATH environment variable with ~/go-work/bin and execute addition command binary from anywhere on the filesystem. The bin directory can also be exported as GOBIN environment variable and will contain all the binaries generated from building main package

ubuntu:~/go-work/bin$ ./addition 
Hello, Gopher! Your result is 9

ubuntu:~/go-work/bin$ export PATH=$HOME/go-work/bin:$PATH

ubuntu:~/go-work/bin$ cd 
ubuntu:~$ addition 
Hello, Gopher! Your result is 9

Building a package object

A package can be imported by any other package and has its own name other than main. Packages are uniquely identified by import paths and import path is corresponds to location in your repository. Create a new folder mathematics under src/github.com/cjgiridhar and add the file mathematics.go with following source code.

There are a few things that need to be noted:

  1. Function names that are exportable need to begin with a uppercase letter, function names that begin with lowercase letters can’t be exported (so use Add and not add)
  2. Package is exported by the name mathematicss. Care should be taken that the package name does not collide with packages from standard library or external libraries
  3. Base import path in our case refers to github.com/cjgiridhar; also even though the package is under github.com, it’s not imperative to publish it

Now let us use go tools (go build) to build the package and see if there are any errors. No errors indicate the package has no errors. But we don’t have any object file created yet.

ubuntu:~/go-work/src/github.com/cjgiridhar/mathematics$ go build
ubuntu:~/go-work/src/github.com/cjgiridhar/mathematics$ ls ~/go-work/pkg/

To create the package object run go install, you would now see the package file located at ~/go-work/pkg/linux_amd64/github.com/cjgiridhar/mathematics.a

ubuntu:~/go-work/src/github.com/cjgiridhar/mathematics$ go install

ubuntu:~/go-work/src/github.com/cjgiridhar/mathematics$ ls ~/go-work/pkg/
linux_amd64

ubuntu:~/go-work/pkg/linux_amd64$ cd github.com/cjgiridhar

ubuntu:~/go-work/pkg/linux_amd64/github.com/cjgiridhar$ ls
mathematics.a

This is how our tree looks like now with the command binary and the package object

ubuntu:~/go-work$ tree
.
├── bin
│   └── addition
├── pkg
│   └── linux_amd64
│     └── github.com
│       └── cjgiridhar
│         └── mathematics.a
└── src
  └── github.com
    └── cjgiridhar
      ├── addition
      │   └── addition.go
      └── mathematics
          └── mathematics.go

10 directories, 4 files

Importing package objects

Great, now we have the mathematics package ready to be imported and reused.

Let’s go back to addition.go and comment out the add() function and reuse the Add() function from the newly created package, mathematics, as shown in the below snippet. Note we use the package name mathematics to call the Add() function and import path is github.com/cjgiridhar/mathematics. Go’s convention is that the package name is the last element of the import path: the package imported as “abc/xyz” should be named “xyz”

Using go install, build and install the addition command binary and run it as an executable.

ubuntu:~/go-work/src/github.com/cjgiridhar/addition$ go install
ubuntu:~/go-work/src/github.com/cjgiridhar/addition$ addition
Hello, Gopher! Your result is 9

Wohoo! we get the same result, we have successfully reused our package and so can others😁

Importing external packages

While we can build our own packages, we may want to leverage the work done by the community. So what do we do to include 3rd party libraries in our workspace?

Go tool provides an easy to use command go get to fetch remote packages. Let’s import one to our workspace

ubuntu:~/go-work$ go get github.com/golang/example/hello

Let’s now look at our tree structure – note we have one more namespace golang along with cjgiridhar

You can now browse to any of the packages from golang/example folder, build and install it and either run it as command or import it in your package, cool!

ubuntu:~$ cd go-work/src/github.com/golang/example/hello/

ubuntu:~/go-work/src/github.com/golang/example/hello$ go install

ubuntu:~/go-work/src/github.com/golang/example/hello$ hello 
Hello, Go examples!

ubuntu:~/go-work/src/github.com/golang/example/outyet$ ls ~/go-work/bin/
addition hello

Summary

Phew! that was something – take a deep breath, we have achieved a lot in this blog. You can now create your own Go package, use third party packages, import packages and publish it, wow! Here are a few things we should keep in mind:

  1. Every Go program is made up of packages, programs start running in package main
  2. Executable commands must always use package main
  3. By convention, the package name is the same as the last element of the import path
  4. All files in a package must use the same package name
  5. A name in a package is exported if it begins with a capital letter
  6. There can be two packages with same name, only that the import paths (their full file names) must be unique

See you in the next blog, bye for now 🙂

3 thoughts on “Go project structure, building commands and packages

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.