Dynamically Run Golang Code

Golang, Google’s GO language, emphatically has no support for dynamically loading modules. I have a project, however, that can require a completely different configuration based on very minor changes in situations and environment. Nightmare! I hate it when there is no clear solution to a problem. Then I realized I can write my own code dynamically!

In my main package, I read a configuration file and then write out a new source file named runner.go. Apart from some house keeping and logging, my routine runs that generated code with an exec. In this initial form, that’s a smidgen slow and inefficient, but it’s only a few cycles–who cares!

Here’s my basic solution. If you use it, please email me so I can keep you up to date on enhancements before I ship it off to github. This is very much a developing technique. I will be adding some state and compilation controls.

The config file modules.conf contains a list of modules I want to add. No real smarts here, but you can see how some logic might selectively pick a config file.

modules.conf

    “mytest”

The actual source is pretty straight forward:

initer.go

package main

import (
“bytes”
“os”
“fmt”
“strings”
“os/exec”
“log”
“bufio”
“io”
)

func readLines(path string) (lines []string, err error) {
var (
file *os.File
part []byte
prefix bool
)
if file, err = os.Open(path); err != nil {
return
}
defer file.Close()

reader := bufio.NewReader(file)
buffer := bytes.NewBuffer(make([]byte, 0))
for {
if part, prefix, err = reader.ReadLine(); err != nil {
break
}
buffer.Write(part)
if !prefix {
lines = append(lines, buffer.String())
buffer.Reset()
}
}
if err == io.EOF {
err = nil
}
return
}

func writeLines(lines1 []string, modules []string, lines2 []string, path string) (err error) {
var (
file *os.File
)

if file, err = os.Create(path); err != nil {
return
}
defer file.Close()

for _,item := range lines1 {
_, err := file.WriteString(strings.TrimSpace(item) + “\n”);
if err != nil {
fmt.Println(err)
break
}
}
for _,item := range modules {
_, err := file.WriteString(strings.TrimSpace(item) + “\n”);
if err != nil {
fmt.Println(err)
break
}
}
for _,item := range lines2 {
_, err := file.WriteString(strings.TrimSpace(item) + “\n”);
if err != nil {
fmt.Println(err)
break
}
}
return
}

func main() {

//
// create the runnable source to load imports at run time
//

var modules []string

modules, err := readLines(“modules.conf”)
if err != nil {
fmt.Println(err)
}
for _,ii := range modules {
fmt.Printf(ii)
}

lines1 := []string{
“package main”,
“”,
“import (“, // add new import modules here
”    \”fmt\””,
}

lines2 := []string{
“)”,
“”,
“func main(){“,
”   mystruct.TestFunction()”,
”   fmt.Println(\”Hello World\\n\”)\n”,
“}”,
}

var i int
var c int

for i = range lines1 {
fmt.Printf(“.”)
}

c = i + 1

for i = range modules {
fmt.Printf(“o”)
}

c = c + i + 1

for i = range lines2 {
fmt.Printf(“.”)
}

c = c + i + 1

fmt.Println(“\n”, c, ” lines processed by initer”)

fmt.Printf(“\nCreating runner module\n”)

err = writeLines(lines1, modules, lines2, “runner.go”)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf(“\nRunning runner module\n”)

runnerCommand := exec.Command(“go”, “run”, “runner.go”)
var out bytes.Buffer
runnerCommand.Stdout = &out
err = runnerCommand.Start()

if err != nil {
log.Fatal(err)
}
log.Printf(“Waiting for command to finish…”)
err = runnerCommand.Wait()
fmt.Printf(“%s\n”, out.String())
if err != nil {
log.Printf(“Runner finished with error: %v”, err)
}else{
log.Printf(“Normal execution finished”)
}
}
}

Now run initer,go to perform the execution:

$go run initer.go
    “mytest”….o……
 11  lines processed by initer
Creating runner module
Running runner module
2014/10/23 21:46:53 Waiting for command to finish…
Hello World
2014/10/23 21:46:53 Normal execution finished

This is a work in-progress so send me an email at lonnie at lonniewebb.com so I can keep you in the loop!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.