記一次go get安裝hugo的坑、go module、goinstall、gobuild。

該文章的golang版本爲 go version go1.14.5 darwin/amd64

心路歷程

1. 報錯

晚上原本想裝個hugo玩玩,hugo提供了挺多安裝方法好比最直接的二進制包安裝、homebrew等包管理安裝、docker,正好電腦裏也有golang的環境,平時裝go寫的軟件也很方便go get就解決了,天然而然想到go get -v github.com/gohugoio/hugo,看着控制檯Download了半天最後居然給我報了個錯:html

package github.com/jdkato/prose/transform: cannot find package "github.com/jdkato/prose/transform" in any of:
        /usr/local/Cellar/go/1.14.5/libexec/src/github.com/jdkato/prose/transform (from $GOROOT)
        /Users/jabin/go/src/github.com/jdkato/prose/transform (from $GOPATH)

大概意思就是找不到github.com/jdkato/prose/transform這個包,我下意識點開https://github.com/jdkato/prose/transform一探究竟,發現github確實報了404錯誤,說明這個包確實有點問題。git

2. 嘗試

而後我尋思着不對勁怎麼裝都裝不上,打開hugo的github倉庫看到了這樣的嚮導:github

mkdir $HOME/src
cd $HOME/src
git clone https://github.com/gohugoio/hugo.git
cd hugo
go install

我不信邪,就試了一下先clonego install會怎麼樣,而後發現居然成功了,在golang的bin目錄(在個人電腦下是~/go/bin)下生成了一個名爲hugo的可執行文件。golang

3. 排錯

我尋思着go get是把項目先拉下來而後編譯放在~/go/bin下,go install是本身手動進入了某個項目目錄下執行編譯輸出到~/go/bin,兩者應該只有本身把項目拉下來而後進入該項目的區別,怎麼會出現這樣奇怪的錯誤。docker

3.1 靈異事件

一遍思考一遍手上又執行了一遍go get -v github.com/gohugoio/hugo,這一遍居然什麼報錯也沒有,我趕忙刪掉~/go/bin下的編譯產物hugo,檢驗是否真的go get可以正常編譯出可執行文件了,結果是確實沒有報錯且正常編譯出結果了緩存

我百思不得其解,可是回想一開始獲得的錯誤,彷佛錯誤中提到了兩個路徑:閉包

  • /usr/local/Cellar/go/1.14.5/libexec/src/github.com/jdkato/prose/transform (from $GOROOT)
  • /Users/jabin/go/src/github.com/jdkato/prose/transform

我逐一檢查後發現確實兩個地址都沒法找到須要的這個包,因而我開始想這個項目是否是有什麼問題,熟悉go包和github地址的人應該清楚,這個地址說明這個包的做者是jdkato,項目名是prosetransform應該是項目下的一個文件夾。app

3.2 計算機不會耍賴皮

寫文章時 https://github.com/jdkato/prose的最新commit是 c2b2f78b870e41bec89843648b04b1716a0fb9c6
hugo的github倉庫的最新commit是 c84ad8db821c10225c0e603c6ec920c67b6ce36f

因而我開始在github搜jdkato這個名字,發現了這個做者確實有prose這個項目https://github.com/jdkato/prose,點進去一看發現這個項目也的的確確沒有transform這個文件夾。函數

再翻看hugo的github倉庫發現最新的release是三天前,說明hugo的代碼自己沒有什麼問題。ui

這時候我得出一種推論,hugo可能用了prose這個項目的老版本,老版本里存在transform這個文件夾,hugo多是本身的golang環境裏緩存了prose這個項目的老版本之類的。可是很快這種想法就被我本身推翻了,由於個人的確確在本身的電腦上go install成功了hugo的最新源碼,而且在go install成功以前我還處於package github.com/jdkato/prose/transform: cannot find 的錯誤中,根本不存在緩存了老版本項目代碼的可能。

3.3 真相只有一個

推測到這裏,我想起來一個重要的東西,go.mod正是那個能夠緩存舊版本的關鍵人物啊!我趕忙再翻看hugo的github倉庫,果真發現了go.mod這個文件,裏面也有這樣一段記錄:

module github.com/gohugoio/hugo

require (
    ...省略
    github.com/jdkato/prose v1.2.0
    ...省略
)

replace github.com/markbates/inflect => github.com/markbates/inflect v0.0.0-20171215194931-a12c3aec81a6

go 1.12

能夠看到require中指出了對項目github.com/jdkato/prose的版本要求爲v1.2.0,我啪的一下就打開了https://github.com/jdkato/prose/tree/v1.2.0,果真在v1.2.0這個舊版本的tag下有咱們想要的transform文件夾。

那麼go get報錯的真相就只有一個,go get獲取了最新版本的github.com/jdkato/prose,正好最新版本的github.com/jdkato/prose重構了,項目結構發生了改變。而go install成功後go get也能成功的靈異事件也能說的過去了,就是由於go install獲取到了對的版本的項目,致使go get也可以找到須要的文件了。

3.4 案件遠沒有結束

儘管得出了一個看起來講的過去的解釋,可是問題又來了:爲何go install就能獲取到對應的版本的項目?

