Go Modules 教程:項目、依賴和 gopls

引言

模塊是集成到 Go 系統中,爲依賴管理提供支持。這意味着模塊幾乎可以觸及任何與源代碼相關的內容,包括編輯器支持。爲了向編輯器提供模塊支持(以及其他原因) ,Go 團隊構建了一個名爲 gopls[1] 的服務,它實現了語言服務器協議(LSP[2])。LSP 最初是由微軟爲 VSCode 開發的,現在已經成爲一個開放標準。該協議的思想是爲編輯器提供對語言特性的支持,比如自動完成、定義和查找所有引用。

當你使用模塊和 VSCode 時,在編輯器中點擊保存將不再直接運行 go build 命令。現在發生的情況是,一個請求被髮送給 gopls,gopls 運行適當的 Go 命令和相關的 API 來提供編輯器反饋和支持。Gopls 還可以向編輯器發送信息而不需要請求。有時,由於 LSP 的特性或運行 Go 命令的固有延遲,編輯器似乎滯後或與代碼更改不同步。團隊正在努力達到一個 1.0 版本的 gopls 來處理這些邊緣情況,這樣你就可以有最平滑的編輯器體驗。

在這篇文章中,我將介紹在項目中添加和刪除依賴項的基本工作流程。本文使用了 VSCode 編輯器、 gopls 的 0.2.0 版本和 Go 的 1.13.3 版本。(GCTT 注:目前 gopls 的版本做了大量的優化,個人感覺已經很好用了)

本文是 Go 語言中文網組織的 GCTT 翻譯,發佈在 Go 語言中文網公衆號,轉載請聯繫我們授權。

模塊緩存

爲了幫助加快構建速度並快速更新項目中的依賴關係更改,Go 維護一個緩存,其中包含它在本地計算機上下載的所有模塊。該緩存可以在 $GOPATH/pkg 中找到。如果沒有 GOPATH 設置,默認的 GOPATH 是 $HOME/go

注意: 有一個提案建議提供一個環境變量,允許用戶控制模塊緩存的位置。如果沒有更改,$GOPATH/pkg 將是默認值。(GCTT 注:現在已經有了這個環境變量:GOMODCACHE)

清單 1:

$HOME/code/go/pkg
$ ls -l
total 0
drwxr-xr-x  11 bill  staff  352 Oct 16 15:53 mod
drwxr-xr-x   3 bill  staff   96 Oct  3 16:49 sumdb

清單 1 顯示了我當前 $GOPATH/pkg 文件夾的樣子。你可以看到有兩個文件夾,mod 和 sumdb。如果你查看 mod 文件夾內部,你可以瞭解更多關於模塊緩存佈局的信息。

清單 2:

$HOME/code/go/pkg
$ ls -l mod/
total 0
drwxr-xr-x   5 bill  staff   160 Oct  7 10:37 cache
drwxr-xr-x   3 bill  staff    96 Oct  3 16:55 contrib.go.opencensus.io
drwxr-xr-x  40 bill  staff  1280 Oct 16 15:53 github.com
dr-x------  26 bill  staff   832 Oct  3 16:50 go.opencensus.io@v0.22.1
drwxr-xr-x   3 bill  staff    96 Oct  3 16:56 golang.org
drwxr-xr-x   4 bill  staff   128 Oct  7 10:37 google.golang.org
drwxr-xr-x   7 bill  staff   224 Oct 16 15:53 gopkg.in
drwxr-xr-x   7 bill  staff   224 Oct 16 15:53 k8s.io
drwxr-xr-x   5 bill  staff   160 Oct 16 15:53 sigs.k8s.io

清單 2 顯示了當前模塊緩存的頂級結構。你可以看到如何將與模塊名稱關聯的 URL 的第一部分用作模塊緩存中的頂級文件夾。如果我導航到 github.com/ardanlabs,可以向你展示 2 個實際的模塊。

清單 3:

$HOME/code/go/pkg
$ ls -l mod/github.com/ardanlabs/
total 0
dr-x------  13 bill  staff  416 Oct  3 16:49 conf@v1.1.0
dr-x------  18 bill  staff  576 Oct 12 10:08 service@v0.0.0-20191008203700-49ed4b4f1088

