Golang — GOROOT、GOPATH、Go-Modules-三者的關係介紹

陳冠億 Kenny
企鵝也懂程式設計
13 min readJan 12, 2020

相信初學Golang語言的時候,一定會對GOROOT、GOPATH感到疑惑,究竟它們的差別在哪裡?而現在Golang語言版本已經來到1.13 Version,在1.11 Version新增了一個重大的功能那就是Go Modules,所以三者的差別是需要好好釐清的。

GOROOT介紹

在安裝完Golang語言的時候,所謂的安裝路徑其實就是你的GOROOT路徑,也就是說GOROOT存放的Golang語言內建的程式庫的所在位置,而通常你安裝完後,你電腦的環境變數就會設好GOROOT路徑,當你開發Golang程式的時候,當你import內建的程式庫的時候,並不需要額外安裝,而當程式運行後,預設也會先去GOROOT路徑下尋找相對應的程式庫來運行。

首先我們先看目前GOROOT路徑,透過以下指令:

go env

這樣可以看到當前有關Golang語言的全部相關環境變數設定,如下:

...
set GOPATH=C:\Users\kenny\go
set GOROOT=c:\go
...

通常如果你是初次安裝Golang語言並且沒做什麼環境變數設定的話,GOROOT設定路徑就是你當初安裝Golang語言的路徑,而GOPATH通常預設會是使用者目錄下的go資料夾。

那假設先在隨便的資料夾目錄下新增main.go檔案,程式碼內容如下:

package main

import "fmt"

func main() {
fmt.Println("Hello World")
}

然後再執行以下指令:

go run main.go

就會成功輸出Hello World的字樣。go run其實會幫你將程式碼進行編譯並產生執行檔,而編譯檔跟執行檔事實上是存在一個暫存資料夾裡面,當運行完此程式就會自動刪除。該指令可以想成類似直譯的方式運行,而不需要做其他任何環境設定,即可運行。

那如果我們將程式碼改成這樣:

package main

import "github.com/gin-gonic/gin"

func main() {
router := gin.Default()
router.Run()
}

可以注意到import了一個github.com/gin-gonic/gin套件,這個是別人開發的Web Framework套件,是不存在於官方程式庫的,而是放在GitHub上的第三方套件。

那再執行這份程式碼,會出現以下的錯誤:

main.go:3:8: cannot find package "github.com/gin-gonic/gin" in any of:
c:\go\src\github.com\gin-gonic\gin (from $GOROOT)
C:\Users\kenny\go\src\github.com\gin-gonic\gin (from $GOPATH)

從這個訊息可以看出兩件事情:

  1. 當執行Golang程式碼,當需要存取套件時,會先去GOROOT路徑下的src資料夾找同等於我們在程式碼中import的路徑下去找有沒有gin這個資料夾,而這資料夾裡面就是包含了所有有關於該套件的程式庫。
  2. 如果在GOROOT路徑下沒有找到,則會往GOPATH路徑下的src資料夾找同等於我們在程式碼中import的路徑下去找有沒有gin這個資料夾。

所以只要GOROOT跟GOPATH路徑下都沒有找到該套件的話,就無法執行該程式碼。

GOPATH介紹

根據上面GOROOT的介紹,我們可以知道官方的程式庫所在位置就是在GOROOT裡面,而GOPATH就是專門存放第三方套件以供我們程式碼的需要。

那通常開發Golang的話,通常會在重新設定GOPATH的位置,例如像我習慣把我所有不同程式語言的專案都統一放在一個資料夾下,在去用語言去分類專案,所以這時候就需要設定GOPATH路徑。

採用指令如下:

set GOPATH=<PATH>

<PATH>就是你想要放置Golang語言專案的地方,但是需要做成以下的分層:

D:\CodingProject\GolangProject ---> bin
---> pkg
---> src

也就是<PATH>,實際上是D:\CodingProject\GolangProject,並不是D:\CodingProject\GolangProject\src,依照Golang語言的慣例(強制),GOPATH是指src路徑的上一層,如果你去看GOROOT的設計,也是這樣的。而我們要在GOPATH路徑下主動新增src資料夾,所謂src就是代表source code的意思,也就是放我們開發Golang程式碼的相關專案的原始碼。

至於pkg、bin兩個資料夾是做什麼用的之後會提到。

所以一般開發上,會在src下創立專案資料夾,然後在專案資料夾在去新增package資料夾,在去寫相關的go語言程式碼。

