go标准命令详解0.1 go build
搬運自github赫林的go_command_tutorial,絕對干貨,感謝作者。
為了讓講解更具關聯性,也為了讓讀者能夠更容易的理解這些命令和工具,本教程并不會按照這些命令的字典順序講解它們,而會按照我們在實際開發過程中通常的使用順序以及它們的重要程度的順序推進說明。 我們先從Go?build命令開始。
0.1 go build
go build命令用來編譯指定的代碼或代碼包及它們的依賴包。
例如,如果我們在執行go build命令時不后跟任何代碼包,那么命令將試圖編譯當前目錄所對應的代碼包。例如,我們想編譯goc2p項目的代碼包logging。其中一個方法是進入logging目錄并直接執行該命令:
hc@ubt:~/golang/goc2p/src/logging$ go build- 1
- 1
因為在代碼包logging中只有庫源碼文件和測試源碼文件,所以在執行go build命令之后不會在當前目錄和goc2p項目的pkg目錄中產生任何文件。
還有另外一種編譯logging包的方式:
hc@ubt:~/golang/goc2p/src$ go build logging- 1
- 1
在這里,我們把代碼包logging的導入路徑作為參數傳遞給go build命令。又例如,如果我們要編譯代碼包cnet/ctcp,只需要在任意目錄下執行命令go build cnet/ctcp即可。
當然,我們也可以通過指定多個Go源碼文件來完成編譯行為:
hc@ubt:~/golang/goc2p/src$ go build logging/base.go logging/console_logger.go logging/log_manager.go logging/tag.go- 1
- 1
但是,使用這種方法會有一個限制。作為參數的多個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命令同時編譯它們時就會失敗。示例如下:
hc@ubt:~/golang/goc2p/src/cmd$ go build showds.go initpkg_demo.go # command-line-arguments ./initpkg_demo.go:19: main redeclared in this blockprevious declaration at ./showds.go:56- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
請注意上面示例中的“command-line-arguments”。在這個位置上應該顯示的是作為參數的源碼文件所屬代碼包的導入路徑。但是,這里顯示的并不是它們所屬的代碼包的導入路徑cmd。這是因為,命令程序在分析參數的時候如果發現第一個參數是Go源碼文件而不是代碼包,則會在內部生成一個虛擬代碼包。這個虛擬代碼包的導入路徑和名稱都會是“command-line-arguments”。在其他基于編譯流程的命令程序中也有與之一致的操作。比如go install命令和go run命令。
現在我們使用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- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
我們在執行命令go build -v initpkg_demo.go之后被打印出的“command-line-arguments”就是命令程序為命令源碼文件initpkg_demo.go生成的虛擬代碼包的包名。
go build命令會把編譯命令源碼文件后生成的結果文件存放到執行該命令時所在的目錄下。這個所說的結果文件就是與命令源碼文件對應的可執行文件。它的名稱會與命令源碼文件的主文件名相同。
我們可以自定義生成的可執行文件的名字,示例如下:
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- 1
- 2
- 3
- 1
- 2
- 3
使用-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時創建的臨時工作目錄的路徑:
hc@ubt:~/golang/goc2p/src$ go build -v -work logging WORK=/tmp/go-build888760008 logging- 1
- 2
- 3
- 1
- 2
- 3
上面命令的結果輸出的第一行是為了編譯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 os reflect fmt log logging- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
怎么會多編譯了這么多代碼包呢?代碼包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- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
我們可以認為,以上示例中所顯示的代碼包的順序,就是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。
總結
以上是生活随笔為你收集整理的go标准命令详解0.1 go build的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: golang导包
- 下一篇: go标准命令详解0.2 go insta