36

For every project I create, I have to do export GOPATH={path_to_project} every time I cd into the project dir. There has to be an easier way. Isn't there some way I can create a .bashrc or .bash_profile file for a given directory to define the GOPATH for that project?

For example, I have two go projects A and B. If I have a singular GOPATH that isn't redefined when I move between projects, then binaries for both projects will be stored in the same place. More importantly, binaries for third party libraries will be stored in the same place, so I have no way of maintaining multiple versions of the same library on a per project basis.

However, if I am able to define GOPATH on a per project basis, then all binaries and third party libraries are project dependent. This seems to be the common way of handling package management in most other language environments (ruby rbenv, python vertiualenv, etc.)

Pang
  • 9,564
  • 146
  • 81
  • 122
Daniel Nill
  • 5,539
  • 10
  • 45
  • 63
  • 2
    http://stackoverflow.com/questions/3360738/execute-a-bash-function-upon-entering-a-directory, do what you want with that. – Mat Jul 22 '13 at 05:55
  • Are you looking to use GOPATH for your own uses, or as it's used by go utilities? Because if it's just for the former, I'd define something /other/ than GOPATH since `go` commands rely on it pointing to your go installation root. – joshlf Jul 22 '13 at 06:03
  • @joshlf13 correct me if I'm wrong but I was under the impression that GOPATH defines where you bin and pkg directories are. If you have one GOPATH across all projects then your packages will not be unique to each project. This is why I'm wanting to redefine GOPATH for each project. – Daniel Nill Jul 22 '13 at 06:08
  • So, as I understand it, the structure is supposed to be as follows: A single `go` directory to contain all go-related things. Inside this, three directories: `bin`, `pkg`, and `src`. When you install compiled code from src, it is placed in the bin folder. Similarly, when you compile a non-executable library (ie, code without a `main` function) from src, it is placed in a similarly-named folder inside `pkg`. So, for example, if I had `go/src/myproject`, and I compiled source in that folder to a library file, it would be placed in `go/pkg/myproject`. Thus, Go need only know about `go`. – joshlf Jul 22 '13 at 06:14
  • @joshlf13 yep that is what the GOPATH defines. But in a dev environment where you have multiple go projects living within the same bash profile there is no automated way to tell the shell to redefine GOPATH based on the project you're in. Added a bit more detail to the question to explain. – Daniel Nill Jul 22 '13 at 06:20
  • Note: I have edited [my answer below](https://stackoverflow.com/a/28394596/6309) to add the VSCode alternative. – VonC Sep 28 '17 at 08:40
  • Note: modules (with Go 1.11) should eventually render GOPATH obsolete. see my [edited answer below](https://stackoverflow.com/a/28394596/6309). – VonC Sep 13 '18 at 14:43

8 Answers8

25

(Q2 2018: Note that with the vgo (now "module") project, GOPATH might end up being deprecated in favor of a project-based workflow. That would avoid the manual project-based GOPATH I was proposing below, two years ago)

With Go 1.11 (August 2018), GOPATH can be optional, with modules.


You have a similar idea expressed in Manage multiple GOPATH dirs with ease, by Herbert Fischer (hgfischer), for a Linux/Unix environment (base on the question already mention in the comments above):

Just include the following snippet in your ~/.bashrc (or ~/.bash_profile) and reload your shell environment with source ~/.bashrc.
This snippet will create a shell function that will override the builtin command cd with a customized one that scans the entered directory, and every other above, for a file named .gopath.

cd () {
    builtin cd "$@"
    cdir=$PWD
    while [ "$cdir" != "/" ]; do
        if [ -e "$cdir/.gopath" ]; then
            export GOPATH=$cdir
            break
        fi
        cdir=$(dirname "$cdir")
    done
}

Now you just need to create a .gopath file in every directory you want as your GOPATH and every time you enter this directory, the redefined cd function will set the GOPATH of your current environment to this directory.


Update 2017: if you don't want to modify your environment, you can still use one GOPATH per project, by opening the src folder of that project in Visual Studio Code (vscode, which is a multi-platform IDE), combined with the extension "Go for Visual Studio Code".

In that IDE, you can:

  • keep your global unique GOPATH in a setting called go.toolsGopath.
  • infer your current GOPATH with a setting called go.inferGopath

vscode user settings

That way, VSCode will install a collection of tools in your global GOPATH (for you to use outside VSCode as well).
See "Go tools that the Go extension depends on": godep, golint, guru, godoc, ...

And yet, your GOPATH for your project will be the parent folder of src:

local GOPATH

That works when you compile/install your project from the IDE.
If you want to do it from the command line, the original answer above would still apply.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
10

You can use a tool like autoenv to set up a script that is automatically executed when you cd into a particular directory.

For your purposes, an example /happy/go/path/yay/.env file might look like:

export GOPATH="/happy/go/path/yay"
export PATH="$GOPATH/bin:$PATH"
Brent
  • 1,149
  • 8
  • 11
  • 3
    Wouldn't this extend the PATH variable each time someone enters the directory? – kupsef Sep 20 '15 at 15:44
  • 1
    @kupsef I would recommend https://github.com/direnv/direnv , since it support unload env while leaving the directory. – vr3C Nov 22 '16 at 02:17
5

I would write a script which can infer the proper GOPATH from the current directory, and then alias the go command to first call this script. For example, a very simple implementation:

#!/bin/bash
# infer-gopath.sh

pwd

And then, in .bash_aliases (or wherever you keep your aliases):

alias go='GOPATH=$(infer-gopath.sh) go'

This sets GOPATH to whatever infer-gopath.sh outputs just for the invocation of the go command, so it won't have any lasting effect on your shell.

joshlf
  • 21,822
  • 11
  • 69
  • 96
  • 1
    That won't work. The `GOPATH` defined in `infer-gopath.sh` vanishes as soon as that shell script terminates. `export` exports to children, not to parents. You could redeem it by putting the `go` command inside of the shell script as `go "$@"` – rici Jul 22 '13 at 17:43
  • Sorry, good point. You can actually do it like this: `. infer-gopath.sh` (that's how bash init scripts work). – joshlf Jul 22 '13 at 21:17
  • But then your GOPATH is changed in the current shell, not just in the go process. – Jonathan Hall Aug 20 '15 at 17:02
5

I know this is not very clever but I find that if I simply go to the base directory of the go project where I have the src, pkg and bin folders I can simply type:

export GOPATH=$(pwd)

and thats it all good!

BoomShadow
  • 912
  • 1
  • 18
  • 31
3

My impression is that the go tool actively discourages "maintaining multiple versions of the same library on a per project basis" for the precise reason that experience has shown that that strategy doesn't work on large codebases (such as Google's). There has been quite a lot of discussion about package versioning on golang-nuts: (search the list), and it seems that the discussion is still open, as indicated by Ian Lance Taylor in this June 6, 2013 interview (search for the word "versioning").

The go packaging system is designed to allow every project to have its own directory structure; the only restriction is that they all need to be children of (some) directory in GOPATH. This has the advantage that it interacts well with version control systems, as long as the VCS master always builds. In the blog interview referenced above, ILT suggests:

What we do internally is take a snapshot of the imported code, and update that snapshot from time to time. That way, our code base won't break unexpectedly if the API changes.

Substituting "my other libraries" for "the imported code", that seems like a possibility; you could have two go directories, production and development; for development, you could put the development directory first in the path so that development binaries and libraries don't pollute the production directories. I don't know if that is sufficient.

If you really want to have a separate GOPATH for each project, I'd suggest the following:

1) Make every project's GOPATH end at a directory named go (or some such)

2) Deduce the GOPATH using the something like the following shell function (almost totally untested):

