【Go命令教程】4. go get


hc@ubt:~$ go get github.com/hyper-carrot/go_lib/logging

命令 go get 可以根據要求和實際情況從互聯網上下載或更新指定的代碼包及其依賴包,並對它們進行編譯和安裝。在上面這個示例中,我們從著名的代碼托管站點 Github 上下載了一個項目(或稱代碼包),並安裝到了環境變量 GOPATH 中包含的第一個工作區中。與此同時,我們也知道了這個代碼包的導入路徑就是 github.com/hyper-carrot/go_lib/logging

一般情況下,為了分離自己與第三方的代碼,我們會設置兩個或更多的工作區。我們現在有一個目錄路徑為 /home/hc/golang/lib 的工作區,並且它是環境變量 GOPATH 值中的第一個目錄路徑。注意,環境變量 GOPATH 中包含的路徑不能與環境變量 GOROOT 的值重復。好了,如果我們使用 go get 命令下載和安裝代碼包,那么這些代碼包都會被安裝在上面這個工作區中。我們暫且把這個工作區叫做Lib 工作區。在我們運行 go get github.com/hyper-carrot/go_lib/logging 之后,這個代碼包就應該會被保存在Lib工作的src目錄下,並且已經被安裝妥當,如下所示:

/home/hc/golang/lib:
    bin/
    pkg/
        linux_386/
            github.com/
            hyper-carrot/
        go_lib/
            logging.a
    src/
        github.com/
            hyper-carrot/
                 go_lib/
                     logging/
    ...

另一方面,如果我們想把一個項目上傳到 Github 網站(或其他代碼托管網站)上並被其他人使用的話,那么我們就應該把這個項目當做一個代碼包來看待。其實我們在之前已經提到過原因,go get 命令會將項目下的所有子目錄和源碼文件存放到第一個工作區的 src 目錄下,而 src 目錄下的所有子目錄都會是某個代碼包導入路徑的一部分或者全部。也就是說,我們應該直接在項目目錄下存放子代碼包和源碼文件,並且直接存放在項目目錄下的源碼文件所聲明的包名應該與該項目名相同(除非它是命令源碼文件)。這樣做可以讓其他人使用 go get 命令從 Github 站點上下載你的項目之后直接就能使用它。

實際上,像 goc2p 項目這樣直接以項目根目錄的路徑作為工作區路徑的做法是不被推薦的。之所以這樣做主要是想讓讀者更容易的理解 Go 語言的工程結構和工作區概念,也可以讓讀者看到另一種項目結構。當然,如果你的項目使用了 gb 這樣的工具那就是另外一回事了。這樣的項目的根目錄就應該被視為一個工作區(但是你不必把它加入到 GOPATH 環境變量中)。它應該由 git clone 下載到 Go 語言工作區之外的某處,而不是使用 go get 命令。

遠程導入路徑分析

實際上,go get 命令所做的動作也被叫做代碼包遠程導入,而傳遞給該命令的作為代碼包導入路徑的那個參數又被叫做代碼包遠程導入路徑。

go get 命令不僅可以從像 Github 這樣著名的代碼托管站點上下載代碼包,還可以從任何命令支持的代碼版本控制系統(英文為 Version Control System,簡稱為 VCS)檢出代碼包。任何代碼托管站點都是通過某個或某些代碼版本控制系統來提供代碼上傳下載服務的。所以,更嚴格地講,go get 命令所做的是從代碼版本控制系統的遠程倉庫中檢出/更新代碼包並對其進行編譯和安裝。

該命令所支持的 VCS 的信息如下表:

表0-2 go get 命令支持的 VCS

名稱 主命令 說明
Mercurial hg Mercurial是一種輕量級分布式版本控制系統,采用Python語言實現,易於學習和使用,擴展性強。
Git git Git最開始是Linux Torvalds為了幫助管理 Linux 內核開發而開發的一個開源的分布式版本控制軟件。但現在已被廣泛使用。它是被用來進行有效、高速的各種規模項目的版本管理。
Subversion svn Subversion是一個版本控制系統,也是第一個將分支概念和功能納入到版本控制模型的系統。但相對於Git和Mercurial而言,它只算是傳統版本控制系統的一員。
Bazaar bzr Bazaar是一個開源的分布式版本控制系統。但相比而言,用它來作為VCS的項目並不多。

go get 命令在檢出代碼包之前必須要知道代碼包遠程導入路徑所對應的版本控制系統和遠程倉庫的 URL。

