Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/go: plugin versions do not match when built within vs. outside a module #31354

Open
zimnx opened this issue Apr 8, 2019 · 17 comments
Open

cmd/go: plugin versions do not match when built within vs. outside a module #31354

zimnx opened this issue Apr 8, 2019 · 17 comments

Comments

@zimnx
Copy link

@zimnx zimnx commented Apr 8, 2019

What did you do?

I have simple application that loads Go plugins and allows them to communicate with app via exported interface.

Simplified version can be found here.
Shared interface is stored under ext directory, real implementation is located underplugin directory.
Application passes pointer to real implementation to dynamically loaded plugin which expects interface.

Example plugin code can be found here.

Unfortunately combination of Go Modules and Go Plugins doesn't work unless go.mod of plugins module has replace entry with local relative path for shared interface path.
When I use remote location in plugins module, application crashes because of wrong version of packages.

Build app:

~/tmp/go-plugin-bug/central on  master ⌚ 16:12:19
$ go clean -modcache 

~/tmp/go-plugin-bug/central on  master ⌚ 16:12:21
$ git checkout v1.0.0
Note: checking out 'v1.0.0'.

~/tmp/go-plugin-bug/central on  f1d7e9f ⌚ 16:12:22
$ go install -a

Then build plugin

~/tmp/go-plugin-bug/central on  f1d7e9f ⌚ 16:12:27
$ cd ../plugins 

~/tmp/go-plugin-bug/plugins on  master ⌚ 16:12:28
$ cat go.mod 
module github.com/zimnx/plugins

go 1.12

require github.com/zimnx/central v1.0.0


~/tmp/go-plugin-bug/plugins on  master ⌚ 16:12:30
$ go build -buildmode=plugin -o plugin.so
go: finding github.com/zimnx/central v1.0.0
go: downloading github.com/zimnx/central v1.0.0
go: extracting github.com/zimnx/central v1.0.0

~/tmp/go-plugin-bug/plugins on  master ⌚ 16:12:39
$ central plugin.so 
2019/04/08 16:12:42 cant open plugin: plugin.Open("plugin"): plugin was built with a different version of package github.com/zimnx/central/ext

When I change plugins go mod to use local path instead of remote one everything works.

~/tmp/go-plugin-bug/plugins on  master ⌚ 16:12:42
$ cat go.mod                             
module github.com/zimnx/plugins

go 1.12

require github.com/zimnx/central v1.0.0

replace github.com/zimnx/central => ../central

~/tmp/go-plugin-bug/plugins on  master! ⌚ 16:14:30
$ go build -buildmode=plugin -o plugin.so

~/tmp/go-plugin-bug/plugins on  master! ⌚ 16:14:33
$ central plugin.so 
hello = world

What did you expect to see?

Go Modules and plugins working fine when remote path is used.

What did you see instead?

Error about different package versions.

Does this issue reproduce with the latest release (go1.12.3)?

Yes.

System details

go version go1.12.3 linux/amd64
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/maciej/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/maciej/work"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/maciej/tmp/go-plugin-bug/plugins/go.mod"
GOROOT/bin/go version: go version go1.12.3 linux/amd64
GOROOT/bin/go tool compile -V: compile version go1.12.3
uname -sr: Linux 4.14.13-041413-generic
Distributor ID:	Ubuntu
Description:	Ubuntu 17.10
Release:	17.10
Codename:	artful
/lib/x86_64-linux-gnu/libc.so.6: GNU C Library (Ubuntu GLIBC 2.26-0ubuntu2.1) stable release version 2.26, by Roland McGrath et al.
gdb --version: GNU gdb (Ubuntu 8.0.1-0ubuntu1) 8.0.1
@zimnx
Copy link
Author

@zimnx zimnx commented Apr 10, 2019

Bump. Any plans to fix this? Seems that algorithm which verifies package version integrity needs alignment.

@bcmills
Copy link
Member

@bcmills bcmills commented Apr 10, 2019

This is the same underlying problem as #29814.

The workaround should be to build both the plugin and the main binary from outside of their respective repositories, or (as you note) to use a replace directive when building locally.

@bcmills bcmills changed the title plugin: Plugins doesn't work when remote path of dependency is used in Go Module cmd/go: plugin versions do not match when built within vs. outside a module Apr 10, 2019
@bcmills
Copy link
Member

@bcmills bcmills commented Apr 10, 2019

@bcmills
Copy link
Member

@bcmills bcmills commented May 7, 2019

See also #31278, of which this may be a duplicate.

@juhwany
Copy link