清單 3 顯示了我正在使用的來自 ArdanLabs 的兩個模塊及其版本。第一個是 conf 模塊,另一個模塊與我用來講解 kubernetes 和服務的服務項目相關聯。

Gopls 服務器還維護一個保存在內存中的模塊緩存。在啓動 VSCode 並處於模塊模式時,將啓動一個 gopls 服務器來支持該編輯器會話。內部 gopls 模塊緩存與當前在磁盤上的內容同步。Gopls 使用這個內部模塊緩存來處理編輯器請求。

在這篇文章中,我將在開始之前清空模塊緩存,這樣我就有了一個乾淨的工作環境。我還將在啓動 VSCode 編輯器之前設置我的項目。這將允許我向你展示如何處理你需要的模塊尚未下載到本地模塊緩存或更新到 gopls 內部模塊緩存的情況。

注意: 在任何正常的工作流中,您都不應該清除模塊緩存。

清單 4:

$ go clean -modcache

清單 4 顯示瞭如何清除磁盤上的本地模塊緩存。清理命令通常用於清理本地的 GOPATH 工作目錄和 GOPATH/bin 文件夾。現在使用新的 -mocache 標誌,可以使用該命令清理模塊緩存。

注意: 這個命令不會清除任何正在運行的 gopls 實例的內部緩存。

新項目

我將在 GOPATH 之外開始一個新項目,在編寫代碼的過程中,我將介紹添加和刪除依賴項的基本工作流程。

清單 5:

cd $HOME
$ mkdir service
$ cd service
$ mkdir cmd
$ mkdir cmd/sales-api
$ touch cmd/sales-api/main.go

清單 5 顯示了一些命令,這些命令用於設置工作目錄文件、創建初始項目結構並添加 main.go 文件。

使用模塊時的第一步是初始化項目源樹的根。這是通過使用 go mod init 命令完成的。

清單 6:

$ go mod init github.com/ardanlabs/service

清單 6 顯示了對 go mod init 的調用,將模塊的名稱作爲參數傳遞。正如在第一篇文章 [3] 中所討論的,模塊的名稱允許在模塊內部解析內部導入。按照倉庫代碼的 URL 來命名模塊是慣用法。在這篇文章中,我假設這個模塊與 Github 中 Ardan Labs 下的 service repo[4] 相關聯。

一旦調用 go mod init 完成,就會在當前工作目錄中創建一個 go.mod 文件。這個文件將表示項目的根。

清單 7:

01 module github.com/ardanlabs/service
02
03 go 1.13

清單 7 顯示了這個項目的初始模塊文件的內容。有了這些,就可以進行項目編碼了。

清單 8:

$ code .

清單 8 顯示了啓動 VSCode 實例的命令。這將反過來啓動 gopls 服務器的實例,以支持這個編輯器實例。

圖 1

圖 1 顯示了在運行所有命令之後,我的 VSCode 編輯器中的項目是什麼樣子的。爲了確保你使用的設置與我相同,我將列出我當前的 VSCode 設置。

清單 9:

{
    // Important Settings
    "go.lintTool""golint",
    "go.goroot""/usr/local/go",
    "go.gopath""/Users/bill/code/go",

    "go.useLanguageServer": true,
    "[go]"{
        "editor.snippetSuggestions""none",
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave"{
            "source.organizeImports"true
        }
    },
    "gopls"{
        "usePlaceholders": true,    // add parameter placeholders when completing a function
        "completeUnimported": true, // autocomplete unimported packages
        "deepCompletion": true,     // enable deep completion
    },
    "go.languageServerFlags"[
        "-rpc.trace", // for more detailed debug logging
    ],
}

清單 9 顯示了我當前的 VSCode 設置。如果你一直跟着做,沒有看到相同的行爲,檢查你的這些設置。如果你想查看當前推薦的 VSCode 設置,請點擊這裏 [5]。

應用編碼

我將從這個應用程序的初始代碼開始。