如果該代碼包在本地工作區中已經存在,則會直接通過分析其路徑來確定這幾項信息。go get 命令支持的幾個版本控制系統都有一個共同點,那就是會在檢出的項目目錄中存放一個元數據目錄,名稱為“.”前綴加其主命令名。例如,Git 會在檢出的項目目錄中加入一個名為 “.git” 的子目錄。所以,這樣就很容易判定代碼包所用的版本控制系統。另外,又由於代碼包已經存在,我們只需通過代碼版本控制系統的更新命令來更新代碼包,因此也就不需要知道其遠程倉庫的 URL 了。對於已存在於本地工作區的代碼包,除非要求強行更新代碼包,否則 go get 命令不會進行重復下載。如果想要強行更新代碼包,可以在執行 go get 命令時加入 -u 標記。這一標記會稍后介紹。

如果本地工作區中不存在該代碼包,那么就只能通過對代碼包遠程導入路徑進行分析來獲取相關信息了。首先,go get 命令會對代碼包遠程導入路徑進行靜態分析。為了使分析過程更加方便快捷,go get 命令程序中已經預置了幾個著名代碼托管網站的信息。如下表:

表0-3 預置的代碼托管站點的信息

名稱 主域名 支持的VCS 代碼包遠程導入路徑示例
Bitbucket bitbucket.org Git, Mercurial bitbucket.org/user/project
bitbucket.org/user/project/sub/directory
GitHub github.com Git github.com/user/project
github.com/user/project/sub/directory
Google Code Project Hosting code.google.com Git, Mercurial, Subversion code.google.com/p/project
code.google.com/p/project/sub/directory
code.google.com/p/project.subrepository
code.google.com/p/project.subrepository/sub/directory
Launchpad launchpad.net Bazaar launchpad.net/project
launchpad.net/project/series
launchpad.net/project/series/sub/directory
launchpad.net/~user/project/branch
launchpad.net/~user/project/branch/sub/directory
IBM DevOps Services hub.jazz.net Git hub.jazz.net/git/user/project
hub.jazz.net/git/user/project/sub/directory

一般情況下,代碼包遠程導入路徑中的第一個元素就是代碼托管網站的主域名。在靜態分析的時候,go get 命令會將代碼包遠程導入路徑與預置的代碼托管站點的主域名進行匹配。如果匹配成功,則在對代碼包遠程導入路徑的初步檢查后返回正常的返回值或錯誤信息。如果匹配不成功,則會再對代碼包遠程導入路徑進行動態分析。至於動態分析的過程,我就不在這里詳細展開了。

如果對代碼包遠程導入路徑的靜態分析或/和動態分析成功並獲取到對應的版本控制系統和遠程倉庫 URL,那么 go get 命令就會進行代碼包檢出或更新的操作。隨后,go get 命令會在必要時以同樣的方式檢出或更新這個代碼包的所有依賴包。

自定義代碼包遠程導入路徑

如果你想把你編寫的(被托管在不同的代碼托管網站上的)代碼包的遠程導入路徑統一起來,或者不希望讓你的代碼包中夾雜某個代碼托管網站的域名,那么你可以選擇自定義你的代碼包遠程導入路徑。這種自定義的實現手段叫做“導入注釋”。導入注釋的寫法示例如下:

package analyzer // import "hypermind.cn/talon/analyzer"

代碼包 analyzer 實際上屬於我的一個網絡爬蟲項目。這個項目的代碼被托管在了 Github 網站上。它的網址是:https://github.com/hyper-carrot/talon。如果用標准的導入路徑來下載 analyzer 代碼包的話,命令應該這樣寫 go get github.com/hyper-carrot/talon/analyzer。不過,如果我們像上面的示例那樣在該代碼包中的一個源碼文件中加入導入注釋的話,這樣下載它就行不通了。我們來看一看這個導入注釋。

導入注釋的寫法如同一條代碼包導入語句。不同的是,它出現在了單行注釋符//的右邊,因此 Go 語言編譯器會忽略掉它。另外,它必須出現在源碼文件的第一行語句(也就是代碼包聲明語句)的右邊。只有符合上述這兩個位置條件的導入注釋才是有效的。再來看其中的引號部分。被雙引號包裹的應該是一個符合導入路徑語法規則的字符串。其中,hypermind.cn 是我自己的一個域名。實際上,這也是用來替換掉我想隱去的代碼托管網站域名及部分路徑(這里是 github.com/hyper-carrot)的那部分。在 hypermind.cn 右邊的依次是我的項目的名稱以及要下載的那個代碼包的相對路徑。這些與其標准導入路徑中的內容都是一致的。為了清晰起見,我們再來做下對比。