gopath() {
  GOPATH="$(
    ( while [[ $PWD != / && $(basename $PWD) != go ]]; do
        cd ..
      done
      if [[ $PWD == / ]]; then
        echo $GOPATH
      else
        echo $PWD
      fi
    ))" go "$@"
}

Then you can use gopath instead of go as long as your current working directory is somewhere inside the project's repository. (More sophisticated possibilities might include using the explicitly provided project path, if any, to deduce GOPATH.)

rici
  • 234,347
  • 28
  • 237
  • 341
  • "strategy doesn't work on large codebases" Why? Any links? I am curious, as it seems that many other, older, more established environments have large, per-project workspaces. It'd be interesting to see why Google does this differently. – alphadogg Apr 22 '14 at 01:59
  • @alphadogg: These large per-project workspaces have independent versions of included libraries? The practical issue is resolving version inter-compatibilities when trying to upgrade (or backporting security fixes, for example, to old versions of libraries because upgrading is too hard). – rici Apr 22 '14 at 05:57
  • Yes. And, sorry, I am not sure on what your second statement means. Is it in answer to my "Why?". If so, I don't see where "resolving version inter-compatibilities" is a function of large codebases? – alphadogg Apr 22 '14 at 20:16
  • @alphadogg: large code bases have more libraries, and the number of possible incompatibilities is roughly quadratic in the number of library/version combinations. That's the argument, anyway, and I certainly have had painful experiences trying to do upgrades. Unfortunately, I don't have any links handy, and I no longer work in that environment, so I'm not really inclined to engage in an argument/discussion. (Oh, and I meant "resolving incompatibilities between different versions of different libraries", but my fingers weren't co-operating when I was typing and you can't edit old comments.) – rici Apr 22 '14 at 20:30
  • The idea of having a single repo for an entire organization is called a "monorepo". See: http://blog.rocketpoweredjetpants.com/2015/04/monorepo-one-source-code-repository-to.html – Brian Slesinsky Apr 25 '15 at 17:22
  • However, this has nothing to do with wanting to set GOPATH automatically, which is a reasonable thing to ask for. Even in organizations where we have a single repo, we still have multiple working directories with different versions of the repo checked out. Setting GOPATH automatically based on which working directory you're in is useful. – Brian Slesinsky Apr 25 '15 at 17:26
  • @BrianSlesinsky: Well, I did try to provide a possible answer to that request. I'm not (and never have been) responsible for the `go` tool; I was just trying to explain my understanding of why it was designed the way it was (and my understanding is very much from the outside). I am not in any way arguing against what you call "monorepo"; the logic of such an environment would be to only have one GOPATH. (for example, https://www.digitalocean.com/company/blog/taming-your-go-dependencies/) – rici Apr 25 '15 at 17:56