我天然而然想到了go.mod這個關鍵人物,我一樣得出一個大膽的推論:go get不會理會go.mod中的限制,而go install則會在乎。

3.5 口說無憑,怎麼證實

粗略了翻閱了有關go getgo install的一些說明,發現沒什麼和我這個問題相關的,只好來點硬核的了,正好go也是自舉的,go語言的go語言源碼仍是能夠看看的。

3.6 源碼中的線索

git clone https://github.com/golang/go.git
cd go
code .

上來直接一個閃電三連碼,用命令行打開我習慣的vscode開始看。

經過搜索找到了go getgo install的源碼,分別位於

命令 源碼位置
go get ./go/internal/get/get.go
go install ./go/internal/work/build.go

爲何go install不是在install.go裏?根據個人觀察,由於go install和go build的功能和實現都很接近,因此這兩個命令的源碼都在/go/internal/work/build.go。

主要看看get.go的源碼,裏面有一個runGet方法,這是執行go get的入口方法,該方法中存在這樣的調用鏈:
load.PackagesForBuild->PackagesAndErrors->ImportPaths

方法名 源碼位置
load.PackagesForBuild .go/internal/load/pkg.go
PackagesAndErrors .go/internal/load/pkg.go
ImportPaths .go/internal/load/pkg.go

這個ImportPaths方法完整以下:

func ImportPaths(args []string) []*search.Match {
    if ModInit(); cfg.ModulesEnabled {
        return ModImportPaths(args)
    }
    return search.ImportPaths(args)
}

可一看到源碼中關於ModInit(); cfg.ModulesEnabled是否成立有兩種處理模式。

3.7 關鍵信息

由於源碼裏對於ModInit()只是簡單的var ModInit func(),沒有方法體能夠看到,也許是再別的地方進行了實現,我也沒有深究。主要是看到了cfg.ModulesEnabled這個變量,直接想到了go module是否生效的問題,因而百度了go module而且看到了這篇文章[go module 基本使用](https://www.cnblogs.com/chnmig/p/11806609.html),裏面有這樣一段話:

go在1.13版本默認是auto, 表明 當項目在 GOPATH/src 外且項目根目錄有 go.mod 文件時,開啓 go module.也就是說,若是你不把代碼放置在 GOPATH/src 下則默認使用 MODULE 管理.
很差意思看錯了,1.13+的版本判斷開不開啓MODULE的依據是根目錄下有沒有go.mod文件
咱們也可手動更改成 on(所有開啓)/off(所有不開啓)

我恍然大悟,由於我go get的地方正好就是隨便一個地方->沒有在項目裏->天然沒有go.mod->也就沒有開啓module->ModInit(); cfg.ModulesEnabled不成立->go get源碼進入了另外一種處理方式->hugo源碼中的go.mod沒有生效,一切都解釋的通了。

3.8 驗證

驗證的方法也很簡單,根據前文的判斷關鍵,新建一個有go.mod的環境再執行一次go get就能夠了。

mkdir testgo
cd testgo 
go mod init xxx
go: creating new go.mod: module xxx
go get -v github.com/gohugoio/hugo

在有go.mod存在的環境下是能夠安裝成功的,也證實了前面的說法,至此一切都解決了。

4. 結尾

此次的問題也花了一整個下午的時間去排查,先是花了一些時間思考可能的緣由,又花了很長時間去看源碼,讀源碼真的花了很長很長的時間,一連看了幾個小時直到頭都有些暈了在起身去吃飯,吃完飯回來有精神了繼續看了一會就發現問題了,可能在文章中就是簡單的調用鏈路和幾行關鍵代碼,可是怎麼在上千行的源碼中找到這些關鍵信息遠沒有文章中的那麼簡單,首先是要看懂,而後是順着調用鏈往下繼續看懂,有時候還會誤解而後找錯方向找了好久……

但結果仍是很是好的,解決了本身的疑惑,更難能的是在源碼裏學到了幾個頗有意思很妙的寫法,好比這兩個:

// 這個else if的順序和做用域利用的很充分
func CleanPatterns(patterns []string) []string {
    // 省略
    var out []string
    for _, a := range patterns {
        var p, v string
        if build.IsLocalImport(a) || filepath.IsAbs(a) {
            p = a
        } else if i := strings.IndexByte(a, '@'); i < 0 {
            p = a
        } else {
            p = a[:i]
            v = a[i:]
        }
        // 省略
     }
     // 省略
}
// 匿名函數+函數變量+遞歸+閉包的妙用
func PackageList(roots []*Package) []*Package {
    seen := map[*Package]bool{}
    all := []*Package{}
    var walk func(*Package)
    walk = func(p *Package) {
        if seen[p] {
            return
        }
        seen[p] = true
        for _, p1 := range p.Internal.Imports {
            walk(p1)
        }
        all = append(all, p)
    }
    for _, root := range roots {
        walk(root)
    }
    return all
}

寫的有點長了,但仍是想完整的記錄一下心路歷程,折騰的過程仍是頗有意思的。

相關文章
相關標籤/搜索