I code a plugin generator in Go. The repo is open.
The project creates some Go code in some temporary file defining a function taken from command-line arguments, compiles that code into a plugin, loads that plugin, gets the function in it, and invokes that with parameters, then prints the results.
We want to be able to process several functions and several plugins. For example, a function SUM
of body return x+y;
, a function PROD
of body return x*y
; and so on.
I don't want the generated code to always use the
constant name FUNCTION. Can't the generated .go file contain a
function whose name is given at runtime, i.e. my funame
in the code below? Is there some feature of the Go language forbidding that?
//TODO: Investigate how to relax the name FUNCTION into a variable
type Xinterface interface {
FUNCTION(x int, y int) int
}
Complete code
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"plugin"
"reflect"
"strconv"
"strings"
)
//TODO: Investigate how to relax the name FUNCTION into a variable
type Xinterface interface {
FUNCTION(x int, y int) int
}
func main() {
//Declare good variable names and create the file for the code
funame := os.Args[1]
funamel := strings.ToLower(funame)
funamet := strings.Title(funame)
fubody := os.Args[2]
x1, err := strconv.Atoi(os.Args[3])
y1, err := strconv.Atoi(os.Args[4])
filename := fmt.Sprintf("/tmp/%s.go", funame)
f, err := os.Create(filename)
if err != nil {
fmt.Println(err)
return
}
//Here comes the program
strprg := fmt.Sprintf(`package main
import (
"fmt"
)
type %s string
func(s %s) FUNCTION (x int, y int) int { fmt.Println("")
%s}
var %s %s`, funamel, funamel, fubody, funamet, funamel)
fmt.Printf("func(s %s) FUNCTION (x int, y int) int {
", funamel)
fmt.Printf("start of %s: x=%d, y=%d
", funamel, x1, y1)
l, err := f.WriteString(strprg)
if err != nil {
fmt.Println(err)
f.Close()
return
}
fmt.Println(l, "bytes written successfully")
err = f.Close()
if err != nil {
fmt.Println(err)
return
}
ex, err := os.Executable()
if err != nil {
panic(err)
}
exPath := filepath.Dir(ex)
fmt.Println(exPath)
fmt.Println("compiling plugin")
cmd := exec.Command("go", "build", "-buildmode=plugin", "-o", fmt.Sprintf("%s%s%s", "/tmp/", funame, ".so"), fmt.Sprintf("%s%s%s", "/tmp/", funame, ".go"))
out, err2 := cmd.Output()
fmt.Println(out)
if err2 != nil {
fmt.Println(err2)
return
}
fmt.Println("loading module")
// load module
// 1. open the so file to load the symbols
plug, err := plugin.Open(fmt.Sprintf("%s%s%s", "/tmp/", funame, ".so"))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println("looking up symbol")
// 2. look up a symbol (an exported function or variable)
// in this case, variable funame
symX, err := plug.Lookup(funame)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println("checking module")
// 3. Assert that loaded symbol is of a desired type
// in this case interface type X (defined above)
var myvar Xinterface
myvar, ok := symX.(Xinterface)
if !ok {
fmt.Println(fmt.Sprintf("unexpected type from module symbol %s", reflect.TypeOf(symX.(Xinterface))))
os.Exit(1)
}
// 4. use the module
fmt.Println(myvar.FUNCTION(x1, y1))
fmt.Println(fmt.Sprintf("Generated code: %s", fmt.Sprintf("/tmp/%s%s", funamet , ".go") ))
fmt.Println(fmt.Sprintf("Generated object file: %s", fmt.Sprintf("/tmp/%s%s", funamet , ".so") ))
}
Example usage:
$ go run forbasile.go SUM 'return x+y' 3 5
func(s sum) FUNCTION (x int, y int) int {
start of sum: x=3, y=5
131 bytes written successfully
/tmp/go-build104174513/b001/exe
compiling plugin
[]
loading module
looking up symbol
checking module
8
Generated code: /tmp/SUM.go
Generated object file: /tmp/SUM.so
$ go run forbasile.go SUMSQUARE 'return x*x + y*y' 3 4
func(s sumsquare) FUNCTION (x int, y int) int {
start of sumsquare: x=3, y=4
161 bytes written successfully
/tmp/go-build555823501/b001/exe
compiling plugin
[]
loading module
looking up symbol
checking module
25
Generated code: /tmp/SUMSQUARE.go
Generated object file: /tmp/SUMSQUARE.so