Series Index

Why and What
Projects, Dependencies and Gopls
Minimal Version Selection
Mirrors, Checksums and Athens
Gopls Improvements
Vendoring

Prelude

This is a guest post written by Rohan Challa, a member of the Go team working on gopls.

This document is a follow up to Bill Kennedy’s post on projects, dependencies and gopls. His post showed that gopls did not work well with modules, particularly when adding and removing dependencies from a project inside of your editor. Over the last 3 months, my work on the Go tools team has involved improving support for modules in gopls .

Introduction

When the go command needs to download a new dependency to resolve an import, the project’s go.mod file may be updated as part of running the command. Since many editors run go commands in the background, these changes occur without the developer’s knowledge. We believe this had lead to an editor experience that is confusing and unintuitive.

To address this issue, Go 1.14 supports a new flag: -modfile. This flag instructs the go command to read from and write to an alternate go.mod file instead of the one associated with the project. gopls has been updated to use this new flag so that the editor can provide suggestions, instead of directly changing the project’s go.mod without the developer’s knowledge. This way, gopls gives the developer the information they need to make the best decision for the project.

In this post, I will show you the new features in version 0.4.0 of gopls that will improve the editor experience with modules.

Walkthrough

This post will serve as a companion to Bill’s post and will use the same example code. It will follow a similar workflow, demonstrating how the gopls team has improved the editor experience of adding and removing dependencies. These features work for any editor that supports the Language Server Protocol. In this case, I will be using VS Code with the Go extension.

First, it’s important to confirm that your environment is properly configured.

Listing 1

// Check Go plugin version : >= v0.13.0
$ code --list-extensions --show-versions

// Check gopls version : >= v0.4.0
$ gopls version

// Check Go Version : >= v1.14
$ go version

Listing 1 shows the commands to verify the versions of the VS Code Go extension, gopls, and Go you are using. It’s important to make sure you are using at least version 0.13.0 of the VS Code Go extension, version 0.4.0 of gopls, and version 1.14 of go.

As of the writing of this post, version 0.4.0 is the most recent version of gopls.

Listing 2

// To install the latest version of gopls.
$ cd $HOME
$ GO111MODULE=on go get golang.org/x/tools/gopls@latest

Listing 2 shows how to install the latest version of gopls. Run this command from your $HOME directory.

Module Cache

As Bill did, I am going to clear my module cache before I begin so I have a clean working environment. This will allow me to show you how to handle situations when the module you need hasn’t been downloaded yet to your local module cache.

Listing 3

$ go clean -modcache

Listing 3 shows the call to go clean with the -modcache flag. This is not something you should need to do under normal workflows.

New Project

To start, I am going to create a new project. For more information about creating a new Go project using modules, you can look at this documentation.

Listing 4
https://play.golang.org/p/4zDoHbGT4Mz

$ cd $HOME  
$ mkdir modtest  
$ cd modtest  
$ touch main.go
$ go mod init modtest

The commands in listing 4 create a new project folder containing a source code file: main.go as well as a go.mod file which creates a module.

Listing 5

$ code .

Running the command in listing 5 will start VS Code and open the project. This will also automatically start a gopls server in the background to service VS Code for this project.

Application Code

Listing 6
https://play.golang.org/p/fUha75miwFB

01 package main
02
03 import (
04     "log"
05 )
06
07 func main() {
08     log.Println("This is package main")
09 }

To start, copy the code from listing 6 into the main.go file.

I want the project to use a function from github.com/ardanlabs/conf like Bill did in his post.

Listing 7
https://play.golang.org/p/QioJFbiXGye

07 func main() {
08     var cfg struct {
09         Web struct {
10             APIHost         string        `conf:"default:0.0.0.0:3000"`
11             DebugHost       string        `conf:"default:0.0.0.0:4000"`
12             ReadTimeout     time.Duration `conf:"default:5s"`
13             WriteTimeout    time.Duration `conf:"default:5s"`
14             ShutdownTimeout time.Duration `conf:"default:5s"`
15         }
16     }
17
18     if err := conf.Parse(os.Args[1:], "SALES", &cfg); err != nil {
19         log.Fatal("parsing config : %w", err)
20     }
21     log.Println("This is package main")
22 }

Add the code from listing 7 into the main function and save the file.

Figure 1

Once you save the file, figure 1 shows you the error you should see about the use of conf.Parse. Since there is no information about the conf package in the module cache (remember, I cleared the module cache before I started) gopls can’t direct VS Code to add an import for the conf package. gopls also can’t find any information about that package.

Adding Dependencies

You are responsible for adding this import manually since gopls only knows about packages that are in the standard library or in your local module cache. Once a package exists in your local cache, gopls can add imports for it automatically in all of your projects.

Listing 8

03 import (
04     "log"
05     "os"
06     "time"
07
08     "github.com/ardanlabs/conf"
09 )

Add an import for the conf package inside the import section of the main.go file as shown on line 08 of listing 8 and hit save. Adding this import will direct gopls to download the module to your local module cache. Once this is done, the reference to conf.Parse on line 22 can be resolved. Be aware that you need to wait for the download to complete before the editor can resolve the reference and provide any information about the module.

