Go語言核心36講(Go語言基礎知識一)–學習筆記

01 | 工作區和GOPATH

從 Go 1.5 版本的自舉(即用 Go 語言編寫程式來實現 Go 語言自身),到 Go 1.7 版本的極速 GC(也稱垃圾回收器),再到 2018 年 2 月發布的 Go 1.10 版本對其自帶工具的全面升級,以及可預見的後續版本關鍵特性(比如用來做程式依賴管理的go mod命令),這一切都令我們歡欣鼓舞。Go 語言在一步步走向輝煌的同時,顯然已經成為軟體工程師們最喜愛的程式語言之一。

我們學習 Go 語言時,要做的第一件事,都是根據自己電腦的計算架構(比如,是 32 位的電腦還是 64 位的電腦)以及作業系統(比如,是 Windows 還是 Linux),從Go 語言官網 //golang.google.cn/ 下載對應的二進位包,也就是可以拿來即用的安裝包。

隨後,我們會解壓縮安裝包、放置到某個目錄、配置環境變數,並通過在命令行中輸入go version來驗證是否安裝成功。

在這個過程中,我們還需要配置 3 個環境變數,也就是 GOROOT、GOPATH 和 GOBIN。

  • GOROOT:Go 語言安裝根目錄的路徑,也就是 GO 語言的安裝路徑。
  • GOPATH:若干工作區目錄的路徑。是我們自己定義的工作空間。
  • GOBIN:GO 程式生成的可執行文件(executable file)的路徑。

其中,GOPATH 背後的概念是最多的,也是最重要的。那麼,今天我們的面試問題是:你知道設置 GOPATH 有什麼意義嗎?

關於這個問題,它的典型回答是這樣的:

你可以把 GOPATH 簡單理解成 Go 語言的工作目錄,它的值是一個目錄的路徑,也可以是多個目錄路徑,每個目錄都代表 Go 語言的一個工作區(workspace)。

我們需要利於這些工作區,去放置 Go 語言的源碼文件(source file),以及安裝(install)後的歸檔文件(archive file,也就是以「.a」為擴展名的文件)和可執行文件(executable file)。

事實上,由於 Go 語言項目在其生命周期內的所有操作(編碼、依賴管理、構建、測試、安裝等)基本上都是圍繞著 GOPATH 和工作區進行的。所以,它的背後至少有 3 個知識點,分別是:

  • Go 語言源碼的組織方式是怎樣的;
  • 你是否了解源碼安裝後的結果(只有在安裝後,Go 語言源碼才能被我們或其他程式碼使用);
  • 你是否理解構建和安裝 Go 程式的過程(這在開發程式以及查找程式問題的時候都很有用,否則你很可能會走彎路)。

知識擴展

1. Go 語言源碼的組織方式

與許多程式語言一樣,Go 語言的源碼也是以程式碼包為基本組織單位的。在文件系統中,這些程式碼包其實是與目錄一一對應的。由於目錄可以有子目錄,所以程式碼包也可以有子包。

一個程式碼包中可以包含任意個以.go 為擴展名的源碼文件,這些源碼文件都需要被聲明屬於同一個程式碼包。

程式碼包的名稱一般會與源碼文件所在的目錄同名。如果不同名,那麼在構建、安裝的過程中會以程式碼包名稱為準。

每個程式碼包都會有導入路徑。程式碼包的導入路徑是其他程式碼在使用該包中的程式實體時,需要引入的路徑。在實際使用程式實體之前,我們必須先導入其所在的程式碼包。具體的方式就是import該程式碼包的導入路徑。就像這樣:

import "github.com/labstack/echo"

在工作區中,一個程式碼包的導入路徑實際上就是從 src 子目錄,到該包的實際存儲位置的相對路徑。

所以說,Go 語言源碼的組織方式就是以環境變數 GOPATH、工作區、src 目錄和程式碼包為主線的。一般情況下,Go 語言的源碼文件都需要被存放在環境變數 GOPATH 包含的某個工作區(目錄)中的 src 目錄下的某個程式碼包(目錄)中。

