Go插件依赖关系如何工作? [英] How do Go plugin dependencies work?

查看:149
本文介绍了Go插件依赖关系如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



据我所知,Go 1.8支持Go插件,该插件只公开 main 包中的函数和变量。即 plugin.Lookup()将会失败 - main 变量/函数。

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



所以我测试如下:

plugin1 github.com/vimal/testplugin

  $ cat myplugin.go 
包主

导入C
导入fmt
导入帮助github。 com / vimal / testplugin1 / plug

func init(){
fmt.Printf(main.init invoked \\\

}
// TestPlugin
func TestPlugin()string {
return help.Help()
}

plugin2 github.com/vimal/testplugin1

  $ cat myplugin.go 
包主
$ b导入C

func HelperFunc()字符串{
返回帮助
}
$猫plug / helper.go
软件包帮助

func他lp()string {
returnhelp234
}

plugin1调用内部的非 main 插件2的功能。



主程序

主程序加载许多作为参数给出的插件,并从最后一个插件调用 TestPlugin()。 / b>

测试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版本go1.8rc3 linux / amd64

解决方案

您没有按照您自己的想法去做。



您的 plugin1 导入并使用,即 github.com/vimal/testplugin1/plug 。这与 plugin2 不是平等的!



这里发生的情况是,当您构建 plugin1 时,依赖关系构建在插件文件中,包括 ... / testplugin1 / plug 包。并且,当您加载 plugin1 时,其所有依赖项也会从插件文件加载,包括插件包。之后,无论 plugin2 的加载状态如何,它都可以正常工作,这并不奇怪。这两个插件是相互独立的。


$ b

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



确保插件具有所需的所有内容的唯一方法是,如果它也包含它的所有依赖项,包括标准库中的依赖项,它将工作。 (这就是为什么构建简单的插件生成相对较大的文件的原因,类似于构建简单的Go可执行文件导致大文件。)



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



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



深入洞察



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



如果这个通常引用的包有状态,例如全局变量,就可以测试它。让我们假设一个共享的包,名为 mymath



pre $ myc $ c $ package

var S string

func SetS(s string){
S = s
}

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

 包主
$ b $进口(
C
mymath
fmt


func开始(){
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

$ b 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 {
恐慌(错误)
}

开始,错误:= p.Lookup(开始)
如果犯错!=无{
panic(err)$ (func())()

fmt.Println(mymath.S,mymath.S)
} $ b(


start) $ b

构建插件:

  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是用不同版本的软件包mymath构建的

goroutine 1 [running]:
main.main()
< GOPATH> /src/play/play.go:16 + 0x4b5
退出状态2

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



(请注意,如果您不更改所使用的 mymath 包的导出标识符(和签名),仅用于实现;例如 func SetS(s string){S = s + +} 。)


Go 1.8 supports Go plugins.

I have created two plugins as follows.

As I understand, the plugin exposes only the functions and variables in the main package. i.e. plugin.Lookup() will fail for non-main variable/function.

But I wanted to test if a plugin can internally invoke a method from another plugin, similar to how a C++ library can invoke another library.

So I tested as follows:

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"
}

Idea here is that plugin1 invokes an internal, non-main function of plugin2.

main program

Main program loads a number of plugins given as arguments, and invokes TestPlugin() from the last plugin.

test 1:

Build both plugins, and load both plugins, and invoke TestPlugin(), the output contains "help234", i.e. the inner function is invoked. This can be understood, since both plugins are loaded, one plugin can invoke another plugin's inner code.

test 2:

Load only plugin1, and invoke TestPlugin(), the output contains "help234", i.e. the inner function is invoked. The same output is observed as in test1. Perhaps this time the method is found from GOPATH.

test 3:

Rename the folder "github.com/vimal/testplugin1" to "github.com/vimal/junk1", delete the plugin2, and load only plugin1, and invoke TestPlugin(). the output still contains "help234", i.e. the inner function is invoked.

I am not able to understand how test3 produces the same output. Does plugin1 contain plugin2 code also? How can I understand the Go plugin dependency on other Go plugins?

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

解决方案

You're not doing exactly what you think you are.

Your plugin1 imports and uses a package, namely github.com/vimal/testplugin1/plug. This is not "equal" to plugin2!

What happens here is that when you build plugin1, all its dependencies are built into the plugin file, including the .../testplugin1/plug package. And when you load plugin1, all its dependencies are also loaded from the plugin file, including the plug package. After this, it's not surprising that it works no matter the loaded status of plugin2. These 2 plugins are independent from each another.

The -buildmode=plugin instructs the compiler that you want to build a plugin and not a standalone app, but it doesn't mean dependencies must not be included. They have to be, because the plugin cannot have any guarantee what Go app will load it, and what packages that Go app will have. Because a runnable app also only contains packages even from the standard library that the app itself references explicitly.

The only way to guarantee that the plugin will have everything it needs and so that it will work if it also contains all its dependencies, including those from the standard library. (This is the reason why building simple plugins generate relatively big files, similarly to building simple Go executables resulting in big files.)

Few things that don't need to be added to plugins include the Go runtime for example, because a running Go app that will load the plugin will already have a Go runtime running. (Note that you can only load a plugin from an app compiled with the same version of Go.) But beyond that, the plugin has to contain everything it needs.

Go is a statically linked language. Once a Go app or plugin is compiled, they do not rely nor check the value of GOPATH, it is only used by the Go tool during building them.

Deeper insight

It's possible that your main app and a plugin refers to the same package ("same" by import path). In such cases only one "instance" of the package will be used.

This can be tested if this commonly referred package has "state", e.g a global variable. Let's assume a common, shared package called mymath:

package mymath

var S string

func SetS(s string) {
    S = s
}

And a plugin called pg that uses it:

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)
}

And the main app that uses mymath and loads pg (which uses it):

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)
}

Building the plugin:

cd pg
go build -buildmode=plugin

Running the main app, the output is:

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

Analysis: first the main app plays with mymath.S, sets it to "hi" eventually. Then comes the plugin, which prints it (we see the value set by the main app "hi"), then changes it to "pghi". Then comes again the main app and prints mymath.S, and again, sees the last value set by the plugin: "pghi".

So there is only one "instance" of mymath. Now if you go ahead and change mymath, e.g. rename myMath.SetS() to mymath.SetS2(), and you update the call in the main app (to mymath.SetS2("hi")), and without rebuilding the plugin, just running the main app, you get the following output:

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

As you can see, when building the main app and the plugin, the package version (which is most likely a hash) is recorded, which must match if their import paths match in the main app and in the plugin.

(Note that you will also get the above error if you don't change the exported identifiers (and signatures) of the used mymath package, only the implementation; e.g. func SetS(s string) { S = s + "+" }.)

这篇关于Go插件依赖关系如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