At this point in Bill’s post, the old version of gopls surfaced a diagnostic with a message about the import which stated, undeclared name: conf. Now, the latest version of gopls doesn’t provide this vague error message, but provides help instead.

Figure 2

You should see a squiggly line under the import to indicate a warning exists. When you hover over the import, you should see the warning as shown in figure 2. This message indicates that the conf module is not listed as a dependency in the project’s go.mod file and that you need to add the new dependency to the go.mod file to satisfy the import. Fortunately, the warning comes with a Quick Fix link.

Figure 3

If you look closely at figure 3, you will see the Quick Fix link. Clicking this link will bring up an option to add the module to the go.mod file. When you select the option to add, the go.mod file is updated but left unsaved so that you can decide to keep the change or not.

Figure 4

The unsaved go.mod file should look like the image in figure 4 with the conf module listed. The version might vary since gopls downloads the latest version of the module. Save the file to keep the changes. At this point, the module with a selected version is recorded in go.mod and the squiggly line with the warning message on the import disappears.

Removing Dependencies

Now, what happens if you change your mind and no longer want to use the conf package?

Listing 9
https://play.golang.org/p/RwC0aWzXf3F

11 func main() {
12     log.Println("This is package main")
13 }

Change the code in the main function as provided in listing 9, which will remove the dependency on the conf package. Once you hit save, gopls will organize the imports and remove any unused dependencies.

Listing 10
https://play.golang.org/p/fUha75miwFB

01 package main
02
03 import (
04     "log"
05 )
06
07 func main() {
08     log.Println("This is package main")
09 }

Listing 10 shows what the main.go file looks like after the imports are cleaned up when you save the file. Since the code is no longer using the conf dependency, it should be removed from the project’s go.mod file.

In Bill’s previous post, he needed to leave VS Code and run go mod tidy to clean up the projects go.mod file. Now, you can tidy the go.mod file from within VS Code.

Figure 5

Navigate to the go.mod file, it should look like figure 5. You should see a squiggly line on line 5 related to the conf module.

Figure 6

When you hover over the module, you will see the warning in figure 6. You are also provided a Quick Fix link, which when clicked will provide the option to remove the dependency from the go.mod file.

Figure 7

Figure 7 shows how after clicking the Quick Fix option the module is removed. Don’t forget to save the go.mod file after this operation.

Upgrading Dependencies

Another new feature is the ability to upgrade dependencies to their latest version from within VS Code. This feature works with any version of Go that supports modules. To show this, I will put the code that requires the conf module back in the main function.

Listing 11
https://play.golang.org/p/NB6CMA52Zyf

01 package main
02
03 import (
04     "log"
05     "os"
06     "time"
07
08     "github.com/ardanlabs/conf"
09 )
10
11 func main() {
12     var cfg struct {
13         Web struct {
14             APIHost         string        `conf:"default:0.0.0.0:3000"`
15             DebugHost       string        `conf:"default:0.0.0.0:4000"`
16             ReadTimeout     time.Duration `conf:"default:5s"`
17             WriteTimeout    time.Duration `conf:"default:5s"`
18             ShutdownTimeout time.Duration `conf:"default:5s"`
19         }
20     }
21
22     if err := conf.Parse(os.Args[1:], "SALES", &cfg); err != nil {
23         log.Fatal("parsing config : %w", err)
24     }
25     log.Println("This is package main")
26 }

Listing 11 shows the main.go file again using the conf module. Next, I will intentionally use an older version of the conf module in the go.mod file.

Listing 12

01 module modtest
02
03 go 1.14
04
05 require github.com/ardanlabs/conf v1.2.0

Listing 12 shows the go.mod file after I manually changed the module from using the latest version to version 1.2.0. When I save that change, I get a suggestion about upgrading the version.

Figure 8

In figure 8, you see a suggestion link to upgrade the conf module from version 1.2.0 to version 1.2.1. Version 1.2.1 is the latest greatest version at the time of writing this post. When you click on this suggestion link, gopls will upgrade the dependency to the version specified. You also see a suggestion link at the top of the go.mod file. This suggestion link will upgrade all the modules in the files that have an individual suggestion.

Figure 9

After clicking either suggestion link, your go.mod file will list the latest greatest version for the conf module like the image in figure 9.

Conclusion

The gopls team would like to thank Bill for providing such a detailed post explaining the pain points of using gopls with Go modules. The steps outlined in the post helped us test and better understand user interactions with modules. Experience reports are very helpful, and we value your feedback.

The features described in this post are available once you upgrade your development environment to Go 1.14 and gopls to the latest version (v0.4.0), pre-releases included.

To see these new features in action, take a look at this screencast, which demonstrates the features outlined in this post.

Issues with gopls can be filed on the issue tracker.

Any questions can be asked on Slack inside of the #gopls channel, use the invite app for access.

Trusted by top technology companies

We've built our reputation as educators and bring that mentality to every project. When you partner with us, your team will learn best practices and grow along the way.

30,000+

Engineers Trained

1,000+

Companies Worldwide

12+

Years in Business