例如實際上架構就會像是:

D:\CodingProject\GolangProject\src\webDemo  ---> controller
---> model
---> main.go

假設是web專案的話,在controller跟model有你所設計的程式碼,而main.go就是所謂的主程式入口,裡面定義了main function。

這樣就能透過 go run main.go來啟動整個專案。

go run、go build、go install的差別

前面我們都只有提到用go run 來執行程式碼,但其實還有所謂的go build、go install等指令可以執行程式碼。

go run介紹

go run指令簡單來說就像是直譯的方式來執行程式碼,而在細節上它其實會將程式碼進行編譯,最後產生可執行檔案,然後運行該可執行檔案,產生程式執行後的結果,而可執行檔會放在特定的暫存檔裡面,執行完就會自動刪除了。通常開發上就是用於假設只是要測試一段程式碼,那就會用go run來執行。

go build介紹

go build指令就是將程式碼編譯為執行檔,例如:

go build main.go

這樣會在main.go當前目錄下產生一個main名稱的可執行檔,如果是Windows的話就是main.exe。也可以在build後面寫專案的名稱,那麼它會自動在當前專案資料夾下找package為main並且有main function的go檔案,在編譯成可執行檔。

例如:

go build webDemo

但記得如果把main function的go檔案放置在webDemo資料夾下的另一個package裡面的話,會讀取不到。

也可以透過以下指令指定build出來的可執行檔位置放置在哪:

go build -o bin/main.exe src/webDemo/main.go

-o 後面第一個參數代表產生出來執行檔路徑要放哪及名稱要取什麼,第二個參數代表要編譯哪一個go程式碼的路徑。從這邊就可以知道為什麼我們在GOPATH下要新增一個bin資料夾,依照官方慣例,GOPATH下會有一個bin資料夾專門就是放專案build出來的可執行檔。

但是go build有個缺點就是每次編譯程式碼,比較沒有效率,當專案架構越來越大,build的速度也就越來越慢。

go install介紹

因應go build的缺點,因此有go install指令,go install可以做兩件事情:

  • 將套件編譯成.a file
  • 如果是main套件則編譯成可執行檔

而有了第一項功能,在下次編譯的時候,就不會將套件的程式碼重新編譯,而是直接用編譯好的.a file。而.a file就位於GOPATH/pkg裡面。這就是為什麼golangGO慣例要在GOPATH下新增此三個資料夾,都是有用處的。

來看例子:

假設在src下新增兩個資料夾分別是mypkg跟mytest,而mypkg下新增一個mypkg.go檔案,程式碼如下:

package mypkg

import "fmt"

func MyFunc() {
fmt.Println("MyFunc")
}

在mytest下新增一個mytest.go檔案,程式碼如下:

package main

import (
"mypkg"
)

func main() {
mypkg.MyFunc()
}

意思就是在mytest.go檔案中用了mypkg套件的相關函式。而可以透過go install指令先將mypkg編譯成.a file;

go install mypkg

以上是在src目錄下執行這個指令,也可以在mypkg下執行以下指令:

go install

就會發現在GOPATH/pkg/windows_amd64/mypkg.a有該檔案。

windows_amd64其實就是由GOOS_GOARCH這兩個環境變數所組合而成的,執行go env指令就可以看到,通常安裝golang語言時就會根據作業系統設定好名稱了。

特別注意:

  • go install 如果要在非GOPATH路徑下使用的話,要先設定GOBIN環境變數,否則會出現錯誤
  • 通常GOBIN環境變數就是設定GOPATH/bin。

GOPATH的缺點

講完了GOROOT、GOPATH,不知道大家有沒有發現GOPATH的一個很大的缺點,那就是你相關的第三方套件只要不是官方程式庫,都需要放置在GOPATH/src的路徑下才可以使用。

也就是說通常我們會使用go get指令來獲取網路上的第三方套件,例如:

go get github.com/gin-gonic/gin

go get最常用在當我們想用別人公開在GitHub上的套件,可以幫我們從網路上clone到GOPATH/src裡面。雖然這樣很方便,但是你會發現GOPATH/src下的程式碼會很複雜,除了有你自己開發的專案資料夾,也包含其他第三方程式庫的專案資料夾。

再來,如果你開發的專案採用第三方套件是不同版本怎麼辦?以往的解決方法是要設定多組不同的GOPATH。雖然社群也有開發相對應的package manager,如Vendor、Dep來解決該問題,但畢竟不是官方主導的。

