| Using Go Modules |
| 19 Mar 2019 |
| Tags: tools, versioning |
| |
| Tyler Bui-Palsulich |
| |
| Eno Compton |
| |
| * Introduction |
| |
| This post is part 1 in a series. |
| |
| - *Part*1*—*Using*Go*Modules* (this post) |
| - Part 2 — [[/migrating-to-go-modules][Migrating To Go Modules]] |
| - Part 3 — [[/publishing-go-modules][Publishing Go Modules]] |
| - Part 4 — [[/v2-go-modules][Go Modules: v2 and Beyond]] |
| |
| 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 an introduction to the basic operations needed |
| to get started using modules. |
| |
| 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 |
| $ |
| |
| 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.HelloV3()`: |
| |
| package hello |
| |
| import quoteV3 "rsc.io/quote/v3" |
| |
| func Hello() string { |
| return quoteV3.HelloV3() |
| } |
| |
| 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.HelloV3() |
| } |
| |
| 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. |