GOPATH Workspace

Before the introduction of Go modules, Go dependency management was primarily handled using the GOPATH workspace. The GOPATH environment variable defined the root of the workspace where all Go code and dependencies were stored. This approach centralized all dependencies in a single workspace, which sometimes led to conflicts and difficulties in managing different versions of the same dependency across multiple projects.

Setting Up GOPATH

To set up the GOPATH workspace, you need to define the GOPATH environment variable. Here’s an example:

  export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
  

The structure of the GOPATH workspace typically looked like this:

  GOPATH/
├── bin/
├── pkg/
└── src/
    ├── github.com/
    ├── golang.org/
    └── example/
        └── myproject/
            ├── main.go
            ├── go.mod (if modules are being used in hybrid mode)
            └── somepackage/
                └── somefile.go
  
  • bin: Compiled binaries are stored here.
  • pkg: Compiled package objects are stored here.
  • src: Source files for Go projects and their dependencies are stored here.

Dependency Management in GOPATH

Dependencies were typically managed by directly placing them in the src directory within the GOPATH. When you ran go get to fetch a dependency, it was placed in the appropriate path under $GOPATH/src.

For example:

  go get github.com/some/dependency
  

This command would download the dependency and place it in:

  $GOPATH/src/github.com/some/dependency
  

Project Example Using GOPATH

Let’s create a simple Go project to demonstrate how dependencies were managed in GOPATH.

  1. Setting Up the Project First, set up the GOPATH and create the project directory:

      export GOPATH=$HOME/go
    export PATH=$PATH:$GOPATH/bin
    
    mkdir -p $GOPATH/src/example.com/myproject
    cd $GOPATH/src/example.com/myproject
      
  2. Writing the Code Create a simple Go program that uses an external dependency.

      // main.go
    package main
    
    import (
       "fmt"
       "github.com/sirupsen/logrus"
    )
    
    func main() {
       logrus.Info("Hello, World!")
       fmt.Println("Hello, World!")
    }
      
  3. Fetching the Dependency Use the go get command to fetch the logrus dependency:

      go get github.com/sirupsen/logrus
      

    This command downloads the logrus package and places it in $GOPATH/src/github.com/sirupsen/logrus.

  4. Running the Project Run the project using the go run command:

      go run main.go
      

    This should output:

      INFO[0000] Hello, World!
    Hello, World!
      

Vendor Directory

The vendor directory allowed projects to include copies of their dependencies within the project directory itself. This ensured that each project could use specific versions of dependencies without conflict.

Using the Vendor Directory

  1. Create a Vendor Directory: In the root of your project, create a vendor directory:

      mkdir -p myproject/vendor
      
  2. Add Dependencies to Vendor: You can manually copy dependencies to the vendor directory, but it’s more common to use a tool to automate this process.

  3. Example Structure:

      myproject/
    ├── main.go
    └── vendor/
       └── github.com/
          └── some/
                └── dependency/
      

    Dependencies are placed in the vendor directory, ensuring that the project uses the exact versions specified.

Dependency Management Tools

Several tools were developed to help manage dependencies and the vendor directory before Go modules:

  1. Godep: Godep was one of the early tools for managing dependencies. It could save the state of dependencies and restore them when needed.

      go get -u github.com/tools/godep
    cd myproject
    godep save ./...
      

    This created a Godeps directory with a Godeps.json file, storing the versions of the dependencies.

  2. Glide: Glide provided more features and flexibility. It used a glide.yaml file to specify dependencies and their versions.

      curl https://glide.sh/get | sh
    cd myproject
    glide init
    glide install
      

    The glide.yaml file contained the list of dependencies, and glide.lock stored the exact versions.

  3. Govendor: Govendor offered similar functionality, allowing developers to manage dependencies and their versions.

      go get -u github.com/kardianos/govendor
    cd myproject
    govendor init
    govendor add +external
      

    Govendor used a vendor.json file to keep track of dependencies.

Project Example Using GOPATH and Vendor

  1. Set Up Project Directory:

      export GOPATH=$HOME/go
    export PATH=$PATH:$GOPATH/bin
    
    mkdir -p $GOPATH/src/example.com/myproject
    cd $GOPATH/src/example.com/myproject
      
  2. Create main.go and Add Dependency:

      // main.go
    package main
    
    import (
       "github.com/sirupsen/logrus"
    )
    
    func main() {
       logrus.Info("Hello, Vendor!")
    }
      
  3. Fetch Dependency:

      go get github.com/sirupsen/logrus
      
  4. Create Vendor Directory and Copy Dependency:

      mkdir -p vendor/github.com/sirupsen/
    cp -r $GOPATH/src/github.com/sirupsen/logrus vendor/github.com/sirupsen/
      
  5. Update Import Paths (if needed): Update import paths to ensure the project uses the vendor directory first:

      // main.go
    package main
    
    import (
       "example.com/myproject/vendor/github.com/sirupsen/logrus"
    )
    
    func main() {
       logrus.Info("Hello, Vendor!")
    }
      
  6. Run the Project:

      go run main.go