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
usesgithub.com/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