mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
coremodels: Automatically generate coremodel registries (#50057)
* coremodel: Generate static registry * Actually make codegen work Also, remove the per-coremodel assignability test from generator set. * Make wire gen depend on cue gen This is necessary now that we're generating a wire set as part of coremodel registry generation. * Add wire inject bits to http server * s/staticregistry/registry/ * move to static and dynamic wording * Move registry type into registry package * Use static registry in http handler * Oi comments
This commit is contained in:
@@ -103,11 +103,26 @@ func ExtractLineage(path string, lib thema.Library) (*ExtractedLineage, error) {
|
||||
return ec, nil
|
||||
}
|
||||
|
||||
// toTemplateObj extracts creates a struct with all the useful strings for template generation.
|
||||
func (ls *ExtractedLineage) toTemplateObj() tplVars {
|
||||
lin := ls.Lineage
|
||||
sch := thema.SchemaP(lin, thema.LatestVersion(lin))
|
||||
|
||||
return tplVars{
|
||||
Name: lin.Name(),
|
||||
LineagePath: ls.RelativePath,
|
||||
PkgPath: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", ls.RelativePath)),
|
||||
TitleName: strings.Title(lin.Name()), // nolint
|
||||
LatestSeqv: sch.Version()[0],
|
||||
LatestSchv: sch.Version()[1],
|
||||
}
|
||||
}
|
||||
|
||||
func isCanonical(name string) bool {
|
||||
return canonicalCoremodels[name]
|
||||
}
|
||||
|
||||
// FIXME specificying coremodel canonicality DOES NOT belong here - it should be part of the coremodel declaration.
|
||||
// FIXME specifying coremodel canonicality DOES NOT belong here - it should be part of the coremodel declaration.
|
||||
var canonicalCoremodels = map[string]bool{
|
||||
"dashboard": false,
|
||||
}
|
||||
@@ -153,12 +168,7 @@ func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error
|
||||
return nil, fmt.Errorf("openapi generation failed: %w", err)
|
||||
}
|
||||
|
||||
vars := goPkg{
|
||||
Name: lin.Name(),
|
||||
LineagePath: ls.RelativePath,
|
||||
LatestSeqv: sch.Version()[0],
|
||||
LatestSchv: sch.Version()[1],
|
||||
}
|
||||
vars := ls.toTemplateObj()
|
||||
var buuf bytes.Buffer
|
||||
err = tmplAddenda.Execute(&buuf, vars)
|
||||
if err != nil {
|
||||
@@ -184,23 +194,16 @@ func (ls *ExtractedLineage) GenerateGoCoremodel(path string) (WriteDiffer, error
|
||||
return nil, fmt.Errorf("goimports processing failed: %w", err)
|
||||
}
|
||||
|
||||
// Generate the assignability test. TODO do this in a framework test instead
|
||||
var buf3 bytes.Buffer
|
||||
err = tmplAssignableTest.Execute(&buf3, vars)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed generating assignability test file: %w", err)
|
||||
}
|
||||
|
||||
wd := NewWriteDiffer()
|
||||
wd[filepath.Join(path, "coremodel_gen.go")] = byt
|
||||
wd[filepath.Join(path, "coremodel_gen_test.go")] = buf3.Bytes()
|
||||
|
||||
return wd, nil
|
||||
}
|
||||
|
||||
type goPkg struct {
|
||||
type tplVars struct {
|
||||
Name string
|
||||
LineagePath string
|
||||
LineagePath, PkgPath string
|
||||
TitleName string
|
||||
LatestSeqv, LatestSchv uint
|
||||
IsComposed bool
|
||||
}
|
||||
@@ -274,9 +277,38 @@ func (m modelReplacer) replacePrefix(str string) string {
|
||||
return str
|
||||
}
|
||||
|
||||
// GenerateCoremodelRegistry produces Go files that define a static registry
|
||||
// with references to all the Go code that is expected to be generated from the
|
||||
// provided lineages.
|
||||
func GenerateCoremodelRegistry(path string, ecl []*ExtractedLineage) (WriteDiffer, error) {
|
||||
var cml []tplVars
|
||||
for _, ec := range ecl {
|
||||
cml = append(cml, ec.toTemplateObj())
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := tmplRegistry.Execute(&buf, struct {
|
||||
Coremodels []tplVars
|
||||
}{
|
||||
Coremodels: cml,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed generating template: %w", err)
|
||||
}
|
||||
|
||||
byt, err := imports.Process(path, buf.Bytes(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("goimports processing failed: %w", err)
|
||||
}
|
||||
|
||||
wd := NewWriteDiffer()
|
||||
wd[path] = byt
|
||||
return wd, nil
|
||||
}
|
||||
|
||||
var genHeader = `// This file is autogenerated. DO NOT EDIT.
|
||||
//
|
||||
// To regenerate, run "make gen-cue" from repository root.
|
||||
// Run "make gen-cue" from repository root to regenerate.
|
||||
//
|
||||
// Derived from the Thema lineage at %s
|
||||
|
||||
@@ -331,8 +363,10 @@ func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error)
|
||||
}
|
||||
|
||||
var _ thema.LineageFactory = Lineage
|
||||
var _ coremodel.Interface = &Coremodel{}
|
||||
|
||||
// Coremodel contains the foundational schema declaration for {{ .Name }}s.
|
||||
// It implements coremodel.Interface.
|
||||
type Coremodel struct {
|
||||
lin thema.Lineage
|
||||
}
|
||||
@@ -353,7 +387,12 @@ func (c *Coremodel) GoType() interface{} {
|
||||
return &Model{}
|
||||
}
|
||||
|
||||
func ProvideCoremodel(lib thema.Library) (*Coremodel, error) {
|
||||
// New returns a new instance of the {{ .Name }} coremodel.
|
||||
//
|
||||
// Note that this function does not cache, and initially loading a Thema lineage
|
||||
// can be expensive. As such, the Grafana backend should prefer to access this
|
||||
// coremodel through a registry (pkg/framework/coremodel/registry), which does cache.
|
||||
func New(lib thema.Library) (*Coremodel, error) {
|
||||
lin, err := Lineage(lib)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -365,30 +404,6 @@ func ProvideCoremodel(lib thema.Library) (*Coremodel, error) {
|
||||
}
|
||||
`))
|
||||
|
||||
var tmplAssignableTest = template.Must(template.New("addenda").Parse(fmt.Sprintf(genHeader, "{{ .LineagePath }}") + `package {{ .Name }}
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/cuectx"
|
||||
"github.com/grafana/thema"
|
||||
)
|
||||
|
||||
func TestSchemaAssignability(t *testing.T) {
|
||||
lin, err := Lineage(cuectx.ProvideThemaLibrary())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sch := thema.SchemaP(lin, currentVersion)
|
||||
|
||||
err = thema.AssignableTo(sch, &Model{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
`))
|
||||
|
||||
var tmplTypedef = `{{range .Types}}
|
||||
{{ with .Schema.Description }}{{ . }}{{ else }}// {{.TypeName}} defines model for {{.JsonName}}.{{ end }}
|
||||
//
|
||||
@@ -397,3 +412,103 @@ var tmplTypedef = `{{range .Types}}
|
||||
type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
var tmplRegistry = template.Must(template.New("registry").Parse(`
|
||||
// This file is autogenerated. DO NOT EDIT.
|
||||
//
|
||||
// Generated by pkg/framework/coremodel/gen.go
|
||||
// Run "make gen-cue" from repository root to regenerate.
|
||||
|
||||
package registry
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/wire"
|
||||
{{range .Coremodels }}
|
||||
"{{ .PkgPath }}"{{end}}
|
||||
"github.com/grafana/grafana/pkg/cuectx"
|
||||
"github.com/grafana/grafana/pkg/framework/coremodel"
|
||||
"github.com/grafana/thema"
|
||||
)
|
||||
|
||||
// CoremodelSet contains all of the wire-style providers related to coremodels.
|
||||
var CoremodelSet = wire.NewSet(
|
||||
ProvideStatic,
|
||||
ProvideGeneric,
|
||||
)
|
||||
|
||||
var (
|
||||
staticOnce sync.Once
|
||||
defaultStatic *Static
|
||||
defaultStaticErr error
|
||||
|
||||
genericOnce sync.Once
|
||||
defaultGeneric *Generic
|
||||
defaultGenericErr error
|
||||
)
|
||||
|
||||
// Static is a registry that provides access to individual coremodels via
|
||||
// explicit method calls, to aid with static analysis.
|
||||
type Static struct {
|
||||
{{- range .Coremodels }}
|
||||
{{ .Name }} *{{ .Name }}.Coremodel{{end}}
|
||||
}
|
||||
|
||||
// type guards
|
||||
var (
|
||||
{{- range .Coremodels }}
|
||||
_ coremodel.Interface = &{{ .Name }}.Coremodel{}{{end}}
|
||||
)
|
||||
|
||||
{{range .Coremodels }}
|
||||
// {{ .TitleName }} returns the {{ .Name }} coremodel. The return value is guaranteed to
|
||||
// implement coremodel.Interface.
|
||||
func (s *Static) {{ .TitleName }}() *{{ .Name }}.Coremodel {
|
||||
return s.{{ .Name }}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
func provideStatic(lib *thema.Library) (*Static, error) {
|
||||
if lib == nil {
|
||||
staticOnce.Do(func() {
|
||||
defaultStatic, defaultStaticErr = doProvideStatic(cuectx.ProvideThemaLibrary())
|
||||
})
|
||||
return defaultStatic, defaultStaticErr
|
||||
}
|
||||
|
||||
return doProvideStatic(*lib)
|
||||
}
|
||||
|
||||
func doProvideStatic(lib thema.Library) (*Static, error) {
|
||||
var err error
|
||||
reg := &Static{}
|
||||
|
||||
{{range .Coremodels }}
|
||||
reg.{{ .Name }}, err = {{ .Name }}.New(lib)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
{{end}}
|
||||
|
||||
return reg, nil
|
||||
}
|
||||
|
||||
func provideGeneric() (*Generic, error) {
|
||||
ereg, err := provideStatic(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
genericOnce.Do(func() {
|
||||
defaultGeneric, defaultGenericErr = doProvideGeneric(ereg)
|
||||
})
|
||||
return defaultGeneric, defaultGenericErr
|
||||
}
|
||||
|
||||
func doProvideGeneric(ereg *Static) (*Generic, error) {
|
||||
return NewRegistry({{ range .Coremodels }}
|
||||
ereg.{{ .TitleName }}(),{{ end }}
|
||||
)
|
||||
}
|
||||
`))
|
||||
|
Reference in New Issue
Block a user