【问题标题】:How do Go plugin dependencies work?Go 插件依赖项如何工作?
【发布时间】:2017-02-14 05:22:57
【问题描述】:

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",删除plugin2,只加载plugin1,调用TestPlugin()。输出仍然包含"help234",即调用了内部函数。

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

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

【问题讨论】:

    标签: go plugins dependencies


    【解决方案1】:

    你没有做你认为的那样。

    您的plugin1 导入并使用一个,即github.com/vimal/testplugin1/plug。这不“等于” plugin2

    这里发生的是,当您构建 plugin1 时,它的所有依赖项都内置到插件文件中,包括 .../testplugin1/plug 包。而当你加载 plugin1 时,它的所有依赖项也会从插件文件中加载,包括plug 包。此后,无论 plugin2 的加载状态如何,它都能正常工作也就不足为奇了。这两个插件是相互独立的。

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

    唯一保证插件将拥有所需的一切,并且如果它还包含其所有依赖项(包括标准库中的依赖项),它将正常工作的唯一方法。 (这就是为什么构建简单的插件会产生相对较大的文件,类似于构建简单的 Go 可执行文件会产生大文件。)

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

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

    更深入的洞察

    您的主应用程序和插件可能引用同一个包(“相同”的导入路径)。在这种情况下,只会使用包的一个“实例”。

    如果这个通常提到的包具有“状态”,例如全局变量,则可以进行测试。让我们假设一个名为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"

    所以mymath 只有一个“实例”。现在,如果您继续更改 mymath,例如将myMath.SetS() 重命名为mymath.SetS2(),然后将主应用程序中的调用更新为mymath.SetS2("hi"),并且无需重新构建插件,只需运行主应用程序,就会得到以下输出:

    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 + "+" },也会出现上述错误。)

    【讨论】:

    • @weima 添加了更多细节和示例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-29
    • 2015-11-14
    • 2013-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多