Go Modules的誕生

為了解決不被GOPATH的問題,因此官方在1.11開始推出了Go Modules的功能。Go Modules解決方式很像是Java看到Maven的做法,將第三方程式庫儲存在本地的空間,並且給程式去引用。

首先要先設定GO111MODULE環境變數,總共可以三種不同的值:

  • auto

默認值,go命令會根據當前目錄来决定是否啟用modules功能。需要滿足兩種情形:

  1. 該專案目錄不在GOPATH/src/下
  2. 當前或上一層目錄存在go.mod檔案
  • on

go命令會使用modules,而不會GOPATH目錄下查找。

  • off

go命令將不會支持module功能,尋找套件如以前GOPATH的做法去尋找。

我是建議要開發Go專案就不再使用GOPATH的功能了,而是採用Go Modules的做法,因此建議都設定為on。

而採用Go Modules,下載下來的第三方套件都在哪呢?其實就位在GOPATH/pkg/mod資料夾裡面。

來看個例子:

在GOPATH/src下建立一個專案資料夾:

mkdir modtest

然後在該資料夾下執行以下指令:

go mod init <module name>

<module name>可填可不填,不填的話預設就是採用專案資料夾的名稱。執行之後可以看到會出現一個go.mod檔案,這檔案內容如下:

module modtest

go 1.13

在此檔案內可以寫以下幾個關鍵字:

  • module

定義模組路徑

  • go

定義go語言 version

  • require

指定依賴的套件,預設是最新版,可以指定版本號

  • exclude

排除該套件和其版本

  • replace

使用不同的套件版本並替換原有的套件版本

  • 註解
  1. // 單行註解
  2. /* 多行註解 */
  3. indirect 代表被間接導入的依賴包

假設現在我要引入GitHub上的gin-gonic/gin的套件,如下定義:

module modtest

go 1.13

require github.com/gin-gonic/gin v1.5.0

再執行以下指令:

go mod download

會將需要的套件安裝在GOPATH/pkg/mod資料夾裡面。而且會發現出現一個go.sum的檔案,這個檔案基本上用來記錄套件版本的關係,確保是正確的,是不太需要理會的。

因此當下載完之後就可以在Go程式碼中引用囉:

package main

import "github.com/gin-gonic/gin"

func main() {
router := gin.Default()
router.Run()
}

這個就是在local 8080 port開啟一個web server~

如果將gin版本改成 v1.4.0再重新執行go mod download,就會發現在GOPATH/pkg/mod裡面gin-gonic會有兩個資料夾分別是gin@v1.4.0、gin@v1.5.0,藉此來區分版本。

當然其實也可以不執行go mod download,而直接運行go build or go install 也會自動將套件安裝在相對應的地方。

還有一種方式直接下載套件而不用在go.mod中定義:

go get github.com/gin-gonic/gin@v1.5.0

只要有開啟go modules功能,go get 就不會像以前一樣在GOPATH/src下放置套件檔案,而是會放在GOPATH/pkg/mod裡面,並且go.mod會寫好引入。

這樣也就不用使用go mod download指令了~

總結

Go Modules當然還有其他指令可以去玩玩,但就開發Go語言知道這些就差不多了,其他等有遇到在去看指令也可以,簡單來說Go Modules是Golang套件管理方式,這樣的方式也相對新手也比較好理解,我一開始碰Golang的時候,也被這些環境變數搞得很亂,畢竟之前設定環境既不像Java、Python、Node.JS那樣,會讓人感到難以上手,畢竟Golang語言當初設計是在Google大公司的情境,並且採用同一個Repository的方式去開發專案的。

我個人很喜歡Golang語言的,我大學大部分碰的靜態語言都是Java,雖然我還是滿喜歡Java,但Java有些時候令人詬病的物件開發方式,確實很煩人,而接觸Golang語言的時候,大部分是採用函數式開發,儘管Golang可以設計一些類似物件的方式,但是個人認為如果將兩者合在一起使用,其實是滿不錯的~

最讚的是Golang是專門開發Server Side的語言,特別合我的胃口,而且背後是Google大公司當親爹,以及一堆大公司正在積極地採用Golang開發新專案,或是重構舊專案來提升效能。

其實我覺得程式設計師,要會的語言不管是動態或靜態最好都要會各兩種,同時感受不同語言的奧妙之處,在不同情境下用不同語言,有時候還會迸出新想法呢~ ^^

--

--