When building a library or an application in Go, we should care about having all our dependencies defined or included in the repository. If we depend on some third party code to work in a certain way, we should have some specific configuration saying that those are the versions that allow our core do its part correctly. With dep, Go's official vendoring tool it is a bit tricky since all the dependencies are moved to one place and then used across all the code. This raises a problem if we have one library in two different versions imported in two places at the same time. Fortunately, we can do something about that.

The problem

Imagine that we have a library called golang-examples-log that adds a formatted time to the list of things we want to be logged to the console, and in its version 1.0.0 has an API of:

// golang-examples-log/log.go
...
func Println(v ...interface{}) {
  v = append(v, 0)
  copy(v[1:], v[:])
  v[0] = time.Now().Format(time.RFC3339)
  fmt.Println(v...)
}

Then, in the second (2.0.0) version the authors decided that we should be able to pass any time.Time to the function and have that value formatted as a prefix of the log message:

// golang-examples-log/log.go
...
func Println(t time.Time, v ...interface{}) {
  v = append(v, 0)
  copy(v[1:], v[:])
  v[0] = t.Format(time.RFC3339)
  fmt.Println(v...)
}

Unfortunately, in the meantime, somebody created another library, called golang-examples-ctxlog which prints some values from the context passed to the function, but it was created on top of golang-examples-log@1.0.0:

// golang-examples-ctxlog/ctxlog.go
package ctxlog
import(
  ...
  log "github.com/mycodesmells/golang-examples-log"
)

func Println(ctx context.Context, v ...interface{}) {
  label, ok := ctx.Value("label").(string)
  if ok && label != "" {
    v = append(v, 0)
    copy(v[1:], v[:])
    v[0] = fmt.Sprintf("[label=%s]", label)
  }
  log.Println(v...)
}

Now when we want to use both libraries in our application, we have a problem with a version of golang-examples-log. If we want to use one that fits the logger with context, we would need to settle for 1.0.0, but we don't want to do that. We want to use the latest features and go forward, not backward with our codebase. When we try to choose the latest versions of both dependencies...

# app/Gopkg.toml
[[constraint]]
  name = "github.com/mycodesmells/golang-examples-ctxlog"
  version = "1.0.0"

[[constraint]]
  name = "github.com/mycodesmells/golang-examples-log"
  version = "2.0.0"

... and try to dep ensure the dependencies, we get an error:

$ dep ensure
Solving failure: No versions of github.com/mycodesmells/golang-examples-ctxlog met constraints:
  1.0.0: Could not introduce github.com/mycodesmells/golang-examples-ctxlog@1.0.0, as it has a dependency on github.com/mycodesmells/golang-examples-log with constraint ^1.0.0, which has no overlap with existing constraint ^2.0.0 from (root)
  master: Could not introduce github.com/mycodesmells/golang-examples-ctxlog@master, as it has a dependency on github.com/mycodesmells/golang-examples-log with constraint ^1.0.0, which has no overlap with existing constraint ^2.0.0 from (root)

Right now you might feel disappointed an overwhelmed, but fear no more! There is a way out of this mess.

The solution

We can safely assume, that the authors of golang-examples-ctxlog didn't update their library just yet. So, as a good citizen in open source community, we should go ahead and fork the repository and apply the changes. But wait, wouldn't it force us to rewrite all import paths to aim at our fork? No! dep allows a specific constraint to point to your fork, with import still stating that we are using the original repository. This is very handy for handling forks, where we know that some feature is coming up (and we want to use it already), but it's not yet merged with master branch of the main repo. We don't want to switch the imports back and forth, so this is a true life (and time) saver.

In order to use code from a different location than defined in an import, we need to add a source property to a certain constraint in Gopkg.toml:

[[constraint]]
  name = "github.com/mycodesmells/golang-examples-ctxlog"
  source = "github.com/slomek/golang-examples-ctxlog"

[[constraint]]
  name = "github.com/mycodesmells/golang-examples-log"
  version = "2.0.0"

Now when we run dep ensure it works without any errors or warnings! Also, make sure that you submit a PR to the original repository with your changes!

Summary

While this might not be the most elegant solution, it might come in handy one day if you are already using dep as your vendoring tool. The full source codes for this example are available on Github: