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

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

GOROOT 介紹

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

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

1
go env

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

1
2
3
4
...
set GOPATH=C:\Users\kenny\go
set GOROOT=c:\go
...

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

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

1
2
3
4
5
6
7
package main

import "fmt"

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

然後再執行以下指令:

1
go run main.go

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

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

1
2
3
4
5
6
7
8
package main

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

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

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

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

1
2
3
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 路徑。

採用指令如下:

1
set GOPATH=<PATH>

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

1
2
3
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 語言程式碼。

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

1
2
3
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 指令就是將程式碼編譯為執行檔,例如:

1
go build main.go

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

例如:

1
go build webDemo

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

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

1
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 檔案,程式碼如下:

1
2
3
4
5
6
7
package mypkg

import "fmt"

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

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

1
2
3
4
5
6
7
8
9
package main

import (
"mypkg"
)

func main() {
mypkg.MyFunc()
}

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

1
go install mypkg

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

1
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 指令來獲取網路上的第三方套件,例如:

1
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 下建立一個專案資料夾:

1
mkdir modtest

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

1
go mod init <module name>

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

1
2
3
module modtest

go 1.13

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

  • module

    定義模組路徑

  • go

    定義 go 語言 version

  • require

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

  • exclude

    排除該套件和其版本

  • replace

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

  • 註解

    1. // 單行註解

    2. /* 多行註解 */

    3. indirect 代表被間接導入的依賴包

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

1
2
3
4
5
module modtest

go 1.13

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

再執行以下指令:

1
go mod download

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

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

1
2
3
4
5
6
7
8
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 會有兩個資料夾分別是 [email protected][email protected],藉此來區分版本。

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

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

1
go get github.com/gin-gonic/[email protected]

只要有開啟 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 開發新專案,或是重構舊專案來提升效能。

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

最後最後!請聽我一言!

如果你還沒有註冊 Like Coin,你可以在文章最下方看到 Like 的按鈕,點下去後即可申請帳號,透過申請帳號後可以幫我的文章按下 Like,而 Like 最多可以點五次,而你不用付出任何一塊錢,就能給我寫這篇文章的最大的回饋!