1. 程式人生 > >go標準命令詳解0.1 go build

go標準命令詳解0.1 go build

轉自:https://blog.csdn.net/u012210379/article/details/50443636

為了讓講解更具關聯性,也為了讓讀者能夠更容易的理解這些命令和工具,本教程並不會按照這些命令的字典順序講解它們,而會按照我們在實際開發過程中通常的使用順序以及它們的重要程度的順序推進說明。 我們先從go build命令開始。

0.1 go build

go build命令用來編譯指定的程式碼或程式碼包及它們的依賴包。

例如,如果我們在執行go build命令時不後跟任何程式碼包,那麼命令將試圖編譯當前目錄所對應的程式碼包。例如,我們想編譯goc2p專案的程式碼包logging。其中一個方法是進入logging

目錄並直接執行該命令:

[email protected]:~/golang/goc2p/src/logging$ go build

因為在程式碼包logging中只有庫原始碼檔案和測試原始碼檔案,所以在執行go build命令之後不會在當前目錄和goc2p專案的pkg目錄中產生任何檔案。

還有另外一種編譯logging包的方式:

[email protected]:~/golang/goc2p/src$ go build logging

在這裡,我們把程式碼包logging的匯入路徑作為引數傳遞給go build命令。又例如,如果我們要編譯程式碼包cnet/ctcp,只需要在任意目錄下執行命令go build cnet/ctcp

即可。

當然,我們也可以通過指定多個Go原始碼檔案來完成編譯行為:

[email protected]:~/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包。

注意,go build命令既不能編譯包含多個命令原始碼檔案的程式碼包,也不能同時編譯多個命令原始碼檔案。因為,如果把多個命令原始碼檔案作為一個整體看待,那麼每個檔案中的main函式就屬於重名函式,在編譯時會丟擲重複定義錯誤。假如,在goc2p專案的程式碼包cmd(此程式碼包僅用於示例目的,並不會永久存在於該專案中)中包含有兩個命令原始碼檔案showds.go和initpkg_demo.go,那麼我們在使用go build命令同時編譯它們時就會失敗。示例如下:

[email protected]:~/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命令。

現在我們使用go build命令編譯單一命令原始碼檔案。我們在執行命令時加入一個標記-v。這個標記的意義在於可以使命令把執行過程中構建的包名打印出來。我們會在稍後對這個標記進行詳細說明。現在我們先來看一個示例:

[email protected]:~/golang/goc2p/src/basic/pkginit$ ls
initpkg_demo.go
[email protected]:~/golang/goc2p/src/basic/pkginit$ go build -v initpkg_demo.go 
command-line-arguments
[email protected]:~/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命令會把編譯命令原始碼檔案後生成的結果檔案存放到執行該命令時所在的目錄下。這個所說的結果檔案就是與命令原始碼檔案對應的可執行檔案。它的名稱會與命令原始碼檔案的主檔名相同。

我們可以自定義生成的可執行檔案的名字,示例如下:

[email protected]:~/golang/goc2p/src/basic/pkginit$ go build -o initpkg initpkg_demo.go 
[email protected]:~/golang/goc2p/src/basic/pkginit$ ls
initpkg    initpkg_demo.go

 

使用-o標記可以指定輸出檔案(在這個示例中是可執行檔案)的名稱。它是最常用的一個go build命令標記。但需要注意的是,當使用標記-o的時候,不能同時對多個程式碼包進行編譯。

除此之外,還有一些標記在我們日常開發過程中可能會被用到。如下表。

表0-1 go build命令的常用標記說明

| 標記名稱 | 標記描述 | 
| -o | 指定輸出檔案。 | 
| -a | 強行對所有涉及到的程式碼包(包括標準庫中的程式碼包)進行重新構建,即使它們已經是最新的了。 | 
| -n | 列印構建期間所用到的其它命令,但是並不真正執行它們。 | 
| -p n | 構建的並行數量(n)。預設情況下並行數量與CPU數量相同。 | 
| -race | 開啟資料競爭檢測。此標記目前僅在linux/amd64、darwin/amd64和windows/amd64平臺下被支援。 | 
| -v | 打印出被構建的程式碼包的名字。 | 
| -work | 打印出臨時工作目錄的名字,並且取消在構建完成後對它的刪除操作。 | 
| -x | 打印出構建期間所用到的其它命令。 |

