Go插件依赖项如何工作?

时间:2017-02-14 05:22:57

标签: go plugins dependencies

Go 1.8支持Go插件。

我创建了两个插件,如下所示。

据我了解,该插件仅公开main包中的函数和变量。对于非plugin.Lookup()变量/函数,main将失败。

但我想测试一个插件是否可以在内部调用另一个插件的方法,类似于C ++库如何调用另一个库。

所以我测试如下:

plugin1 github.com/vimal/testplugin

$ cat myplugin.go
package main

import "C"
import "fmt"
import help "github.com/vimal/testplugin1/plug"

func init() {
        fmt.Printf("main.init invoked\n")
}
// TestPlugin 
func TestPlugin() string {
        return help.Help()
}

plugin2 github.com/vimal/testplugin1

$ cat myplugin.go
package main

import "C"

func HelperFunc() string {
        return "help"
}
$ cat plug/helper.go
package help

func Help() string {
        return "help234"
}

这里的想法是plugin1调用plugin2的内部非main函数。

主程序

主程序加载一些作为参数给出的插件,并从上一个插件中调用TestPlugin()

测试1:

构建两个插件,并加载两个插件,并调用TestPlugin(),输出包含"help234",即调用内部函数。这可以理解,因为两个插件都被加载,一个插件可以调用另一个插件的内部代码。

测试2:

仅加载plugin1,并调用TestPlugin(),输出包含"help234",即调用内部函数。观察到与test1中相同的输出。也许这次可以从GOPATH找到方法。

测试3:

将文件夹"github.com/vimal/testplugin1"重命名为"github.com/vimal/junk1",删除插件2,仅加载插件1,然后调用TestPlugin()。输出仍然包含"help234",即调用内部函数。

我无法理解test3如何产生相同的输出。 plugin1也包含plugin2代码吗?如何理解Go插件对其他Go插件的依赖?

转到版本:go version go1.8rc3 linux/amd64

1 个答案:

答案 0 :(得分:4)

你并没有完全按照自己的想法行事。

您的 plugin1 导入并使用,即github.com/vimal/testplugin1/plug。这不等于#34;到 plugin2

这里发生的是当你构建 plugin1 时,它的所有依赖项都内置在插件文件中,包括.../testplugin1/plug包。当您加载 plugin1 时,其所有依赖项也会从插件文件加载,包括plug包。在此之后,无论 plugin2 的加载状态如何,它都能正常运行并不奇怪。这两个插件彼此独立。

-buildmode=plugin指示编译器要构建插件而不是独立应用程序,但它并不意味着不能包含依赖项。它们必须是,因为插件无法保证Go应用程序将加载它,以及Go应用程序将具有哪些包。因为可运行的应用程序也只包含应用程序本身明确引用的标准库中的包。

保证插件将拥有所需内容的唯一方法,如果它还包含所有依赖项(包括标准库中的依赖项),它将起作用。 (这就是构建简单插件生成相对较大的文件的原因,类似于构建简单的Go可执行文件,从而产生大文件。)

不需要添加到插件中的东西包括Go运行时,例如,因为正在运行的Go应用程序将加载插件将运行Go运行时。 (请注意,您只能从使用相同版本的Go编译的应用程序加载插件。)除此之外,插件必须包含所需的一切。

Go是一种静态链接语言。编译Go应用程序或插件后,他们不依赖也不检查GOPATH的值,它仅在构建它们时由Go工具使用。

更深入的见解

您的主应用和插件可能引用相同的包("相同"通过导入路径)。在这种情况下只有一个"实例"将使用该包。

如果这个通常引用的包具有" state",例如全局变量,则可以对此进行测试。我们假设一个名为mymath的公共共享包:

package mymath

var S string

func SetS(s string) {
    S = s
}

一个名为pg的插件使用它:

package main

import (
    "C"
    "mymath"
    "fmt"
)

func Start() {
    fmt.Println("pg:mymath.S", mymath.S)
    mymath.SetS("pghi")
    fmt.Println("pg:mymath.S", mymath.S)
}

主要应用使用mymath并加载pg(使用它):

package main

import (
    "plugin"
    "mymath"
    "fmt"
)

func main() {
    fmt.Println("mymath.S", mymath.S)
    mymath.SetS("hi")
    fmt.Println("mymath.S", mymath.S)

    p, err := plugin.Open("../pg/pg.so")
    if err != nil {
        panic(err)
    }

    start, err := p.Lookup("Start")
    if err != nil {
        panic(err)
    }

    start.(func())()

    fmt.Println("mymath.S", mymath.S)
}

构建插件:

cd pg
go build -buildmode=plugin

运行主应用程序,输出为:

mymath.S 
mymath.S hi
pg:mymath.S hi
pg:mymath.S pghi
mymath.S pghi

分析:首先主应用与mymath.S一起播放,最终将其设置为"hi"。然后是插件,它打印它(我们看到主应用程序"hi"设置的值),然后将其更改为"pghi"。然后是主应用程序并打印mymath.S,再次看到插件设置的最后一个值:"pghi"

所以只有一个&#34;实例&#34; mymath。现在,如果您继续更改mymath,例如将myMath.SetS()重命名为mymath.SetS2(),然后在主应用中更新呼叫(至mymath.SetS2("hi")),无需重建插件,只需运行主应用,即可获得以下输出:< / p>

mymath.S 
mymath.S hi
panic: plugin.Open: plugin was built with a different version of package mymath

goroutine 1 [running]:
main.main()
    <GOPATH>/src/play/play.go:16 +0x4b5
exit status 2

正如您所看到的,在构建主应用程序和插件时,会记录包版本(很可能是哈希),如果它们的导入路径在主应用程序和插件中匹配,则必须匹配。

(请注意,如果您不更改已使用的mymath包的导出标识符(和签名),只会执行实例,例如func SetS(s string) { S = s + "+" },您也会收到上述错误。)