Skip to content

Plugin System

Leonard Ramminger edited this page Aug 14, 2026 · 1 revision

Plugin-System

Diese Seite dokumentiert das Beez-Plugin-System auf dem Branch milestone-17/plugin-system: wie Plugins aufgebaut sind, was sie exportieren können, welche neuen DSL-Funktionen es gibt, und welche Standard-Phasen, Standard-Scopes und Standard-Workflows das Coditary-Ökosystem verwendet.

Inhalt


Überblick: Was sich geändert hat

Bereich Neu / geändert
Plugins Vollständiges Plugin-Format mit plugin(), config, steps, optional workflows und tasks
Konfiguration Plugin Config DSL: defaults, profile_defs, finalize, optional schema
Projekt-Konfiguration configure({ ... }) ersetzt viele einzelne configure_step()-Aufrufe für Plugins
Workflows Gestufte Workflows ({ "setup", { "setup[app]" } }), Import via workflows({ name = "org/plugin:workflow" })
Tasks Plugin-Tasks exportieren und in build.lua importieren
Phasen / Scopes Standardisierte Namen (setup, quality, compile, …) und Scopes (app, lint, audit, …)
Referenz-Syntax phase[scope] und step[scope] statt separater scope-Felder in Tasks
Lua-API beez.env_or, beez.shell.run, beez.char.quote, beez.increment.run

Plugin-Verzeichnis

Ein Beez-Plugin liegt unter einem Pfad wie:

plugins/<organization>/<plugin-name>/<version>/
    beez_plugin.lua      # Einstieg: plugin(), workflows{}, tasks{}
    src/                 # Lua-Module (config, runner, command, defaults, …)

In build.lua wird das Plugin über ReqPack geladen:

reqpack {
    beez = {
        {
            name = "coditary/cppcheck",
            path = "./plugins/coditary/cppcheck",
            version = "1.0.0",
        },
    },
}

Beez lädt dann plugins/coditary/cppcheck/1.0.0/beez_plugin.lua. Der path zeigt auf das Plugin-Verzeichnis ohne Versions-Suffix; die version wählt den Unterordner.

Installierte Plugins

Versionierte Plugin-Steps können auch per CLI referenziert werden (plugin:step@1.0.0). Nicht installierte Versionen können bei Bedarf aus dem Plugin-Cache nachgeladen werden.


plugin() — Plugin deklarieren

In beez_plugin.lua:

plugin("cppcheck", {
    version = "1.0.0",
    description = "Incremental cppcheck static analysis",
    organization = "coditary",

    config = { ... },   -- optional: Plugin Config DSL
    steps = { ... },    -- Pflicht: Tabelle der Steps (darf leer sein, z. B. nur Workflow-Plugin)
})
Feld Pflicht Bedeutung
version ja Semantische Version (muss zu reqpack.beez passen)
organization empfohlen Organisation für qualifizierte Namen (coditary/cppcheck)
description nein Kurzbeschreibung für beez --list
config nein Plugin-weite Konfiguration (siehe unten)
steps ja Map step_name → step_table

Der erste Parameter von plugin("name", …) muss mit dem Verzeichnisnamen übereinstimmen (cppcheck, nicht coditary/cppcheck).

Step-Tabelle in Plugins

Gleiche Felder wie bei globalem step() in build.lua:

steps = {
    cppcheck_check = {
        phase = "quality",
        scope = "analyze",
        input = { "src/**/*.cpp" },
        description = "cppcheck (configurable profiles)",
        config = {
            profile = "analyze",
            profiles = { "analyze" },
        },
        run = function(ctx)
            return require("src.runner").check(ctx)
        end,
    },
}

Steps werden registriert als:

  • Kurzname: cppcheck_check (wenn nur ein Plugin diesen Namen hat)
  • Qualifiziert: coditary/cppcheck:cppcheck_check
  • Mit Scope-Alias: cppcheck_check:analyze (wenn der Step-Scope gesetzt ist)

Lazy Steps: Ein Step-Eintrag kann eine Funktion sein, die beim Laden eine Step-Tabelle zurückgibt — nützlich für viele ähnliche Steps (z. B. Conan-Configure pro Build-Profil).


Plugin Config DSL

Plugin-Konfiguration wird zentral in config definiert und pro Step-Aufruf zusammengeführt.