我們在這裡忽略了一些並不常用的或作用於編譯器或聯結器的標記。在本小節的最後將會對這些標記進行簡單的說明。如果讀者有興趣,也可以檢視Go語言的官方文件以獲取相關資訊。

下面我們就用其中幾個標記來檢視一下在構建程式碼包logging時建立的臨時工作目錄的路徑:

[email protected]:~/golang/goc2p/src$ go build -v -work logging
WORK=/tmp/go-build888760008
logging

 

 

上面命令的結果輸出的第一行是為了編譯logging包,Go建立的一個臨時工作目錄,這個目錄被建立到了Linux的臨時目錄下。輸出的第二行是對標記-v的響應,意味著這個命令執行時僅編譯了logging包。關於臨時工作目錄的用途和內容,我們會在講解go run命令和go test命令的時候詳細說明。

現在我們再來看看如果強制重新編譯會涉及到哪些程式碼包:

[email protected]:~/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
os
reflect
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“包:

[email protected]:~/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,則編譯程式只會輸出所用到的命令而不會真正執行。在這種情況下,編譯過程不會使用併發模式。

關於go build命令可接受但不常用的標記的說明如下:

  • -ccflags:需要傳遞給每一個5c、6c或者8c編譯器的引數的列表。

  • -compiler:指定作為執行時編譯器的編譯器名稱。其值可以為gccgo或gc。

  • -gccgoflags:需要傳遞給每一個gccgo編譯器或連結器的引數的列表。

  • -gcflags:需要傳遞給每一個5g、6g或者8g編譯器的引數的列表。

  • -installsuffix;為了使當前的輸出的目錄與預設的編譯輸出目錄分離,可以使用這個標記。此標記的值會作為結果檔案的父目錄名稱的字尾。實際上,如果使用了-race標記,這個值會被自動設定為race。如果同時使用了這兩個標記,則會在-installsuffix標記的值的後面再加上_race,以此來作為實際使用的字尾。

  • -ldflags:需要傳遞給每一個5l、6l或者8l連結器的引數的列表。

  • -tags:在實際編譯期間需要考慮滿足的編譯標籤(也可被稱為編譯約束)的列表。可以檢視程式碼包go/build的文件已獲得更多的關於編譯標籤的資訊。

其中,gccgo是GNU專案針對於Go語言出品的編譯器。GNU是一個眾所周知的自由軟體工程專案。在開源軟體界不應該有人不知道它。好吧,如果你確實不知道它,趕緊去google吧。

gc是Go語言的官方編譯器。8g、6g和5g分別是gc編譯器在x86(32bit)計算架構、x86-64(64bit)計算架構和ARM計算架構的計算機上的編譯程式。

8c、6c和5c分別是Plan 9版本的gc編譯器在x86(32bit)計算架構、x86-64(64bit)計算架構和ARM計算架構的計算機上的編譯程式。而Plan 9是一個分散式作業系統,由貝爾實驗室的計算科學研究中心在1980年代中期至2002年開發,以作為UNIX的後繼者。Plan 9作業系統與Go語言的淵源很深。Go語言的三個設計者Robert Griesemer、Rob Pike和Ken Thompson不但是C語言和Unix作業系統的設計者,也同樣是Plan 9作業系統的開發者。

8l、6l和5l分別是在x86(32bit)計算架構、x86-64(64bit)計算架構和ARM計算架構的計算機上的連結器。

順便提一下,Go語言的專用環境變數GOARCH對應於x86(32bit)計算架構、x86-64(64bit)計算架構和ARM計算架構的值分別為386、amd64和arm。