go install 是Go语言提供的非常方便的编译工具。但是最近在项目使用过程中遇到一些问题,在这里记录一下。
问题
通常情况下,修改了代码之后使用go install编译,运行,都能得到正确的结果。
但是有时候发现,修改了源代码,却没有被重新编译,于是导致许多诡异的问题。
为了弄清楚这个问题,下面用一个简单的例子来做个实验。也顺带把go install的工作方式简单介绍一遍。
简单的代码
现在有一个名叫hello的项目,目录在/tmp/gomain/src/hello/hello.go
package main import "github.com/my/human" func main() { human.Say("hello!") }hello项目引用了一个human的第三方包,这个包在另一个目录/tmp/gopkg/src/github.com/my/human/human.go
package human func Say(s string) { println("human say:", s) }
编译hello项目
好了,代码就这么简单。那么现在要编译这个hello项目,首先设置GOPATH环境变量
export GOPATH=/tmp/gopkg:/tmp/gomain export GOBIN=/tmp
编译hello项目
go install hello
go install会依次查找所有GOPATH中的目录寻找hello包和它依赖的github.com/my/human包。然后会将报名为main的包生成二进制文件放到GOBIN目录下。将非main包编译成.a文件放到项目对于的pkg目录下。
所以执行以上语句之后会在/tmp目录下生成hello可执行文件。并且在/tmp/gopkg/pkg/linux_amd64/github.com/my/human.a生成一个.a文件。
执行/tmp/hello
/tmp/hello human say: hello!
当hello.go被修改后
我们修改一行hello.go
human.Say("hello world!")再次编译并执行
go install hello /tmp/hello human say: hello world!可以看到,go install 会自动检测代码更新,如果有变化则重新编译。
如果要更详细知道这个过程,可以加上-x参数,这个参数会输出go install过程中实际执行的命令。
go install -x hello WORK=/tmp/go-build868125788 mkdir -p $WORK/hello/_obj/ mkdir -p $WORK/hello/_obj/exe/ cd /tmp/gomain/src/hello /usr/local/go/pkg/tool/linux_amd64/6g -o $WORK/hello.a -trimpath $WORK -p hello -complete -D _/tmp/gomain/src/hello -I $WORK -I /tmp/gopkg/pkg/linux_amd64 -pack ./hello.go cd . /usr/local/go/pkg/tool/linux_amd64/6l -o $WORK/hello/_obj/exe/a.out -L $WORK -L /tmp/gopkg/pkg/linux_amd64 -extld=gcc $WORK/hello.a mkdir -p /tmp/ mv $WORK/hello/_obj/exe/a.out /tmp/hello如果hello.go文件没有变化,这时候再次执行go install
go install -x hello WORK=/tmp/go-build100249567结果非常明显,代码没有变化的时候什么都没做。
当human.go被修改后,出问题了
现在我们修改一行human.go文件
println("god say:", s)再次编译并执行:
go install -x hello <pre name="code" class="plain">WORK=/tmp/go-build179497989
/tmp/hellohuman say: hello world! 什么情况,修改了代码竟然没有更新!也就是说,对于其他文件夹下的依赖包,如果发现存在.a文件,则不会再重新编译。
这种情况解决方案有几个:
1. 使用 -a 参数。go install -a强制更新所有的依赖包,包括Go内置的包。这个方案最简单可靠,不过编译时间会稍长。
2. 使用...手动更新其他目录的包。在编译hello之前,先重新编译gopkg下的所有包,go install /tmp/gopkg/...。使用三个点号go install会遍历目录下的所有包,检查代码如果有更新则重新编译。
3. 删掉.a文件。这样还不如方案2。
再多做一点实验
1. 如果删掉human.go文件,保留human.a文件,go install会报错找不到pkg。
2. 如果将human包移到goman目录下,也就是跟hello项目在同一个GOPATH内。则无论怎么修改human.go,编译hello项目时human包都会被更新。
结论
所以现在看来情况是这样的,go install只会检查“参数指定的包所在的GOPATH”内的源码是否有更新,如果有则重新编译。对于依赖的其他GOPATH下的包,如果存在已经编译好的.a文件,则不会再检查源码是否有更新,不会重新编译。
有疑问加站长微信联系(非本文作者)