github.com/hyper-carrot/talon/analyzer // 標准的導入路徑
hypermind.cn           /talon/analyzer // 導入注釋中的導入路徑                   

你想用你自己的域名替換掉標准導入路徑中的哪部分由你自己說了算。不過一般情況下,被替換的部分包括代碼托管網站的域名以及你在那里的用戶ID就可以了。這足以達到我們最開始說的那兩個目的。

雖然我們在talon項目中的所有代碼包中都加入了類似的導入注釋,但是我們依然無法通過 go get hypermind.cn/talon/analyzer 命令來下載這個代碼包。因為域名 hypermind.cn 所指向的網站並沒有加入相應的處理邏輯。具體的實現步驟應該是這樣的:

  1. 編寫一個可處理HTTP請求的程序。這里無所謂用什么編程語言去實現。當然,我推薦你用 Go 語言去做。

  2. 將這個處理程序與hypermind.cn/talon這個路徑關聯在一起,並總是在作為響應的HTML文檔的頭中寫入下面這行內容:

    <meta name="go-import" content="hypermind.cn/talon git https://github.com/hyper-carrot/talon">

    hypermind.cn/talon/analyzer 熟悉 HTML 的讀者都應該知道,這行內容會被視為 HTML 文檔的元數據。它實際上 go get 命令的文檔中要求的寫法。它的模式是這樣的:

<meta name="go-import" content="import-prefix vcs repo-root">

實際上,content 屬性中的 import-prefix 的位置上應該填入我們自定義的遠程代碼包導入路徑的前綴。這個前綴應該與我們的處理程序關聯的那個路徑相一致。而 vsc 顯然應該代表與版本控制系統有關的標識。還記得表0-2中的主命令列嗎?這里的填入內容就應該該列中的某一項。在這里,由於 talon 項目使用的是 Git,所以這里應該填入 git。至於 repo-root,它應該是與該處理程序關聯的路徑對應的 Github 網站的 URL。在這里,這個路徑是 hypermind.cn/talon,那么這個 URL 就應該是 https://github.com/hyper-carrot/talon。后者也是 talon 項目的實際網址。

好了,在我們做好上述處理程序之后,go get hypermind.cn/talon/analyzer 命令的執行結果就會是正確的。analyzer 代碼包及其依賴包中的代碼會被下載到 GOPATH 環境變量中的第一個工作區目錄的 src 子目錄中,然后被編譯並安裝。

注意,具體的代碼包源碼存放路徑會是 /home/hc/golang/lib/src/hypermind.cn/talon/analyzer。也就是說,存放路徑(包括代碼包源碼文件以及相應的歸檔文件的存放路徑)會遵循導入注釋中的路徑(這里是 hypermind.cn/talon/analyzer),而不是原始的導入路徑(這里是 github.com/hyper-carrot/talon/analyzer)。另外,我們只需在 talon 項目的每個代碼包中的某一個源碼文件中加入導入注釋,但這些導入注釋中的路徑都必須是一致的。在這之后,我們就只能使用 hypermind.cn/talon/ 作為 talon 項目中的代碼包的導入路徑前綴了。一個反例如下:

hc@ubt:~$ go get github.com/hyper-carrot/talon/analyzer
package github.com/hyper-carrot/talon/analyzer: code in directory /home/hc/golang/lib/src/github.com/hyper-carrot/talon/analyzer expects import "hypermind.cn/talon/analyzer"
# 反例
#

與自定義的代碼包遠程導入路徑有關的內容我們就介紹到這里。從中我們也可以看出,Go 語言為了讓使用者的項目與代碼托管網站隔離所作出的努力。只要你有自己的網站和一個不錯的域名,這就很容易搞定並且非常值得。這會在你的代碼包的使用者面前強化你的品牌,而不是某個代碼托管網站的。當然,使你的代碼包導入路徑整齊划一是最直接的好處。

OK,言歸正傳,我下面繼續關注 go get 這個命令本身。

命令特有標記

go get 命令可以接受所有可用於 go build 命令和 go install 命令的標記。這是因為 go get 命令的內部步驟中完全包含了編譯和安裝這兩個動作。另外,go get 命令還有一些特有的標記,如下表所示:

表0-4 go get 命令的特有標記說明

