共计 5057 个字符,预计需要花费 13 分钟才能阅读完成。
该文章的 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 了半天最初居然给我报了个错:
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 谬误,阐明这个包的确有点问题。
2. 尝试
而后我寻思着不对劲怎么装都装不上,关上 hugo 的 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
的可执行文件。
3. 排错
我寻思着 go get
是把我的项目先拉下来而后编译放在 ~/go/bin
下,go install
是本人手动进入了某个我的项目目录下执行编译输入到 ~/go/bin
,二者应该只有 本人把我的项目拉下来而后进入该我的项目 的区别,怎么会呈现这样奇怪的谬误。
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
,我的项目名是prose
,transform
应该是我的项目下的一个文件夹。
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 的代码自身没有什么问题。
这时候我得出一种推论,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 get
和go install
的一些阐明,发现没什么和我这个问题相干的,只好来点硬核的了,正好 go 也是自举的,go 语言的 go 语言源码还是能够看看的。
3.6 源码中的线索
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
是否成立有两种解决模式。
3.7 要害信息
因为源码里对于 ModInit()
只是简略的 var ModInit func()
,没有办法体能够看到,兴许是再别的中央进行了实现,我也没有深究。次要是看到了cfg.ModulesEnabled
这个变量,间接想到了 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
}
写的有点长了,但还是想残缺的记录一下心路历程,折腾的过程还是很有意思的。