Semantic import versioning is a key concept for Go modules. Russ' Semantic Import Versioning post goes into this in great detail, as does the wiki.
In this example we introduce the concept of semantic import versioning by example from the ground up, including making a breaking change to force a major version change.
This example creates two modules:
- Module
github.com/go-modules-by-example/goinfo
contains two packages that give us information about the Go programming language:- Package
github.com/go-modules-by-example/goinfo/contributors
gives detail about contributors to the language - Package
github.com/go-modules-by-example/goinfo/designers
gives the names of those who designed the language. It importscontributors
to create a trivial intra-module dependency
- Package
- Module
github.com/go-modules-by-example/peopleprinter
usesgit.colasdn.top/go-modules-by-example/goinfo
to print some interesting information to standard out
Each module is committed to its own repository. The results of this example can be seen at https://github.com/go-modules-by-example/goinfo and https://github.com/go-modules-by-example/peopleprinter respectively.
Prepare the github.com/go-modules-by-example/goinfo
module:
$ mkdir -p /home/gopher/scratchpad/goinfo
$ cd /home/gopher/scratchpad/goinfo
$ git init -q
$ git remote add origin https://github.com/go-modules-by-example/goinfo
$ go mod init
go: creating new go.mod: module github.com/go-modules-by-example/goinfo
$ mkdir contributors designers
Create the contributors
package:
$ cat contributors/contributors.go
package contributors
type Person struct {
FullName string
}
var all = [...]Person{
Person{FullName: "Robert Griesemer"},
Person{FullName: "Rob Pike"},
Person{FullName: "Ken Thompson"},
Person{FullName: "Russ Cox"},
Person{FullName: "Ian Lance Taylor"},
}
func Details() []Person {
res := all
return res[:]
}
Create the designers
package:
$ cat designers/designers.go
package designers
import "github.com/go-modules-by-example/goinfo/contributors"
func Names() []string {
var res []string
for _, p := range contributors.Details() {
switch p.FullName {
case "Rob Pike", "Ken Thompson", "Robert Griesemer":
res = append(res, p.FullName)
}
}
return res
}
Prepare the github.com/go-modules-by-example/peopleprinter
module:
$ cd /home/gopher/scratchpad
$ mkdir peopleprinter
$ cd peopleprinter
$ git init -q
$ git remote add origin https://github.com/go-modules-by-example/peopleprinter
$ go mod init
go: creating new go.mod: module github.com/go-modules-by-example/peopleprinter
Create a single main
package in github.com/go-modules-by-example/peopleprinter
.
$ cat main.go
package main
import (
"fmt"
"github.com/go-modules-by-example/goinfo/designers"
"strings"
)
func main() {
fmt.Printf("The designers of Go: %v\n", strings.Join(designers.Names(), ", "))
}
We have not yet published a commit or version of github.com/go-modules-by-example/goinfo
. Hence for now we use a replace
directive to
use the github.com/go-modules-by-example/goinfo
defined locally:
$ go mod edit -require=github.com/go-modules-by-example/[email protected] -replace=github.com/go-modules-by-example/goinfo=/home/gopher/scratchpad/goinfo
We see the effect in the go.mod
file:
$ cat go.mod
module github.com/go-modules-by-example/peopleprinter
go 1.12
require github.com/go-modules-by-example/goinfo v0.0.0
replace github.com/go-modules-by-example/goinfo => /home/gopher/scratchpad/goinfo
Run the main
package as a "test":
$ go run .
The designers of Go: Robert Griesemer, Rob Pike, Ken Thompson
Commit, push and tag to release version v1.0.0
of github.com/go-modules-by-example/goinfo
:
$ cd /home/gopher/scratchpad/goinfo
$ git add -A
$ git commit -q -am 'Initial commit of goinfo'
$ git push -q origin
$ git tag v1.0.0
$ git push -q origin v1.0.0
Use version v1.0.0
of github.com/go-modules-by-example/goinfo
in github.com/go-modules-by-example/peopleprinter
:
$ cd /home/gopher/scratchpad/peopleprinter
$ go mod edit -require=github.com/go-modules-by-example/[email protected] -dropreplace=github.com/go-modules-by-example/goinfo
Confirm the effect in go.mod
:
$ cat go.mod
module github.com/go-modules-by-example/peopleprinter
go 1.12
require github.com/go-modules-by-example/goinfo v1.0.0
Re-run our "test":
$ go run .
go: finding github.com/go-modules-by-example/goinfo v1.0.0
go: downloading github.com/go-modules-by-example/goinfo v1.0.0
go: extracting github.com/go-modules-by-example/goinfo v1.0.0
The designers of Go: Robert Griesemer, Rob Pike, Ken Thompson
Commit, push and tag version v1.0.0
of github.com/go-modules-by-example/peopleprinter
:
$ git add -A
$ git commit -q -am 'Initial commit of peopleprinter'
$ git push -q origin
$ git tag v1.0.0
$ git push -q origin v1.0.0
At this point, let's assume we want to make a breaking change to github.com/go-modules-by-example/goinfo
. This will require us to
release a new major version of github.com/go-modules-by-example/goinfo
. According to semantic import versioning, this new major version
will be imported as github.com/go-modules-by-example/goinfo/v2
.
Before we make the breaking change, we need to decide what git repository structure we want going forward. This is
largely determined by whether we want to maintain support for the v1
series any longer. This particular question is
covered in more detail in the wiki. For this
example we use the major branch strategy so that we can easily make changes and releases to both the v1
and v2
series.
We create a v1
branch and push:
$ cd /home/gopher/scratchpad/goinfo
$ git branch master.v1
$ git push -q origin master.v1
remote:
remote: Create a pull request for 'master.v1' on GitHub by visiting:
remote: https://github.com/go-modules-by-example/goinfo/pull/new/master.v1
remote:
Now we make the breaking change to designers
:
$ cat designers/designers.go
package designers
import "github.com/go-modules-by-example/goinfo/contributors"
func FullNames() []string {
var res []string
for _, p := range contributors.Details() {
switch p.FullName {
case "Rob Pike", "Ken Thompson", "Robert Griesemer":
res = append(res, p.FullName)
}
}
return res
}
Now we need to prepare github.com/go-modules-by-example/goinfo
to become a v2
module. This will involve fixing our go.mod
and any
intra-module import paths. We will use github.com/marwan-at-work/mod
to
do this.
Install mod
using gobin
:
$ gobin github.com/marwan-at-work/mod/cmd/mod
Installed github.com/marwan-at-work/mod/cmd/[email protected] to /home/gopher/bin/mod
Verify mod
is working:
$ mod -help
NAME:
mod - upgrade/downgrade semantic import versioning
USAGE:
mod [global options] command [command options] [arguments...]
...
Prepare our module for v2
(the next major version):
$ cd /home/gopher/scratchpad/goinfo
$ mod upgrade
$ go mod edit -module github.com/go-modules-by-example/goinfo/v2
Commit, push and tag a new major version of github.com/go-modules-by-example/goinfo
, which we now refer to as github.com/go-modules-by-example/goinfo/v2
:
$ git add -A
$ git commit -q -am 'Breaking commit of goinfo'
$ git push -q origin
$ git tag v2.0.0
$ git push -q origin v2.0.0
Review the diff between v1.0.0
and v2.0.0
.
Adapt peopleprinter
to use both github.com/go-modules-by-example/goinfo
and github.com/go-modules-by-example/goinfo/v2
:
$ cat main.go
package main
import (
"fmt"
"github.com/go-modules-by-example/goinfo/designers"
v2designers "github.com/go-modules-by-example/goinfo/v2/designers"
"strings"
)
func main() {
fmt.Printf("The designers of Go: %v\n", strings.Join(designers.Names(), ", "))
fmt.Printf("The designers of Go: %v\n", strings.Join(v2designers.FullNames(), ", "))
}
Check the new behaviour via a run "test":
$ cd /home/gopher/scratchpad/peopleprinter
$ go run .
go: finding github.com/go-modules-by-example/goinfo/v2 v2.0.0
go: downloading github.com/go-modules-by-example/goinfo/v2 v2.0.0
go: extracting github.com/go-modules-by-example/goinfo/v2 v2.0.0
The designers of Go: Robert Griesemer, Rob Pike, Ken Thompson
The designers of Go: Robert Griesemer, Rob Pike, Ken Thompson
Review all the dependencies of peopleprinter
:
$ go list -m all
github.com/go-modules-by-example/peopleprinter
github.com/go-modules-by-example/goinfo v1.0.0
github.com/go-modules-by-example/goinfo/v2 v2.0.0
Commit, push and tag a new version of peopleprinter
:
$ git add -A
$ git commit -q -am 'Use goinfo v2 in peopleprinter'
$ git push -q origin
$ git tag v1.1.0
$ git push -q origin v1.1.0
go version go1.12.5 linux/amd64
/home/gopher/.cache/gobin/github.com/marwan-at-work/mod/@v/v0.2.1/github.com/marwan-at-work/mod/cmd/mod/mod