config = {
  defaults = {
    binary = "cppcheck",
    parallelism = 16,
    -- alle Standardwerte des Plugins
  },

  profile_defs = {
    analyze = {
      patterns = { "src/**/*.cpp" },
      log_prefix = "[cppcheck/analyze]",
    },
    security = {
      patterns = { "src/**/*.cpp" },
      log_prefix = "[cppcheck/security]",
    },
  },

  finalize = function(resolved)
    -- letzte Anpassung nach Merge
    resolved.enable = config.normalize_enable(resolved.enable)
    return resolved
  end,

  schema = { ... },  -- optional: JSON-Schema-Subset für Validierung
},

Merge-Reihenfolge

Für einen Plugin-Step mit config = { profile = "analyze", foo = "bar" }:

  1. defaults — Plugin-Basis
  2. profile_defs[profile] — nur wenn profile gesetzt ist und profile_defs nicht leer ist
  3. Step-config aus der Step-Definition
  4. configure() / configure_plugin() aus build.lua (global oder pro Step unter steps)
  5. finalize(cfg) — optional, normalisiert das Ergebnis

Wichtig: profile mit zwei Bedeutungen

Kontext Bedeutung von profile
In profile_defs Schlüssel für ein Plugin-Profil (analyze, security, fast, …)
Ohne profile_defs Beliebiges semantisches Feld (z. B. Build-Profil code, debug) — wird nicht gegen profile_defs geprüft

Plugins wie conan und clang-build nutzen profile = "code" als Build-Profil, ohne profile_defs.

Konfiguration im Step-Callback lesen

run = function(ctx)
    local cfg = ctx.get_config()
    -- cfg enthält das zusammengeführte Plugin-Config-Table
    return require("src.runner").check(ctx)
end

Empfohlenes Plugin-internes Muster:

  • src/config.lua — resolve(step_cfg) mit beez.data.clone und Normalisierung
  • src/runner.lua — Orchestrierung, ruft beez.shell.run / beez.increment.run auf
  • src/command.lua — reine Shell-Befehlsstrings
  • src/defaults.lua — Konstanten und Default-Muster

Projekt-Konfiguration: configure()

In build.lua konfigurierst du alle Plugins gebündelt:

configure({
    { "coditary/cppcheck", {
        check_rev = "2",
        steps = {
            cppcheck_check = { profiles = { "analyze", "security" } },
        },
    }},

    { "coditary/conan", {
        reports_dir = "report",
        lock_rev = "1",
    }},

    { ":clean:artifacts", {
        -- Standalone-Step (führender Doppelpunkt)
    }},
})

Jeder Eintrag ist { ziel, config_tabelle }:

Ziel Wirkung
"coditary/plugin" Plugin-Config mergen; optional steps = { step_name = { ... } } pro Step
":step_name" Nur diesen Step konfigurieren (lokal oder Plugin-Step)

Äquivalent für ein einzelnes Plugin:

configure_plugin("coditary/demo", { foo = "bar" })

configure() ist die bevorzugte Form für mehrere Plugins in einem Block.


Workflows aus Plugins exportieren

Nach plugin() kannst du Workflows definieren:

workflows {
    build = {
        { "setup",    { "setup[app]" } },
        { "compile",  { "compile[app]" } },
        { "bundle",   { "bundle[app]" } },
        { "test",     { "test[test]" } },
    },

    quality = {
        { "quality", { "quality[lint]", "quality[format]", "quality[analyze]" } },
        { "verify",  { "verify[security]" } },
    },
}

Gestuftes Format (staged)

Jeder Workflow-Eintrag ist { "<stufenname>", { "<phase[scope]>", ... } }:

  • Stufenname — nur für Lesbarkeit und Logging (z. B. "setup", "compile")
  • Invocations — Liste von Phase/Scope-Referenzen, die parallel in dieser Stufe laufen
  • Stufen laufen sequenziell nacheinander

Phase/Scope-Referenzen in Workflows

Syntax Bedeutung
"compile[app]" Phase compile, Scope app
"setup" Phase setup, alle Scopes dieser Phase
"compile:app" Alternative Schreibweise (Doppelpunkt)

Legacy-Format (weiterhin gültig, nicht mit staged mischen):

workflow("ci", {
    { phase = "compile", scope = "code" },
})

Workflows in build.lua importieren

workflows({
    build   = "coditary/pipeline:build",
    quality = "coditary/pipeline:quality",
    all     = "coditary/pipeline:all",
})