1

I can't comment, but to build off the answer from @joshlf :::

alias go by prepending the GOPATH -- my method doesn't require you deal with/create an extra file:

alias go='GOPATH=$(echo $(pwd)) go'

cheers

lukeed
  • 409
  • 2
  • 5
  • 13
0

I like the guts of the gopath() answer above, but I don't like (a) having the GOPATH set only for the go command (I use it in vim plugins, etc), nor do I like (b) having to remember to type gopath instead of go :)

This is what I ultimately added to my ~/.bash_profile:

export GOPATH="... a default path ..."
function cd() {
  builtin cd $@ &&
  export GOPATH="$(
    ( while [[ $PWD != / && $(basename $PWD) != go ]]; do
        cd ..
      done
      if [[ $PWD == / ]]; then
        echo $GOPATH
      else
        echo $PWD
      fi
    ))"
}

(I also wrote up the above with a little extra discussion of requirements in this blog post)

Community
  • 1
  • 1
el_bhs
  • 1
0

I'm a Golang newb, but the best way to do this would probably be to create a shell script in each of your projects, to build/run your project, and put this script in your project root :)

#!/usr/bin/env bash

// get absolute path to the directory which contains this script
PROJECT_DIR=$(cd $(dirname $0) && pwd)

// set GOPATH as you wish, then run go build
GOPATH=${GOPATH}:${PROJECT_DIR} && cd ${PROJECT_DIR} && go build

this script should work, even if you execute it from a directory that is not the project root.

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817