【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/logginghtml

通常狀況下,爲了分離本身與第三方的代碼,咱們會設置兩個或更多的工做區。咱們如今有一個目錄路徑爲 /home/hc/golang/lib 的工做區,而且它是環境變量 GOPATH 值中的第一個目錄路徑。注意,環境變量 GOPATH 中包含的路徑不能與環境變量 GOROOT 的值重複。好了,若是咱們使用 go get 命令下載和安裝代碼包,那麼這些代碼包都會被安裝在上面這個工做區中。咱們暫且把這個工做區叫作Lib 工做區。在咱們運行 go get github.com/hyper-carrot/go_lib/logging 以後,這個代碼包就應該會被保存在Lib工做的src目錄下,而且已經被安裝穩當,以下所示:linux

/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 站點上下載你的項目以後直接就能使用它。git

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

遠程導入路徑分析

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

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

該命令所支持的 VCS 的信息以下表:安全

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

名稱 主命令 說明
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語言學習筆記 . 雨痕》 

相關文章
相關標籤/搜索