引言
Go 語言這兩年在語言排行榜上的上升勢頭非常猛婚惫,Go 語言雖然是靜態(tài)編譯型語言氧骤,但是它卻擁有腳本化的語法奈泪,支持多種編程范式(函數(shù)式和面向?qū)ο?疾棵。Go 語言最最吸引人的地方可能是其原生支持并發(fā)編程(語言層面原生支持和通過第三方庫支持是有很大區(qū)別的)戳稽。Go 語言的對網(wǎng)絡(luò)通信馆蠕、并發(fā)和并行編程的支持度極高,從而可以更好地利用大量的分布式和多核的計算機惊奇。開發(fā)者可以通過 goroutine 這種輕量級線程的概念來實現(xiàn)這個目標互躬,然后通過 channel 來實現(xiàn)各個 goroutine 之間的通信。他們實現(xiàn)了分段棧增長和 goroutine 在線程基礎(chǔ)上多路復(fù)用技術(shù)的自動化颂郎。
2017年7月 TIOBE 語言排行榜 Go 首次進入前十吨铸。今天就讓我們來探究探究 Go 的編譯命令執(zhí)行過程。
一. 理解 Go 的環(huán)境變量
1. GOROOT
該環(huán)境變量的值為 Go 語言的當前安裝目錄祖秒。
2. GOPATH
該環(huán)境變量的值為 Go 語言的工作區(qū)的集合(意味著可以有很多個)诞吱。工作區(qū)類似于工作目錄。每個不同的目錄之間用:
分隔竭缝。(不同操作系統(tǒng)房维,GOPATH 列表分隔符不同,UNIX-like 使用 :
冒號抬纸,Windows 使用;
分號)
正因為搜索優(yōu)先級和默認下載位置等原因咙俩,社區(qū)對于是否為每個項目單獨設(shè)置環(huán)境變量,還是將所有項目組織到同一個工作空間內(nèi)存在爭議湿故,有一個推薦的做法是寫一個腳本工具阿趁,作用類似 Python Virtual Environment,在激活某個項目的時候坛猪,自動設(shè)置相關(guān)的環(huán)境變量脖阵。
工作區(qū)是放置 Go 源碼文件的目錄。一般情況下墅茉,Go 源碼文件都需要存放到工作區(qū)中命黔。
工作區(qū)一般會包含3個子文件夾,自己手動新建以下三個目錄:src 目錄就斤,pkg 目錄悍募,bin 目錄。
/home/halfrost/gorepo
├── bin
├── pkg
└── src
這里需要額外說的一點:關(guān)于 IDE 新建 Go 項目洋机。IDE 在新建完 Go 的項目以后坠宴,會自動的執(zhí)行 go get 命令去把相應(yīng)的基礎(chǔ)包拉過來,
在這個過程中會新建 bin绷旗、pkg喜鼓、src 三個目錄副砍。不用 IDE 的同學(xué),需要自己手動創(chuàng)建這三個目錄颠通。
上圖是 Atom 的 go-plus 插件在一個新的項目打開的時候址晕,自動 go get 的一些基礎(chǔ)包。
bin 目錄里面存放的都是通過 go install 命令安裝后顿锰,由 Go 命令源碼文件生成的可執(zhí)行文件( 在 Mac 平臺下是 Unix executable 文件谨垃,在 Windows 平臺下是 exe 文件)。
注意:有兩種情況下硼控,bin 目錄會變得沒有意義刘陶。
- 當設(shè)置了有效的 GOBIN 環(huán)境變量以后,bin 目錄就變得沒有意義牢撼。
- 如果 GOPATH 里面包含多個工作區(qū)路徑的時候匙隔,必須設(shè)置 GOBIN 環(huán)境變量,否則就無法安裝 Go 程序的可執(zhí)行文件熏版。
pkg 目錄是用來存放通過 go install 命令安裝后的代碼包的歸檔文件(.a 文件)纷责。歸檔文件的名字就是代碼包的名字。所有歸檔文件都會被存放到該目錄下的平臺相關(guān)目錄中撼短,即在 $GOPATH/pkg/$GOOS_$GOARCH 中再膳,同樣以代碼包為組織形式。
這里有兩個隱藏的環(huán)境變量曲横,GOOS 和 GOARCH喂柒。這兩個環(huán)境變量是不用我們設(shè)置的,系統(tǒng)就默認的禾嫉。GOOS 是 Go 所在的操作系統(tǒng)類型灾杰,GOARCH 是 Go 所在的計算架構(gòu)。平臺相關(guān)目錄是以
$GOOS_$GOARCH 命名的熙参,Mac 平臺上這個目錄名就是 darwin_amd64艳吠。
src 目錄是以代碼包的形式組織并保存 Go 源碼文件的。每個代碼包都和 src 目錄下的文件夾一一對應(yīng)尊惰。每個子目錄都是一個代碼包讲竿。
代碼包包名和文件目錄名,不要求一致弄屡。比如文件目錄叫 myPackage,但是代碼包包名可以聲明為 “package service”鞋诗,但是同一個目錄下的源碼文件第一行聲明的所屬包膀捷,必須一致!
這里有一個特例削彬,命令源碼文件并不一定必須放在 src 文件夾中的全庸。
這里需要糾正一個錯誤的觀點:“所有的 Go 的代碼都要放在 GOPATH 目錄下”(這個觀點是錯誤的)
說到這里需要談到 Go 的源碼文件分類:
如上圖秀仲,分為三類:
(1)命令源碼文件:
聲明自己屬于 main 代碼包、包含無參數(shù)聲明和結(jié)果聲明的 main 函數(shù)壶笼。
命令源碼文件被安裝以后神僵,GOPATH 如果只有一個工作區(qū),那么相應(yīng)的可執(zhí)行文件會被存放當前工作區(qū)的 bin 文件夾下覆劈;如果有多個工作區(qū)保礼,就會安裝到 GOBIN 指向的目錄下。
命令源碼文件是 Go 程序的入口责语。
同一個代碼包中最好也不要放多個命令源碼文件炮障。多個命令源碼文件雖然可以分開單獨 go run 運行起來,但是無法通過 go build 和 go install坤候。
YDZ ~/LeetCode_Go/helloworld/src/me $ ls
helloworld.go helloworldd.go
先說明一下胁赢,在上述文件夾中放了兩個命令源碼文件,同時都聲明自己屬于 main 代碼包白筹。helloworld.go 文件輸出 hello world智末,helloworldd.go 文件輸出 worldd hello。接下來執(zhí)行 go build 和 go install 徒河,看看會發(fā)生什么系馆。
YDZ ~/LeetCode_Go/helloworld/src/me $ go build
# _/Users/YDZ/LeetCode_Go/helloworld/src/me
./helloworldd.go:7: main redeclared in this block
previous declaration at ./helloworld.go:50
YDZ ~/LeetCode_Go/helloworld/src/me $ go install
# _/Users/YDZ/LeetCode_Go/helloworld/src/me
./helloworldd.go:7: main redeclared in this block
previous declaration at ./helloworld.go:50
這也就證明了多個命令源碼文件雖然可以分開單獨 go run 運行起來,但是無法通過 go build 和 go install虚青。
同理它呀,如果命令源碼文件和庫源碼文件也會出現(xiàn)這樣的問題,庫源碼文件不能通過 go build 和 go install 這種常規(guī)的方法編譯和安裝棒厘。具體例子和上述類似纵穿,這里就不再貼代碼了。
所以命令源碼文件應(yīng)該是被單獨放在一個代碼包中奢人。
(2)庫源碼文件
庫源碼文件就是不具備命令源碼文件上述兩個特征的源碼文件谓媒。存在于某個代碼包中的普通的源碼文件。
庫源碼文件被安裝后何乎,相應(yīng)的歸檔文件(.a 文件)會被存放到當前工作區(qū)的 pkg 的平臺相關(guān)目錄下句惯。
(3)測試源碼文件
名稱以 _test.go 為后綴的代碼文件,并且必須包含 Test 或者 Benchmark 名稱前綴的函數(shù)支救。
func TestXXX( t *testing.T) {
}
名稱以 Test 為名稱前綴的函數(shù)抢野,只能接受 *testing.T 的參數(shù),這種測試函數(shù)是功能測試函數(shù)各墨。
func BenchmarkXXX( b *testing.B) {
}
名稱以 Benchmark 為名稱前綴的函數(shù)指孤,只能接受 *testing.B 的參數(shù)甥雕,這種測試函數(shù)是性能測試函數(shù)鄙漏。
現(xiàn)在答案就很明顯了:
命令源碼文件是可以單獨運行的拗窃◇案可以使用 go run 命令直接運行,也可以通過 go build 或 go install 命令得到相應(yīng)的可執(zhí)行文件叉跛。所以命令源碼文件是可以在機器的任何目錄下運行的松忍。
舉個例子:
比如平時我們在 LeetCode 上刷算法題,這時候?qū)懙木褪且粋€程序筷厘,這就是命令源碼文件鸣峭,可以在電腦的任意一個文件夾新建一個 go 文件就可以開始刷題了,寫完就可以運行敞掘,對比執(zhí)行結(jié)果叽掘,答案對了就可以提交代碼。
但是公司項目里面的代碼就不能這樣了玖雁,只能存放在 GOPATH 目錄下更扁。因為公司項目不可能只有命令源碼文件的,肯定是包含庫源碼文件赫冬,甚至包含測試源碼文件的浓镜。
3.GOBIN
該環(huán)境變量的值為 Go 程序的可執(zhí)行文件的目錄。
4.PATH
為了方便使用 Go 語言命令和 Go 程序的可執(zhí)行文件劲厌,需要添加其值膛薛。追加的操作還是用:
分隔。
export PATH=$PATH:$GOBIN
以上就是關(guān)于 Go 的4個重要環(huán)境變量的理解补鼻。還有一些其他的環(huán)境變量哄啄,用 go env 命令就可以查看。
YDZ ~ $ go env
GOARCH="amd64"
GOBIN="/Users/YDZ/Ele_Project/clairstormeye/bin"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/YDZ/Ele_Project/clairstormeye"
GORACE=""
GOROOT="/usr/local/Cellar/go/1.8.3/libexec"
GOTOOLDIR="/usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/66/dcf61ty92rgd_xftrsxgx5yr0000gn/T/go-build977187889=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
PKG_CONFIG="pkg-config"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
名稱 | 說明 |
---|---|
CGO_ENABLED | 指明 cgo 工具是否可用的標識 |
GOARCH | 程序構(gòu)建環(huán)境的目標計算架構(gòu) |
GOBIN | 存放可執(zhí)行文件的目錄的絕對路徑 |
GOCHAR | 程序構(gòu)建環(huán)境的目標計算架構(gòu)的單字符標識 |
GOEXE | 可執(zhí)行文件的后綴 |
GOHOSTARCH | 程序運行環(huán)境的目標計算架構(gòu) |
GOOS | 程序構(gòu)建環(huán)境的目標操作系統(tǒng) |
GOHOSTOS | 程序運行環(huán)境的目標操作系統(tǒng) |
GOPATH | 工作區(qū)目錄的絕對路徑 |
GORACE | 用于數(shù)據(jù)競爭檢測的相關(guān)選項 |
GOROOT | Go 語言的安裝目錄的絕對路徑 |
GOTOOLDIR | Go 工具目錄的絕對路徑 |
在探索 Go 的編譯命令之前风范,需要說明的一點是:
Go 程序是通過 package 來組織的咨跌。
package <pkgName>(假設(shè)我們的例子中是 package main)這一行告訴我們當前文件屬于哪個包,而包名 main 則告訴我們它是一個可獨立運行的包硼婿,它在編譯后會產(chǎn)生可執(zhí)行文件锌半。除了 main 包之外,其它的包最后都會生成 *.a 文件(也就是包文件)并放置在 $GOPATH/pkg/$GOOS_$GOARCH中(以 Mac 為例就是
$GOPATH/pkg/darwin_amd64 )寇漫。
Go 使用 package(和 Python 的模塊類似)來組織代碼刊殉。main.main() 函數(shù)(這個函數(shù)位于主包)是每一個獨立的可運行程序的入口點。
每一個可獨立運行的 Go 程序州胳,必定包含一個 package main记焊,在這個 main 包中必定包含一個入口函數(shù) main,而這個函數(shù)既沒有參數(shù)栓撞,也沒有返回值亚亲。
二. 初探 Go 的編譯過程
目前 Go 最新版1.8.3里面基本命令只有以下的16個。
build compile packages and dependencies
clean remove object files
doc show documentation for package or symbol
env print Go environment information
bug start a bug report
fix run go tool fix on packages
fmt run gofmt on package sources
generate generate Go files by processing source
get download and install packages and dependencies
install compile and install packages and dependencies
list list packages
run compile and run Go program
test test packages
tool run specified go tool
version print Go version
vet run go tool vet on packages
其中和編譯相關(guān)的有 build腐缤、get捌归、install、run 這4個岭粤。接下來就依次看看這四個的作用惜索。
在詳細分析這4個命令之前,先羅列一下通用的命令標記剃浇,以下這些命令都可適用的:
名稱 | 說明 |
---|---|
-a | 用于強制重新編譯所有涉及的 Go 語言代碼包(包括 Go 語言標準庫中的代碼包)巾兆,即使它們已經(jīng)是最新的了。該標記可以讓我們有機會通過改動底層的代碼包做一些實驗虎囚。 |
-n | 使命令僅打印其執(zhí)行過程中用到的所有命令角塑,而不去真正執(zhí)行它們。如果不只想查看或者驗證命令的執(zhí)行過程淘讥,而不想改變?nèi)魏螙|西圃伶,使用它正好合適。 |
-race | 用于檢測并報告指定 Go 語言程序中存在的數(shù)據(jù)競爭問題蒲列。當用 Go 語言編寫并發(fā)程序的時候窒朋,這是很重要的檢測手段之一。 |
-v | 用于打印命令執(zhí)行過程中涉及的代碼包蝗岖。這一定包括我們指定的目標代碼包侥猩,并且有時還會包括該代碼包直接或間接依賴的那些代碼包。這會讓你知道哪些代碼包被執(zhí)行過了抵赢。 |
-work | 用于打印命令執(zhí)行時生成和使用的臨時工作目錄的名字欺劳,且命令執(zhí)行完成后不刪除它。這個目錄下的文件可能會對你有用铅鲤,也可以從側(cè)面了解命令的執(zhí)行過程划提。如果不添加此標記,那么臨時工作目錄會在命令執(zhí)行完畢前刪除彩匕。 |
-x | 使命令打印其執(zhí)行過程中用到的所有命令腔剂,并同時執(zhí)行它們。 |
1. go run
專門用來運行命令源碼文件的命令驼仪,注意掸犬,這個命令不是用來運行所有 Go 的源碼文件的!
go run 命令只能接受一個命令源碼文件以及若干個庫源碼文件(必須同屬于 main 包)作為文件參數(shù)绪爸,且不能接受測試源碼文件湾碎。它在執(zhí)行時會檢查源碼文件的類型。如果參數(shù)中有多個或者沒有命令源碼文件奠货,那么 go run 命令就只會打印錯誤提示信息并退出介褥,而不會繼續(xù)執(zhí)行。
這個命令具體干了些什么事情呢?來分析分析:
YDZ ~/LeetCode_Go/helloworld/src/me $ go run -n helloworld.go
#
# command-line-arguments
#
mkdir -p $WORK/command-line-arguments/_obj/
mkdir -p $WORK/command-line-arguments/_obj/exe/
cd /Users/YDZ/LeetCode_Go/helloworld/src/me
/usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64/compile -o $WORK/command-line-arguments.a -trimpath $WORK -p main -complete -buildid 2841ae50ca62b7a3671974e64d76e198a2155ee7 -D _/Users/YDZ/LeetCode_Go/helloworld/src/me -I $WORK -pack ./helloworld.go
cd .
/usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64/link -o $WORK/command-line-arguments/_obj/exe/helloworld -L $WORK -w -extld=clang -buildmode=exe -buildid=2841ae50ca62b7a3671974e64d76e198a2155ee7 $WORK/command-line-arguments.a
$WORK/command-line-arguments/_obj/exe/helloworld
這里可以看到創(chuàng)建了兩個臨時文件夾 _obj 和 exe柔滔,先執(zhí)行了 compile 命令溢陪,然后 link,生成了歸檔文件.a 和 最終可執(zhí)行文件睛廊,最終的可執(zhí)行文件放在 exe 文件夾里面形真。命令的最后一步就是執(zhí)行了可執(zhí)行文件。
總結(jié)一下如下圖:
舉個例子超全,生成的臨時文件可以用go run -work
看到咆霜,比如當前生成的臨時文件夾是如下的路徑:
/var/folders/66/dcf61ty92rgd_xftrsxgx5yr0000gn/T/go-build876472071
打印目錄結(jié)構(gòu):
├── command-line-arguments
│ └── _obj
│ └── exe
│ └── helloworld
└── command-line-arguments.a
可以看到,最終go run
命令是生成了2個文件嘶朱,一個是歸檔文件蛾坯,一個是可執(zhí)行文件。command-line-arguments 這個歸檔文件是 Go 語言為命令源碼文件臨時指定的一個代碼包疏遏。在接下來的幾個命令中脉课,生成的臨時代碼包都叫這個名字。
go run 命令在第二次執(zhí)行的時候改览,如果發(fā)現(xiàn)導(dǎo)入的代碼包沒有發(fā)生變化下翎,那么 go run 不會再次編譯這個導(dǎo)入的代碼包。直接靜態(tài)鏈接進來宝当。
go run -a
加上-a
的標記可以強制編譯所有的代碼视事,即使歸檔文件.a存在,也會重新編譯庆揩。
如果嫌棄編譯速度慢俐东,可以加上-p n
,這個是并行編譯订晌,n是并行的數(shù)量虏辫。n一般為邏輯 CPU 的個數(shù)。
2. go build
當代碼包中有且僅有一個命令源碼文件的時候锈拨,在文件夾所在目錄中執(zhí)行 go build 命令砌庄,會在該目錄下生成一個與目錄同名的可執(zhí)行文件。
// 假設(shè)當前文件夾名叫 myGoRepo
YDZ:~/helloworld/src/myGoRepo $ ls
helloworld.go
YDZ:~/helloworld/src/myGoRepo $ go build
YDZ:~/helloworld/src/myGoRepo $ ls
helloworld.go myGoRepo
于是在當前目錄直接生成了以當前文件夾為名的可執(zhí)行文件( 在 Mac 平臺下是 Unix executable 文件奕枢,在 Windows 平臺下是 exe 文件)
我們先記錄一下這個可執(zhí)行文件的 md5 值
YDZ ~/helloworld/src/myGoRepo $ md5 /Users/YDZ/helloworld/src/myGoRepo/myGoRepo
MD5 (/Users/YDZ/helloworld/src/myGoRepo/myGoRepo) = 1f23f6efec752ed34b9bd22b5fa1ddce
但是這種情況下娄昆,如果使用 go install 命令,如果 GOPATH 里面只有一個工作區(qū)缝彬,就會在當前工作區(qū)的 bin 目錄下生成相應(yīng)的可執(zhí)行文件萌焰。如果 GOPATH 下有多個工作區(qū),則是在 GOBIN 下生成對應(yīng)的可執(zhí)行文件谷浅。
咱們先接著剛剛 go build 繼續(xù)操作扒俯。
YDZ:~/helloworld/src/myGoRepo $ ls
helloworld.go myGoRepo
YDZ:~/helloworld/src/myGoRepo $ go install
YDZ:~/helloworld/src/myGoRepo $ ls
helloworld.go
執(zhí)行完 go install 會發(fā)現(xiàn)可執(zhí)行文件不見了奶卓!去哪里了呢?其實是被移動到了 bin 目錄下了(如果 GOPATH 下有多個工作區(qū)撼玄,就會放在
GOBIN 目錄下)夺姑。
YDZ:~/helloworld/bin $ ls
myGoRepo
再來比對一下這個文件的 md5 值:
YDZ ~/helloworld/bin $ md5 /Users/YDZ/helloworld/bin/myGoRepo
MD5 (/Users/YDZ/helloworld/bin/myGoRepo) = 1f23f6efec752ed34b9bd22b5fa1ddce
和 go build 命令執(zhí)行出來的可執(zhí)行文件完全一致。我們可以大膽猜想互纯,是把剛剛 go build 命令執(zhí)行出來的可執(zhí)行文件移動到了 bin 目錄下(如果 GOPATH 下有多個工作區(qū)瑟幕,就會放在 GOBIN 目錄下)。
那 go build 和 go install 究竟干了些什么呢留潦?
這個問題一會再來解釋,先來說說 go build辣往。
go build 用于編譯我們指定的源碼文件或代碼包以及它們的依賴包兔院。,但是注意如果用來編譯非命令源碼文件站削,即庫源碼文件坊萝,go build 執(zhí)行完是不會產(chǎn)生任何結(jié)果的。這種情況下许起,go build 命令只是檢查庫源碼文件的有效性十偶,只會做檢查性的編譯,而不會輸出任何結(jié)果文件园细。
go build 編譯命令源碼文件惦积,則會在該命令的執(zhí)行目錄中生成一個可執(zhí)行文件,上面的例子也印證了這個過程猛频。
go build 后面不追加目錄路徑的話狮崩,它就把當前目錄作為代碼包并進行編譯。go build 命令后面如果跟了代碼包導(dǎo)入路徑作為參數(shù)鹿寻,那么該代碼包及其依賴都會被編譯睦柴。
go run 的-a
標記在 go build 這里同樣奏效,go build 加了-a
強制編譯所有涉及到的代碼包毡熏,不加-a
只會編譯歸檔文件不是最新的代碼包坦敌。
go build 使用-o
標記可以指定輸出文件(在這個示例中指的是可執(zhí)行文件)的名稱。它是最常用的一個 go build 命令標記痢法。但需要注意的是狱窘,當使用標記-o
的時候,不能同時對多個代碼包進行編譯疯暑。
標記-i
會使 go build 命令安裝那些編譯目標依賴的且還未被安裝的代碼包训柴。這里的安裝意味著產(chǎn)生與代碼包對應(yīng)的歸檔文件,并將其放置到當前工作區(qū)目錄的 pkg 子目錄的相應(yīng)子目錄中妇拯。在默認情況下幻馁,這些代碼包是不會被安裝的洗鸵。
go build 常用的一些標記如下:
標記名稱 | 標記描述 |
---|---|
-a | 強行對所有涉及到的代碼包(包含標準庫中的代碼包)進行重新構(gòu)建,即使它們已經(jīng)是最新的了仗嗦。 |
-n | 打印編譯期間所用到的其它命令膘滨,但是并不真正執(zhí)行它們。 |
-p n | 指定編譯過程中執(zhí)行各任務(wù)的并行數(shù)量(確切地說應(yīng)該是并發(fā)數(shù)量)稀拐。在默認情況下火邓,該數(shù)量等于CPU的邏輯核數(shù)。但是在darwin/arm 平臺(即iPhone和iPad所用的平臺)下德撬,該數(shù)量默認是1 铲咨。 |
-race | 開啟競態(tài)條件的檢測。不過此標記目前僅在linux/amd64 蜓洪、freebsd/amd64 纤勒、darwin/amd64 和windows/amd64 平臺下受到支持。 |
-v | 打印出那些被編譯的代碼包的名字隆檀。 |
-work | 打印出編譯時生成的臨時工作目錄的路徑摇天,并在編譯結(jié)束時保留它。在默認情況下恐仑,編譯結(jié)束時會刪除該目錄泉坐。 |
-x | 打印編譯期間所用到的其它命令。注意它與-n 標記的區(qū)別裳仆。 |
go build 命令究竟做了些什么呢腕让?我們來打印一下每一步的執(zhí)行過程。先看看命令源碼文件執(zhí)行了 go build 干了什么事情鉴逞。
#
# command-line-arguments
#
mkdir -p $WORK/command-line-arguments/_obj/
mkdir -p $WORK/command-line-arguments/_obj/exe/
cd /Users/YDZ/MyGitHub/LeetCode_Go/helloworld/src/me
/usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64/compile -o $WORK/command-line-arguments.a -trimpath $WORK -p main -complete -buildid 2841ae50ca62b7a3671974e64d76e198a2155ee7 -D _/Users/YDZ/MyGitHub/LeetCode_Go/helloworld/src/me -I $WORK -pack ./helloworld.go
cd .
/usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64/link -o $WORK/command-line-arguments/_obj/exe/a.out -L $WORK -extld=clang -buildmode=exe -buildid=2841ae50ca62b7a3671974e64d76e198a2155ee7 $WORK/command-line-arguments.a
mv $WORK/command-line-arguments/_obj/exe/a.out helloworld
可以看到记某,執(zhí)行過程和 go run 大體相同,唯一不同的就是在最后一步构捡,go run 是執(zhí)行了可執(zhí)行文件液南,但是 go build 命令是把可執(zhí)行文件移動到了當前目錄的文件夾中。
打印看看生成的臨時文件夾的樹形結(jié)構(gòu)
.
├── command-line-arguments
│ └── _obj
│ └── exe
└── command-line-arguments.a
和 go run 命令的結(jié)構(gòu)基本一致勾徽,唯一的不同可執(zhí)行文件不在 exe 文件夾中了滑凉,被移動到了當前執(zhí)行 go build 的文件夾中了。
在來看看庫源碼文件執(zhí)行了 go build 以后干了什么事情:
#
# _/Users/YDZ/Downloads/goc2p-master/src/pkgtool
#
mkdir -p $WORK/_/Users/YDZ/Downloads/goc2p-master/src/pkgtool/_obj/
mkdir -p $WORK/_/Users/YDZ/Downloads/goc2p-master/src/
cd /Users/YDZ/Downloads/goc2p-master/src/pkgtool
/usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64/compile -o $WORK/_/Users/YDZ/Downloads/goc2p-master/src/pkgtool.a -trimpath $WORK -p _/Users/YDZ/Downloads/goc2p-master/src/pkgtool -complete -buildid cef542c3da6d3126cdae561b5f6e1470aff363ba -D _/Users/YDZ/Downloads/goc2p-master/src/pkgtool -I $WORK -pack ./envir.go ./fpath.go ./ipath.go ./pnode.go ./util.go
這里可以看到 go build 命令只是把庫源碼文件編譯了一遍喘帚,其他什么事情都沒有干畅姊。
再看看生成的臨時文件夾的樹形結(jié)構(gòu)
.
└── _
└── Users
└── YDZ
└── Downloads
└── goc2p-master
└── src
├── pkgtool
│ └── _obj
└── pkgtool.a
可以看到它的目錄結(jié)構(gòu)層級前段部分是該代碼包所在本機的路徑的相對路徑。然后生成了歸檔文件 .a 文件吹由。
總結(jié)一下如下圖:
關(guān)于 go build 和 go install 的不同若未,接下來分析完 go install 就會明白了,接下來繼續(xù)看 go install倾鲫。
3. go install
go install 命令是用來編譯并安裝代碼包或者源碼文件的粗合。
go install 用于編譯并安裝指定的代碼包及它們的依賴包萍嬉。當指定的代碼包的依賴包還沒有被編譯和安裝時,該命令會先去處理依賴包隙疚。與 go build 命令一樣壤追,傳給 go install 命令的代碼包參數(shù)應(yīng)該以導(dǎo)入路徑的形式提供。并且供屉,go build 命令的絕大多數(shù)標記也都可以用于
go install 命令行冰。實際上,go install 命令只比 go build 命令多做了一件事伶丐,即:安裝編譯后的結(jié)果文件到指定目錄悼做。
安裝代碼包會在當前工作區(qū)的 pkg 的平臺相關(guān)目錄下生成歸檔文件(即 .a 文件)。
安裝命令源碼文件會在當前工作區(qū)的 bin 目錄(如果 GOPATH 下有多個工作區(qū)撵割,就會放在 GOBIN 目錄下)生成可執(zhí)行文件贿堰。
同樣,go install 命令如果后面不追加任何參數(shù)啡彬,它會把當前目錄作為代碼包并安裝。這和 go build 命令是完全一樣的故硅。
go install 命令后面如果跟了代碼包導(dǎo)入路徑作為參數(shù)庶灿,那么該代碼包及其依賴都會被安裝。
go install 命令后面如果跟了命令源碼文件以及相關(guān)庫源碼文件作為參數(shù)的話吃衅,只有這些文件會被編譯并安裝往踢。
go install 命令究竟做了些什么呢?我們來打印一下每一步的執(zhí)行過程徘层。
#
# command-line-arguments
#
mkdir -p $WORK/command-line-arguments/_obj/
mkdir -p $WORK/command-line-arguments/_obj/exe/
cd /Users/YDZ/MyGitHub/LeetCode_Go/helloworld/src/me
/usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64/compile -o $WORK/command-line-arguments.a -trimpath $WORK -p main -complete -buildid 2841ae50ca62b7a3671974e64d76e198a2155ee7 -D _/Users/YDZ/MyGitHub/LeetCode_Go/helloworld/src/me -I $WORK -pack ./helloworld.go
cd .
/usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64/link -o $WORK/command-line-arguments/_obj/exe/a.out -L $WORK -extld=clang -buildmode=exe -buildid=2841ae50ca62b7a3671974e64d76e198a2155ee7 $WORK/command-line-arguments.a
mkdir -p /Users/YDZ/Ele_Project/clairstormeye/bin/
mv $WORK/command-line-arguments/_obj/exe/a.out /Users/YDZ/Ele_Project/clairstormeye/bin/helloworld
前面幾步依舊和 go run 峻呕、go build 完全一致,只是最后一步的差別趣效,go install 會把命令源碼文件安裝到當前工作區(qū)的 bin 目錄(如果 GOPATH 下有多個工作區(qū)瘦癌,就會放在 GOBIN 目錄下)。如果是庫源碼文件跷敬,就會被安裝到當前工作區(qū)的 pkg 的平臺相關(guān)目錄下讯私。
還是來看看 go install 生成的臨時文件夾的結(jié)構(gòu):
.
├── command-line-arguments
│ └── _obj
│ └── exe
└── command-line-arguments.a
結(jié)構(gòu)和運行了 go build 命令一樣,最終生成的文件也都被移動到了相對應(yīng)的目標目錄中西傀。
總結(jié)一下如下圖:
在安裝多個庫源碼文件時有可能遇到如下的問題:
hc@ubt:~/golang/goc2p/src/pkgtool$ go install envir.go fpath.go ipath.go pnode.go util.go
go install: no install location for .go files listed on command line (GOBIN not set)
而且斤寇,在我們?yōu)榄h(huán)境變量 GOBIN 設(shè)置了正確的值之后,這個錯誤提示信息仍然會出現(xiàn)拥褂。這是因為娘锁,只有在安裝命令源碼文件的時候,命令程序才會將環(huán)境變量 GOBIN 的值作為結(jié)果文件的存放目錄饺鹃。而在安裝庫源碼文件時莫秆,在命令程序內(nèi)部的代表結(jié)果文件存放目錄路徑的那個變量不會被賦值间雀。最后,命令程序會發(fā)現(xiàn)它依然是個無效的空值馏锡。所以雷蹂,命令程序會同樣返回一個關(guān)于“無安裝位置”的錯誤。這就引出一個結(jié)論杯道,我們只能使用安裝代碼包的方式來安裝庫源碼文件匪煌,而不能在 go install 命令羅列并安裝它們。另外党巾,go install 命令目前無法接受標記-o
以自定義結(jié)果文件的存放位置萎庭。這也從側(cè)面說明了
go install 命令不支持針對庫源碼文件的安裝操作。
4. go get
go get 命令用于從遠程代碼倉庫(比如 Github )上下載并安裝代碼包齿拂。注意驳规,go get 命令會把當前的代碼包下載到 $GOPATH 中的第一個工作區(qū)的 src 目錄中,并安裝署海。
使用 go get 下載第三方包的時候吗购,依舊會下載到 $GOPATH 的第一個工作空間,而非 vendor 目錄砸狞。當前工作鏈中并沒有真正意義上的包依賴管理捻勉,不過好在有不少第三方工具可選。
如果在 go get 下載過程中加入-d
標記刀森,那么下載操作只會執(zhí)行下載動作踱启,而不執(zhí)行安裝動作。比如有些非常特殊的代碼包在安裝過程中需要有特殊的處理研底,所以我們需要先下載下來埠偿,所以就會用到-d
標記。
還有一個很有用的標記是-u
標記榜晦,加上它可以利用網(wǎng)絡(luò)來更新已有的代碼包及其依賴包冠蒋。如果已經(jīng)下載過一個代碼包,但是這個代碼包又有更新了芽隆,那么這時候可以直接用-u
標記來更新本地的對應(yīng)的代碼包浊服。如果不加這個-u
標記,執(zhí)行 go get 一個已有的代碼包胚吁,會發(fā)現(xiàn)命令什么都不執(zhí)行牙躺。只有加了-u
標記,命令會去執(zhí)行 git pull 命令拉取最新的代碼包的最新版本腕扶,下載并安裝孽拷。
命令 go get 還有一個很值得稱道的功能——智能下載。在使用它檢出或更新代碼包之后半抱,它會尋找與本地已安裝 Go 語言的版本號相對應(yīng)的標簽(tag)或分支(branch)脓恕。比如膜宋,本機安裝 Go 語言的版本是1.x,那么 go get 命令會在該代碼包的遠程倉庫中尋找名為 “go1” 的標簽或者分支炼幔。如果找到指定的標簽或者分支秋茫,則將本地代碼包的版本切換到此標簽或者分支。如果沒有找到指定的標簽或者分支乃秀,則將本地代碼包的版本切換到主干的最新版本肛著。
go get 常用的一些標記如下:
標記名稱 | 標記描述 |
---|---|
-d | 讓命令程序只執(zhí)行下載動作,而不執(zhí)行安裝動作跺讯。 |
-f | 僅在使用-u 標記時才有效枢贿。該標記會讓命令程序忽略掉對已下載代碼包的導(dǎo)入路徑的檢查。如果下載并安裝的代碼包所屬的項目是你從別人那里 Fork 過來的刀脏,那么這樣做就尤為重要了局荚。 |
-fix | 讓命令程序在下載代碼包后先執(zhí)行修正動作,而后再進行編譯和安裝愈污。 |
-insecure | 允許命令程序使用非安全的 scheme(如 HTTP )去下載指定的代碼包耀态。如果你用的代碼倉庫(如公司內(nèi)部的 Gitlab )沒有HTTPS 支持,可以添加此標記暂雹。請在確定安全的情況下使用它茫陆。 |
-t | 讓命令程序同時下載并安裝指定的代碼包中的測試源碼文件中依賴的代碼包。 |
-u | 讓命令利用網(wǎng)絡(luò)來更新已有代碼包及其依賴包擎析。默認情況下,該命令只會從網(wǎng)絡(luò)上下載本地不存在的代碼包挥下,而不會更新已有的代碼包揍魂。 |
go get 命令究竟做了些什么呢?我們還是來打印一下每一步的執(zhí)行過程棚瘟。
cd .
git clone https://github.com/go-errors/errors /Users/YDZ/Ele_Project/clairstormeye/src/github.com/go-errors/errors
cd /Users/YDZ/Ele_Project/clairstormeye/src/github.com/go-errors/errors
git submodule update --init --recursive
cd /Users/YDZ/Ele_Project/clairstormeye/src/github.com/go-errors/errors
git show-ref
cd /Users/YDZ/Ele_Project/clairstormeye/src/github.com/go-errors/errors
git submodule update --init --recursive
WORK=/var/folders/66/dcf61ty92rgd_xftrsxgx5yr0000gn/T/go-build124856678
mkdir -p $WORK/github.com/go-errors/errors/_obj/
mkdir -p $WORK/github.com/go-errors/
cd /Users/YDZ/Ele_Project/clairstormeye/src/github.com/go-errors/errors
/usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64/compile -o $WORK/github.com/go-errors/errors.a -trimpath $WORK -p github.com/go-errors/errors -complete -buildid bb3526a8c1c21853f852838637d531b9fcd57d30 -D _/Users/YDZ/Ele_Project/clairstormeye/src/github.com/go-errors/errors -I $WORK -pack ./error.go ./parse_panic.go ./stackframe.go
mkdir -p /Users/YDZ/Ele_Project/clairstormeye/pkg/darwin_amd64/github.com/go-errors/
mv $WORK/github.com/go-errors/errors.a /Users/YDZ/Ele_Project/clairstormeye/pkg/darwin_amd64/github.com/go-errors/errors.a
這里可以很明顯的看到现斋,執(zhí)行完 go get 命令以后,會調(diào)用 git clone 方法下載源碼偎蘸,并編譯庄蹋,最終會把庫源碼文件編譯成歸檔文件安裝到 pkg 對應(yīng)的相關(guān)平臺目錄下。
總結(jié)一下如下圖:
關(guān)于工作區(qū)的問題迷雪,這里額外提一下:
一般情況下限书,為了分離自己與第三方的代碼,我們會設(shè)置兩個或更多的工作區(qū)章咧。我們現(xiàn)在有一個目錄路徑為 /home/hc/golang/lib 的工作區(qū)倦西,并且它是環(huán)境變量 GOPATH 值中的第一個目錄路徑。注意赁严,環(huán)境變量 GOPATH 中包含的路徑不能與環(huán)境變量GOROOT的值重復(fù)扰柠。好了粉铐,如果我們使用 go get 命令下載和安裝代碼包,那么這些代碼包都會被安裝在上面這個工作區(qū)中卤档。我們暫且把這個工作區(qū)叫做
Lib 工作區(qū)蝙泼。
三. 靜態(tài)鏈接 or 動態(tài)鏈接 ?
Go 在最初剛剛發(fā)布的時候劝枣,靜態(tài)鏈接被當做優(yōu)點宣傳汤踏,只須編譯后的一個可執(zhí)行文件,無須附加任何東西就能部署哨免。將運行時茎活、依賴庫直接打包到可執(zhí)行文件內(nèi)部,簡化了部署和發(fā)布的操作琢唾,無須事先安裝運行環(huán)境和下載諸多第三方庫载荔。不過最新版本卻又加入了動態(tài)鏈接的內(nèi)容了。
普通的 go build 采桃、go install 用的都是靜態(tài)鏈接懒熙。可以驗證一下:
上圖是筆者用 MachOView 打開的 gofmt 文件普办,可以看到 fmt.Println 的地址是確定的工扎,所以可以確定是靜態(tài)鏈接的。
目前最新版的 Go 是如何支持動態(tài)鏈接的呢衔蹲?
在 go build 肢娘、go install 的時候加上 -buildmode 參數(shù)。
這些是以下 buildmode 的選項:
archive: 將非 main 包構(gòu)建為 .a 文件 . main 包將被忽略舆驶。
c-archive: 將 main 軟件包及其導(dǎo)入的所有軟件包構(gòu)建到 C 歸檔文件中
c-shared: 將列出的主要軟件包橱健,以及它們導(dǎo)入的所有軟件包構(gòu)建到
C 動態(tài)庫中。
shared: 將所有列出的非 main 軟件包合并到一個動態(tài)庫中沙廉。
exe: 構(gòu)建列出的 main 包及其導(dǎo)入到可執(zhí)行文件中的一切拘荡。 將忽略未命名為 main 的包。
默認情況下撬陵,列出的 main 軟件包內(nèi)置到可執(zhí)行文件中珊皿,列出的非
main 軟件包內(nèi)置到 .a 文件中。
關(guān)于動態(tài)庫巨税,筆者還沒有實踐過蟋定,這里就不繼續(xù)深入了,以后充分實踐后垢夹,再開一篇單獨的文章談?wù)?Go 的動態(tài)鏈接溢吻。這里只是想說明一點,Go 目前不僅僅只有靜態(tài)鏈接,動態(tài)鏈接也支持了促王!
Reference:
《GO 命令教程》
《Go 并發(fā)編程實戰(zhàn)》
GitHub Repo:Halfrost-Field
Follow: halfrost · GitHub
Source: https://halfrost.com/go_command/