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

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

问题描述

Go 1.8 支持 Go 插件.

Go 1.8 supports Go plugins.

我创建了两个插件如下.

I have created two plugins as follows.

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

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.

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

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.

所以我测试如下:

plugin1 github.com/vimal/testplugin

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
")
}
// TestPlugin 
func TestPlugin() string {
        return help.Help()
}

plugin2 github.com/vimal/testplugin1

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 函数.

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

主程序

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

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

测试 1:

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

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.

测试 2:

只加载plugin1,调用TestPlugin(),输出包含"help234",即调用内部函数.观察到与 test1 中相同的输出.或许这一次是从 GOPATH 中找到方法的.

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.

测试 3:

将文件夹"github.com/vimal/testplugin1"重命名为"github.com/vimal/junk1",删除plugin2,只加载plugin1,调用 TestPlugin().输出仍然包含 "help234",即调用了内部函数.

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.

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

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

推荐答案

你没有做你认为的那样.

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

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

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

这里发生的情况是,当您构建 plugin1 时,它的所有依赖项都内置到插件文件中,包括 .../testplugin1/plug 包.而当你加载 plugin1 时,它的所有依赖项也会从插件文件中加载,包括 plug 包.此后,无论 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.

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

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.

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

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

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

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

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.

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

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.

如果这个通常提到的包具有状态",例如全局变量,则可以进行测试.让我们假设一个名为 mymath 的通用共享包:

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
}

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

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

以及使用 mymath 并加载 pg (使用它)的主应用程序:

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

构建插件:

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

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

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

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.

(请注意,如果不更改使用的 mymath 包的导出标识符(和签名),仅更改实现,也会出现上述错误;例如 func SetS(s 字符串) { S = s + "+" }.)

(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天全站免登陆