@juhwany juhwany commented Jun 14, 2019

@bcmills

Is this issue related to #16860 ?

Building plugin and main binary with -trimpath flag doesn't solve this issue even using a go toolchain built from head.

@xxxserxxx
Copy link

@xxxserxxx xxxserxxx commented Mar 7, 2020

There are several similar tickets; apologies if this is not the right place to comment.

If the path is different but the version of the libraries are identical, we get version mismatch errors. A way of replicating this is to build the calling program in a container, and build the plugin outside of the container. The paths may look like this:

# Calling
/go/pkg/mod/github.com/shirou/gopsutil@v2.18.11+incompatible/internal/common/common.go
# Called
/home/ser/go/pkg/mod/github.com/shirou/gopsutil@v2.18.11+incompatible/internal/common/common.go

Building locally, and where:

  • the plugin is built using a go.mod with a specific date+revision version for the calling main program, and
  • the calling application is built from a checkout at that version, verified that the version in the plugin go.mod matches the checked-out version with:
echo $(TZ=UTC git show -s --format=%cd --date=format-local:%Y%m%d%H%M%S HEAD | cut -b -19 |  tr -cd '[:digit:]')-$(git rev-parse HEAD | cut -b -12)
  • and both programs being compiled with the same go executable, and
  • using --trimpath on both, and
  • both go.mod list the same go version dependency
    I get the same version incompatibility error with a different package:
# Error:
2:31:33 main.go:485: plugin.Open("dummy"): plugin was built with a different version of package github.com/xxxserxxx/gotop/v3/devices
$ #######################################################
$ strings ./gotop | grep 'v3/devices$'
go.link.pkghashbytes.github.com/xxxserxxx/gotop/v3/devices
go.link.pkghash.github.com/xxxserxxx/gotop/v3/devices
%github.com/xxxserxxx/gotop/v3/devices
go.link.pkghashbytes.github.com/xxxserxxx/gotop/v3/devices
go.link.pkghash.github.com/xxxserxxx/gotop/v3/devices
$ #######################################################
$ strings dummy.so | grep 'v3/devices$'
go.link.pkghashbytes.github.com/xxxserxxx/gotop/v3/devices
go.link.pkghash.github.com/xxxserxxx/gotop/v3/devices
github.com/xxxserxxx/gotop-dummygithub.com/xxxserxxx/gotop-dummygithub.com/xxxserxxx/gotop/v3/devices
%github.com/xxxserxxx/gotop/v3/devices
go.link.pkghashbytes.github.com/xxxserxxx/gotop/v3/devices
go.link.pkghash.github.com/xxxserxxx/gotop/v3/devices