2. 了解源碼安裝後的結果

了解了 Go 語言源碼的組織方式後,我們很有必要知道 Go 語言源碼在安裝後會產生怎樣的結果。

源碼文件以及安裝後的結果文件都會放到哪裡呢?我們都知道,源碼文件通常會被放在某個工作區的 src 子目錄下。

那麼在安裝後如果產生了歸檔文件(以「.a」為擴展名的文件),就會放進該工作區的 pkg 子目錄;如果產生了可執行文件,就可能會放進該工作區的 bin 子目錄。

歸檔文件存放的具體位置和規則

源碼文件會以程式碼包的形式組織起來,一個程式碼包其實就對應一個目錄。安裝某個程式碼包而產生的歸檔文件是與這個程式碼包同名的。

放置它的相對目錄就是該程式碼包的導入路徑的直接父級。比如,一個已存在的程式碼包的導入路徑是

github.com/labstack/echo,

那麼執行命令

go install github.com/labstack/echo@latest

生成的歸檔文件的相對目錄就是 //github.com/labstack,文件名為 echo.a。

訪問github超時可以配置七牛雲代理

go env -w GOPROXY=//goproxy.cn,direct

也可以安裝指定版本,這裡安裝了v4版本

go get github.com/labstack/echo/v4

順便說一下,上面這個程式碼包導入路徑還有另外一層含義,那就是:該程式碼包的源碼文件存在於 GitHub 網站的 labstack 組的程式碼倉庫 echo 中。

再說回來,歸檔文件的相對目錄與 pkg 目錄之間還有一級目錄,叫做平台相關目錄。平台相關目錄的名稱是由 build(也稱「構建」)的目標作業系統、下劃線和目標計算架構的代號組成的。

比如,構建某個程式碼包時的目標作業系統是 Linux,目標計算架構是 64 位的,那麼對應的平台相關目錄就是 linux_amd64。

因此,上述程式碼包的歸檔文件就會被放置在當前工作區的子目錄 pkg/linux_amd64/github.com/labstack 中。

image

(GOPATH 與工作區)

總之,你需要記住的是,某個工作區的 src 子目錄下的源碼文件在安裝後一般會被放置到當前工作區的 pkg 子目錄下對應的目錄中,或者被直接放置到該工作區的 bin 子目錄中。

3. 理解構建和安裝 Go 程式的過程

構建使用命令go build,安裝使用命令go install。構建和安裝程式碼包的時候都會執行編譯、打包等操作,並且,這些操作生成的任何文件都會先被保存到某個臨時的目錄中。

如果構建的是庫源碼文件,那麼操作後產生的結果文件只會存在於臨時目錄中。這裡的構建的主要意義在於檢查和驗證。

如果構建的是命令源碼文件,那麼操作的結果文件會被搬運到源碼文件所在的目錄中。

安裝操作會先執行構建,然後還會進行鏈接操作,並且把結果文件搬運到指定目錄。

進一步說,如果安裝的是庫源碼文件,那麼結果文件會被搬運到它所在工作區的 pkg 目錄下的某個子目錄中。

如果安裝的是命令源碼文件,那麼結果文件會被搬運到它所在工作區的 bin 目錄中,或者環境變數GOBIN指向的目錄中。

這裡你需要記住的是,構建和安裝的不同之處,以及執行相應命令後得到的結果文件都會出現在哪裡。

思考題

  • Go 語言在多個工作區中查找依賴包的時候是以怎樣的順序進行的?
  • 如果在多個工作區中都存在導入路徑相同的程式碼包會產生衝突嗎?

這兩個問題之間其實是有一些關聯的。答案並不複雜,做幾個試驗幾乎就可以找到它了。你也可以看一下 Go 語言標準庫中go build包及其子包的源碼。那裡面的寶藏也很多,可以助你深刻理解 Go 程式的構建過程。

補充閱讀

go build 命令一些可選項的用途和用法

在運行go build命令的時候,默認不會編譯目標程式碼包所依賴的那些程式碼包。當然,如果被依賴的程式碼包的歸檔文件不存在,或者源碼文件有了變化,那它還是會被編譯。

如果要強制編譯它們,可以在執行命令的時候加入標記-a。此時,不但目標程式碼包總是會被編譯,它依賴的程式碼包也總會被編譯,即使依賴的是標準庫中的程式碼包也是如此。

另外,如果不但要編譯依賴的程式碼包,還要安裝它們的歸檔文件,那麼可以加入標記-i。

那麼我們怎麼確定哪些程式碼包被編譯了呢?有兩種方法。

  • 運行go build命令時加入標記-x,這樣可以看到go build命令具體都執行了哪些操作。另外也可以加入標記-n,這樣可以只查看具體操作而不執行它們。
  • 運行go build命令時加入標記-v,這樣可以看到go build命令編譯的程式碼包的名稱。它在與-a標記搭配使用時很有用。

下面再說一說與 Go 源碼的安裝聯繫很緊密的一個命令:go get。

命令go get會自動從一些主流公用程式碼倉庫(比如 GitHub)下載目標程式碼包,並把它們安裝到環境變數GOPATH包含的第 1 工作區的相應目錄中。如果存在環境變數GOBIN,那麼僅包含命令源碼文件的程式碼包會被安裝到GOBIN指向的那個目錄。

最常用的幾個標記有下面幾種。

  • -u:下載並安裝程式碼包,不論工作區中是否已存在它們。
  • -d:只下載程式碼包,不安裝程式碼包。
  • -fix:在下載程式碼包後先運行一個用於根據當前 Go 語言版本修正程式碼的工具,然後再安裝程式碼包。
  • -t:同時下載測試所需的程式碼包。
  • -insecure:允許通過非安全的網路協議下載和安裝程式碼包。HTTP 就是這樣的協議。

Go 語言官方提供的go get命令是比較基礎的,其中並沒有提供依賴管理的功能。目前 GitHub 上有很多提供這類功能的第三方工具,比如glide、gb以及官方出品的dep、vgo等等,它們在內部大都會直接使用go get。

有時候,我們可能會出於某種目的變更存儲源碼的程式碼倉庫或者程式碼包的相對路徑。這時,為了讓程式碼包的遠程導入路徑不受此類變更的影響,我們會使用自定義的程式碼包導入路徑。

對程式碼包的遠程導入路徑進行自定義的方法是:在該程式碼包中的庫源碼文件的包聲明語句的右邊加入導入注釋,像這樣:

package semaphore // import "golang.org/x/sync/semaphore"

這個程式碼包原本的完整導入路徑是 github.com/golang/sync/semaphore 。這與實際存儲它的網路地址對應的。該程式碼包的源碼實際存在 GitHub 網站的 golang 組的 sync 程式碼倉庫的 semaphore 目錄下。而加入導入注釋之後,用以下命令即可下載並安裝該程式碼包了:

go get golang.org/x/sync/semaphore

而 Go 語言官網 golang.org 下的路徑 /x/sync/semaphore 並不是存放semaphore包的真實地址。我們稱之為程式碼包的自定義導入路徑。

不過,這還需要在 golang.org 這個域名背後的服務端程式上,添加一些支援才能使這條命令成功。

關於自定義程式碼包導入路徑的完整說明可以參看這裡 //github.com/hyper0x/go_command_tutorial/blob/master/0.3.md

好了,對於go build命令和go get命令的簡短介紹就到這裡。如果你想查閱更詳細的文檔,那麼可以訪問 Go 語言官方的命令文檔頁面 //golang.google.cn/cmd/go/,或者在命令行下輸入諸如go help build這類的命令。

課程鏈接

//gk.link/a/10AqZ

知識共享許可協議

本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。

歡迎轉載、使用、重新發布,但務必保留文章署名 鄭子銘 (包含鏈接: //www.cnblogs.com/MingsonZheng/ ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發布。