mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 04:59:15 -06:00
be06d37a20
* Add go code generator for coremodels * Just generate the entire coremodel for now Maybe we'll need more flexibility as more coremodels are added, but for now this is fine. * Add note on type comment about stability, grodkit * Remove local replace directive for thema * Generate typescript from coremodel * Update pkg/coremodel/dashboard/addenda.go Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * Update cuetsy to new release * Update thema to latest * Fix enum generation for FieldColorModeId * Put main generated object at the end of the file * Tweaks to generated Go output * Retweak back to var * Add generated coremodel test * Remove local replace statement again * Add Make target and call into cuetsy cmd from gen * Rename and comment linsrc for readability * Move key codegen bits into reusable package * Move body of cuetsifier into codegen pkg Also genericize the diffing output into reusable WriteDiffer. * Refactor coremodel generator to use WriteDiffer * Add gen-cue step to CI * Whip all the codegen automation into shape * Add simplistic coremodel canonicality controls * Remove erroneously committed test * Bump thema version * Remove dead code * Improve wording of non-canonicality comment Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
135 lines
3.6 KiB
Go
135 lines
3.6 KiB
Go
package codegen
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/go-multierror"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
// WriteDiffer is a pseudo-filesystem that supports batch-writing its contents
|
|
// to the real filesystem, or batch-comparing its contents to the real
|
|
// filesystem. Its intended use is for idiomatic `go generate`-style code
|
|
// generators, where it is expected that the results of codegen are committed to
|
|
// version control.
|
|
//
|
|
// In such cases, the normal behavior of a generator is to write files to disk,
|
|
// but in CI, that behavior should change to verify that what is already on disk
|
|
// is identical to the results of code generation. This allows CI to ensure that
|
|
// the results of code generation are always up to date. WriteDiffer supports
|
|
// these related behaviors through its Write() and Verify() methods, respectively.
|
|
//
|
|
// Note that the statelessness of WriteDiffer means that, if a particular input
|
|
// to the code generator goes away, it will not notice generated files left
|
|
// behind if their inputs are removed.
|
|
// TODO introduce a search/match system
|
|
type WriteDiffer map[string][]byte
|
|
|
|
func NewWriteDiffer() WriteDiffer {
|
|
return WriteDiffer(make(map[string][]byte))
|
|
}
|
|
|
|
type writeSlice []struct {
|
|
path string
|
|
contents []byte
|
|
}
|
|
|
|
// Verify checks the contents of each file against the filesystem. It emits an error
|
|
// if any of its contained files differ.
|
|
func (wd WriteDiffer) Verify() error {
|
|
var result error
|
|
|
|
for _, item := range wd.toSlice() {
|
|
if _, err := os.Stat(item.path); err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
result = multierror.Append(result, fmt.Errorf("%s: generated file should exist, but does not", item.path))
|
|
} else {
|
|
result = multierror.Append(result, fmt.Errorf("%s: could not stat generated file: %w", item.path, err))
|
|
}
|
|
continue
|
|
}
|
|
|
|
f, err := os.Open(filepath.Clean(item.path))
|
|
if err != nil {
|
|
result = multierror.Append(result, fmt.Errorf("%s: %w", item.path, err))
|
|
continue
|
|
}
|
|
|
|
ob, err := io.ReadAll(f)
|
|
if err != nil {
|
|
result = multierror.Append(result, fmt.Errorf("%s: %w", item.path, err))
|
|
continue
|
|
}
|
|
dstr := cmp.Diff(string(ob), string(item.contents))
|
|
if dstr != "" {
|
|
result = multierror.Append(result, fmt.Errorf("%s would have changed:\n\n%s", item.path, dstr))
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Write writes all of the files to their indicated paths.
|
|
func (wd WriteDiffer) Write() error {
|
|
g, _ := errgroup.WithContext(context.TODO())
|
|
g.SetLimit(12)
|
|
|
|
for _, item := range wd.toSlice() {
|
|
it := item
|
|
g.Go(func() error {
|
|
err := os.MkdirAll(filepath.Dir(it.path), os.ModePerm)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: failed to ensure parent directory exists: %w", it.path, err)
|
|
}
|
|
|
|
if err := os.WriteFile(it.path, it.contents, 0644); err != nil {
|
|
return fmt.Errorf("%s: error while writing file: %w", it.path, err)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
return g.Wait()
|
|
}
|
|
|
|
func (wd WriteDiffer) toSlice() writeSlice {
|
|
sl := make(writeSlice, 0, len(wd))
|
|
type ws struct {
|
|
path string
|
|
contents []byte
|
|
}
|
|
|
|
for k, v := range wd {
|
|
sl = append(sl, ws{
|
|
path: k,
|
|
contents: v,
|
|
})
|
|
}
|
|
|
|
sort.Slice(sl, func(i, j int) bool {
|
|
return sl[i].path < sl[j].path
|
|
})
|
|
|
|
return sl
|
|
}
|
|
|
|
// Merge combines all the entries from the provided WriteDiffer into the callee
|
|
// WriteDiffer. Duplicate paths result in an error.
|
|
func (wd WriteDiffer) Merge(wd2 WriteDiffer) error {
|
|
for k, v := range wd2 {
|
|
if _, has := wd[k]; has {
|
|
return fmt.Errorf("path %s already exists in write differ", k)
|
|
}
|
|
wd[k] = v
|
|
}
|
|
|
|
return nil
|
|
}
|