GitRoot

Craft your forge // Build your project // Grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5package plugin
  6
  7import (
  8	"context"
  9	"fmt"
 10	"path/filepath"
 11	"sync"
 12
 13	"github.com/samber/oops"
 14	"github.com/tetratelabs/wazero"
 15	"github.com/tetratelabs/wazero/api"
 16	"github.com/tetratelabs/wazero/experimental"
 17	pluginLib "gitroot.dev/libs/golang/plugin/model"
 18	"gitroot.dev/server/logger"
 19)
 20
 21type runtimes struct {
 22	lockRuntime sync.Mutex
 23	manager     *Manager
 24	runtime     *runtime
 25	log         *logger.Logger
 26	start       chan runtimeInputs
 27}
 28
 29func newRuntimes(manager *Manager, log *logger.Logger) *runtimes {
 30	chanStart := make(chan runtimeInputs)
 31	compileCache, _ := wazero.NewCompilationCacheWithDir(filepath.Join(manager.conf.PathCache(), "wazero"))
 32	runtimeConfig := wazero.NewRuntimeConfig().WithCompilationCache(compileCache).WithCoreFeatures(api.CoreFeaturesV2 | experimental.CoreFeaturesThreads)
 33	r, err := newRuntime(context.Background(), manager, log, runtimeConfig)
 34	if err != nil {
 35		log.Error("can't create runtime", err)
 36	}
 37	go func() {
 38		r.listen(chanStart)
 39	}()
 40	return &runtimes{
 41		manager: manager,
 42		runtime: r,
 43		log:     log,
 44		start:   chanStart,
 45	}
 46}
 47
 48func (r *runtimes) Compile(ctx context.Context, binary []byte) (wazero.CompiledModule, error) {
 49	r.lockRuntime.Lock()
 50	defer r.lockRuntime.Unlock()
 51	return r.runtime.wazRun.CompileModule(ctx, binary)
 52}
 53
 54func (r *runtimes) Start(ctx context.Context, repoName string, plugins []Plugin, kind runtimeInputsKind, commands []CommandForDiff, then func(newCmds []CommandForDiff)) {
 55	r.lockRuntime.Lock()
 56	wg := sync.WaitGroup{}
 57	for _, command := range commands {
 58		wg.Add(1)
 59		r.start <- runtimeInputs{
 60			ctx:      ctx,
 61			repoName: repoName,
 62			kind:     kind,
 63			plugins:  plugins,
 64			command:  command,
 65			close: func(toRunLater []CommandForDiff) {
 66				wg.Done()
 67				if then != nil {
 68					then(toRunLater)
 69				}
 70			},
 71		}
 72	}
 73	wg.Wait()
 74	r.lockRuntime.Unlock()
 75}
 76
 77func (r *runtimes) Conf(ctx context.Context, plugin Plugin) (*pluginLib.ConfPlugin, error) {
 78	timerStop := r.log.Time(fmt.Sprintf("Timer conf plugin %s", plugin.Log()))
 79	defer timerStop()
 80	r.lockRuntime.Lock()
 81	defer r.lockRuntime.Unlock()
 82	return r.runtime.conf(ctx, plugin)
 83}
 84
 85func (r *runtimes) Schedule(ctx context.Context, repoName string, pluginPurl string, pluginRun *PluginRun) error {
 86	r.log.Debug("start schedule", logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", pluginPurl))
 87	plugins, err := r.manager.usableFromDefaultBranch(ctx, repoName)
 88	if err != nil {
 89		return err
 90	}
 91	var plugin Plugin
 92	found := false
 93	for _, p := range plugins {
 94		if p.PURL.String() == pluginPurl {
 95			plugin = p
 96			found = true
 97			break
 98		}
 99	}
100	if !found {
101		return oops.With("repo", repoName, "pluginPurl", pluginPurl).Errorf("plugin not found")
102	}
103	timerStop := r.log.Time(fmt.Sprintf("Timer schedule plugin %s", plugin.Log()))
104	defer timerStop()
105	r.lockRuntime.Lock()
106	defer r.lockRuntime.Unlock()
107	return r.runtime.schedule(ctx, repoName, plugin, *pluginRun)
108}