鍍金池/ 教程/ GO/ go build
go install
go clean
go list
go test
go doc與godoc
go build
go fix與go tool fix
go tool pprof
go run
go env
go tool cgo
標(biāo)準(zhǔn)命令詳解
go get
go vet與go tool vet

go build

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 installgo 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/amd64freebsd/amd64、darwin/amd64windows/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)前使用的編譯器的名稱。其值可以為gcgccgo。其中,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等)的方式。

上一篇:go tool pprof