该文章的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 }
写的有点长了,但仍是想完整的记录一下心路历程,折腾的过程仍是颇有意思的。