README.md
May 13, 2019 ยท View on GitHub
Semantic import versioning by example
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.
Overview
This example creates two modules:
- Module
github.com/go-modules-by-example/goinfocontains two packages that give us information about the Go programming language:- Package
github.com/go-modules-by-example/goinfo/contributorsgives detail about contributors to the language - Package
github.com/go-modules-by-example/goinfo/designersgives the names of those who designed the language. It importscontributorsto create a trivial intra-module dependency
- Package
- Module
github.com/go-modules-by-example/peopleprinterusesgithub.com/go-modules-by-example/goinfoto 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.
Walk-through
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/goinfo@v0.0.0 -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/goinfo@v1.0.0 -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/mod@v0.2.1 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
Version details
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