Go 語言這兩年在語言排行榜上的上升勢頭很是猛,Go 語言雖然是靜態編譯型語言,可是它卻擁有腳本化的語法,支持多種編程範式(函數式和麪向對象)。Go 語言最最吸引人的地方多是其原生支持併發編程(語言層面原生支持和經過第三方庫支持是有很大區別的)。Go 語言的對網絡通訊、併發和並行編程的支持度極高,從而能夠更好地利用大量的分佈式和多核的計算機。開發者能夠經過 goroutine 這種輕量級線程的概念來實現這個目標,而後經過 channel 來實現各個 goroutine 之間的通訊。他們實現了分段棧增加和 goroutine 在線程基礎上多路複用技術的自動化。node
2017年7月 TIOBE 語言排行榜 Go 首次進入前十。今天就讓咱們來探究探究 Go 的編譯命令執行過程。linux
該環境變量的值爲 Go 語言的當前安裝目錄。git
該環境變量的值爲 Go 語言的工做區的集合(意味着能夠有不少個)。工做區相似於工做目錄。每一個不一樣的目錄之間用:
分隔。github
工做區是放置 Go 源碼文件的目錄。通常狀況下,Go 源碼文件都須要存放到工做區中。golang
工做區通常會包含3個子文件夾,本身手動新建如下三個目錄:src 目錄,pkg 目錄,bin 目錄。算法
/home/halfrost/gorepo
├── bin
├── pkg
└── src複製代碼
這裏須要額外說的一點:關於 IDE 新建 Go 項目。IDE 在新建完 Go 的項目之後,會自動的執行 go get 命令去把相應的基礎包拉過來,
在這個過程當中會新建 bin、pkg、src 三個目錄。不用 IDE 的同窗,須要本身手動建立這三個目錄。編程
上圖是 Atom 的 go-plus 插件在一個新的項目打開的時候,自動 go get 的一些基礎包。vim
bin 目錄裏面存放的都是經過 go install 命令安裝後,由 Go 命令源碼文件生成的可執行文件( 在 Mac 平臺下是 Unix executable 文件,在 Windows 平臺下是 exe 文件)。windows
注意:有兩種狀況下,bin 目錄會變得沒有意義。安全
- 當設置了有效的 GOBIN 環境變量之後,bin 目錄就變得沒有意義。
- 若是 GOPATH 裏面包含多個工做區路徑的時候,必須設置 GOBIN 環境變量,不然就沒法安裝 Go 程序的可執行文件。
pkg 目錄是用來存放經過 go install 命令安裝後的代碼包的歸檔文件(.a 文件)。歸檔文件的名字就是代碼包的名字。全部歸檔文件都會被存放到該目錄下的平臺相關目錄中,即在 $GOPATH\/pkg\/$GOOS_$GOARCH 中,一樣以代碼包爲組織形式。
這裏有兩個隱藏的環境變量,GOOS 和 GOARCH。這兩個環境變量是不用咱們設置的,系統就默認的。GOOS 是 Go 所在的操做系統類型,GOARCH 是 Go 所在的計算架構。平臺相關目錄是以
$GOOS_$GOARCH 命名的,Mac 平臺上這個目錄名就是 darwin_amd64。
src 目錄是以代碼包的形式組織並保存 Go 源碼文件的。每一個代碼包都和 src 目錄下的文件夾一一對應。每一個子目錄都是一個代碼包。
這裏有一個特例,命令源碼文件並不必定必須放在 src 文件夾中的。
這裏須要糾正一個錯誤的觀點:「全部的 Go 的代碼都要放在 GOPATH 目錄下」(這個觀點是錯誤的)
說到這裏須要談到 Go 的源碼文件分類:
如上圖,分爲三類:
(1)命令源碼文件:
聲明本身屬於 main 代碼包、包含無參數聲明和結果聲明的 main 函數。
命令源碼文件被安裝之後,GOPATH 若是隻有一個工做區,那麼相應的可執行文件會被存放當前工做區的 bin 文件夾下;若是有多個工做區,就會安裝到 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。接下來執行 go build 和 go install ,看看會發生什麼。
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。
同理,若是命令源碼文件和庫源碼文件也會出現這樣的問題,庫源碼文件不能經過 go build 和 go install 這種常規的方法編譯和安裝。具體例子和上述相似,這裏就再也不貼代碼了。
因此命令源碼文件應該是被單獨放在一個代碼包中。
(2)庫源碼文件
庫源碼文件就是不具有命令源碼文件上述兩個特徵的源碼文件。存在於某個代碼包中的普通的源碼文件。
庫源碼文件被安裝後,相應的歸檔文件(.a 文件)會被存放到當前工做區的 pkg 的平臺相關目錄下。
(3)測試源碼文件
名稱以 _test.go 爲後綴的代碼文件,而且必須包含 Test 或者 Benchmark 名稱前綴的函數。
func TestXXX( t *testing.T) {
}複製代碼
名稱以 Test 爲名稱前綴的函數,只能接受 *testing.T 的參數,這種測試函數是功能測試函數。
func BenchmarkXXX( b *testing.B) {
}複製代碼
名稱以 Benchmark 爲名稱前綴的函數,只能接受 *testing.B 的參數,這種測試函數是性能測試函數。
如今答案就很明顯了:
命令源碼文件是能夠單獨運行的。可使用 go run 命令直接運行,也能夠經過 go build 或 go install 命令獲得相應的可執行文件。因此命令源碼文件是能夠在機器的任何目錄下運行的。
舉個例子:
好比平時咱們在 LeetCode 上刷算法題,這時候寫的就是一個程序,這就是命令源碼文件,能夠在電腦的任意一個文件夾新建一個 go 文件就能夠開始刷題了,寫完就能夠運行,對比執行結果,答案對了就能夠提交代碼。
可是公司項目裏面的代碼就不能這樣了,只能存放在 GOPATH 目錄下。由於公司項目不可能只有命令源碼文件的,確定是包含庫源碼文件,甚至包含測試源碼文件的。
該環境變量的值爲 Go 程序的可執行文件的目錄。
爲了方便使用 Go 語言命令和 Go 程序的可執行文件,須要添加其值。追加的操做仍是用:
分隔。
export PATH=$PATH:$GOBIN複製代碼
以上就是關於 Go 的4個重要環境變量的理解。還有一些其餘的環境變量,用 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 | 程序構建環境的目標計算架構 |
GOBIN | 存放可執行文件的目錄的絕對路徑 |
GOCHAR | 程序構建環境的目標計算架構的單字符標識 |
GOEXE | 可執行文件的後綴 |
GOHOSTARCH | 程序運行環境的目標計算架構 |
GOOS | 程序構建環境的目標操做系統 |
GOHOSTOS | 程序運行環境的目標操做系統 |
GOPATH | 工做區目錄的絕對路徑 |
GORACE | 用於數據競爭檢測的相關選項 |
GOROOT | Go 語言的安裝目錄的絕對路徑 |
GOTOOLDIR | Go 工具目錄的絕對路徑 |
在探索 Go 的編譯命令以前,須要說明的一點是:
Go 程序是經過 package 來組織的。
package
$GOPATH/pkg/darwin_amd64 )。
Go 使用 package(和 Python 的模塊相似)來組織代碼。main.main() 函數(這個函數位於主包)是每個獨立的可運行程序的入口點。
每個可獨立運行的 Go 程序,一定包含一個 package main,在這個 main 包中一定包含一個入口函數 main,而這個函數既沒有參數,也沒有返回值。
目前 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複製代碼
其中和編譯相關的有 build、get、install、run 這4個。接下來就依次看看這四個的做用。
在詳細分析這4個命令以前,先羅列一下通用的命令標記,如下這些命令均可適用的:
名稱 | 說明 |
---|---|
-a | 用於強制從新編譯全部涉及的 Go 語言代碼包(包括 Go 語言標準庫中的代碼包),即便它們已是最新的了。該標記可讓咱們有機會經過改動底層的代碼包作一些實驗。 |
-n | 使命令僅打印其執行過程當中用到的全部命令,而不去真正執行它們。若是不僅想查看或者驗證命令的執行過程,而不想改變任何東西,使用它正好合適。 |
-race | 用於檢測並報告指定 Go 語言程序中存在的數據競爭問題。當用 Go 語言編寫併發程序的時候,這是很重要的檢測手段之一。 |
-v | 用於打印命令執行過程當中涉及的代碼包。這必定包括咱們指定的目標代碼包,而且有時還會包括該代碼包直接或間接依賴的那些代碼包。這會讓你知道哪些代碼包被執行過了。 |
-work | 用於打印命令執行時生成和使用的臨時工做目錄的名字,且命令執行完成後不刪除它。這個目錄下的文件可能會對你有用,也能夠從側面瞭解命令的執行過程。若是不添加此標記,那麼臨時工做目錄會在命令執行完畢前刪除。 |
-x | 使命令打印其執行過程當中用到的全部命令,並同時執行它們。 |
專門用來運行命令源碼文件的命令,注意,這個命令不是用來運行全部 Go 的源碼文件的!
go run 命令只能接受一個命令源碼文件以及若干個庫源碼文件(必須同屬於 main 包)做爲文件參數,且不能接受測試源碼文件。它在執行時會檢查源碼文件的類型。若是參數中有多個或者沒有命令源碼文件,那麼 go run 命令就只會打印錯誤提示信息並退出,而不會繼續執行。
這個命令具體幹了些什麼事情呢?來分析分析:
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複製代碼
這裏能夠看到建立了兩個臨時文件夾 _obj 和 exe,先執行了 compile 命令,而後 link,生成了歸檔文件.a 和 最終可執行文件,最終的可執行文件放在 exe 文件夾裏面。命令的最後一步就是執行了可執行文件。
總結一下以下圖:
舉個例子,生成的臨時文件能夠用go run -work
看到,好比當前生成的臨時文件夾是以下的路徑:
/var/folders/66/dcf61ty92rgd_xftrsxgx5yr0000gn/T/go-build876472071複製代碼
打印目錄結構:
├── command-line-arguments
│ └── _obj
│ └── exe
│ └── helloworld
└── command-line-arguments.a複製代碼
能夠看到,最終go run
命令是生成了2個文件,一個是歸檔文件,一個是可執行文件。command-line-arguments 這個歸檔文件是 Go 語言爲命令源碼文件臨時指定的一個代碼包。在接下來的幾個命令中,生成的臨時代碼包都叫這個名字。
go run 命令在第二次執行的時候,若是發現導入的代碼包沒有發生變化,那麼 go run 不會再次編譯這個導入的代碼包。直接靜態連接進來。
go run -a複製代碼
加上-a
的標記能夠強制編譯全部的代碼,即便歸檔文件.a存在,也會從新編譯。
若是嫌棄編譯速度慢,能夠加上-p n
,這個是並行編譯,n是並行的數量。n通常爲邏輯 CPU 的個數。
當代碼包中有且僅有一個命令源碼文件的時候,在文件夾所在目錄中執行 go build 命令,會在該目錄下生成一個與目錄同名的可執行文件。
// 假設當前文件夾名叫 myGoRepo
YDZ:~/helloworld/src/myGoRepo $ ls
helloworld.go
YDZ:~/helloworld/src/myGoRepo $ go build
YDZ:~/helloworld/src/myGoRepo $ ls
helloworld.go myGoRepo複製代碼
因而在當前目錄直接生成了以當前文件夾爲名的可執行文件( 在 Mac 平臺下是 Unix executable 文件,在 Windows 平臺下是 exe 文件)
咱們先記錄一下這個可執行文件的 md5 值
YDZ ~/helloworld/src/myGoRepo $ md5 /Users/YDZ/helloworld/src/myGoRepo/myGoRepo
MD5 (/Users/YDZ/helloworld/src/myGoRepo/myGoRepo) = 1f23f6efec752ed34b9bd22b5fa1ddce複製代碼
可是這種狀況下,若是使用 go install 命令,若是 GOPATH 裏面只有一個工做區,就會在當前工做區的 bin 目錄下生成相應的可執行文件。若是 GOPATH 下有多個工做區,則是在 GOBIN 下生成對應的可執行文件。
我們先接着剛剛 go build 繼續操做。
YDZ:~/helloworld/src/myGoRepo $ ls
helloworld.go myGoRepo
YDZ:~/helloworld/src/myGoRepo $ go install
YDZ:~/helloworld/src/myGoRepo $ ls
helloworld.go複製代碼
執行完 go install 會發現可執行文件不見了!去哪裏了呢?實際上是被移動到了 bin 目錄下了(若是 GOPATH 下有多個工做區,就會放在
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 命令執行出來的可執行文件徹底一致。咱們能夠大膽猜測,是把剛剛 go build 命令執行出來的可執行文件移動到了 bin 目錄下(若是 GOPATH 下有多個工做區,就會放在 GOBIN 目錄下)。
那 go build 和 go install 究竟幹了些什麼呢?
這個問題一會再來解釋,先來講說 go build。
go build 用於編譯咱們指定的源碼文件或代碼包以及它們的依賴包。,可是注意若是用來編譯非命令源碼文件,即庫源碼文件,go build 執行完是不會產生任何結果的。這種狀況下,go build 命令只是檢查庫源碼文件的有效性,只會作檢查性的編譯,而不會輸出任何結果文件。
go build 編譯命令源碼文件,則會在該命令的執行目錄中生成一個可執行文件,上面的例子也印證了這個過程。
go build 後面不追加目錄路徑的話,它就把當前目錄做爲代碼包並進行編譯。go build 命令後面若是跟了代碼包導入路徑做爲參數,那麼該代碼包及其依賴都會被編譯。
go run 的-a
標記在 go build 這裏一樣奏效,go build 加了-a
強制編譯全部涉及到的代碼包,不加-a
只會編譯歸檔文件不是最新的代碼包。
go build 使用-o
標記能夠指定輸出文件(在這個示例中指的是可執行文件)的名稱。它是最經常使用的一個 go build 命令標記。但須要注意的是,當使用標記-o
的時候,不能同時對多個代碼包進行編譯。
標記-i
會使 go build 命令安裝那些編譯目標依賴的且還未被安裝的代碼包。這裏的安裝意味着產生與代碼包對應的歸檔文件,並將其放置到當前工做區目錄的 pkg 子目錄的相應子目錄中。在默認狀況下,這些代碼包是不會被安裝的。
go build 經常使用的一些標記以下:
標記名稱 | 標記描述
|:-------|:-------:|
-a | 強行對全部涉及到的代碼包(包含標準庫中的代碼包)進行從新構建,即便它們已是最新的了。
-n | 打印編譯期間所用到的其它命令,可是並不真正執行它們。
-p n | 指定編譯過程當中執行各任務的並行數量(確切地說應該是併發數量)。在默認狀況下,該數量等於CPU的邏輯核數。可是在darwin/arm
平臺(即iPhone和iPad所用的平臺)下,該數量默認是1
。
-race | 開啓競態條件的檢測。不過此標記目前僅在linux/amd64
、freebsd/amd64
、darwin/amd64
和windows/amd64
平臺下受到支持。
-v | 打印出那些被編譯的代碼包的名字。
-work | 打印出編譯時生成的臨時工做目錄的路徑,並在編譯結束時保留它。在默認狀況下,編譯結束時會刪除該目錄。
-x | 打印編譯期間所用到的其它命令。注意它與-n
標記的區別。
go build 命令究竟作了些什麼呢?咱們來打印一下每一步的執行過程。先看看命令源碼文件執行了 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複製代碼
能夠看到,執行過程和 go run 大致相同,惟一不一樣的就是在最後一步,go run 是執行了可執行文件,可是 go build 命令是把可執行文件移動到了當前目錄的文件夾中。
打印看看生成的臨時文件夾的樹形結構
.
├── command-line-arguments
│ └── _obj
│ └── exe
└── command-line-arguments.a複製代碼
和 go run 命令的結構基本一致,惟一的不一樣可執行文件不在 exe 文件夾中了,被移動到了當前執行 go build 的文件夾中了。
在來看看庫源碼文件執行了 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 命令只是把庫源碼文件編譯了一遍,其餘什麼事情都沒有幹。
再看看生成的臨時文件夾的樹形結構
.
└── _
└── Users
└── YDZ
└── Downloads
└── goc2p-master
└── src
├── pkgtool
│ └── _obj
└── pkgtool.a複製代碼
能夠看到它的目錄結構層級前段部分是該代碼包所在本機的路徑的相對路徑。而後生成了歸檔文件 .a 文件。
總結一下以下圖:
關於 go build 和 go install 的不一樣,接下來分析完 go install 就會明白了,接下來繼續看 go install。
go install 命令是用來編譯並安裝代碼包或者源碼文件的。
go install 用於編譯並安裝指定的代碼包及它們的依賴包。當指定的代碼包的依賴包尚未被編譯和安裝時,該命令會先去處理依賴包。與 go build 命令同樣,傳給 go install 命令的代碼包參數應該以導入路徑的形式提供。而且,go build 命令的絕大多數標記也均可以用於
go install 命令。實際上,go install 命令只比 go build 命令多作了一件事,即:安裝編譯後的結果文件到指定目錄。
安裝代碼包會在當前工做區的 pkg 的平臺相關目錄下生成歸檔文件(即 .a 文件)。
安裝命令源碼文件會在當前工做區的 bin 目錄(若是 GOPATH 下有多個工做區,就會放在 GOBIN 目錄下)生成可執行文件。
一樣,go install 命令若是後面不追加任何參數,它會把當前目錄做爲代碼包並安裝。這和 go build 命令是徹底同樣的。
go install 命令後面若是跟了代碼包導入路徑做爲參數,那麼該代碼包及其依賴都會被安裝。
go install 命令後面若是跟了命令源碼文件以及相關庫源碼文件做爲參數的話,只有這些文件會被編譯並安裝。
go install 命令究竟作了些什麼呢?咱們來打印一下每一步的執行過程。
#
# 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 會把命令源碼文件安裝到當前工做區的 bin 目錄(若是 GOPATH 下有多個工做區,就會放在 GOBIN 目錄下)。若是是庫源碼文件,就會被安裝到當前工做區的 pkg 的平臺相關目錄下。
仍是來看看 go install 生成的臨時文件夾的結構:
.
├── command-line-arguments
│ └── _obj
│ └── exe
└── command-line-arguments.a複製代碼
結構和運行了 go build 命令同樣,最終生成的文件也都被移動到了相對應的目標目錄中。
總結一下以下圖:
在安裝多個庫源碼文件時有可能遇到以下的問題:
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)複製代碼
並且,在咱們爲環境變量 GOBIN 設置了正確的值以後,這個錯誤提示信息仍然會出現。這是由於,只有在安裝命令源碼文件的時候,命令程序纔會將環境變量 GOBIN 的值做爲結果文件的存放目錄。而在安裝庫源碼文件時,在命令程序內部的表明結果文件存放目錄路徑的那個變量不會被賦值。最後,命令程序會發現它依然是個無效的空值。因此,命令程序會一樣返回一個關於「無安裝位置」的錯誤。這就引出一個結論,咱們只能使用安裝代碼包的方式來安裝庫源碼文件,而不能在 go install 命令羅列並安裝它們。另外,go install 命令目前沒法接受標記-o
以自定義結果文件的存放位置。這也從側面說明了
go install 命令不支持針對庫源碼文件的安裝操做。
go get 命令用於從遠程代碼倉庫(好比 Github )上下載並安裝代碼包。注意,go get 命令會把當前的代碼包下載到 $GOPATH 中的第一個工做區的 src 目錄中,並安裝。
若是在 go get 下載過程當中加入-d
標記,那麼下載操做只會執行下載動做,而不執行安裝動做。好比有些很是特殊的代碼包在安裝過程當中須要有特殊的處理,因此咱們須要先下載下來,因此就會用到-d
標記。
還有一個頗有用的標記是-u
標記,加上它能夠利用網絡來更新已有的代碼包及其依賴包。若是已經下載過一個代碼包,可是這個代碼包又有更新了,那麼這時候能夠直接用-u
標記來更新本地的對應的代碼包。若是不加這個-u
標記,執行 go get 一個已有的代碼包,會發現命令什麼都不執行。只有加了-u
標記,命令會去執行 git pull 命令拉取最新的代碼包的最新版本,下載並安裝。
命令 go get 還有一個很值得稱道的功能——智能下載。在使用它檢出或更新代碼包以後,它會尋找與本地已安裝 Go 語言的版本號相對應的標籤(tag)或分支(branch)。好比,本機安裝 Go 語言的版本是1.x,那麼 go get 命令會在該代碼包的遠程倉庫中尋找名爲 「go1」 的標籤或者分支。若是找到指定的標籤或者分支,則將本地代碼包的版本切換到此標籤或者分支。若是沒有找到指定的標籤或者分支,則將本地代碼包的版本切換到主幹的最新版本。
go get 經常使用的一些標記以下:
標記名稱 | 標記描述 |
---|---|
-d | 讓命令程序只執行下載動做,而不執行安裝動做。 |
-f | 僅在使用-u 標記時纔有效。該標記會讓命令程序忽略掉對已下載代碼包的導入路徑的檢查。若是下載並安裝的代碼包所屬的項目是你從別人那裏 Fork 過來的,那麼這樣作就尤其重要了。 |
-fix | 讓命令程序在下載代碼包後先執行修正動做,然後再進行編譯和安裝。 |
-insecure | 容許命令程序使用非安全的 scheme(如 HTTP )去下載指定的代碼包。若是你用的代碼倉庫(如公司內部的 Gitlab )沒有HTTPS 支持,能夠添加此標記。請在肯定安全的狀況下使用它。 |
-t | 讓命令程序同時下載並安裝指定的代碼包中的測試源碼文件中依賴的代碼包。 |
-u | 讓命令利用網絡來更新已有代碼包及其依賴包。默認狀況下,該命令只會從網絡上下載本地不存在的代碼包,而不會更新已有的代碼包。 |
go get 命令究竟作了些什麼呢?咱們仍是來打印一下每一步的執行過程。
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複製代碼
這裏能夠很明顯的看到,執行完 go get 命令之後,會調用 git clone 方法下載源碼,並編譯,最終會把庫源碼文件編譯成歸檔文件安裝到 pkg 對應的相關平臺目錄下。
總結一下以下圖:
關於工做區的問題,這裏額外提一下:
通常狀況下,爲了分離本身與第三方的代碼,咱們會設置兩個或更多的工做區。咱們如今有一個目錄路徑爲 /home/hc/golang/lib 的工做區,而且它是環境變量 GOPATH 值中的第一個目錄路徑。注意,環境變量 GOPATH 中包含的路徑不能與環境變量GOROOT的值重複。好了,若是咱們使用 go get 命令下載和安裝代碼包,那麼這些代碼包都會被安裝在上面這個工做區中。咱們暫且把這個工做區叫作
Lib 工做區。
Go 在最初剛剛發佈的時候,靜態連接被當作優勢宣傳,只須編譯後的一個可執行文件,無須附加任何東西就能部署。將運行時、依賴庫直接打包到可執行文件內部,簡化了部署和發佈的操做,無須事先安裝運行環境和下載諸多第三方庫。不過最新版本卻又加入了動態連接的內容了。
普通的 go build 、go install 用的都是靜態連接。能夠驗證一下:
上圖是筆者用 MachOView 打開的 gofmt 文件,能夠看到 fmt.Println 的地址是肯定的,因此能夠肯定是靜態連接的。
目前最新版的 Go 是如何支持動態連接的呢?
在 go build 、go install 的時候加上 -buildmode 參數。
這些是如下 buildmode 的選項:
archive: 將非 main 包構建爲 .a 文件 . main 包將被忽略。
c-archive: 將 main 軟件包及其導入的全部軟件包構建到 C 歸檔文件中
c-shared: 將列出的主要軟件包,以及它們導入的全部軟件包構建到
C 動態庫中。
shared: 將全部列出的非 main 軟件包合併到一個動態庫中。
exe: 構建列出的 main 包及其導入到可執行文件中的一切。 將忽略未命名爲 main 的包。
默認狀況下,列出的 main 軟件包內置到可執行文件中,列出的非
main 軟件包內置到 .a 文件中。
關於動態庫,筆者尚未實踐過,這裏就不繼續深刻了,之後充分實踐後,再開一篇單獨的文章談談 Go 的動態連接。這裏只是想說明一點,Go 目前不只僅只有靜態連接,動態連接也支持了!
Reference:
《GO 命令教程》
《Go 併發編程實戰》
GitHub Repo:Halfrost-Field
Follow: halfrost · GitHub
Source: halfrost.com/go_command/