go build
命令用於編譯我們指定的源碼文件或代碼包以及它們的依賴包。
例如,如果我們在執行go build
命令時不后跟任何代碼包,那么命令將試圖編譯當前目錄所對應的代碼包。例如,我們想編譯goc2p項目的代碼包logging
。其中一個方法是進入logging
目錄並直接執行該命令:
hc@ubt:~/golang/goc2p/src/logging$ go build
因為在代碼包logging
中只有庫源碼文件和測試源碼文件,所以在執行go build
命令之后不會在當前目錄和goc2p項目的pkg目錄中產生任何文件。
插播:Go語言的源碼文件有三大類,即:命令源碼文件、庫源碼文件和測試源碼文件。他們的功用各不相同,而寫法也各有各的特點。命令源碼文件總是作為可執行的程序的入口。庫源碼文件一般用於集中放置各種待被使用的程序實體(全局常量、全局變量、接口、結構體、函數等等)。而測試源碼文件主要用於對前兩種源碼文件中的程序實體的功能和性能進行測試。另外,后者也可以用於展現前兩者中程序的使用方法。
另外一種編譯logging
包的方法是:
hc@ubt:~/golang/goc2p/src$ go build logging
在這里,我們把代碼包logging
的導入路徑作為參數傳遞給go build
命令。另一個例子:如果我們要編譯代碼包cnet/ctcp
,只需要在任意目錄下執行命令go build cnet/ctcp
即可。
插播:之所以這樣的編譯方法可以正常執行,是因為我們已經在環境變量GOPATH
中加入了goc2p項目的根目錄(即~/golang/goc2p/
)。這時,goc2p項目的根目錄就成為了一個工作區目錄。只有這樣,Go語言才能正確識別我們提供的goc2p項目中某個代碼包的導入路徑。而代碼包的導入路徑是指,相對於Go語言自身的源碼目錄(即$GOROOT/src
)或我們在環境變量GOPATH
中指定的某個目錄的src
子目錄下的子路徑。例如,這里的代碼包logging
的絕對路徑是~/golang/goc2p/src/logging
。而不論goc2p項目的根文件夾被放在哪兒,logging
包的導入路徑都是logging
。顯而易見,我們在稱呼一個代碼包的時候總是以其導入路徑作為其稱謂。
言歸正傳,除了上面的簡單用法,我們還可以同時編譯多個Go源碼文件:
hc@ubt:~/golang/goc2p/src$ go build logging/base.go logging/console_logger.go logging/log_manager.go logging/tag.go
但是,使用這種方法會有一個限制。作為參數的多個Go源碼文件必須在同一個目錄中。也就是說,如果我們想用這種方法既編譯logging
包又編譯basic
包是不可能的。不過別擔心,在需要的時候,那些被編譯目標依賴的代碼包會被go build
命令自動的編譯。例如,如果有一個導入路徑為app
的代碼包,同時依賴了logging
包和basic
包。那么在執行go build app
的時候,該命令就會自動的在編譯app
包之前去檢查logging
包和basic
包的編譯狀態。如果發現它們的編譯結果文件不是最新的,那么該命令就會先去的編譯這兩個代碼包,然后再編譯app
包。
注意,go build
命令在編譯只包含庫源碼文件的代碼包(或者同時編譯多個代碼包)時,只會做檢查性的編譯,而不會輸出任何結果文件。
另外,go build
命令既不能編譯包含多個命令源碼文件的代碼包,也不能同時編譯多個命令源碼文件。因為,如果把多個命令源碼文件作為一個整體看待,那么每個文件中的main函數就屬於重名函數,在編譯時會拋出重復定義錯誤。假如,在goc2p項目的代碼包cmd
(此代碼包僅用於示例目的,並不會永久存在於該項目中)中包含有兩個命令源碼文件showds.go和initpkg_demo.go,那么我們在使用go build
命令同時編譯它們時就會失敗。示例如下:
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
請注意上面示例中的command-line-arguments
。在這個位置上應該顯示的是作為編譯目標的源碼文件所屬的代碼包的導入路徑。但是,這里顯示的並不是它們所屬的代碼包的導入路徑cmd
。這是因為,命令程序在分析參數的時候如果發現第一個參數是Go源碼文件而不是代碼包,則會在內部生成一個虛擬代碼包。這個虛擬代碼包的導入路徑和名稱都會是command-line-arguments
。在其他基於編譯流程的命令程序中也有與之一致的操作,比如go install
命令和go run
命令。
另一方面,如果我們編譯的多個屬於main
包的源碼文件中沒有main
函數的聲明,那么就會使編譯器立即報出“未定義main
函數聲明”的錯誤並中止編譯。換句話說,在我們同時編譯多個main
包的源碼文件時,要保證其中有且僅有一個main
函數聲明,否則編譯是無法成功的。
現在我們使用go build
命令編譯單一命令源碼文件。我們在執行命令時加入一個標記-v
。這個標記的意義在於可以使命令把執行過程中構建的包名打印出來。我們會在稍后對這個標記進行詳細說明。現在我們先來看一個示例:
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
我們在執行命令go build -v initpkg_demo.go
之后被打印出的command-line-arguments
”`就是命令程序為命令源碼文件initpkg_demo.go生成的虛擬代碼包的包名。順帶說一句,
命令go build
會把編譯命令源碼文件后生成的結果文件存放到執行該命令時所在的目錄下。這個所說的結果文件就是與命令源碼文件對應的可執行文件。它的名稱會與命令源碼文件的主文件名相同。
順便說一下,如果我們有多個聲明為屬於main
包的源碼文件,且其中只有一個文件聲明了main
函數的話,那么是可以使用go build
命令同時編譯它們的。在這種情況下,不包含main
函數聲明的那幾個源碼文件會被視為庫源碼文件(理所當然)。如此編譯之后的結果文件的名稱將會與我們指定的編譯目標中最左邊的那個源碼文件的主文件名相同。
其實,除了讓Go語言編譯器自行決定可執行文件的名稱,我們還可以自定義它。示例如下:
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
標記可以指定輸出文件(在這個示例中指的是可執行文件)的名稱。它是最常用的一個go build
命令標記。但需要注意的是,當使用標記-o
的時候,不能同時對多個代碼包進行編譯。
標記-i
會使go build
命令安裝那些編譯目標依賴的且還未被安裝的代碼包。這里的安裝意味着產生與代碼包對應的歸檔文件,並將其放置到當前工作區目錄的pkg
子目錄的相應子目錄中。在默認情況下,這些代碼包是不會被安裝的。
除此之外,還有一些標記不但受到go build
命令的支持,而且對於后面會提到的go install
、go run
、go test
等命令同樣是有效的。下表列出了其中比較常用的標記。
表0-1 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語言的官方文檔以獲取相關信息。
下面我們就用其中幾個標記來查看一下在構建代碼包logging
時創建的臨時工作目錄的路徑:
hc@ubt:~/golang/goc2p/src$ go build -v -work logging
WORK=/tmp/go-build888760008
logging
上面命令的結果輸出的第一行是為了編譯logging
包,Go創建的一個臨時工作目錄,這個目錄被創建到了Linux的臨時目錄下。輸出的第二行是對標記-v
的響應。這意味着此次命令執行時僅編譯了logging
包。關於臨時工作目錄的用途和內容,我們會在講解go run
命令和go test
命令的時候詳細說明。
現在我們再來看看如果強制重新編譯會涉及到哪些代碼包:
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
怎么會多編譯了這么多代碼包呢?可以確定的是,代碼包logging
中的代碼直接依賴了標准庫中的runtime
包、strings
包、fmt
包和log
包。那么其他的代碼包為什么也會被重新編譯呢?
從代碼包編譯的角度來說,如果代碼包A依賴代碼包B,則稱代碼包B是代碼包A的依賴代碼包(以下簡稱依賴包),代碼包A是代碼包B的觸發代碼包(以下簡稱觸發包)。
go build
命令在執行時,編譯程序會先查找目標代碼包的所有依賴包,以及這些依賴包的依賴包,直至找到最深層的依賴包為止。在此過程中,如果發現有循環依賴的情況,編譯程序就會輸出錯誤信息並立即退出。此過程完成之后,所有的依賴關系也就形成了一棵含有重復元素的依賴樹。對於依賴樹中的一個節點(代碼包)來說,它的直接分支節點(前者的依賴包),是按照代碼包導入路徑的字典序從左到右排列的。最左邊的分支節點會最先被編譯。編譯程序會依此設定每個代碼包的編譯優先級。
執行go build
命令的計算機如果擁有多個邏輯CPU核心,那么編譯代碼包的順序可能會存在一些不確定性。但是,它一定會滿足這樣的約束條件:依賴代碼包 -> 當前代碼包 -> 觸發代碼包
。
標記-p n
可以限制編譯過程中任務執行的並發數量,n
默認為當前計算機的CPU邏輯核數。如果在執行go build
命令時加入標記-p 1
,那么就可以保證代碼包編譯順序嚴格按照預先設定好的優先級進行。現在我們再來編譯logging
包:
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
我們可以認為,以上示例中所顯示的代碼包的順序,就是logging
包直接或間接依賴的代碼包按照優先級從高到低排列后的排序。
另外,如果在命令中加入標記-n
,那么編譯程序只會輸出所用到的命令而不會真正運行。在這種情況下,編譯過程不會使用並發模式。
在本節的最后,我們對一些並不太常用的標記進行簡要的說明:
-asmflags
此標記可以后跟另外一些標記,如-D
、-I
、-S
等。這些后跟的標記用於控制Go語言編譯器編譯匯編語言文件時的行為。
-buildmode
此標記用於指定編譯模式,使用方式如-buildmode=default
(這等同於默認情況下的設置)。此標記支持的編譯模式目前有6種。借此,我們可以控制編譯器在編譯完成后生成靜態鏈接庫(即.a文件,也就是我們之前說的歸檔文件)、動態鏈接庫(即.so文件)或/和可執行文件(在Windows下是.exe文件)。
-compiler
此標記用於指定當前使用的編譯器的名稱。其值可以為gc
或gccgo
。其中,gc編譯器即為Go語言自帶的編輯器,而gccgo編譯器則為GCC提供的Go語言編譯器。而GCC則是GNU項目出品的編譯器套件。GNU是一個眾所周知的自由軟件項目。在開源軟件界不應該有人不知道它。好吧,如果你確實不知道它,趕緊去google吧。
-gccgoflags
此標記用於指定需要傳遞給gccgo編譯器或鏈接器的標記的列表。
-gcflags
此標記用於指定需要傳遞給go tool compile
命令的標記的列表。
-installsuffix
為了使當前的輸出目錄與默認的編譯輸出目錄分離,可以使用這個標記。此標記的值會作為結果文件的父目錄名稱的后綴。其實,如果使用了-race
標記,這個標記會被自動追加且其值會為race
。如果我們同時使用了-race
標記和-installsuffix
,那么在-installsuffix
標記的值的后面會再被追加_race
,並以此來作為實際使用的后綴。
-ldflags
此標記用於指定需要傳遞給go tool link
命令的標記的列表。
-linkshared
此標記用於與-buildmode=shared
一同使用。后者會使作為編譯目標的非main
代碼包都被合並到一個動態鏈接庫文件中,而前者則會在此之上進行鏈接操作。
-pkgdir
使用此標記可以指定一個目錄。編譯器會只從該目錄中加載代碼包的歸檔文件,並會把編譯可能會生成的代碼包歸檔文件放置在該目錄下。
-tags
此標記用於指定在實際編譯期間需要受理的編譯標簽(也可被稱為編譯約束)的列表。這些編譯標簽一般會作為源碼文件開始處的注釋的一部分,例如,在$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
最后一行注釋即包含了與編譯標簽有關的內容。大家可以查看代碼包go/build
的文檔已獲得更多的關於編譯標簽的信息。
-toolexec
此標記可以讓我們去自定義在編譯期間使用一些Go語言自帶工具(如vet
、asm
等)的方式。