go build
命令用于編譯我們指定的源碼文件或代碼包以及它們的依賴包。
例如,如果我們?cè)趫?zhí)行go build
命令時(shí)不后跟任何代碼包,那么命令將試圖編譯當(dāng)前目錄所對(duì)應(yīng)的代碼包。例如,我們想編譯goc2p項(xiàng)目的代碼包logging
。其中一個(gè)方法是進(jìn)入logging
目錄并直接執(zhí)行該命令:
hc@ubt:~/golang/goc2p/src/logging$ go build
因?yàn)樵诖a包logging
中只有庫(kù)源碼文件和測(cè)試源碼文件,所以在執(zhí)行go build
命令之后不會(huì)在當(dāng)前目錄和goc2p項(xiàng)目的pkg目錄中產(chǎn)生任何文件。
插播:Go語言的源碼文件有三大類,即:命令源碼文件、庫(kù)源碼文件和測(cè)試源碼文件。他們的功用各不相同,而寫法也各有各的特點(diǎn)。命令源碼文件總是作為可執(zhí)行的程序的入口。庫(kù)源碼文件一般用于集中放置各種待被使用的程序?qū)嶓w(全局常量、全局變量、接口、結(jié)構(gòu)體、函數(shù)等等)。而測(cè)試源碼文件主要用于對(duì)前兩種源碼文件中的程序?qū)嶓w的功能和性能進(jìn)行測(cè)試。另外,后者也可以用于展現(xiàn)前兩者中程序的使用方法。
另外一種編譯logging
包的方法是:
hc@ubt:~/golang/goc2p/src$ go build logging
在這里,我們把代碼包logging
的導(dǎo)入路徑作為參數(shù)傳遞給go build
命令。另一個(gè)例子:如果我們要編譯代碼包cnet/ctcp
,只需要在任意目錄下執(zhí)行命令go build cnet/ctcp
即可。
插播:之所以這樣的編譯方法可以正常執(zhí)行,是因?yàn)槲覀円呀?jīng)在環(huán)境變量GOPATH
中加入了goc2p項(xiàng)目的根目錄(即~/golang/goc2p/
)。這時(shí),goc2p項(xiàng)目的根目錄就成為了一個(gè)工作區(qū)目錄。只有這樣,Go語言才能正確識(shí)別我們提供的goc2p項(xiàng)目中某個(gè)代碼包的導(dǎo)入路徑。而代碼包的導(dǎo)入路徑是指,相對(duì)于Go語言自身的源碼目錄(即$GOROOT/src
)或我們?cè)诃h(huán)境變量GOPATH
中指定的某個(gè)目錄的src
子目錄下的子路徑。例如,這里的代碼包logging
的絕對(duì)路徑是~/golang/goc2p/src/logging
。而不論goc2p項(xiàng)目的根文件夾被放在哪兒,logging
包的導(dǎo)入路徑都是logging
。顯而易見,我們?cè)诜Q呼一個(gè)代碼包的時(shí)候總是以其導(dǎo)入路徑作為其稱謂。
言歸正傳,除了上面的簡(jiǎn)單用法,我們還可以同時(shí)編譯多個(gè)Go源碼文件:
hc@ubt:~/golang/goc2p/src$ go build logging/base.go logging/console_logger.go logging/log_manager.go logging/tag.go
但是,使用這種方法會(huì)有一個(gè)限制。作為參數(shù)的多個(gè)Go源碼文件必須在同一個(gè)目錄中。也就是說,如果我們想用這種方法既編譯logging
包又編譯basic
包是不可能的。不過別擔(dān)心,在需要的時(shí)候,那些被編譯目標(biāo)依賴的代碼包會(huì)被go build
命令自動(dòng)的編譯。例如,如果有一個(gè)導(dǎo)入路徑為app
的代碼包,同時(shí)依賴了logging
包和basic
包。那么在執(zhí)行go build app
的時(shí)候,該命令就會(huì)自動(dòng)的在編譯app
包之前去檢查logging
包和basic
包的編譯狀態(tài)。如果發(fā)現(xiàn)它們的編譯結(jié)果文件不是最新的,那么該命令就會(huì)先去的編譯這兩個(gè)代碼包,然后再編譯app
包。
注意,go build
命令在編譯只包含庫(kù)源碼文件的代碼包(或者同時(shí)編譯多個(gè)代碼包)時(shí),只會(huì)做檢查性的編譯,而不會(huì)輸出任何結(jié)果文件。
另外,go build
命令既不能編譯包含多個(gè)命令源碼文件的代碼包,也不能同時(shí)編譯多個(gè)命令源碼文件。因?yàn)椋绻讯鄠€(gè)命令源碼文件作為一個(gè)整體看待,那么每個(gè)文件中的main函數(shù)就屬于重名函數(shù),在編譯時(shí)會(huì)拋出重復(fù)定義錯(cuò)誤。假如,在goc2p項(xiàng)目的代碼包cmd
(此代碼包僅用于示例目的,并不會(huì)永久存在于該項(xiàng)目中)中包含有兩個(gè)命令源碼文件showds.go和initpkg_demo.go,那么我們?cè)谑褂?code>go build命令同時(shí)編譯它們時(shí)就會(huì)失敗。示例如下:
hc@ubt:~/golang/goc2p/src/cmd$ go build showds.go initpkg_demo.go
# command-line-arguments
./initpkg_demo.go:19: main redeclared in this block
previous declaration at ./showds.go:56
請(qǐng)注意上面示例中的command-line-arguments
。在這個(gè)位置上應(yīng)該顯示的是作為編譯目標(biāo)的源碼文件所屬的代碼包的導(dǎo)入路徑。但是,這里顯示的并不是它們所屬的代碼包的導(dǎo)入路徑cmd
。這是因?yàn)?,命令程序在分析參?shù)的時(shí)候如果發(fā)現(xiàn)第一個(gè)參數(shù)是Go源碼文件而不是代碼包,則會(huì)在內(nèi)部生成一個(gè)虛擬代碼包。這個(gè)虛擬代碼包的導(dǎo)入路徑和名稱都會(huì)是command-line-arguments
。在其他基于編譯流程的命令程序中也有與之一致的操作,比如go install
命令和go run
命令。
另一方面,如果我們編譯的多個(gè)屬于main
包的源碼文件中沒有main
函數(shù)的聲明,那么就會(huì)使編譯器立即報(bào)出“未定義main
函數(shù)聲明”的錯(cuò)誤并中止編譯。換句話說,在我們同時(shí)編譯多個(gè)main
包的源碼文件時(shí),要保證其中有且僅有一個(gè)main
函數(shù)聲明,否則編譯是無法成功的。
現(xiàn)在我們使用go build
命令編譯單一命令源碼文件。我們?cè)趫?zhí)行命令時(shí)加入一個(gè)標(biāo)記-v
。這個(gè)標(biāo)記的意義在于可以使命令把執(zhí)行過程中構(gòu)建的包名打印出來。我們會(huì)在稍后對(duì)這個(gè)標(biāo)記進(jìn)行詳細(xì)說明。現(xiàn)在我們先來看一個(gè)示例:
hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls
initpkg_demo.go
hc@ubt:~/golang/goc2p/src/basic/pkginit$ go build -v initpkg_demo.go
command-line-arguments
hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls
initpkg_demo initpkg_demo.go
我們?cè)趫?zhí)行命令go build -v initpkg_demo.go
之后被打印出的command-line-arguments
”`就是命令程序?yàn)槊钤创a文件initpkg_demo.go生成的虛擬代碼包的包名。順帶說一句,
命令go build
會(huì)把編譯命令源碼文件后生成的結(jié)果文件存放到執(zhí)行該命令時(shí)所在的目錄下。這個(gè)所說的結(jié)果文件就是與命令源碼文件對(duì)應(yīng)的可執(zhí)行文件。它的名稱會(huì)與命令源碼文件的主文件名相同。
順便說一下,如果我們有多個(gè)聲明為屬于main
包的源碼文件,且其中只有一個(gè)文件聲明了main
函數(shù)的話,那么是可以使用go build
命令同時(shí)編譯它們的。在這種情況下,不包含main
函數(shù)聲明的那幾個(gè)源碼文件會(huì)被視為庫(kù)源碼文件(理所當(dāng)然)。如此編譯之后的結(jié)果文件的名稱將會(huì)與我們指定的編譯目標(biāo)中最左邊的那個(gè)源碼文件的主文件名相同。
其實(shí),除了讓Go語言編譯器自行決定可執(zhí)行文件的名稱,我們還可以自定義它。示例如下:
hc@ubt:~/golang/goc2p/src/basic/pkginit$ go build -o initpkg initpkg_demo.go
hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls
initpkg initpkg_demo.go
使用-o
標(biāo)記可以指定輸出文件(在這個(gè)示例中指的是可執(zhí)行文件)的名稱。它是最常用的一個(gè)go build
命令標(biāo)記。但需要注意的是,當(dāng)使用標(biāo)記-o
的時(shí)候,不能同時(shí)對(duì)多個(gè)代碼包進(jìn)行編譯。
標(biāo)記-i
會(huì)使go build
命令安裝那些編譯目標(biāo)依賴的且還未被安裝的代碼包。這里的安裝意味著產(chǎn)生與代碼包對(duì)應(yīng)的歸檔文件,并將其放置到當(dāng)前工作區(qū)目錄的pkg
子目錄的相應(yīng)子目錄中。在默認(rèn)情況下,這些代碼包是不會(huì)被安裝的。
除此之外,還有一些標(biāo)記不但受到go build
命令的支持,而且對(duì)于后面會(huì)提到的go install
、go run
、go test
等命令同樣是有效的。下表列出了其中比較常用的標(biāo)記。
表0-1 go build
命令的常用標(biāo)記說明
標(biāo)記名稱 | 標(biāo)記描述 |
---|---|
-a | 強(qiáng)行對(duì)所有涉及到的代碼包(包含標(biāo)準(zhǔn)庫(kù)中的代碼包)進(jìn)行重新構(gòu)建,即使它們已經(jīng)是最新的了。 |
-n | 打印編譯期間所用到的其它命令,但是并不真正執(zhí)行它們。 |
-p n | 指定編譯過程中執(zhí)行各任務(wù)的并行數(shù)量(確切地說應(yīng)該是并發(fā)數(shù)量)。在默認(rèn)情況下,該數(shù)量等于CPU的邏輯核數(shù)。但是在darwin/arm 平臺(tái)(即iPhone和iPad所用的平臺(tái))下,該數(shù)量默認(rèn)是1 。 |
-race | 開啟競(jìng)態(tài)條件的檢測(cè)。不過此標(biāo)記目前僅在linux/amd64 、freebsd/amd64 、darwin/amd64 和windows/amd64 平臺(tái)下受到支持。 |
-v | 打印出那些被編譯的代碼包的名字。 |
-work | 打印出編譯時(shí)生成的臨時(shí)工作目錄的路徑,并在編譯結(jié)束時(shí)保留它。在默認(rèn)情況下,編譯結(jié)束時(shí)會(huì)刪除該目錄。 |
-x | 打印編譯期間所用到的其它命令。注意它與-n 標(biāo)記的區(qū)別。 |
我們?cè)谶@里忽略了一些并不常用的或作用于編譯器或連接器的標(biāo)記。在本小節(jié)的最后將會(huì)對(duì)這些標(biāo)記進(jìn)行簡(jiǎn)單的說明。如果讀者有興趣,也可以查看Go語言的官方文檔以獲取相關(guān)信息。
下面我們就用其中幾個(gè)標(biāo)記來查看一下在構(gòu)建代碼包logging
時(shí)創(chuàng)建的臨時(shí)工作目錄的路徑:
hc@ubt:~/golang/goc2p/src$ go build -v -work logging
WORK=/tmp/go-build888760008
logging
上面命令的結(jié)果輸出的第一行是為了編譯logging
包,Go創(chuàng)建的一個(gè)臨時(shí)工作目錄,這個(gè)目錄被創(chuàng)建到了Linux的臨時(shí)目錄下。輸出的第二行是對(duì)標(biāo)記-v
的響應(yīng)。這意味著此次命令執(zhí)行時(shí)僅編譯了logging
包。關(guān)于臨時(shí)工作目錄的用途和內(nèi)容,我們會(huì)在講解go run
命令和go test
命令的時(shí)候詳細(xì)說明。
現(xiàn)在我們?cè)賮砜纯慈绻麖?qiáng)制重新編譯會(huì)涉及到哪些代碼包:
hc@ubt:~/golang/goc2p/src$ go build -a -v -work logging
WORK=/tmp/go-build929017331
runtime
errors
sync/atomic
math
unicode/utf8
unicode
sync
io
syscall
strings
time
strconv
reflect
os
fmt
log
logging
怎么會(huì)多編譯了這么多代碼包呢?可以確定的是,代碼包logging
中的代碼直接依賴了標(biāo)準(zhǔn)庫(kù)中的runtime
包、strings
包、fmt
包和log
包。那么其他的代碼包為什么也會(huì)被重新編譯呢?
從代碼包編譯的角度來說,如果代碼包A依賴代碼包B,則稱代碼包B是代碼包A的依賴代碼包(以下簡(jiǎn)稱依賴包),代碼包A是代碼包B的觸發(fā)代碼包(以下簡(jiǎn)稱觸發(fā)包)。
go build
命令在執(zhí)行時(shí),編譯程序會(huì)先查找目標(biāo)代碼包的所有依賴包,以及這些依賴包的依賴包,直至找到最深層的依賴包為止。在此過程中,如果發(fā)現(xiàn)有循環(huán)依賴的情況,編譯程序就會(huì)輸出錯(cuò)誤信息并立即退出。此過程完成之后,所有的依賴關(guān)系也就形成了一棵含有重復(fù)元素的依賴樹。對(duì)于依賴樹中的一個(gè)節(jié)點(diǎn)(代碼包)來說,它的直接分支節(jié)點(diǎn)(前者的依賴包),是按照代碼包導(dǎo)入路徑的字典序從左到右排列的。最左邊的分支節(jié)點(diǎn)會(huì)最先被編譯。編譯程序會(huì)依此設(shè)定每個(gè)代碼包的編譯優(yōu)先級(jí)。
執(zhí)行go build
命令的計(jì)算機(jī)如果擁有多個(gè)邏輯CPU核心,那么編譯代碼包的順序可能會(huì)存在一些不確定性。但是,它一定會(huì)滿足這樣的約束條件:依賴代碼包 -> 當(dāng)前代碼包 -> 觸發(fā)代碼包
。
標(biāo)記-p n
可以限制編譯過程中任務(wù)執(zhí)行的并發(fā)數(shù)量,n
默認(rèn)為當(dāng)前計(jì)算機(jī)的CPU邏輯核數(shù)。如果在執(zhí)行go build
命令時(shí)加入標(biāo)記-p 1
,那么就可以保證代碼包編譯順序嚴(yán)格按照預(yù)先設(shè)定好的優(yōu)先級(jí)進(jìn)行?,F(xiàn)在我們?cè)賮砭幾glogging
包:
hc@ubt:~/golang/goc2p/src$ go build -a -v -work -p 1 logging
WORK=/tmp/go-build114039681
runtime
errors
sync/atomic
sync
io
math
syscall
time
os
unicode/utf8
strconv
reflect
fmt
log
unicode
strings
logging
我們可以認(rèn)為,以上示例中所顯示的代碼包的順序,就是logging
包直接或間接依賴的代碼包按照優(yōu)先級(jí)從高到低排列后的排序。
另外,如果在命令中加入標(biāo)記-n
,那么編譯程序只會(huì)輸出所用到的命令而不會(huì)真正運(yùn)行。在這種情況下,編譯過程不會(huì)使用并發(fā)模式。
在本節(jié)的最后,我們對(duì)一些并不太常用的標(biāo)記進(jìn)行簡(jiǎn)要的說明:
-asmflags
此標(biāo)記可以后跟另外一些標(biāo)記,如-D
、-I
、-S
等。這些后跟的標(biāo)記用于控制Go語言編譯器編譯匯編語言文件時(shí)的行為。
-buildmode
此標(biāo)記用于指定編譯模式,使用方式如-buildmode=default
(這等同于默認(rèn)情況下的設(shè)置)。此標(biāo)記支持的編譯模式目前有6種。借此,我們可以控制編譯器在編譯完成后生成靜態(tài)鏈接庫(kù)(即.a文件,也就是我們之前說的歸檔文件)、動(dòng)態(tài)鏈接庫(kù)(即.so文件)或/和可執(zhí)行文件(在Windows下是.exe文件)。
-compiler
此標(biāo)記用于指定當(dāng)前使用的編譯器的名稱。其值可以為gc
或gccgo
。其中,gc編譯器即為Go語言自帶的編輯器,而gccgo編譯器則為GCC提供的Go語言編譯器。而GCC則是GNU項(xiàng)目出品的編譯器套件。GNU是一個(gè)眾所周知的自由軟件項(xiàng)目。在開源軟件界不應(yīng)該有人不知道它。好吧,如果你確實(shí)不知道它,趕緊去google吧。
-gccgoflags
此標(biāo)記用于指定需要傳遞給gccgo編譯器或鏈接器的標(biāo)記的列表。
-gcflags
此標(biāo)記用于指定需要傳遞給go tool compile
命令的標(biāo)記的列表。
-installsuffix
為了使當(dāng)前的輸出目錄與默認(rèn)的編譯輸出目錄分離,可以使用這個(gè)標(biāo)記。此標(biāo)記的值會(huì)作為結(jié)果文件的父目錄名稱的后綴。其實(shí),如果使用了-race
標(biāo)記,這個(gè)標(biāo)記會(huì)被自動(dòng)追加且其值會(huì)為race
。如果我們同時(shí)使用了-race
標(biāo)記和-installsuffix
,那么在-installsuffix
標(biāo)記的值的后面會(huì)再被追加_race
,并以此來作為實(shí)際使用的后綴。
-ldflags
此標(biāo)記用于指定需要傳遞給go tool link
命令的標(biāo)記的列表。
-linkshared
此標(biāo)記用于與-buildmode=shared
一同使用。后者會(huì)使作為編譯目標(biāo)的非main
代碼包都被合并到一個(gè)動(dòng)態(tài)鏈接庫(kù)文件中,而前者則會(huì)在此之上進(jìn)行鏈接操作。
-pkgdir
使用此標(biāo)記可以指定一個(gè)目錄。編譯器會(huì)只從該目錄中加載代碼包的歸檔文件,并會(huì)把編譯可能會(huì)生成的代碼包歸檔文件放置在該目錄下。
-tags
此標(biāo)記用于指定在實(shí)際編譯期間需要受理的編譯標(biāo)簽(也可被稱為編譯約束)的列表。這些編譯標(biāo)簽一般會(huì)作為源碼文件開始處的注釋的一部分,例如,在$GOROOT/src/os/file_posix.go
開始處的注釋為:
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
最后一行注釋即包含了與編譯標(biāo)簽有關(guān)的內(nèi)容。大家可以查看代碼包go/build
的文檔已獲得更多的關(guān)于編譯標(biāo)簽的信息。
-toolexec
此標(biāo)記可以讓我們?nèi)プ远x在編譯期間使用一些Go語言自帶工具(如vet
、asm
等)的方式。