覺得我們的內容實用嗎?MyApollo 電子報讀者募集中!歡迎訂閱電子報!
初學 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
完成上述搬運後,只要將 hello.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