在 Go 1.8 出现以前,一直觉得 Go 语言不能像 C/C++一样可以使用动态库的方式动态修改服务。每次升级操作都不得不重新编译整个工程,重新部署服务。这对于很多比较重型的服务来说是一个很致命的弱点。
目前在 Go 1.8 只在 Linux 和 Darwin 系统下支持 Plugin。从 Go 1.8 源码中 plugin 包中 plugin.go 文件开头中有对应的说明。在 Go 1.8 中 plugin 包在操作系统的支持并不十分完善,即使在 Linux 系统下也需要特定 gcc 的编译器及连接器的支持。后续版本应该会有做相应的改进。
plugin was built with a different version of package internal/abi
```
从这个报错的文本可以得知,具体有问题的库是 runtime/internal/sys ,很显然这是一个 go 的内置标准库。看到这里,你可能会有很大的疑惑:我明明用的是同一个本地环境编译主程序和插件,为什么报标准库不是一个版本?
答案是,go 的 error 日志描述不准确。而这个报错出现的根本原因可以归结为:主程序和插件的某些关键编译 flag 不一致,跟“版本”没啥关系。
比如,你使用下面的命令编译插件:
```bash
go build -buildmode=plugin
```
但是你使用 goland 的 debug 模式调试主程序(比如 VSCode 等 IDE 调试 Go 程序),此时,goland 会帮你把 go build 命令按下面的例子组装好:
```bash
go test -c -o /private/var/folders/gy/2zv22t710sd7m0x9bcfzq23r0000gp/T/GoLand/___Test_TaskC_in_github_com_fdingiit_mpl_test.test -gcflags="all=-N -l" github.com/fdingiit/mpl/test
这真的是一个非常令人头大,并难以排查的原因。除了修改代码的那个人,和已经在其他 case 中被“坑”过的那些人,没人会知道这件事情。如果修改的 vendor 代码出现在主程序里,你就几乎没有任何靠谱的办法让它们正常工作起来。
不要直接在 vendor 里改代码,回馈开源社区,或者 fork-replace。
好消息是,你不需要解决这个问题。因为即使解决了,也还会有更大的问题等着你。
#### 3.2.3. case 3. 路径不一致
当按照 [3.2.1. Case 1. 版本不一致](#321-case-1-版本不一致) 和 [3.2.2. case 2. 版本号一致,代码不一致](#322-case-2-版本号一致代码不一致) 的思路都把问题排查、解决完,但它还是报 different version of package 的时候,可能你就会开始对 Go 的插件机制口吐芬芳了:版本真的一毛一样,代码真的一行没动,为什么还报不同版本???
原因是:插件机制会校验依赖库源码的「路径」,因此不能使用 vendor 管理依赖。
举个例子:你的主程序源码放在 /path/to/main 目录下,因此,你的某个三方库依赖的目录应该是 /path/to/main/vendor/some/thrid/part/lib;同理,你的插件源码放在 /path/to/plugin 目录下,因此,同一个三方库依赖的目录应该是 /path/to/plugin/vendor/some/thrid/part/lib。这些「文件路径」数据会被打包到二进制可执行文件里并用于校验,当主程序加载插件时,Go 的“运行时”“聪明的”通过「文件路径」的差异认定它和插件用的不是同一份代码,然后报了个 different version of package。