初學 golang 的人應該都會對環境變數 GOPATH 感到困惑。

譬如 PYTHONPATH 是 Python 尋找模組與套件的路徑,一般來說並不需要特別設定,也可以用得很開心。

然而 GOPATH 則不一樣,一旦程式涉及 package 就會需要留心 GOPATH 的設定,很容易令人感到困惑,但是只要弄清楚 GOPATH 的基本,就能夠很輕鬆地開發 Go 應用程式。

那麼,就從 Hello World 開始認識 GOPATH 吧!

本文環境

  • macOS 10.15
  • go 1.14.6

Hello World

以下是 Go 的 Hello World 範例:

// hello.go
package main

import "fmt"

func main() {
    fmt.Printf("hello, world\n")
}

接著用以下指令編譯:

$ go build hello.go

最後執行剛剛編譯出來的程式看看,成功的話,就可以看到程式列印出 hello, world

$ ./hello

以上就是 Go 最簡單的程式。

截至目前為止,我們都還沒碰到 package.的部分,因此沒碰到 GOPATH ,接下來我們試著將列印 hello, world 的部分改為 package 進行模組化,再將之 import 至我們的主程式 main 中試試。

首先建立一個資料夾作為工作區:

$ mkdir $HOME/goworkspace

接著在該資料夾中新增 2 個檔案如下, hello.go 就是方才的 hello world 範例程式,而 tutor.go 則是一個用來存放列印 hello, world 的 package 。

.
├── hello.go
└── tutor.go

上述 2 個檔案的內容分別如下:

// tutor.go
package tutor

import "fmt"

func HelloWorld() {
    fmt.Println("hello, world")
}
// hello.go
package main

import t "tutor"

func main() {
    t.HelloWorld()
}

p.s. 本篇不贅述各種 package, import, func 等基本 golang 知識

目前我們已經在 tutor.go 中新增 1 個函式 HelloWorld() ,並且在 hello.go 中使用該 package ,並且呼叫 HelloWorld()

如果我們試圖編譯 hello.go 將會出現以下錯誤:

hello.go:3:8: cannot find package "tutor" in any of:
    /usr/local/go/src/tutor (from $GOROOT)
    /usr/local/go/bin/src/tutor (from $GOPATH)

原來是 Go 在編譯時無法找到 package 啊!這是由於 Go 會去 $GOROOT$GOPATH 中試圖尋找 tutor 這個 package ,然而這些路徑底下並沒有我們剛剛寫的 tutor.go 。

這時候通常用慣其他程式語言的人就會感到相當困惑,為何當前目錄下的 package 並不會被載入呢?

GOPATH

原來 GOPATH 其實是 go 工作區(workspace)的概念,你可以把所有的 Go 專案與 package 都放在 GOPATH 底下。

(我想應該是由於 monorepo 的概念而導致 GOPATH 如此機制)

先來看看 GOPATH 的值:

$ echo $GOPATH
/usr/local/go/bin/

原來 macOS 預設的 GOPATH 指向 /usr/local/go/bin/ 。但我們的 Go 工作區其實在 $HOME/goworkspace ,所以我們用以下指令修改 GOPATH 的值:

$ export GOPATH=$HOME/goworkspace

接著在 GOPATH 中新增 src 資料夾,這是 Go 規定會在 GOPATH 中尋找的資料夾,所以一定要新增,否則 Go 一樣會找不到 package:

$ mkdir -p $GOPATH/src

接著建立 1 個資料夾 tutor 存放 tutor.go ,這是由於 Go 規定 package 名稱必須與存放它的資料夾名稱一樣,最後將 tutor.go 移進 $GOPATH/src/tutor 中:

$ mkdir -p $GOPATH/src/tutor
$ mv tutor.go $GOPATH/src/tutor

截至目前為止,我們的專案資料夾結構會長這樣:

.
├── hello.go
└── src
    └── tutor
        └── tutor.go

設定好 GOPATH 與將 package 放進 src 資料夾後,編譯 hello.go 就正常了!

使用第 3 方套件

前面談到 GOPATH 是工作區(workspace)的概念,所有 Go 專案都會存在 GOPATH 底下,包含第 3 方套件也是如此。

而現代開發應用也不太可能完全不使用第 3 方套件,接著我們試著使用套件將列印 hello, world 變成紅色的文字做為範例,看看 Go 會怎麼存放第 3 方套件。

首先我們將 tutor.go 改成以下,可以看到我們使用 github.com/fatih/color package 將文字變為紅色:

package tutor

import "github.com/fatih/color"

func HelloWorld() {
    color.Red("hello, world")
}

接著用以下指令安裝該 package :

$ go get github.com/fatih/color

安裝完後的 GOPATH 資料夾結構會變成以下:

.
├── hello
├── hello.go
├── pkg
│   └── darwin_amd64
│       └── github.com
│           └── fatih
└── src
    ├── github.com
    │   └── fatih
    │       └── color
    └── tutor
        └── tutor.go

可以看到 src 資料夾底下多了 github.com/fatih/color ,也正是因為 go get 自動將該套件的原始碼安裝到 src 資料夾底下,我們才能夠順利進行地 import 該模組。

P.s. 多出來的 pkg 中存放的是被預編譯好的 package ,用來加速編譯過程,通常不需要理會這個資料夾,如果該資料夾被刪除也沒關係, Go 會自行重建

也由於我們經常會透過 Github 開放 package/專案,所以通常就會將程式碼放在 src/github.com/<username>/<project> 路徑底下,並且對該資料夾進行版本控制,如此一來就能夠讓所有人都能夠用 go get 的方式安裝該 package/專案,方便許多(不過缺點則是程式碼不美觀與 src 資料夾各種 package 與自己的專案 package 混雜在一起)。

當然,如果沒有開放的打算,不這麼做也是可以的, Go 並沒有規定一定要如何存放你的程式碼,可以直接在 $GOPATH/src 底下建立專案資料夾進行管理,只要能夠讓 Go 順利找到 package 並順利完成編譯就可以了。

最後試著將所有的程式移進 $GOPATH/src/github.com/<your github username>/HelloGo 資料夾,並且用 git 進行版本控制:

$ mkdir -p src/github.com/<your github username>/HelloGo
$ git init src/github.com/<your github username>/HelloGo
$ mv hello.go src/github.com/<your github username>/HelloGo
$ mv src/tutor src/github.com/<your github username>/HelloGo

完成上述搬運後,只要將 tutor.go 再次改成以下,就可以囉!

package main

import t "github.com/<your github username>/HelloGo/tutor"

func main() {
    t.HelloWorld()
}

最後可以編譯一次試試是否正常!

$ go build hello.go

如果正常的話,就可以將該專案上傳至 GitHub ,別人就能夠以 go get 的方式取得你的程式碼囉!

GOROOT

最後談談 GOROOT ,其實 GOROOT 就是 Go 的指令、工具等存放的地方,一般也不太會存取它,所以其實不知道也沒關係,但也不建議將 GOROOT 當作 GOPATH 使用喔!

結語

理解 GOPATH 之後,真的會對於 Go 的開發會相當有助益。

以上, Happy Coding!

References

https://www.digitalocean.com/community/tutorials/understanding-the-gopath

The Go Programming Language