Developing Go Plugins on non-Linux Operating Systems

In one of my many (incomplete) side-projects I need to dynamically load modules into a Go application. Vladimir Vivien has a great post explaining how to do this using Go plugins but Go's plugin functionality currently only works on Linux.

If we try from another operating system, such as MacOS we're stopped in our tracks:

$ go build --buildmode=plugin -o eng/eng.so eng/greeter.go
-buildmode=plugin not supported on darwin/amd64

Go plugins being limited to Linux isn't a blocker for my side-project which will require Linux anyway but I'd still like to develop the project on my main development machine which runs MacOS. Docker to the rescue!

By using the golang Docker image we can build and run our Go application inside a Linux container without leaving the comfort of our normal development environment and we can hide the (minimal) complexity of this behind a simple Makefile that delegates the build and test steps out to the Docker container.

The following Makefile provides targets (all, greeter, eng/eng.so, and chi/chi.so) to build the plugins and host, as well as a target (test) to run the resulting project:

APP_DIR=`pwd | sed "s@$(GOPATH)@@"`
DOCKER_CMD=docker run -t --mount type=bind,source=$(GOPATH),target=/mnt/go --env GOPATH=$(GOPATH) -w /mnt/go$(APP_DIR)
BUILD_IMAGE=golang:1.9
BUILD_CMD=$(DOCKER_CMD) $(BUILD_IMAGE) go build
RUN_IMAGE=golang:1.9
RUN_ARGS=

.PHONY=clean test all
all: eng/eng.so chi/chi.so greeter

eng/eng.so:
	$(BUILD_CMD) --buildmode=plugin -o eng/eng.so eng/greeter.go

chi/chi.so:
	$(BUILD_CMD) --buildmode=plugin -o chi/chi.so chi/greeter.go

greeter:
	$(BUILD_CMD) greeter.go

clean:
	rm greeter chi/chi.so eng/eng.so

test: all
	$(DOCKER_CMD) $(RUN_IMAGE) /mnt/go$(APP_DIR)/greeter $(RUN_ARGS)

This Makefile mounts the GOPATH into the container so the source of the project as well as any dependencies are available to the Go compiler. Also, the results of the build are written to the same directory so they're available from the host after the build completes. Although, you would be unable to run the project from the host because of the Linux requirement for plugins.

With this Makefile we can now build the project from any operating system:

$ make
docker run -t --mount type=bind,source=/Users/willbar/Code/go,target=/mnt/go --env GOPATH=/Users/willbar/Code/go -w /mnt/go`pwd | sed "s@/Users/willbar/Code/go@@"` golang:1.9 go build --buildmode=plugin -o eng/eng.so eng/greeter.go
docker run -t --mount type=bind,source=/Users/willbar/Code/go,target=/mnt/go --env GOPATH=/Users/willbar/Code/go -w /mnt/go`pwd | sed "s@/Users/willbar/Code/go@@"` golang:1.9 go build --buildmode=plugin -o chi/chi.so chi/greeter.go
docker run -t --mount type=bind,source=/Users/willbar/Code/go,target=/mnt/go --env GOPATH=/Users/willbar/Code/go -w /mnt/go`pwd | sed "s@/Users/willbar/Code/go@@"` golang:1.9 go build greeter.go

As well as run the resulting project:

$ make RUN_ARGS=chinese test
docker run -t --mount type=bind,source=/Users/willbar/Code/go,target=/mnt/go --env GOPATH=/Users/willbar/Code/go -w /mnt/go`pwd | sed "s@/Users/willbar/Code/go@@"` golang:1.9 /mnt/go`pwd | sed "s@/Users/willbar/Code/go@@"`/greeter chinese
你好宇宙

I've created a fork of Vladimir's original sample project and added a Makefile as an example:
https://github.com/iamwillbar/go-plugin-example