Referenzformat: "<organization>/<plugin>:<workflow_name>".

Lokale Definition inline:

workflows({
    quick = {
        { "test", { "test[test]" } },
    },
})

Tasks aus Plugins exportieren

tasks {
    format = {
        { plugin = "coditary/clang-format", step = "format_check" },
        "echo done",
    },
}

Import in build.lua

Kurzform (Task-Name = Plugin-Task-Name):

task("coditary/demo:format")

Alias:

task("my_format", {
    plugin = "coditary/clang-format",
    task = "format",
})

Direkte Plugin-Schritt-Kette (ohne exportierten Plugin-Task):

task("debug", {
    { plugin = "coditary/conan",        step = "configure[debug]" },
    { plugin = "coditary/clang-build", step = "compile[debug]" },
    { plugin = "coditary/clang-build", step = "link[debug]" },
})

Scoped Step-Syntax in Tasks

Statt separatem scope-Feld:

-- neu (empfohlen)
{ plugin = "coditary/conan", step = "configure[debug]" }

-- mehrere Scopes parallel als separate Step-Aufrufe
{ step = "compile[app,coverage]" }   -- nicht kombinierbar mit plugin:step:name

Veraltet (wird abgelehnt):

{ name = "compile", scope = "debug" }  -- Fehler: use step[name[scope]]

Phase-Aktionen in Tasks

task("rebuild", {
    { phase = "compile[app]" },
    { phase = "bundle[app]" },
})

Standard-Phasen

Das Coditary-Pipeline-Plugin und die mitgelieferten Plugins verwenden diese Phasen (in typischer Reihenfolge):

Phase Typische Inhalte
setup Conan install, CMake configure, Verzeichnisse anlegen
generate Code-Generierung, CompDB-Index
quality Lint, Format-Check, statische Analyse (cppcheck analyze, clang-tidy)
compile Kompilierung (Release, Debug, Coverage, Sanitize, Fuzz, …)
bundle Link, Binaries bauen
test Unit-, Integration-, Performance-, Fuzz-Tests
package SBOM, Lockfiles, Coverage-Reports, Audit-Artefakte
verify Security-Scans, OSV-Audit, Fuzz-Seed-Verifikation
publish Veröffentlichung (reserviert für zukünftige Steps)

Alte Namen (configure, build, qa, fuzz, report, clean) wurden durch diese Standard-Phasen ersetzt.


Standard-Scopes

Scope Verwendung
app Haupt-Release-Build (Conan + Compile + Link Release)
debug Debug-Build
coverage Coverage-Instrumentierung und Coverage-Tests
sanitize ASan/UBSan-Build und -Tests
tsan Thread-Sanitizer
fuzz Fuzz-Build und kurzer Fuzz-Lauf
fuzz-corpus Fuzz mit committed Corpus
fuzz-torture Langer Fuzz-Lauf
test Normale Tests (ctest, Integration, …)
lint clang-tidy Lint-Profil
format clang-format Check
analyze cppcheck/clang-tidy Analyze-Profil
security Security-orientierte Static-Analysis
audit Supply-Chain: SBOM, Lockfiles, OSV
docs Dokumentation
code Semantisches Build-Profil in Conan/CMake (kein Workflow-Scope-Zwang)
repo Repository-weite Wartung (z. B. clean)

Scopes sind Konventionen im Coditary-Ökosystem. Eigene Projekte können weitere Scopes definieren; Workflows wählen dann per phase[scope].


Standard-Workflows (coditary/pipeline)

Das Plugin coditary/pipeline exportiert vorgefertigte Workflows. Import in build.lua:

workflows({
    build          = "coditary/pipeline:build",
    quality        = "coditary/pipeline:quality",
    debug          = "coditary/pipeline:debug",
    coverage       = "coditary/pipeline:coverage",
    sanitize       = "coditary/pipeline:sanitize",
    tsan           = "coditary/pipeline:tsan",
    fuzzer_smoke   = "coditary/pipeline:fuzzer_smoke",
    fuzzer_corpus  = "coditary/pipeline:fuzzer_corpus",
    fuzzer_torture = "coditary/pipeline:fuzzer_torture",
    all            = "coditary/pipeline:all",
    clean          = "coditary/pipeline:clean",
    standard       = "coditary/pipeline:standard",
})
Workflow Kurzbeschreibung
build setup → compile → bundle → test (app)
quality lint, format, analyze → audit package → security verify
debug Debug-Setup, Compile, Bundle
coverage Coverage-Setup, Compile, Coverage-Tests, Coverage-Report
sanitize / tsan Sanitizer-Varianten
fuzzer_* Fuzz-Build und unterschiedliche Test-Intensitäten
all Vollständige CI-Pipeline (mehrere Build-Varianten + Quality + Verify)
clean setup[repo] — Artefakte löschen
standard Alle Standard-Phasen ohne Scope-Einschränkung (setup, generate, quality, …)

