aboutsummaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
1 files changed, 556 insertions, 0 deletions
diff --git a/content/using-go-modules.article b/content/using-go-modules.article
new file mode 100644
index 0000000..bce1875
--- /dev/null
+++ b/content/using-go-modules.article
@@ -0,0 +1,556 @@
+Using Go Modules
+19 Mar 2019
+Tags: tools, versioning
+
+Tyler Bui-Palsulich
+
+Eno Compton
+
+* Introduction
+
+Go 1.11 and 1.12 include preliminary
+[[https://golang.org/doc/go1.11#modules][support for modules]],
+Go’s
+[[https://blog.golang.org/versioning-proposal][new dependency management system]]
+that makes dependency version information explicit
+and easier to manage.
+This blog post is a tutorial to introduction to the basic operations needed
+to get started using modules.
+A followup post will cover releasing modules for others to use.
+
+A module is a collection of
+[[https://golang.org/ref/spec#Packages][Go packages]]
+stored in a file tree with a `go.mod` file at its root.
+The `go.mod` file defines the module’s _module_path_,
+which is also the import path used for the root directory,
+and its _dependency_requirements_,
+which are the other modules needed for a successful build.
+Each dependency requirement is
+written as a module path and a specific
+[[http://semver.org/][semantic version]].
+
+As of Go 1.11, the go command enables the use of modules
+when the current directory or any parent directory has a `go.mod`,
+provided the directory is _outside_ `$GOPATH/src`.
+(Inside `$GOPATH/src`, for compatibility, the go command
+still runs in the old GOPATH mode, even if a `go.mod` is found.
+See the
+[[https://golang.org/cmd/go/#hdr-Preliminary_module_support][go command documentation]]
+for details.)
+Starting in Go 1.13, module mode will be the default for all development.
+
+This post walks through a sequence of common operations
+that arise when developing Go code with modules:
+
+
+- Creating a new module.
+- Adding a dependency.
+- Upgrading dependencies.
+- Adding a dependency on a new major version.
+- Upgrading a dependency to a new major version.
+- Removing unused dependencies.
+
+* Creating a new module
+
+Let's create a new module.
+
+Create a new, empty directory somewhere outside `$GOPATH/src`,
+`cd` into that directory, and then create a new source file, `hello.go`:
+
+ package hello
+
+ func Hello() string {
+ return "Hello, world."
+ }
+
+Let's write a test, too, in `hello_test.go`:
+
+ package hello
+
+ import "testing"
+
+ func TestHello(t *testing.T) {
+ want := "Hello, world."
+ if got := Hello(); got != want {
+ t.Errorf("Hello() = %q, want %q", got, want)
+ }
+ }
+
+At this point, the directory contains a package, but not a module,
+because there is no `go.mod` file.
+If we were working in `/home/gopher/hello` and ran `go`test` now,
+we'd see:
+
+ $ go test
+ PASS
+ ok _/home/gopher/hello 0.020s
+ $
+
+The last line summarizes the overall package test.
+Because we are working outside `$GOPATH`
+and also outside any module,
+the `go` command knows no import path for
+the current directory and makes up a fake one based
+on the directory name: `_/home/gopher/hello`.
+
+Let's make the current directory the root of a module
+by using `go`mod`init` and then try `go`test` again:
+
+ $ go mod init example.com/hello
+ go: creating new go.mod: module example.com/hello
+ $ go test
+ PASS
+ ok example.com/hello 0.020s
+ $
+
+Congratulations! You’ve written and tested your first module.
+
+The `go`mod`init` command wrote a `go.mod` file:
+
+ $ cat go.mod
+ module example.com/hello
+
+ go 1.12
+ $
+
+The `go.mod` file only appears in the root of the module.
+Packages in subdirectories have import paths consisting of
+the module path plus the path to the subdirectory.
+For example, if we created a subdirectory `world`,
+we would not need to (nor want to) run `go`mod`init` there.
+The package would automatically be recognized as part of the
+`example.com/hello` module, with import path
+`example.com/hello/world`.
+
+* Adding a dependency
+
+The primary motivation for Go modules was to improve the
+experience of using (that is, adding a dependency on)
+code written by other developers.
+
+Let's update our `hello.go` to import `rsc.io/quote`
+and use it to implement `Hello`:
+
+ package hello
+
+ import "rsc.io/quote"
+
+ func Hello() string {
+ return quote.Hello()
+ }
+
+Now let’s run the test again:
+
+ $ go test
+ go: finding rsc.io/quote v1.5.2
+ go: downloading rsc.io/quote v1.5.2
+ go: extracting rsc.io/quote v1.5.2
+ go: finding rsc.io/sampler v1.3.0
+ go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
+ go: downloading rsc.io/sampler v1.3.0
+ go: extracting rsc.io/sampler v1.3.0
+ go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
+ go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
+ PASS
+ ok example.com/hello 0.023s
+ $
+
+
+The `go` command resolves imports by using the specific
+dependency module versions listed in `go.mod`.
+When it encounters an `import` of a package not provided
+by any module in `go.mod`, the `go` command automatically
+looks up the module containing that package and adds it to
+`go.mod`, using the latest version.
+(“Latest” is defined as the
+latest tagged stable (non-[[https://semver.org/#spec-item-9][prerelease]]) version,
+or else the latest tagged prerelease version,
+or else the latest untagged version.)
+In our example, `go`test` resolved the new import `rsc.io/quote`
+to the module `rsc.io/quote`v1.5.2`.
+It also downloaded two dependencies used by `rsc.io/quote`,
+namely `rsc.io/sampler` and `golang.org/x/text`.
+Only direct dependencies are recorded in the `go.mod` file:
+
+ $ cat go.mod
+ module example.com/hello
+
+ go 1.12
+
+ require rsc.io/quote v1.5.2
+ $
+
+A second `go`test` command will not repeat this work,
+since the `go.mod` is now up-to-date and the downloaded
+modules are cached locally (in `$GOPATH/pkg/mod`):
+
+ $ go test
+ PASS
+ ok example.com/hello 0.020s
+ $
+
+
+Note that while the `go` command makes adding a new dependency
+quick and easy, it is not without cost.
+Your module now literally _depends_ on the new dependency
+in critical areas such as correctness, security, and proper licensing,
+just to name a few.
+For more considerations, see Russ Cox's blog post,
+“[[https://research.swtch.com/deps][Our Software Dependency Problem]].”
+
+As we saw above, adding one direct dependency often
+brings in other indirect dependencies too.
+The command `go`list`-m`all` lists the current module
+and all its dependencies:
+
+ $ go list -m all
+ example.com/hello
+ golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
+ rsc.io/quote v1.5.2
+ rsc.io/sampler v1.3.0
+ $
+
+In the `go`list` output, the current module,
+also known as the _main_module_,
+is always the first line,
+followed by dependencies sorted by module path.
+
+The `golang.org/x/text` version `v0.0.0-20170915032832-14c0d48ead0c`
+is an example of a
+[[https://golang.org/cmd/go/#hdr-Pseudo_versions][pseudo-version]],
+which is the `go` command's version syntax
+for a specific untagged commit.
+
+In addition to `go.mod`, the `go` command
+maintains a file named `go.sum` containing
+the expected [[https://golang.org/cmd/go/#hdr-Module_downloading_and_verification][cryptographic hashes]] of the content of specific module versions:
+
+ $ cat go.sum
+ golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO...
+ golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq...
+ rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3...
+ rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX...
+ rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q...
+ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9...
+ $
+
+The `go` command uses the `go.sum` file to ensure that
+future downloads of these modules retrieve the same bits
+as the first download,
+to ensure the modules your project depends on
+do not change unexpectedly,
+whether for malicious, accidental, or other reasons.
+Both `go.mod` and `go.sum` should be checked into version control.
+
+* Upgrading dependencies
+
+With Go modules, versions are referenced with semantic version tags.
+A semantic version has three parts: major, minor, and patch.
+For example, for `v0.1.2`, the major version is 0, the minor version is 1,
+and the patch version is 2.
+Let's walk through a couple minor version upgrades.
+In the next section, we’ll consider a major version upgrade.
+
+From the output of `go`list`-m`all`,
+we can see we're using an untagged version of `golang.org/x/text`.
+Let's upgrade to the latest tagged version and test that everything still works:
+
+ $ go get golang.org/x/text
+ go: finding golang.org/x/text v0.3.0
+ go: downloading golang.org/x/text v0.3.0
+ go: extracting golang.org/x/text v0.3.0
+ $ go test
+ PASS
+ ok example.com/hello 0.013s
+ $
+
+Woohoo! Everything passes.
+Let's take another look at `go`list`-m`all` and the `go.mod` file:
+
+ $ go list -m all
+ example.com/hello
+ golang.org/x/text v0.3.0
+ rsc.io/quote v1.5.2
+ rsc.io/sampler v1.3.0
+ $ cat go.mod
+ module example.com/hello
+
+ go 1.12
+
+ require (
+ golang.org/x/text v0.3.0 // indirect
+ rsc.io/quote v1.5.2
+ )
+ $
+
+The `golang.org/x/text` package has been upgraded to the latest tagged version (`v0.3.0`).
+The `go.mod` file has been updated to specify `v0.3.0` too.
+The `indirect` comment indicates a dependency is not used directly
+by this module, only indirectly by other module dependencies.
+See `go`help`modules` for details.
+
+Now let's try upgrading the `rsc.io/sampler` minor version.
+Start the same way, by running `go`get` and running tests:
+
+ $ go get rsc.io/sampler
+ go: finding rsc.io/sampler v1.99.99
+ go: downloading rsc.io/sampler v1.99.99
+ go: extracting rsc.io/sampler v1.99.99
+ $ go test
+ --- FAIL: TestHello (0.00s)
+ hello_test.go:8: Hello() = "99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."
+ FAIL
+ exit status 1
+ FAIL example.com/hello 0.014s
+ $
+
+Uh, oh! The test failure shows that the
+latest version of `rsc.io/sampler` is incompatible with our usage.
+Let's list the available tagged versions of that module:
+
+ $ go list -m -versions rsc.io/sampler
+ rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99
+ $
+
+We had been using v1.3.0; v1.99.99 is clearly no good.
+Maybe we can try using v1.3.1 instead:
+
+ $ go get rsc.io/sampler@v1.3.1
+ go: finding rsc.io/sampler v1.3.1
+ go: downloading rsc.io/sampler v1.3.1
+ go: extracting rsc.io/sampler v1.3.1
+ $ go test
+ PASS
+ ok example.com/hello 0.022s
+ $
+
+Note the explicit `@v1.3.1` in the `go`get` argument.
+In general each argument passed to `go`get` can take
+an explicit version; the default is `@latest`,
+which resolves to the latest version as defined earlier.
+
+* Adding a dependency on a new major version
+
+Let's add a new function to our package:
+`func`Proverb` returns a Go concurrency proverb,
+by calling `quote.Concurrency`, which is provided by
+the module `rsc.io/quote/v3`.
+First we update `hello.go` to add the new function:
+
+ package hello
+
+ import (
+ "rsc.io/quote"
+ quoteV3 "rsc.io/quote/v3"
+ )
+
+ func Hello() string {
+ return quote.Hello()
+ }
+
+ func Proverb() string {
+ return quoteV3.Concurrency()
+ }
+
+Then we add a test to `hello_test.go`:
+
+ func TestProverb(t *testing.T) {
+ want := "Concurrency is not parallelism."
+ if got := Proverb(); got != want {
+ t.Errorf("Proverb() = %q, want %q", got, want)
+ }
+ }
+
+Then we can test our code:
+
+ $ go test
+ go: finding rsc.io/quote/v3 v3.1.0
+ go: downloading rsc.io/quote/v3 v3.1.0
+ go: extracting rsc.io/quote/v3 v3.1.0
+ PASS
+ ok example.com/hello 0.024s
+ $
+
+
+Note that our module now depends on both `rsc.io/quote` and `rsc.io/quote/v3`:
+
+ $ go list -m rsc.io/q...
+ rsc.io/quote v1.5.2
+ rsc.io/quote/v3 v3.1.0
+ $
+
+Note that our module now depends on both `rsc.io/quote` and `rsc.io/quote/v3`:
+
+ $ go list -m rsc.io/q...
+ rsc.io/quote v1.5.2
+ rsc.io/quote/v3 v3.1.0
+ $
+
+Each different major version (`v1`, `v2`, and so on) of a Go module
+uses a different module path: starting at `v2`, the path must end in the major version.
+In the example, `v3` of `rsc.io/quote` is no longer `rsc.io/quote`: instead,
+it is identified by the module path `rsc.io/quote/v3`.
+This convention is called
+[[https://research.swtch.com/vgo-import][semantic import versioning]],
+and it gives incompatible packages (those with different major versions)
+different names.
+In contrast, `v1.6.0` of `rsc.io/quote` should be backwards-compatible
+with `v1.5.2`, so it reuses the name `rsc.io/quote`.
+(In the previous section, `rsc.io/sampler` `v1.99.99`
+_should_ have been backwards-compatible
+with `rsc.io/sampler` `v1.3.0`, but bugs or incorrect client assumptions about
+module behavior can both happen.)
+
+The `go` command allows a build to include at most one version of
+any particular module path, meaning at most one of each major
+version: one `rsc.io/quote`, one `rsc.io/quote/v2`, one `rsc.io/quote/v3`,
+and so on.
+This gives module authors a clear rule about possible duplication
+of a single module path: it is impossible for a program to build with both
+`rsc.io/quote`v1.5.2` and `rsc.io/quote`v1.6.0`.
+At the same time, allowing different major versions of a module
+(because they have different paths)
+gives module consumers the ability to
+upgrade to a new major version incrementally.
+In this example, we wanted to use `quote.Concurrency` from `rsc/quote/v3`v3.1.0`
+but are not yet ready to migrate our uses of `rsc.io/quote`v1.5.2`.
+The ability to migrate incrementally
+is especially important in a large program or codebase.
+
+* Upgrading a dependency to a new major version
+
+Let's complete our conversion from using `rsc.io/quote` to using only `rsc.io/quote/v3`.
+Because of the major version change, we should expect that some APIs may have
+been removed, renamed, or otherwise changed in incompatible ways.
+Reading the docs, we can see that `Hello` has become `HelloV3`:
+
+ $ go doc rsc.io/quote/v3
+ package quote // import "rsc.io/quote"
+
+ Package quote collects pithy sayings.
+
+ func Concurrency() string
+ func GlassV3() string
+ func GoV3() string
+ func HelloV3() string
+ func OptV3() string
+ $
+
+(There is also a
+[[https://golang.org/issue/30778][known bug]] in the output;
+the displayed import path has incorrectly dropped the `/v3`.)
+
+We can update our use of `quote.Hello()` in `hello.go` to use `quoteV3.Hello()`:
+
+ package hello
+
+ import quoteV3 "rsc.io/quote/v3"
+
+ func Hello() string {
+ return quoteV3.Hello()
+ }
+
+ func Proverb() string {
+ return quoteV3.Concurrency()
+ }
+
+And then at this point, there's no need for the renamed import anymore,
+so we can undo that:
+
+ package hello
+
+ import "rsc.io/quote/v3"
+
+ func Hello() string {
+ return quote.Hello()
+ }
+
+ func Proverb() string {
+ return quote.Concurrency()
+ }
+
+
+Let's re-run the tests to make sure everything is working:
+
+ $ go test
+ PASS
+ ok example.com/hello 0.014s
+
+* Removing unused dependencies
+
+We've removed all our uses of `rsc.io/quote`,
+but it still shows up in `go`list`-m`all` and in our `go.mod` file:
+
+ $ go list -m all
+ example.com/hello
+ golang.org/x/text v0.3.0
+ rsc.io/quote v1.5.2
+ rsc.io/quote/v3 v3.1.0
+ rsc.io/sampler v1.3.1
+ $ cat go.mod
+ module example.com/hello
+
+ go 1.12
+
+ require (
+ golang.org/x/text v0.3.0 // indirect
+ rsc.io/quote v1.5.2
+ rsc.io/quote/v3 v3.0.0
+ rsc.io/sampler v1.3.1 // indirect
+ )
+ $
+
+Why? Because building a single package, like with `go`build` or `go`test`,
+can easily tell when something is missing and needs to be added,
+but not when something can safely be removed.
+Removing a dependency can only be done after
+checking all packages in a module,
+and all possible build tag combinations for those packages.
+An ordinary build command does not load this information,
+and so it cannot safely remove dependencies.
+
+The `go`mod`tidy` command cleans up these unused dependencies:
+
+ $ go mod tidy
+ $ go list -m all
+ example.com/hello
+ golang.org/x/text v0.3.0
+ rsc.io/quote/v3 v3.1.0
+ rsc.io/sampler v1.3.1
+ $ cat go.mod
+ module example.com/hello
+
+ go 1.12
+
+ require (
+ golang.org/x/text v0.3.0 // indirect
+ rsc.io/quote/v3 v3.1.0
+ rsc.io/sampler v1.3.1 // indirect
+ )
+
+ $ go test
+ PASS
+ ok example.com/hello 0.020s
+ $
+
+* Conclusion
+
+Go modules are the future of dependency management in Go.
+Module functionality is now available in all supported Go versions
+(that is, in Go 1.11 and Go 1.12).
+
+This post introduced these workflows using Go modules:
+
+- `go`mod`init` creates a new module, initializing the `go.mod` file that describes it.
+- `go`build`, `go`test`, and other package-building commands add new dependencies to `go.mod` as needed.
+- `go`list`-m`all` prints the current module’s dependencies.
+- `go`get` changes the required version of a dependency (or adds a new dependency).
+- `go`mod`tidy` removes unused dependencies.
+
+We encourage you to start using modules in your local development
+and to add `go.mod` and `go.sum` files to your projects.
+To provide feedback and help shape the future of dependency management in Go,
+please send us
+[[https://golang.org/issue/new][bug reports]] or [[https://golang.org/wiki/ExperienceReports][experience reports]].
+
+Thanks for all your feedback and help improving modules.