標記名稱 標記描述
-d 讓命令程序只執行下載動作,而不執行安裝動作。
-f 僅在使用-u標記時才有效。該標記會讓命令程序忽略掉對已下載代碼包的導入路徑的檢查。如果下載並安裝的代碼包所屬的項目是你從別人那里Fork過來的,那么這樣做就尤為重要了。
-fix 讓命令程序在下載代碼包后先執行修正動作,而后再進行編譯和安裝。
-insecure 允許命令程序使用非安全的scheme(如HTTP)去下載指定的代碼包。如果你用的代碼倉庫(如公司內部的Gitlab)沒有HTTPS支持,可以添加此標記。請在確定安全的情況下使用它。
-t 讓命令程序同時下載並安裝指定的代碼包中的測試源碼文件中依賴的代碼包。
-u 讓命令利用網絡來更新已有代碼包及其依賴包。默認情況下,該命令只會從網絡上下載本地不存在的代碼包,而不會更新已有的代碼包。
-v 打印出被構建的代碼包的名字
-x 打印出用到的命令

為了更好的理解這幾個特有標記,我們先清除 Lib 工作區的 src 目錄和 pkg 目錄中的所有子目錄和文件。現在我們使用帶有 -d 標記的 go get 命令來下載同樣的代碼包:

hc@ubt:~$ go get -d github.com/hyper-carrot/go_lib/logging

現在,讓我們再來看一下 Lib 工作區的目錄結構:

/home/hc/golang/lib:
    bin/
    pkg/
    src/
        github.com/
            hyper-carrot/
                go_lib/
                    logging/
    ...

我們可以看到,go get 命令只將代碼包下載到了 Lib 工作區的 src 目錄,而沒有進行后續的編譯和安裝動作。這個加入-d標記的結果。

再來看 -fix 標記。我們知道,絕大多數計算機編程語言在進行升級和演進過程中,不可能保證 100% 的向后兼容(Backward Compatibility)。在計算機世界中,向后兼容是指在一個程序或者代碼庫在更新到較新的版本后,用舊的版本程序創建的軟件和系統仍能被正常操作或使用,或在舊版本的代碼庫的基礎上編寫的程序仍能正常編譯運行的能力。Go 語言的開發者們已想到了這點,並提供了官方的代碼升級工具 --fix。fix 工具可以修復因 Go 語言規范變更而造成的語法級別的錯誤。關於 fix 工具,我們將放在本節的稍后位置予以說明。

假設我們本機安裝的Go語言版本是1.5,但我們的程序需要用到一個很早之前用Go語言的0.9版本開發的代碼包。那么我們在使用 go get 命令的時候可以加入 -fix 標記。這個標記的作用是在檢出代碼包之后,先對該代碼包中不符合 Go 語言 1.5 版本的語言規范的語法進行修正,然后再下載它的依賴包,最后再對它們進行編譯和安裝。

標記 -u 的意圖和執行的動作都比較簡單。我們在執行 go get 命令時加入 -u 標記就意味着,如果在本地工作區中已存在相關的代碼包,那么就是用對應的代碼版本控制系統的更新命令更新它,並進行編譯和安裝。這相當於強行更新指定的代碼包及其依賴包。我們來看如下示例:

hc@ubt:~$ go get -v github.com/hyper-carrot/go_lib/logging 

因為我們在之前已經檢出並安裝了這個代碼包,所以我們執行上面這條命令后什么也沒發生。還記得加入標記-v標記意味着會打印出被構建的代碼包的名字嗎?現在我們使用標記 -u 來強行更新代碼包:

hc@ubt:~$ go get -v -u  github.com/hyper-carrot/go_lib/logging
github.com/hyper-carrot/go_lib (download)

其中,“(download)”后綴意味着命令從遠程倉庫檢出或更新了該行顯示的代碼包。如果我們要查看附帶 -u 的 go get 命令到底做了些什么,還可以加上一個 -x 標記,以打印出用到的命令。讀者可以自己試用一下它。

智能的下載

命令 go get 還有一個很值得稱道的功能。在使用它檢出或更新代碼包之后,它會尋找與本地已安裝 Go 語言的版本號相對應的標簽(tag)或分支(branch)。比如,本機安裝 Go 語言的版本是 1.x,那么 go get 命令會在該代碼包的遠程倉庫中尋找名為 “go1” 的標簽或者分支。如果找到指定的標簽或者分支,則將本地代碼包的版本切換到此標簽或者分支。如果沒有找到指定的標簽或者分支,則將本地代碼包的版本切換到主干的最新版本。

