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.
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:
- You should have a Go workspace to start writing your own Go source code or to use 3rd party packages
- Go workspace contains multiple Git (or any version controlled) repositories. Each repository contains one or more packages and each package contains Go source files
- Essentially Go workspace is a directory hierarchy that contains Go source code, packages and commands
- Package objects and command binaries are generated when the Go source code is compiled
A workspace in Go will typically contain three folders:
- src – Contains Go source code files resides (includes 3rd party fetched packages)
- bin – Hosts all the command binaries generated as result of compilation
- 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:
- 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)
- 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
- 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
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:
- Every Go program is made up of packages, programs start running in package main
- Executable commands must always use package main
- By convention, the package name is the same as the last element of the import path
- All files in a package must use the same package name
- A name in a package is exported if it begins with a capital letter
- 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 🙂