Skip to content

Commit

Permalink
feat: migrate to c-shared plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
axtloss committed Apr 21, 2024
1 parent 5eaedc2 commit d19c2a5
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 17 deletions.
9 changes: 2 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,13 @@ We recommend adding the `vib-plugin` tag to your repo so other people can discov

A short tldr:

- vib requires `BuildModule(interface{}, recipe *api.Recipe*) (string, error)` to be available as it is used as the entry point for the plugin. Any other functions can be freely declared and will not be used by vib
- vib requires `BuildModule(moduleInterface *C.char, recipeInterface *C.char) *C.char` to be available as it is used as the entry point for the plugin. Any other functions can be freely declared and will not be used by vib
- Each plugin needs to have a custom struct for the module, with at least two mandatory values: `Name string` and `Type string`
- It is recommended, but not required, to use the api functions for source definition or downloading sources

## Building

NOTE: Plugins will have to be compiled with the same go version as Vib, for builds from vanilla-os, this version is 1.21, it is recommended to use `podman`/`docker` or the GitHub workflow to build the plugins.

Plugins can be built with `go build -trimpath -buildmode=plugin`, producing a `.so` file.
Plugins can be built with `go build -buildmode=c-shared -o plugin.so`, producing a `.so` file.
To use the plugin, the `.so` file has to be moved into the `plugins/` directory of the vib recipe.
Plugins are only loaded when required, if a recipe never uses a plugin example, `example.so` will never be loaded.

This template contains a GitHub workflow that can build the plugin with the right arguments automatically.
Otherwise one can use podman/docker to build the plugin:
`docker run --rm -v "$PWD":/usr/src/myapp -w /usr/src/myapp golang:1.19 go build -trimpath -buildmode=plugin -v`
45 changes: 35 additions & 10 deletions plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package main

import (
"fmt"
"github.com/mitchellh/mapstructure"
"C"
"encoding/json"
"github.com/vanilla-os/vib/api"
)

Expand All @@ -12,7 +13,7 @@ type ExampleModule struct {
Type string `json:"type"`

// Additional values such as Source can be added here
Source api.Source
Source api.Source `json:"Source"`
}

// Plugins can define extra functions that are used internally
Expand All @@ -32,24 +33,48 @@ func fetchSources(source api.Source, name string, recipe *api.Recipe) error {
}

// This is the entry point for plugins that vib calls
// The arguments are required to be (interface{}, recipe) => (string, error)
func BuildModule(moduleInterface interface{}, recipe *api.Recipe) (string, error) {
// The arguments are required to be (*C.char, *C.char) => string
// Make sure NOT to remove the "export BuildModule"

//export BuildModule
func BuildModule(moduleInterface *C.char, recipeInterface *C.char) *C.Char {
// It is advisable to convert the interface to an actual struct
// The use of mapstructure for this is recommended, but not required
var module ExampleModule
err := mapstructure.Decode(moduleInterface, &module)
// The use of json.Unmarshal for this is recommended, but not required
var module *ExampleModule
var recipe *api.Recipe

err := json.Unmarshal([]byte(C.GoString(moduleInterface)), &module)
if err != nil {
return "", err

// Due to the way c-shared builds work, the function has to return a CString
// CGO includes a proper way to ensure strings are converted to CStrings
// Any return in BuildModule will have to be wrapped with C.CString
//
// Since only one value can be returned, vib checks if the return
// starts with "ERROR:", if this is the case, it assumes that the
// plugin has failed and takes anything after the "ERROR:" as the
// error message.
return C.CString(fmt.Sprintf("ERROR: %s", err.Error()))
}

err = json.Unmarshal([]byte(C.GoString(recipeInterface)), &recipe)
if err != nil {
return C.CString(fmt.Sprintf("ERROR: %s", err.Error()))
}

err = fetchSources(module.Source, module.Name, recipe)
if err != nil {
return "", err
return C.CString(fmt.Sprintf("ERROR: %s", err.Error()))
}

// The sources will be made available at /sources/ during build
// if the plugins requires manually downloaded sources, they will
// be available in /sources/<modulename>
cmd := fmt.Sprintf("cd /sources/%s && cp * /etc/%s", module.Name, module.Name)

return cmd, nil
return C.CString(cmd)
}

// Keep this function empty
// It will never be triggered. Any code in here is dead code.
func main() {}

0 comments on commit d19c2a5

Please sign in to comment.