I've played with copying the go.sum into the plugin directory so that it's identical to the other project, and have made sure that the entries in both go.mods are the same (except for the plugin's dependency on the main program).

If I add the following to the plugin's go.mod:

replace github.com/xxxserxxx/gotop/v3 => ../gotop

then it works.

Edit

Rewrote some of the text to be more readable, I hope.

Edit 2

I made an error: I need to remove the -trimpath argument in addition to adding the replace directive in the go.mod, or else I still get a version incompatibility error. -trimpath appears to make things worse.

@xplodwild
Copy link

@xplodwild xplodwild commented Mar 22, 2020

Same thing happens for me for a go.mod-based project regarding the -trimpath option. When building both the main app and the plugin with -trimpath enabled, I get a plugin was built with a different version of package github.com/me/myapp/mypackage. Removing -trimpath makes it load fine, however I'm afraid people with other setups might have trouble getting their plugins loaded due to paths inconsistencies.

@xxxserxxx
Copy link

@xxxserxxx xxxserxxx commented Mar 24, 2020

Another data point which I can't even begin to interpret:

$ git clone https://github.com/xxxserxxx/gotop
$ git clone https://github.com/xxxserxxx/gotop-dummy
$ cd gotop && git checkout v3.5.0
$ go build -o gotop ./cmd/gotop
$ cd ../gotop-dummy && grep gotop go.mod
require github.com/xxxserxxx/gotop/v3 v3.5.0
$ go mod download
$ go mod vendor
$ go build --buildmode=plugin -o ../gotop/dummy.so .
$ cd ../gotop
$ ./gotop -X dummy    # this hangs for some reason; ^C to break
$ cat ~/.local/state/gotop/errors.log
13:03:16 main.go:485: plugin.Open("dummy"): plugin was built with a different version of package github.com/shirou/gopsutil/internal/common
$ go mod graph | grep gopsutil
github.com/xxxserxxx/gotop/v3 github.com/shirou/gopsutil@v2.18.11+incompatible
➜  gotop git:(be42ba5) ✗ git log -1
commit be42ba538cefc892d1dc3d18c783483f4875baff (HEAD, tag: v3.5.0)
$ cd ../gotop-dummy
$ go mod graph | grep gopsutil
github.com/xxxserxxx/gotop/v3@v3.5.0 github.com/shirou/gopsutil@v2.18.11+incompatible

These are the identical dependencies. Why is the system claiming that they are not?

@pnegahdar
Copy link

@pnegahdar pnegahdar commented Mar 31, 2020

Still an issue we're running into as well.

@hoijui
Copy link

@hoijui hoijui commented May 5, 2020

This is the same underlying problem as #29814.

The workaround should be to build both the plugin and the main binary from outside of their respective repositories, or (as you note) to use a replace directive when building locally.

do you mean to say, that the ext folder should be its own module, separate from the main app?
would that work?

@mytototo
Copy link

@mytototo mytototo commented Jun 9, 2020

Has anybody found a way to build reproducible plugins for different machines?

@knipknap
Copy link

@knipknap knipknap commented Jul 31, 2020

@mytototo There is no reliable way, as the problem is by design. Every dependency of every package used in both plugin and main app have to be exactly the same version, making plugin and app extremely highly coupled. Even if you have control over both, plugin and app, this is very hard or impossible to maintain because you don't have control over what 3rd party modules might import.

Unfortunately that means there are very few practical use cases for Go plugins, as far as I can see.

@mark4z
Copy link

@mark4z mark4z commented Dec 25, 2020

A solution for this problem:

There are a project MAIN, If u want build some plugin for MAIN. You can create a project A and import the MAIN as its DockerFile, and create another project B and import the MAIN to build the plugin.
Then A and B import the same version for MAIN, every thing will be work perfect.

@xplodwild
Copy link

@xplodwild xplodwild commented Dec 26, 2020

A solution for this problem:

There are a project MAIN, If u want build some plugin for MAIN. You can create a project A and import the MAIN as its DockerFile, and create another project B and import the MAIN to build the plugin.
Then A and B import the same version for MAIN, every thing will be work perfect.

@mark4z This is not a proper workaround, for me at least. I need to be able to let external developers build plugins for my app, based on a specific set of shared interfaces and structures, without having to (re)build everything inside a single environment.

@mark4z
Copy link

@mark4z mark4z commented Dec 27, 2020

A solution for this problem:

There are a project MAIN, If u want build some plugin for MAIN. You can create a project A and import the MAIN as its DockerFile, and create another project B and import the MAIN to build the plugin.

Then A and B import the same version for MAIN, every thing will be work perfect.

@mark4z This is not a proper workaround, for me at least. I need to be able to let external developers build plugins for my app, based on a specific set of shared interfaces and structures, without having to (re)build everything inside a single environment.

Sure, maybe the last way is extraction everything the plug-in need and both projects import it. I'm working on it now.

@simar7
Copy link

@simar7 simar7 commented Apr 12, 2021

I'm on go 1.16 and still seeing this problem.

@mattn
Copy link
Member

@mattn mattn commented May 26, 2021

The current plugin mechanism does not allow you to avoid mixed package hashes. For example, this error occurs when a main application that references package A loads plugins that references A. I often use gophernotes to try to write notebook with Go. gophernotes reference mattn/go-runewidth. So gophernotes can not import mattn/go-runewidth on the notebooks. I have to disable Go code to avoid this.

diff --git a/src/runtime/plugin.go b/src/runtime/plugin.go
index cd7fc5f848..ace1b13ed9 100644
--- a/src/runtime/plugin.go
+++ b/src/runtime/plugin.go
@@ -48,12 +48,14 @@ func plugin_lastmoduleinit() (path string, syms map[string]interface{}, errstr s
                        throw("plugin: new module data overlaps with previous moduledata")
                }
        }
-       for _, pkghash := range md.pkghashes {
-               if pkghash.linktimehash != *pkghash.runtimehash {
-                       md.bad = true
-                       return "", nil, "plugin was built with a different version of package " + pkghash.modulename
+       /*
+               for _, pkghash := range md.pkghashes {
+                       if pkghash.linktimehash != *pkghash.runtimehash {
+                               md.bad = true
+                               return "", nil, "plugin was built with a different version of package " + pkghash.modulename
+                       }
                }
-       }
+       */

        // Initialize the freshly loaded module.
        modulesinit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet