Plugin defines the minimal interface that build plugins must implement.
Lifecycle hooks like PreBuild, Build and PostBuild are detected
automatically via reflection when present.
Info reports the name and configuration of an active plugin.
Active returns the list of configured plugins in execution order.
{
out := make([]Info, len(active))
for i, e := range active {
out[i] = Info{Name: e.Plugin.Name(), Config: e.cfg}
}
return out
}
Register adds a plugin to the registry.
{ registry[p.Name()] = p }
Install builds and activates a single plugin by name.
{
if p, ok := registry[name]; ok {
active = append(active, entry{Plugin: p, cfg: raw})
return nil
}
return fmt.Errorf("plugin %s not found", name)
}
Configure installs plugins listed in the provided configuration map.
{
active = active[:0]
for name, raw := range cfg {
if err := Install(name, raw); err != nil {
return err
}
}
sort.SliceStable(active, func(i, j int) bool {
return active[i].Plugin.Priority() < active[j].Plugin.Priority()
})
return nil
}
invoke executes the named lifecycle hook on the plugin if it exists.
{
m := reflect.ValueOf(e.Plugin).MethodByName(name)
if !m.IsValid() {
return nil
}
typ := m.Type()
if typ.NumIn() != 1 || typ.In(0) != reflect.TypeOf(json.RawMessage{}) || typ.NumOut() != 1 || typ.Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
return fmt.Errorf("%s has invalid signature for %s", e.Plugin.Name(), name)
}
out := m.Call([]reflect.Value{reflect.ValueOf(e.cfg)})
if err, _ := out[0].Interface().(error); err != nil {
return err
}
return nil
}
{
for _, e := range active {
if err := invoke(e, "PreBuild"); err != nil {
return fmt.Errorf("%s prebuild failed: %w", e.Plugin.Name(), err)
}
}
return nil
}
{
for _, e := range active {
if err := invoke(e, "Build"); err != nil {
return fmt.Errorf("%s build failed: %w", e.Plugin.Name(), err)
}
}
return nil
}
{
for _, e := range active {
if err := invoke(e, "PostBuild"); err != nil {
return fmt.Errorf("%s postbuild failed: %w", e.Plugin.Name(), err)
}
}
return nil
}
NeedsRebuild reports whether any active plugin requires a rebuild for the given path.
{
for _, e := range active {
if e.Plugin.ShouldRebuild(path) {
return true
}
}
return false
}
mockPlugin is a simple implementation of the Plugin interface used for
testing the plugin registry and lifecycle.
{ return m.name }
{ return m.priority }
{ return p == m.rebuild }
{
m.pre = true
return nil
}
{
m.build = true
return nil
}
{
m.post = true
return nil
}
TestLifecycle verifies registration, ordering and lifecycle invocation of
plugins as well as the NeedsRebuild helper.
{
// Reset global state.
registry = map[string]Plugin{}
active = nil
p1 := &mockPlugin{name: "p1", priority: 1, rebuild: "a.go"}
p0 := &mockPlugin{name: "p0", priority: 0}
Register(p1)
Register(p0)
cfg := map[string]json.RawMessage{"p1": nil, "p0": nil}
if err := Configure(cfg); err != nil {
t.Fatalf("configure: %v", err)
}
if len(active) != 2 || active[0].Plugin != p0 || active[1].Plugin != p1 {
t.Fatalf("plugins not sorted by priority: %#v", active)
}
if err := PreBuild(); err != nil {
t.Fatalf("prebuild: %v", err)
}
if !p1.pre || !p0.pre {
t.Fatalf("prebuild not invoked")
}
if err := Build(); err != nil {
t.Fatalf("build: %v", err)
}
if !p1.build || !p0.build {
t.Fatalf("build not invoked")
}
if err := PostBuild(); err != nil {
t.Fatalf("postbuild: %v", err)
}
if !p1.post || !p0.post {
t.Fatalf("postbuild not invoked")
}
if !NeedsRebuild("a.go") {
t.Fatalf("expected rebuild for a.go")
}
if NeedsRebuild("b.go") {
t.Fatalf("unexpected rebuild for b.go")
}
}
{
registry = map[string]Plugin{}
active = nil
p := &mockPlugin{name: "p", priority: 0}
Register(p)
cfg := map[string]json.RawMessage{"p": json.RawMessage("{\"x\":1}")}
if err := Configure(cfg); err != nil {
t.Fatalf("configure: %v", err)
}
list := Active()
if len(list) != 1 || list[0].Name != "p" || string(list[0].Config) != "{\"x\":1}" {
t.Fatalf("unexpected active list: %#v", list)
}
}
import "encoding/json"
import "fmt"
import "reflect"
import "sort"
import "encoding/json"
import "testing"