该文章的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/srccd $HOME/srcgit clone https://github.com/gohugoio/hugo.gitcd hugogo install

我不信邪,就试了一下先clonego 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,我的项目名是prosetransform应该是我的项目下的一个文件夹。

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/hugorequire (    ...省略    github.com/jdkato/prose v1.2.0    ...省略)replace github.com/markbates/inflect => github.com/markbates/inflect v0.0.0-20171215194931-a12c3aec81a6go 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.gitcd gocode .

上来间接一个闪电三连码,用命令行关上我习惯的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 testgocd testgo go mod init xxxgo: creating new go.mod: module xxxgo 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}

写的有点长了,但还是想残缺的记录一下心路历程,折腾的过程还是很有意思的。