前面我們說在執行 go get 命令時也可以加入 -x 標記,這樣可以看到 go get 命令執行過程中所使用的所有命令。不知道讀者是否已經自己嘗試了。下面我們還是以代碼包 github.com/hyper-carrot/go_lib 為例,並且通過之前示例中的命令的執行此代碼包已經被檢出到本地。這時我們再次更新這個代碼包:

hc@ubt:~$ go get -v -u -x github.com/hyper-carrot/go_lib
github.com/hyper-carrot/go_lib (download)
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git fetch
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git show-ref
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git checkout origin/master
WORK=/tmp/go-build034263530

在上述示例中,go get 命令通過 git fetch 命令將所有遠程分支更新到本地,而后有用 git show-ref 命令列出本地和遠程倉庫中記錄的代碼包的所有分支和標簽。最后,當確定沒有名為 “go1” 的標簽或者分支后,go get 命令使用 git checkout origin/master 命令將代碼包的版本切換到主干的最新版本。下面,我們在本地增加一個名為 “go1” 的標簽,看看 go get 命令的執行過程又會發生什么改變:

hc@ubt:~$ cd ~/golang/lib/src/github.com/hyper-carrot/go_lib
hc@ubt:~/golang/lib/src/github.com/hyper-carrot/go_lib$ git tag go1
hc@ubt:~$ go get -v -u -x github.com/hyper-carrot/go_lib
github.com/hyper-carrot/go_lib (download)
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git fetch
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git show-ref
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git show-ref tags/go1 origin/go1
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git checkout tags/go1
WORK=/tmp/go-build636338114

將這兩個示例進行對比,我們會很容易發現它們之間的區別。第二個示例的命令執行過程中使用git show-ref查看所有分支和標簽,當發現有匹配的信息又通過 git show-ref tags/go1 origin/go1 命令進行精確查找,在確認無誤后將本地代碼包的版本切換到標簽 “go1” 之上。

命令 go get 的這一功能是非常有用的。我們的代碼在直接或間接依賴某些同時針對多個 Go 語言版本開發的代碼包時,可以自動的檢出其正確的版本。也可以說,go get 命令內置了一定的代碼包多版本依賴管理的功能。

到這里,我向大家介紹了 go get 命令的使用方式。go get 命令與之前介紹的兩個命令一樣,是我們編寫 Go 語言程序、構建 Go 語言項目時必不可少的輔助工具。

 

 

摘自:

http://wiki.jikexueyuan.com/project/go-command-tutorial/0.3.html

 

 


 

自定義路徑

即便將代碼托管在 GitHub,但我們依然希望使用 自有域名定義下載導入路徑。方法很簡單,在 Web 服務器對應路徑返回中包含 “go-import”跳轉信息即可。

myserver.go

package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, `
		<meta name="go-import" content="qyunhen.com/test git https://githua.com/qyuhen/test">
	`)
}

func main() {
	http.HandleFunc("/test", handler)
	http.ListenAndServe(":80", nil)
}

編譯並啟動服務器后,用新路徑下載該包。

$ go get -v -insecure qyuhen.com/test

Fetching http://qyuhen.com/test?go-get=1
Parsing meta tags from http://qyuhen.com/test?go-get=1 (status code 200)

get "qyuhen.com/test"; found meta tag main.meta Import(
    Prefix:"qyuhen.com/test",
    VCS:"git"
    RepoRoot:"https://github.com/qyuhen/test"
) at http://test.com/test?go-get=1

qyuhen.com/test (download)

從輸出信息中,可以看到解析過程。最終保存路徑不再是 github.com,而是自有域名。只是如此一來,該包就有 2 個下載路徑,本地也可能因此存在 2 個副本。為避免版本不一致等情況發生,可添加“import comment”,讓編譯器檢查導入路徑是否與該注釋一致。

github.com/qyuhen/test/hello.go

package lib // import "qyuhen.com/test"

func Hello() {
	println("Hello!")
}

如此,就要求該包必須以“qyuhen.com/test”路徑導入,否則編譯會出錯。因為 go get 也會執行編譯操作,所以用“github.com/qyuhen/test”下載安裝同樣失敗。

$ go build

can't load package: package test:
   code in directory github.com/qyuhen/test expects import "qyuhen.com/test"

使用唯一的導入路徑,方便日后遷移存儲端。糟心的是,此方式對 vendor 機制失效。

 

 

摘自:《Go語言學習筆記 . 雨痕》 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM