Schema: Refactor plugin code generation (#58901)

* wip

* wip

* almost there..

* wip - change so it can run.

* treelist is working.

* support CODEGEN_VERIFY env variable

* use log.fatal

* comment out old PluginTreeList code generation

* cleanup

* rename corelist package files

* fix makefile

* move pkg/codegen/pluggen.go to pkg/plugins/codegen

* copy and refactor files to pkg/plugins/codegen

* use pkg/plugins/codegen instead of pkg/codegen for core plugins code gen

* remove unneeded files

* remove unused code to resolve linting errors

* adapters first hack

* added flattener

* add back ignore build tags to go generate file

* cleaned up the code a bit.

* seems to work, needs to do some refactoring of the GoTypesJenns and TSTypesJenny.

* one more step, going to get upstream changes in this branch.

* working but need to run import tmpl in jenny_schemapath to have the proper imports.

* added header to generated files.

* added missing jenny.

* preventing plugins with multiple decls/schemas to insert multiple lines in corelist.

* fixed so we use Slot type from kindsys to detect if its group.

* adding a go jenny that only runs if the plugin has a backend.

* added version object to generated ts.

* generating the ts types with the same output as prior to this refactoring.

* removed code that is replaced by the jenny pattern.

* removed the go code that isn't used anymore.

* removed some more unused code and renamed pluggen to util_ts

* fixed linting issue.

* removed unused vars.

* use a jenny list postprocessor for header injection

* moved decl and decl_parser to pfs.

* removed the pre-pended header in the gotypes jenny since it is done in the postprocess.

* moved decl to pfs.

* removed unused template.

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
This commit is contained in:
Marcus Andersson
2022-12-02 08:22:28 +01:00
committed by GitHub
parent 0c560b8b0d
commit 7f92f1df00
28 changed files with 644 additions and 646 deletions

View File

@@ -1,18 +1,22 @@
//go:build ignore
// +build ignore
//go:generate go run gen.go
package main
import (
"errors"
"context"
"fmt"
"log"
"os"
"path/filepath"
"sort"
"strings"
"github.com/grafana/grafana/pkg/codegen"
"github.com/grafana/codejen"
corecodegen "github.com/grafana/grafana/pkg/codegen"
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/grafana/pkg/plugins/codegen"
"github.com/grafana/grafana/pkg/plugins/pfs"
)
@@ -33,106 +37,55 @@ const sep = string(filepath.Separator)
func main() {
if len(os.Args) > 1 {
fmt.Fprintf(os.Stderr, "plugin thema code generator does not currently accept any arguments\n, got %q", os.Args)
os.Exit(1)
log.Fatal(fmt.Errorf("plugin thema code generator does not currently accept any arguments\n, got %q", os.Args))
}
cwd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "could not get working directory: %s", err)
os.Exit(1)
log.Fatal(fmt.Errorf("could not get working directory: %s", err))
}
grootp := strings.Split(cwd, sep)
groot := filepath.Join(sep, filepath.Join(grootp[:len(grootp)-3]...))
rt := cuectx.GrafanaThemaRuntime()
wd := codegen.NewWriteDiffer()
lib := cuectx.GrafanaThemaRuntime()
type ptreepath struct {
Path string
Tree *codegen.PluginTree
}
var ptrees []codegen.TreeAndPath
for _, typ := range []string{"datasource", "panel"} {
dir := filepath.Join(cwd, typ)
treeor, err := codegen.ExtractPluginTrees(os.DirFS(dir), lib)
if err != nil {
fmt.Fprintf(os.Stderr, "extracting plugin trees failed for %s: %s\n", dir, err)
os.Exit(1)
}
for name, option := range treeor {
if skipPlugins[name] {
continue
}
if option.Tree != nil {
ptrees = append(ptrees, codegen.TreeAndPath{
Path: filepath.Join(typ, name),
Tree: option.Tree,
})
} else if !errors.Is(option.Err, pfs.ErrNoRootFile) {
fmt.Fprintf(os.Stderr, "error parsing plugin directory %s: %s\n", filepath.Join(dir, name), option.Err)
os.Exit(1)
}
}
}
// Ensure ptrees are sorted, so that visit order is deterministic. Otherwise
// having multiple core plugins with errors can cause confusing error
// flip-flopping
sort.Slice(ptrees, func(i, j int) bool {
return ptrees[i].Path < ptrees[j].Path
pluginKindGen := codejen.JennyListWithNamer(func(d *pfs.PluginDecl) string {
return d.PluginMeta.Id
})
var wdm codegen.WriteDiffer
for _, ptp := range ptrees {
tfast, err := ptp.Tree.GenerateTypeScriptAST()
if err != nil {
fmt.Fprintf(os.Stderr, "generating typescript failed for %s: %s\n", ptp.Path, err)
os.Exit(1)
}
// nil return if there was nothing to generate (no slot implementations)
if tfast != nil {
wd[filepath.Join(ptp.Path, "models.gen.ts")] = []byte(tfast.String())
}
pluginKindGen.Append(
codegen.PluginTreeListJenny(),
codegen.PluginGoTypesJenny("pkg/tsdb", adaptToPipeline(corecodegen.GoTypesJenny{})),
codegen.PluginTSTypesJenny("public/app/plugins", adaptToPipeline(corecodegen.TSTypesJenny{})),
)
relp, _ := filepath.Rel(groot, ptp.Path)
wdm, err = ptp.Tree.GenerateGo(ptp.Path, codegen.GoGenConfig{
Types: isDatasource(ptp.Tree),
// TODO false until we decide on a consistent codegen format for core and external plugins
ThemaBindings: false,
DocPathPrefix: relp,
})
if err != nil {
fmt.Fprintf(os.Stderr, "generating Go failed for %s: %s\n", ptp.Path, err)
os.Exit(1)
}
wd.Merge(wdm)
}
pluginKindGen.AddPostprocessors(corecodegen.SlashHeaderMapper("public/app/plugins/gen.go"))
wdm, err = codegen.GenPluginTreeList(ptrees, "github.com/grafana/grafana/public/app/plugins", filepath.Join(groot, "pkg", "plugins", "pfs", "corelist", "loadlist_gen.go"), false)
declParser := pfs.NewDeclParser(rt, skipPlugins)
decls, err := declParser.Parse(os.DirFS(cwd))
if err != nil {
fmt.Fprintf(os.Stderr, "generating plugin loader registry failed: %s\n", err)
os.Exit(1)
log.Fatalln(fmt.Errorf("parsing plugins in dir failed %s: %s", cwd, err))
}
jfs, err := pluginKindGen.GenerateFS(decls...)
if err != nil {
log.Fatalln(fmt.Errorf("error writing files to disk: %s", err))
}
wd.Merge(wdm)
if _, set := os.LookupEnv("CODEGEN_VERIFY"); set {
err = wd.Verify()
if err != nil {
fmt.Fprintf(os.Stderr, "generated code is out of sync with inputs:\n%s\nrun `make gen-cue` to regenerate\n\n", err)
os.Exit(1)
}
} else {
err = wd.Write()
if err != nil {
fmt.Fprintf(os.Stderr, "error while writing generated code to disk:\n%s\n", err)
os.Exit(1)
if err = jfs.Verify(context.Background(), groot); err != nil {
log.Fatal(fmt.Errorf("generated code is out of sync with inputs:\n%s\nrun `make gen-cue` to regenerate", err))
}
} else if err = jfs.Write(context.Background(), groot); err != nil {
log.Fatal(fmt.Errorf("error while writing generated code to disk:\n%s", err))
}
}
func isDatasource(pt *codegen.PluginTree) bool {
return string((*pfs.Tree)(pt).RootPlugin().Meta().Type) == "datasource"
func adaptToPipeline(j codejen.OneToOne[corecodegen.SchemaForGen]) codejen.OneToOne[*pfs.PluginDecl] {
return codejen.AdaptOneToOne(j, func(pd *pfs.PluginDecl) corecodegen.SchemaForGen {
return corecodegen.SchemaForGen{
Name: pd.PluginMeta.Name,
Schema: pd.Lineage.Latest(),
IsGroup: pd.Slot.IsGroup(),
}
})
}