清單 10:https://play.studygolang.com/p/AU1xFIVOLu9

01 package main
02
03 func main() {
04     if err := run(); err != nil {
05         log.Println("error :", err)
06         os.Exit(1)
07     }
08 }
09
10 func run() error {
11     return nil
12 }

清單 10 顯示了我添加到 main.go 的前 12 行代碼。它爲應用程序設置一個單一的退出點,並記錄啓動或關閉時的任何錯誤。一旦這 12 行代碼被保存到文件中,編輯器將自動地 (感謝 gopls) 包含從標準庫中所需的導入。

清單 11:https://play.studygolang.com/p/x3hBA6PuW3R

03 import (
04     "log"
05     "os"
06 )

清單 11 顯示了由於編輯器與 gopls 集成而對第 03 至 06 行的源代碼所做的更改。

接下來,我將添加對配置的支持。

清單 12:https://play.studygolang.com/p/4hFXLJj4yT_Z

17 func run() error {
18     var cfg struct {
19         Web struct {
20             APIHost         string        `conf:"default:0.0.0.0:3000"`
21             DebugHost       string        `conf:"default:0.0.0.0:4000"`
22             ReadTimeout     time.Duration `conf:"default:5s"`
23             WriteTimeout    time.Duration `conf:"default:5s"`
24             ShutdownTimeout time.Duration `conf:"default:5s"`
25         }
26     }
27
28     if err := conf.Parse(os.Args[1:]"SALES"&cfg); err != nil {
29         return fmt.Errorf("parsing config : %w", err)
30     }

清單 12 顯示了添加到第 18 行到第 30 行的 run 函數中以支持配置的代碼。當這段代碼被添加到源文件中並點擊保存時,編輯器會將 fmt 和 time 包正確地包含到導入集中。不幸的是,由於 gopls 目前在其內部模塊緩存中沒有關於 conf 包的任何信息,所以 gopls 不能指示編輯器爲 conf 添加一個導入或向編輯器提供包信息。

圖 2

圖 2 顯示了編輯器如何清楚地表明它不能解析與 conf 包相關的任何信息。

添加一個依賴項

爲了解析導入,需要檢索包含 conf 包的模塊。這樣做的一種方法是將導入添加到源代碼文件的頂部,並讓編輯器和 gopls 完成這項工作。

清單 13:

01 package main
02
03 import (
04     "fmt"
05     "log"
06     "os"
07     "time"
08
09     "github.com/ardanlabs/conf"
10 )

在清單 13 中,我在第 09 行添加了 conf 包的導入。一旦我點擊保存,編輯器就會找到 gopls,然後 gopls 會找到、下載並使用 Go 命令和相關的 API 提取這個包的模塊。這些調用還更新 Go 模塊文件以反映這一更改。

清單 14:

~/code/go/pkg/mod/github.com/ardanlabs
$ ls -l
total 0
drwxr-xr-x   3 bill  staff    96B Nov  8 16:02 .
drwxr-xr-x   3 bill  staff    96B Nov  8 16:02 ..
dr-x------  13 bill  staff   416B Nov  8 16:02 conf@v1.2.0

清單 14 顯示了 Go 命令如何完成它的工作,以及如何使用版本 1.2.0 下載 conf 模塊。我們需要解析導入的代碼現在在我的本地模塊緩存中。

圖 3

圖 3 顯示了編輯器仍然不能解析有關包的信息。爲什麼編輯器無法解析此信息?不幸的是,gopls 內部模塊緩存與本地模塊緩存不同步。Gopls 服務器並不知道 Go 命令剛剛做出的更改。由於 gopls 使用它的內部緩存,所以 gopls 不能向編輯器提供它所需要的信息。

注意: 這個缺點目前正在處理中,將在即將發佈的版本中修正。你可以在這裏追蹤這個問題。(https://github.com/golang/go/issues/31999)。(GCTT 譯註:目前版本該問題已經解決了)

使 gopls 內部模塊緩存與本地模塊緩存同步的一個快速方法是重新加載 VS Code 編輯器。這將重新啓動 gopls 服務器並重置其內部模塊緩存。在 VSCode 中,有一個名爲 reload window 的特殊命令可以做到這一點。(新版本不需要此步驟了)

Ctrl + Shift + P and run  > Reload Window

圖 4

圖 4 顯示了在使用 Ctrl + Shift + P 快捷鍵 reload 窗口之後在 VS Code 中出現的對話框。

運行此快速命令後,將解析與導入相關的任何消息。

可傳遞依賴關係

從 Go 工具的角度來看,構建這個應用程序所需的所有代碼現在都在本地模塊緩存中。但是,conf 包的測試依賴於 googlego-cmp 包。

清單 15:

module github.com/ardanlabs/conf

go 1.13

require github.com/google/go-cmp v0.3.1

清單 15 顯示了 conf 模塊的 1.2.0 版本的模塊文件。您可以看到 conf 依賴於 go-cmp 的 0.3.1 版本。此模塊未列入服務的模塊文件中,因爲這樣做是冗餘的。Go 工具可以按照模塊文件的路徑來獲取構建或測試代碼所需的所有模塊的完整圖像。

此時,還沒有找到這個傳遞模塊,也沒有將其下載並提取到本地模塊緩存中。因爲在構建代碼時不需要這個模塊,所以 Go 構建工具還沒有發現需要下載它。如果我在命令行上運行 go mod tidy,那麼 Go 工具將花費時間將 go-cmp 模塊放入本地緩存中。

清單 16:

$ go mod tidy
go: downloading github.com/google/go-cmp v0.3.1
go: extracting github.com/google/go-cmp v0.3.1

清單 16 顯示瞭如何找到、下載和提取 go-cmp 模塊。這個調用 go mod tidy 不會改變項目的模塊文件,因爲這不是一個直接的依賴項。它將更新 go.sum 文件,以便有模塊 hash 的記錄,從而維護持久的、可重複的構建。我將在以後的文章中談論校驗和數據庫。

清單 17:

github.com/ardanlabs/conf v1.2.0 h1:2IntiqlEhRk+sYUbc8QAAZdZlpBWIzNoqILQvV6Jofo=
github.com/ardanlabs/conf v1.2.0/go.mod h1:ILsMo9dMqYzCxDjDXTiwMI0IgxOJd0MOiucbQY2wlJw=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=

清單 17 顯示了運行 go mod tidy 後校驗和文件的樣子。每個與項目相關聯的模塊有兩條記錄。

下載模塊

如果你還沒有準備好在代碼庫中使用某個特定的模塊,但是希望將該模塊下載到本地模塊緩存中,可以選擇手動將該模塊添加到項目 go.mod 文件中,然後在編輯器外運行 go mod tidy。

清單 18:

01 module github.com/ardanlabs/service
02
03 go 1.13
04
05 require (
06     github.com/ardanlabs/conf v1.2.0
07     github.com/pkg/errors latest
08 )

在清單 18 中,你可以看到我如何爲最新版本的 errors 模塊在模塊文件中手動添加第 07 行。手動添加所需模塊的重要部分是使用最新的標記。一旦我對這個更改運行 go mod tidy,它會告訴 Go 找到 errors 模塊的最新版本並將其下載到緩存中。

清單 19:

$HOME/service
$ go mod tidy
go: finding github.com/pkg/errors v0.8.1

清單 19 顯示瞭如何找到、下載和提取 errors 模塊的 0.8.1 版本。一旦命令運行完畢,模塊將從模塊文件中刪除,因爲項目不使用該模塊。但是,該模塊列在校驗和文件中。

清單 20:

github.com/ardanlabs/conf v1.2.0 h1:2IntiqlEhRk+sYUbc8QAAZdZlpBWIzNoqILQvV6Jofo=
github.com/ardanlabs/conf v1.2.0/go.mod h1:ILsMo9dMqYzCxDjDXTiwMI0IgxOJd0MOiucbQY2wlJw=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

清單 20 顯示瞭如何在校驗和文件中列出 errors 模塊模塊文件的散列。記住校驗和文件不是項目使用的所有依賴項的規範記錄,這一點很重要。它可以包含更多的模塊,這是絕對好的。

我喜歡這種通過使用 go get 來下載新模塊的方法,因爲如果不小心的話,go get 也可以嘗試升級項目的依賴關係圖 (直接和間接)。重要的是要知道什麼時候版本升級只是下載你想要的新模塊。在以後的文章中,我將討論使用 go get 來更新現有的模塊依賴關係。

移除依賴

如果我決定不再使用 conf 包會發生什麼?我可以刪除使用包的任何代碼。

清單 21:https://play.studygolang.com/p/x3hBA6PuW3R

01 package main
02
03 import (
04     "log"
05     "os"
06 )
07
08 func main() {
09     if err := run(); err != nil {
10         log.Println("error :", err)
11         os.Exit(1)
12     }
13 }
14
15 func run() error {
16     return nil
17 }

清單 21 顯示了從 main 函數中刪除引用 conf 包的代碼。一旦我點擊保存,編輯器就會從導入集中刪除 conf 的導入。但是,模塊文件沒有更新以反映更改。

清單 22:

01 module github.com/ardanlabs/service
02
03 go 1.13
04
05 require github.com/ardanlabs/conf v1.1.0

清單 22 顯示 conf 包仍然被認爲是必需的。爲了解決這個問題,我需要離開編輯器,再次運行 go mod tidy。

清單 23:

$HOME/service
$ go mod tidy

清單 23 再次顯示了 go mod 的運行情況。這次沒有輸出。一旦這個命令完成,模塊文件再次精確。

清單 24:

$HOME/services/go.mod

01 module github.com/ardanlabs/service
02
03 go 1.13

清單 24 顯示了從模塊文件中刪除了 conf 模塊。這一次,go mod tidy 命令清除了校驗和文件,它將是空的。在你對 VCS 進行任何修改之前,確保你的模塊文件是正確的,並且與你使用的依賴關係一致,這一點很重要。

總結

在不久的將來,我分享的一些解決方案,比如重新加載窗口,將不再需要。團隊意識到了這一點以及當今存在的其他缺陷,他們正在積極地修復這些缺陷。他們非常感謝任何和所有的反饋,所以如果你發現一個問題,請報告它。沒有問題是太大或太小。作爲一個社區,讓我們與 Go 團隊一起快速解決這些遺留問題。

現在正在進行的一個核心特性是 gopls 能夠監視文件系統並自己查看項目更改。這將有助於 gopls 保持其內部模塊緩存與磁盤上的本地模塊緩存同步。一旦這樣做了,重新裝載窗口的需求就會消失。計劃也在制定中,以提供視覺線索,工作是在背景中進行的。(GCTT 注:目前已經解決)

總的來說,我對當前的工具集和刷新窗口的工作方式感到滿意。我希望你考慮開始使用模塊,如果你還沒有。模塊已經可以使用了,越多的項目開始使用它,Go 生態系統對每個人來說就越好。


via: https://www.ardanlabs.com/blog/2019/12/modules-02-projects-dependencies-gopls.html

作者:William Kennedy[6] 譯者:polaris1119[7] 校對:polaris1119[8]

本文由 GCTT[9] 原創編譯,Go 中文網 [10] 榮譽推出,發佈在 Go 語言中文網公衆號,轉載請聯繫我們授權。

參考資料

[1]

gopls: https://github.com/golang/tools/blob/master/gopls/doc/user.md

[2]

LSP: https://microsoft.github.io/language-server-protocol/

[3]

第一篇文章: https://studygolang.com/articles/24580

[4]

service repo: https://github.com/ardanlabs/service

[5]

這裏: https://github.com/golang/tools/blob/master/gopls/doc/vscode.md

[6]

William Kennedy: https://www.ardanlabs.com/

[7]

polaris1119: https://github.com/polaris1119

[8]

polaris1119: https://github.com/polaris1119

[9]

GCTT: https://github.com/studygolang/GCTT

[10]

Go 中文網: https://studygolang.com/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/KfguN8Gu-10wEcPBhTSCbA