該文章的golang版本爲
go version go1.14.5 darwin/amd64
晚上原本想裝個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
而後我尋思着不對勁怎麼裝都裝不上,打開hugo的github倉庫看到了這樣的嚮導:github
mkdir $HOME/src cd $HOME/src git clone https://github.com/gohugoio/hugo.git cd hugo go install
我不信邪,就試了一下先clone
再go install
會怎麼樣,而後發現居然成功了,在golang的bin目錄(在個人電腦下是~/go/bin
)下生成了一個名爲hugo
的可執行文件。golang
我尋思着go get
是把項目先拉下來而後編譯放在~/go/bin
下,go install
是本身手動進入了某個項目目錄下執行編譯輸出到~/go/bin
,兩者應該只有本身把項目拉下來而後進入該項目的區別,怎麼會出現這樣奇怪的錯誤。docker
一遍思考一遍手上又執行了一遍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
,項目名是prose
,transform
應該是項目下的一個文件夾。app
寫文章時 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
的錯誤中,根本不存在緩存了老版本項目代碼的可能。
推測到這裏,我想起來一個重要的東西,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
也可以找到須要的文件了。
儘管得出了一個看起來講的過去的解釋,可是問題又來了:爲何go install
就能獲取到對應的版本的項目?
我天然而然想到了go.mod
這個關鍵人物,我一樣得出一個大膽的推論:go get
不會理會go.mod
中的限制,而go install
則會在乎。
粗略了翻閱了有關go get
和go install
的一些說明,發現沒什麼和我這個問題相關的,只好來點硬核的了,正好go也是自舉的,go語言的go語言源碼仍是能夠看看的。
git clone https://github.com/golang/go.git cd go code .
上來直接一個閃電三連碼,用命令行打開我習慣的vscode開始看。
經過搜索找到了go get
和go 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
是否成立有兩種處理模式。
由於源碼裏對於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
沒有生效,一切都解釋的通了。
驗證的方法也很簡單,根據前文的判斷關鍵,新建一個有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
存在的環境下是能夠安裝成功的,也證實了前面的說法,至此一切都解決了。
此次的問題也花了一整個下午的時間去排查,先是花了一些時間思考可能的緣由,又花了很長時間去看源碼,讀源碼真的花了很長很長的時間,一連看了幾個小時直到頭都有些暈了在起身去吃飯,吃完飯回來有精神了繼續看了一會就發現問題了,可能在文章中就是簡單的調用鏈路和幾行關鍵代碼,可是怎麼在上千行的源碼中找到這些關鍵信息遠沒有文章中的那麼簡單,首先是要看懂,而後是順着調用鏈往下繼續看懂,有時候還會誤解而後找錯方向找了好久……
但結果仍是很是好的,解決了本身的疑惑,更難能的是在源碼裏學到了幾個頗有意思很妙的寫法,好比這兩個:
// 這個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 }
寫的有點長了,但仍是想完整的記錄一下心路歷程,折騰的過程仍是頗有意思的。