命令go install
用于編譯并安裝指定的代碼包及它們的依賴包。當指定的代碼包的依賴包還沒有被編譯和安裝時,該命令會先去處理依賴包。與go build
命令一樣,傳給go install
命令的代碼包參數(shù)應該以導入路徑的形式提供。并且,go build
命令的絕大多數(shù)標記也都可以用于go install
命令。實際上,go install
命令只比go build
命令多做了一件事,即:安裝編譯后的結果文件到指定目錄。
在對go install
命令進行詳細說明之前,讓我們先回顧一下goc2p的目錄結構。為了節(jié)省篇幅,我在這里隱藏了代碼包中的源碼文件。如下:
$HOME/golang/goc2p:
bin/
pkg/
src/
cnet/
logging/
helper/
ds/
pkgtool/
我們看到,goc2p項目中有三個子目錄,分別是bin目錄、pkg目錄和src目錄?,F(xiàn)在只有src目錄中包含了一些目錄,而其他兩個目錄都是空的。
現(xiàn)在,我們來看看安裝代碼包的規(guī)則。
安裝代碼包
如果go install
命令后跟的代碼包中僅包含庫源碼文件,那么go install
命令會把編譯后的結果文件保存在源碼文件所在工作區(qū)的pkg目錄下。對于僅包含庫源碼文件的代碼包來說,這個結果文件就是對應的代碼包歸檔文件(也叫靜態(tài)鏈接庫文件,名稱以.a結尾)。相比之下,我們在使用go build
命令對僅包含庫源碼文件的代碼包進行編譯時,是不會在當前工作區(qū)的src目錄以及pkg目錄下產(chǎn)生任何結果文件的。結果文件會出于編譯的目的被生成在臨時目錄中,但并不會使當前工作區(qū)目錄產(chǎn)生任何變化。
如果我們在執(zhí)行go install
命令時不后跟任何代碼包參數(shù),那么命令將試圖編譯當前目錄所對應的代碼包。比如,我們現(xiàn)在要安裝代碼包pkgtool
:
hc@ubt:~/golang/goc2p/src/pkgtool$ go install -v -work
WORK=D:\cygwin\tmp\go-build758586887
pkgtool
我們在前面說過,執(zhí)行go install
命令后會對指定代碼包先編譯再安裝。其中,編譯代碼包使用了與go build
命令相同的程序。所以,執(zhí)行go install
命令后也會首先建立一個名稱以go-build為前綴的臨時目錄。如果我們想強行重新安裝指定代碼包及其依賴包,那么就需要加入標記-a
:
hc@ubt:~/golang/goc2p/src/pkgtool$ go install -a -v -work
WORK=/tmp/go-build014992994
runtime
errors
sync/atomic
unicode
unicode/utf8
sort
sync
io
syscall
strings
bytes
bufio
time
os
path/filepath
pkgtool
可以看到,代碼包pkgtool
僅僅依賴了Go語言標準庫中的代碼包。
現(xiàn)在我們再來查看一下goc2p項目目錄:
$HOME/golang/goc2p:
bin/
pkg/
linux_386/
pkgtool.a
src/
現(xiàn)在pkg目錄中多了一個子目錄。讀過0.0節(jié)的讀者應該已經(jīng)知道,linux386被叫做平臺相關目錄。它的名字可以由`${GOOS}${GOARCH}來得到。其中,
${GOOS}和
${GOARCH}分別是當前操作系統(tǒng)中的環(huán)境變量GOOS和GOARCH的值。如果它們不存在,那么Go語言就會使用其內(nèi)部的預定值。上述示例在計算架構為386且操作系統(tǒng)為Linux的計算機上運行。所以,這里的平臺相關目錄即為linux_386。我們還看到,在goc2p項目中的平臺相關目錄下存在一個文件,名稱是pkgtool.a。這就是代碼包
pkgtool`的歸檔文件,文件名稱是由代碼包名稱與“.a”后綴組合而來的。
實際上,代碼包的歸檔文件并不都會被直接保存在pkg目錄的平臺相關目錄下,還可能被保存在這個平臺相關目錄的子目錄下。 下面我們來安裝cnet/ctcp
包:
hc@ubt:~/golang/goc2p/src/pkgtool$ go install -a -v -work ../cnet/ctcp
WORK=/tmp/go-build083178213
runtime
errors
sync/atomic
unicode
unicode/utf8
math
sync
sort
io
syscall
internal/singleflight
bytes
strings
strconv
bufio
math/rand
time
reflect
os
fmt
log
runtime/cgo
logging
net
cnet/ctcp
請注意,我們是在代碼包pkgtool
對應的目錄下安裝cnet/ctcp
包的。我們使用了一個目錄相對路徑。
實際上,這種提供代碼包位置的方式被叫做本地代碼包路徑方式,也是被所有Go命令接受的一種方式,這包括之前已經(jīng)介紹過的go build
命令。但是需要注意的是,本地代碼包路徑只能以目錄相對路徑的形式呈現(xiàn),而不能使用目錄絕對路徑。請看下面的示例:
hc@ubt:~/golang/goc2p/src/cnet/ctcp$ go install -v -work ~/golang/goc2p/src/cnet/ctcp
can't load package: package /home/hc/golang/goc2p/src/cnet/ctcp: import "/home/hc/golang/goc2p/src/cnet/ctcp": cannot import absolute path
從上述示例中的命令提示信息我們可知,以目錄絕對路徑的形式提供代碼包位置是不會被Go命令認可的。
這是由于Go認為本地代碼包路徑的表示只能以“./”或“../”開始,再或者直接為“.”或“..”,而代碼包的代碼導入路徑又不允許以“/”開始。所以,這種用絕對路徑表示代碼包位置的方式也就不能被支持了。
上述規(guī)則適用于所有Go命令。讀者可以自己嘗試一下,比如在執(zhí)行go build
命令時分別以代碼包導入路徑、目錄相對路徑和目錄絕對路徑的形式提供代碼包位置,并查看執(zhí)行結果。
我們已經(jīng)通過上面的示例強行的重新安裝了cnet/ctcp
包及其依賴包。現(xiàn)在我們再來看一下goc2p的項目目錄:
$HOME/golang/goc2p:
bin/
pkg/
linux_386/
/cnet
ctcp.a
logging.a
pkgtool.a
src/
我們發(fā)現(xiàn)在pkg目錄的平臺相關目錄下多了一個名為cnet的目錄,而在這個目錄下的就是名為ctcp.a的代碼包歸檔文件。由此我們可知,代碼包歸檔文件的存放目錄的相對路徑(相對于當前工作區(qū)的pkg目錄的平臺相關目錄)即為代碼包導入路徑除去最后一個元素后的路徑。而代碼包歸檔文件的名稱即為代碼包導入路徑中的最后一個元素再加“.a”后綴。再舉一個例子,如果代碼包導入路徑為x/y/z,則它的歸檔文件存放路徑的相對路徑即為x/y/,而這個歸檔文件的名稱即為z.a。
回顧代碼包pkgtool
的歸檔文件的存放路徑。因為它的導入路徑中只有一個元素,所以其歸檔文件就被直接存放到了goc2p項目的pkg目錄的平臺相關目錄下了。
此外,我們還發(fā)現(xiàn)pkg目錄的平臺相關目錄下還有一個名為logging.a的文件。很顯然,我們并沒有顯式的安裝代碼包logging
。這是因為go install
命令在安裝指定的代碼包之前,會先去安裝指定代碼包的依賴包。當依賴包被正確安裝后,指定的代碼包的安裝才會開始。由于代碼包cnet/ctcp
依賴于goc2p項目(即當前工作區(qū))中的代碼包logging
,所以當代碼包logging
被成功安裝之后,代碼包cnet/ctcp
才會被安裝。
還有一個問題:上述的安裝過程涉及到了那么多代碼包,那為什么goc2p項目的pkg目錄中只包含該項目中代碼包的歸檔文件呢?實際上,go install
命令會把標準庫中的代碼包的歸檔文件存放到Go語言安裝目錄的pkg子目錄中,而把指定代碼包依賴的第三方項目的代碼包的歸檔文件存放到當前工作區(qū)的pkg目錄下。這樣就實現(xiàn)了Go語言標準庫代碼包的歸檔文件與用戶代碼包的歸檔文件,以及處在不同工作區(qū)的用戶代碼包的歸檔文件之間的分離。
安裝命令源碼文件
除了安裝代碼包之外,go install
命令還可以安裝命令源碼文件。為了看到安裝命令源碼文件是goc2p項目目錄的變化,我們先把該目錄還原到原始狀態(tài),即清除bin子目錄和pkg子目錄下的所有目錄和文件。然后,我們來安裝代碼包helper/ds
下的命令源碼文件showds.go,如下:
hc@ubt:~/golang/goc2p/src$ go install helper/ds/showds.go
go install: no install location for .go files listed on command line (GOBIN not set)
這次我們沒能成功安裝。該Go命令認為目錄/home/hc/golang/goc2p/src/helper/ds不在環(huán)境GOPATH中。我們可以通過Linux的echo
命令來查看一下環(huán)境變量GOPATH的值:
hc@ubt:~/golang/goc2p/src$ echo $GOPATH
/home/hc/golang/lib:/home/hc/golang/goc2p
環(huán)境變量GOPATH的值中確實包含了goc2p項目的根目錄。這到底是怎么回事呢?
我通過查看Go命令的源碼文件找到了其根本原因。在上一小節(jié)我們提到過,在環(huán)境變量GOPATH中包含多個工作區(qū)目錄路徑時,我們需要在編譯命令源碼文件前先對環(huán)境變量GOBIN進行設置。實際上,這個環(huán)境變量所指的目錄路徑就是命令程序生成的結果文件的存放目錄。go install
命令會把相應的可執(zhí)行文件放置到這個目錄中。
由于命令go build
在編譯庫源碼文件后不會產(chǎn)生任何結果文件,所以自然也不用會在意結果文件的存放目錄。在該命令編譯單一的命令源碼文件或者包含一個命令源碼文件和多個庫源碼文件時,在結果文件存放目錄無效的情況下會將結果文件(也就是可執(zhí)行文件)存放到執(zhí)行該命令時所在的目錄下。因此,即使環(huán)境變量GOBIN的值無效,我們在執(zhí)行go build
命令時也不會見到這個錯誤提示信息。
然而,go install
命令中一個很重要的步驟就是將結果文件(歸檔文件或者可執(zhí)行文件)存放到相應的目錄中。所以,go install
命令在安裝命令源碼文件時,如果環(huán)境變量GOBIN的值無效,則它會在最后檢查結果文件存放目錄的時候發(fā)現(xiàn)這一問題,并打印與上述示例所示內(nèi)容類似的錯誤提示信息,最后直接退出。
這個錯誤提示信息在我們安裝多個庫源碼文件時也有可能遇到。示例如下:
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設置了正確的值之后,這個錯誤提示信息仍然會出現(xiàn)。這是因為,只有在安裝命令源碼文件的時候,命令程序才會將環(huán)境變量GOBIN的值作為結果文件的存放目錄。而在安裝庫源碼文件時,在命令程序內(nèi)部的代表結果文件存放目錄路徑的那個變量不會被賦值。最后,命令程序會發(fā)現(xiàn)它依然是個無效的空值。所以,命令程序會同樣返回一個關于“無安裝位置”的錯誤。這就引出一個結論,我們只能使用安裝代碼包的方式來安裝庫源碼文件,而不能在go install
命令羅列并安裝它們。另外,go install
命令目前無法接受標記-o
以自定義結果文件的存放位置。這也從側(cè)面說明了go install
命令不支持針對庫源碼文件的安裝操作。
至此,我們對怎樣用go install
命令來安裝代碼包以及命令源碼文件進行了說明。如果你已經(jīng)熟知了go build
命令,那么理解這些內(nèi)容應該不在話下。