拜拜了,GOPATH君!新版本Golang的包管理入門教程

ZetaChow曉程式碼發表於2019-03-28

Go 1.11和1.12實現了對包管理的初步支援,Go的新依賴管理系統使依賴版本資訊明確且易於管理。 Using Go Modules - The Go Blog

Golang

新的包管理模式有什麼不同?

作為Go語言的推廣者,常常被問到各種關於Go語言的基礎特性問題。 其中,關於包管理方面的問題會讓我非常尷尬,因為Go的包管理在1.11之前與Python、Node、Java比較起來真的只能算是“僅僅可用”而已。

因為:

  1. 在不使用額外的工具的情況下,Go的依賴包需要手工下載,
  2. 第三方包沒有版本的概念,如果第三方包的作者做了不相容升級,會讓開發者很難受
  3. 協作開發時,需要統一各個開發成員本地$GOPATH/src下的依賴包
  4. 引用的包引用了已經轉移的包,而作者沒改的話,需要自己修改引用。
  5. 第三方包和自己的包的原始碼都在src下,很混亂。對於混合技術棧的專案來說,目錄的存放會有一些問題

新的包管理模式解決了以上問題

  1. 自動下載依賴包
  2. 專案不必放在GOPATH/src內了
  3. 專案內會生成一個go.mod檔案,列出包依賴
  4. 所以來的第三方包會準確的指定版本號
  5. 對於已經轉移的包,可以用replace 申明替換,不需要改程式碼

開始吧用一個小例子來說明問題!

準備工作

  1. 升級golang 版本到 1.12 Go下載
  2. 新增環境變數 GO111MODULEon 或者auto
GO111MODULE=auto
複製程式碼

準備完畢,非常簡單吧!!

建立一個專案

首先,在$GOPATH/src路徑外的你喜歡的地方建立一個目錄,cd 進入目錄,新建一個hello.go檔案,內容如下

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, world!")
}

複製程式碼

初始化模組

在當前目錄下,命令列執行 go mod init + 模組名稱 初始化模組

go mod init hello
複製程式碼

執行完後,會在當前專案目錄下生成一個go.mod 檔案,這是一個關鍵檔案,之後的包的管理都是通過這個檔案管理。

官方說明:除了go.mod之外,go命令還維護一個名為go.sum的檔案,其中包含特定模組版本內容的預期加密雜湊
go命令使用go.sum檔案確保這些模組的未來下載檢索與第一次下載相同的位,以確保專案所依賴的模組不會出現意外更改,無論是出於惡意、意外還是其他原因。 go.mod和go.sum都應檢入版本控制。
go.sum 不需要手工維護,所以可以不用太關注。

生成出來的檔案包含模組名稱和當前的go版本號

module hello

go 1.12
複製程式碼

注意:子目錄裡是不需要init的,所有的子目錄裡的依賴都會組織在根目錄的go.mod檔案裡

來吧,搞點小事情,看看go.mod怎麼工作的

接下來,讓你的專案依賴一下第三方包 以大部分人都熟悉的beego為例吧! 修改Hello.go檔案:

package main

import "github.com/astaxie/beego"

func main() {
    beego.Run()
}

複製程式碼

按照過去的做法,要執行hello.go需要執行go get 命令 下載beego包到 $GOPATH/src

但是,使用了新的包管理就不在需要這樣做了

直接 go run hello.go

稍等片刻… go 會自動查詢程式碼中的包,下載依賴包,並且把具體的依賴關係和版本寫入到go.mod和go.sum檔案中。 檢視go.mod,它會變成這樣:

module hello

go 1.12

require github.com/astaxie/beego v1.11.1

複製程式碼

require 關鍵子是引用,後面是包,最後v1.11.1 是引用的版本號

這樣,一個使用Go包管理方式建立專案的小例子就完成了。

那麼,接下來,幾個小問題來了

問題一:依賴的包下載到哪裡了?還在GOPATH裡嗎?

不在。 使用Go的包管理方式,依賴的第三方包被下載到了$GOPATH/pkg/mod路徑下。

如果你成功執行了本例,可以在您的$GOPATH/pkg/mod 下找到一個這樣的包 github.com/astaxie/beego@v1.11.1

問題二: 依賴包的版本是怎麼控制的?

在上一個問題裡,可以看到最終下載在$GOPATH/pkg/mod 下的包 github.com/astaxie/beego@v1.11.1 最後會有一個版本號 1.11.1,也就是說,$GOPATH/pkg/mod裡可以儲存相同包的不同版本。

版本是在go.mod中指定的。

如果,在go.mod中沒有指定,go命令會自動下載程式碼中的依賴的最新版本,本例就是自動下載最新的版本。

如果,在go.mod用require語句指定包和版本 ,go命令會根據指定的路徑和版本下載包, 指定版本時可以用latest,這樣它會自動下載指定包的最新版本;

依賴包的版本號是什麼? 是包的釋出者標記的版本號,格式為 vn.n.n (n代表數字),本例中的beego的歷史版本可以在其程式碼倉庫release看到Releases · astaxie/beego · GitHub

如果包的作者還沒有標記版本,預設為 v0.0.0

問題三: 可以把專案放在$GOPATH/src下嗎?

可以。 但是go會根據GO111MODULE的值而採取不同的處理方式 預設情況下,GO111MODULE=auto 自動模式

  • auto 自動模式下,專案在$GOPATH/src裡會使用$GOPATH/src的依賴包,在$GOPATH/src外,就使用go.mod 裡 require的包
  • on 開啟模式,1.12後,無論在$GOPATH/src裡還是在外面,都會使用go.mod 裡 require的包
  • off 關閉模式,就是老規矩。

問題三: 依賴包中的地址失效了怎麼辦?比如 golang.org/x/… 下的包都無法下載怎麼辦?

在go快速發展的過程中,有一些依賴包地址變更了。 以前的做法

  1. 修改原始碼,用新路徑替換import的地址
  2. git clone 或 go get 新包後,copy到$GOPATH/src裡舊的路徑下 無論什麼方法,都不便於維護,特別是多人協同開發時。

使用go.mod就簡單了,在go.mod檔案裡用 replace 替換包,例如

replace golang.org/x/text => github.com/golang/text latest

這樣,go會用 github.com/golang/text 替代golang.org/x/text,原理就是下載github.com/golang/text 的最新版本到 $GOPATH/pkg/mod/golang.org/x/text下。

問題四: init生成的go.mod的模組名稱有什麼用?

本例裡,用 go mod init hello 生成的go.mod檔案裡的第一行會申明 module hello

因為我們的專案已經不在$GOPATH/src裡了,那麼引用自己怎麼辦?就用模組名+路徑。

例如,在專案下新建目錄 utils,建立一個tools.go檔案:

package utils

import “fmt”

func PrintText(text string) {
	fmt.Println(text)
}
複製程式碼

在根目錄下的hello.go檔案就可以 import “hello/utils” 引用utils

package main

import (
"hello/utils"

"github.com/astaxie/beego"
)

func main() {

	utils.PrintText("Hi")

	beego.Run()
}


複製程式碼

問題五:以前老專案如何用新的包管理

  1. 如果用auto模式,把專案移動到$GOPATH/src
  2. 進入目錄,執行 go mod init + 模組名稱
  3. go build 或者 go run 一次

關於Go 1.12的包管理介紹大致就到此了

根據官方的說法,從Go 1.13開始,模組管理模式將是Go語言開發的預設模式。

所以,Pick起來吧!

有問題或者需要討論的朋友,可以給我留言,共同學習,一起進步

曉程式碼公眾號:

曉程式碼公眾號

相關文章