Handling Go workspace with direnv
June 28, 2015
When I started to do some Go I quickly hit my first hurdle: The Go Workspace. The go tool is designed to work with code maintained in public repositories using the FQDN and path as a kind of namespace and package name. Eg: github.com/rach/project-x
, where github.com/rach
is a kind of namespace enforce by a directory structure and project-x
is the package name also enforce by directory structure.
Coming from Python, I was surprised that there weren't a solution as simple as [virtualenv][virtualenv]. Go does offer a way but it requires a bit more of code gymnastic.
In this post, I'm going to describe how I made my life easier to work with Go with a bit of shell script and using [direnv][direnv] to automate workspace switching. I didn't know much about go when I wrote this post so feel free to shed some light on any of my mistakes.
Workspaces
Go project must be kept inside a workspace. A workspace is a directory hierarchy with few directories:
src
contains Go source files organized into packages (one package per directory),pkg
contains package objects, andbin
contains executable commands.
The go tool builds source packages and installs the resulting binaries to the pkg and bin directories.
The src
subdirectory typically contains multiple version control repositories (such as for Git or Mercurial) that track the development of one or more source packages.
To give you an idea of how a workspace looks in practice, here's an example:
bin/
hello # command executable
outyet # command executable
pkg/
linux_amd64/
github.com/golang/example/
stringutil.a # package object
src/
github.com/golang/example/
.git/ # Git repository metadata
hello/
hello.go # command source
outyet/
main.go # command source
main_test.go # test source
stringutil/
reverse.go # package source
reverse_test.go # test source
The problem that I hit was:
- how do you work on multiple different projects?
- how should specify which workspace that I working on?
It's when the GOPATH
enter to define the workspace location.
The GOPATH environment variable
The GOPATH environment variable specifies the location of your workspace. To get started, create a workspace directory and set GOPATH accordingly. Your workspace can be located wherever you like.
$ mkdir $HOME/go
$ export GOPATH=$HOME/go
To be able to call the binary build inside your workspace, add bin subdirectory to your PATH:
$ export PATH=$PATH:$GOPATH/bin
For you project can choose any arbitrary path name, as long as it is unique to the standard library and greater Go ecosystem. It's the convention to use an FQDN and path as your folder structure which will behave as namespaces.
We'll use github.com/rach/project-x
as our base path. Create a directory inside your workspace in which to keep source code:
$ mkdir -p $GOPATH/src/github.com/rach/project-x
Update the GOPATH automatically with direnv
Direnv is an environment switcher for the shell. It loads or unloads environment variables depending on the current directory. This allows to have project-specific environment variables. direnv works with bash, zsh, tcsh and fish shell. Direnv checks for the existence of an ".envrc" file in the current and parent directories. If the file exists, the variables declared in .envrc
are made available in the current shell. When you leave the directory or sub-directory where .envrc is present, the variables are unloaded. It also works well with updating existing environment variable.
To install direnv on OSX using zsh, you can follow this steps:
$ brew update
$ brew install direnv
$ echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
Using direnv, it becomes easy to have multiple workspaces and switch between them. Simply create a .envrc
file at the location of your workspace and export the appropriate variable:
$ mkdir $HOME/new-workspace
$ cd $HOME/new-workspace
$ echo 'export GOPATH=$(PWD):$GOPATH' >> .envrc
$ echo 'export PATH=$(PWD)/bin:$PATH' >> .envrc
$ direnv allow
With the code above we now have a workspace which enable itself when you enter it. Having multiple workspace help to experiment with libs/package that you want to test in the same way you can install a python lib just for a one-time use.
Assuming we will be writing a lot of go projects, will not be nice
of a having a helper function to create a workspace which follow the suggested structure with the GOPATH
is handled automatically.
Automate creation of workspace for a project
Now that we know how a workspace should look like and how to make switching them easier. Let's automate the creation new project with workspaces to avoid mistakes, for that I wrote a small zsh
function to do it for me.
function mkgoproject {
TRAPINT() {
print "Caught SIGINT, aborting."
return $(( 128 + $1 ))
}
echo 'Creating new Go project:'
if [ -n "$1" ]; then
project=$1
else
while [[ -z "$project" ]]; do
vared -p 'what is your project name: ' -c project;
done
fi
namespace='github/rach'
while true; do
vared -p 'what is your project namespace: ' -c namespace
if [ -n "$namespace" ] ; then
break
fi
done
mkdir -p $project/src/$namespace/$project
git init -q $project/src/$namespace/$project
main=$project/src/$namespace/$project/main.go
echo 'export GOPATH=$(PWD):$GOPATH' >> $project/.envrc
echo 'export PATH=$(PWD)/bin:$PATH' >> $project/.envrc
echo 'package main' >> $main
echo 'import "fmt"' >> $main
echo 'func main() {' >> $main
echo ' fmt.Println("hello world")' >> $main
echo '}' >> $main
direnv allow $project
echo "cd $project/src/$namespace/$project #to start coding"
}
If you are using zsh then you should be able to copy/paste this function into
your zshrc and after reloading it then you be able to call mkgoproject
.
If you call the function with an argument then it will consider it being
the project name and it will ask you for a namespace (eg: github.com/rach), otherwise it will ask you for both: project name (package) and namespace.
The function create a new worspace with an .envrc
and a main.go
ready to build.
$ mkgoproject test
Creating new Go project:
what is your project namespace: github/rach
cd test/src/github/rach/test #to start coding
I hope this post will help you into automate the switching between your go project and the creation of them.