standard eignet sich, wenn alle registrierten Steps einer Phase laufen sollen — analog zu beez -p setup ohne Scope.


Neue und erweiterte Lua-APIs

beez.env_or(key, …, default)

Liest eine Umgebungsvariable; wenn nicht gesetzt, den Default.

local build_type = beez.env_or("BUILD_TYPE", "Release")
local scanner = beez.env_or("OSV_SCANNER", beez.env_or("HOME", "") .. "/.local/bin/osv-scanner")

Mehrere Keys werden der Reihe nach probiert (wie eine Fallback-Kette).

beez.shell.run(ctx, log_prefix, command [, options])

Führt einen Shell-Befehl synchron über den Step-Worker aus (spawn + wait). Ersetzt manuelle ctx:spawn/ctx:wait-Ketten für einfache Befehle.

local code = beez.shell.run(ctx, "[conan]", "conan install . -of build/conan")
if code ~= 0 then
    return code
end
Option Bedeutung
return_output = true Gibt ein Paar { exit_code, output } zurück (Lua: result.first, result.second)

Bei Fehler wird log_prefix und die Ausgabe auf stdout geschrieben (sofern nicht silent).

beez.char.quote(text)

Escapet einen String für die Verwendung in Shell-Befehlen (POSIX single-quote-Stil).

local cmd = "echo " .. beez.char.quote("it's fine")

beez.increment.run(ctx, config)

Lua-Implementierung für inkrementelle Tool-Läufe über viele Dateien (parallel, Success-Cache, Worker). Wird von cppcheck, clang-tidy, clang-format u. a. genutzt.

return beez.increment.run(ctx, cfg)

config enthält typischerweise patterns, build_command, cache_key, parallelism, output-Filter — siehe src/plugins/lua/api/increment/increment.lua.

beez.data.clone(table)

Tiefe Kopie einer Config-Tabelle — wichtig, wenn ctx.get_config() materialisierte Tabellen liefert, deren Overlay-Keys in pairs() sichtbar, aber per cfg.key nicht zugreifbar sind.


Empfohlene Plugin-Architektur

beez_plugin.lua     → plugin(), workflows{}, tasks{}
src/
  defaults.lua      → Konstanten, Glob-Muster, Revisions-Keys
  config.lua        → resolve(step_cfg): Merge + Normalisierung
  command.lua       → Shell-Befehle als Strings
  runner.lua        → Step-Logik, beez.shell.run / beez.increment.run

Checkliste für ein neues Plugin

  1. Verzeichnis plugins/<org>/<name>/1.0.0/ anlegen
  2. beez_plugin.lua mit plugin(), steps, optional config / workflows / tasks
  3. Standard-phase und scope aus den Tabellen oben wählen
  4. In build.lua: reqpack.beez + configure({ { "org/name", { ... } } })
  5. Workflows importieren oder lokal definieren
  6. input / output / cache_key / *_rev für Step-Cache setzen

Coditary-Referenz-Plugins

Plugin Schwerpunkt
cppcheck Plugin Config DSL mit profile_defs + finalize
clang-tidy Mehrere Profile (lint, analyze, security)
conan Dynamische Steps pro Build-Profil, profile = "code"
clang-build Compile/Link pro Profil (app, debug, coverage, …)
osv-audit beez.shell.run, Config-Resolve mit beez.data.clone
pipeline Nur Workflows, keine Steps

order() — mehrere Order-Paare

order() akzeptiert beliebig viele Step-Namen und registriert eine Kette:

order("step_a", "step_b", "step_c")
-- äquivalent zu: order("step_a","step_b") + order("step_b","step_c")

CLI-Bezug

Aktion Beispiel
Workflow aus Plugin beez all
Einzelner Plugin-Step beez -s coditary/cppcheck:cppcheck_check
Phase+Scope beez -p quality[lint]
Plugin-Task beez coditary/demo:format

Nächste Schritte

Clone this wiki locally