Skip to content

Commit

Permalink
Replace "sandbox" main files by a unified CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
K-Phoen committed Sep 13, 2023
1 parent c23ea0f commit 02891ef
Show file tree
Hide file tree
Showing 14 changed files with 345 additions and 391 deletions.
File renamed without changes.
117 changes: 117 additions & 0 deletions cmd/cli/generate/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package generate

import (
"context"
"fmt"
"strings"

"github.com/grafana/codejen"
"github.com/grafana/cog/internal/ast"
"github.com/grafana/cog/internal/jennies"
"github.com/spf13/cobra"
)

type options struct {
outputDir string
entrypoints []string
schemasType string

// Cue-specific options
cueImports []string
}

func (opts options) cueIncludeImports() ([]cueIncludeImport, error) {
if len(opts.cueImports) == 0 {
return nil, nil
}

imports := make([]cueIncludeImport, len(opts.cueImports))
for i, importDefinition := range opts.cueImports {
parts := strings.Split(importDefinition, ":")
if len(parts) != 2 {
return nil, fmt.Errorf("'%s' is not a valid import definition", importDefinition)
}

imports[i].fsPath = parts[0]
imports[i].importPath = parts[1]
}

return imports, nil
}

func Command() *cobra.Command {
opts := options{}

cmd := &cobra.Command{
Use: "generate",
Short: "Generates code from schemas.", // TODO: better descriptions
Long: `Generates code from schemas.`,
RunE: func(cmd *cobra.Command, args []string) error {
return doGenerate(opts)
},
}

cmd.Flags().StringVarP(&opts.schemasType, "loader", "l", "cue", "Schemas type.") // TODO: better usage text
cmd.Flags().StringVarP(&opts.outputDir, "output", "o", "generated", "Output directory.") // TODO: better usage text
cmd.Flags().StringArrayVarP(&opts.entrypoints, "input", "i", nil, "Schema.") // TODO: better usage text
cmd.Flags().StringArrayVarP(&opts.cueImports, "include-cue-import", "I", nil, "Specify an additional library import directory. Format: [path]:[import]. Example: '../grafana/common-library:github.com/grafana/grafana/packages/grafana-schema/src/common")

_ = cmd.MarkFlagRequired("input")
_ = cmd.MarkFlagDirname("input")
_ = cmd.MarkFlagDirname("output")

return cmd
}

func doGenerate(opts options) error {
loaders := map[string]func(opts options) ([]*ast.File, error){
"cue": cueLoader,
"kindsys-core": kindsysCoreLoader,
"kindsys-custom": kindsysCustomLoader,
"jsonschema": jsonschemaLoader,
}
loader, ok := loaders[opts.schemasType]
if !ok {
return fmt.Errorf("no loader found for '%s'", opts.schemasType)
}

schemas, err := loader(opts)
if err != nil {
return err
}

// Here begins the code generation setup
targetsByLanguage := jennies.All()
rootCodeJenFS := codejen.NewFS()

for language, target := range targetsByLanguage {
fmt.Printf("Running '%s' jennies...\n", language)

var err error
processedAsts := schemas

for _, compilerPass := range target.CompilerPasses {
processedAsts, err = compilerPass.Process(processedAsts)
if err != nil {
return err
}
}

fs, err := target.Jennies.GenerateFS(processedAsts)
if err != nil {
return err
}

err = rootCodeJenFS.Merge(fs)
if err != nil {
return err
}
}

err = rootCodeJenFS.Write(context.Background(), opts.outputDir)
if err != nil {
return err
}

return nil
}
97 changes: 26 additions & 71 deletions sandbox/codegen-cue/main.go → cmd/cli/generate/cue.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main
package generate

import (
"context"
"fmt"
"io"
"io/fs"
Expand All @@ -11,30 +10,24 @@ import (

"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/load"
"github.com/grafana/codejen"
"github.com/grafana/cog/internal/ast"
"github.com/grafana/cog/internal/jennies"
"github.com/grafana/cog/internal/simplecue"
"github.com/yalue/merged_fs"
)

func main() {
entrypoints := []string{
"./schemas/cue/core/dashboard/",
// "./schemas/cue/core/playlist/",

"./schemas/cue/composable/timeseries/",

"github.com/grafana/grafana/packages/grafana-schema/src/common",
}
type cueIncludeImport struct {
fsPath string // path of the library on the filesystem
importPath string // path used in CUE files to import that library
}

cueFsOverlay, err := buildCueOverlay()
func cueLoader(opts options) ([]*ast.File, error) {
cueFsOverlay, err := buildCueOverlay(opts)
if err != nil {
panic(err)
return nil, err
}

allSchemas := make([]*ast.File, 0, len(entrypoints))
for _, entrypoint := range entrypoints {
allSchemas := make([]*ast.File, 0, len(opts.entrypoints))
for _, entrypoint := range opts.entrypoints {
pkg := filepath.Base(entrypoint)

// Load Cue files into Cue build.Instances slice
Expand All @@ -47,90 +40,52 @@ func main() {

values, err := cuecontext.New().BuildInstances(bis)
if err != nil {
panic(err)
return nil, err
}

schemaAst, err := simplecue.GenerateAST(values[0], simplecue.Config{
Package: pkg, // TODO: extract from input schema/?
})
if err != nil {
panic(err)
return nil, err
}

allSchemas = append(allSchemas, schemaAst)
}

// Here begins the code generation setup
targetsByLanguage := jennies.All()
rootCodeJenFS := codejen.NewFS()

for language, target := range targetsByLanguage {
fmt.Printf("Running '%s' jennies...\n", language)

var err error
processedAsts := allSchemas

for _, compilerPass := range target.CompilerPasses {
processedAsts, err = compilerPass.Process(processedAsts)
if err != nil {
panic(err)
}
}

fs, err := target.Jennies.GenerateFS(processedAsts)
if err != nil {
panic(err)
}

err = rootCodeJenFS.Merge(fs)
if err != nil {
panic(err)
}
}

err = rootCodeJenFS.Write(context.Background(), "generated")
if err != nil {
panic(err)
}
return allSchemas, nil
}

func buildCueOverlay() (map[string]load.Source, error) {
libFs, err := buildBaseFSWithLibraries()
func buildCueOverlay(opts options) (map[string]load.Source, error) {
libFs, err := buildBaseFSWithLibraries(opts)
if err != nil {
return nil, err
}

overlay := make(map[string]load.Source)
if err := ToCueOverlay("/", libFs, overlay); err != nil {
if err := toCueOverlay("/", libFs, overlay); err != nil {
return nil, err
}

return overlay, nil
}

func buildBaseFSWithLibraries() (fs.FS, error) {
// TODO: these should be received as inputs/arguments/parameters
importDefinitions := [][2]string{
{
"github.com/grafana/grafana/packages/grafana-schema/src/common",
"../kind-registry/grafana/next/common",
},
{
"github.com/grafana/cog",
".",
},
func buildBaseFSWithLibraries(opts options) (fs.FS, error) {
importDefinitions, err := opts.cueIncludeImports()
if err != nil {
return nil, err
}

var librariesFS []fs.FS
for _, importDefinition := range importDefinitions {
absPath, err := filepath.Abs(importDefinition[1])
absPath, err := filepath.Abs(importDefinition.fsPath)
if err != nil {
return nil, err
}

fmt.Printf("Loading '%s' module from '%s'\n", importDefinition[0], absPath)
fmt.Printf("Loading '%s' module from '%s'\n", importDefinition.importPath, absPath)

libraryFS, err := dirToPrefixedFS(absPath, "cue.mod/pkg/"+importDefinition[0])
libraryFS, err := dirToPrefixedFS(absPath, "cue.mod/pkg/"+importDefinition.importPath)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -164,8 +119,8 @@ func dirToPrefixedFS(directory string, prefix string) (fs.FS, error) {
return commonFS, nil
}

// ToOverlay converts an fs.FS into a CUE loader overlay.
func ToCueOverlay(prefix string, vfs fs.FS, overlay map[string]load.Source) error {
// ToOverlay converts a fs.FS into a CUE loader overlay.
func toCueOverlay(prefix string, vfs fs.FS, overlay map[string]load.Source) error {
// TODO why not just stick the prefix on automatically...?
if !filepath.IsAbs(prefix) {
return fmt.Errorf("must provide absolute path prefix when generating cue overlay, got %q", prefix)
Expand All @@ -183,7 +138,7 @@ func ToCueOverlay(prefix string, vfs fs.FS, overlay map[string]load.Source) erro
if err != nil {
return err
}
defer f.Close() // nolint: errcheck
defer func() { _ = f.Close() }()

b, err := io.ReadAll(f)
if err != nil {
Expand Down
32 changes: 32 additions & 0 deletions cmd/cli/generate/jsonschema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package generate

import (
"os"
"path/filepath"

"github.com/grafana/cog/internal/ast"
"github.com/grafana/cog/internal/jsonschema"
)

func jsonschemaLoader(opts options) ([]*ast.File, error) {
allSchemas := make([]*ast.File, 0, len(opts.entrypoints))
for _, entrypoint := range opts.entrypoints {
pkg := filepath.Base(filepath.Dir(entrypoint))

reader, err := os.Open(entrypoint)
if err != nil {
return nil, err
}

schemaAst, err := jsonschema.GenerateAST(reader, jsonschema.Config{
Package: pkg, // TODO: extract from input schema/folder?
})
if err != nil {
return nil, err
}

allSchemas = append(allSchemas, schemaAst)
}

return allSchemas, nil
}
61 changes: 61 additions & 0 deletions cmd/cli/generate/kindsyscore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package generate

import (
"fmt"
"path/filepath"

"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"github.com/grafana/cog/internal/ast"
"github.com/grafana/cog/internal/simplecue"
"github.com/grafana/kindsys"
"github.com/grafana/thema"
)

func kindsysCoreLoader(opts options) ([]*ast.File, error) {
themaRuntime := thema.NewRuntime(cuecontext.New())

allSchemas := make([]*ast.File, 0, len(opts.entrypoints))
for _, entrypoint := range opts.entrypoints {
pkg := filepath.Base(entrypoint)

overlayFS, err := dirToPrefixedFS(entrypoint, "")
if err != nil {
return nil, err
}

cueInstance, err := kindsys.BuildInstance(themaRuntime.Context(), ".", "kind", overlayFS)
if err != nil {
return nil, fmt.Errorf("could not load kindsys instance: %w", err)
}

props, err := kindsys.ToKindProps[kindsys.CoreProperties](cueInstance)
if err != nil {
return nil, fmt.Errorf("could not convert cue value to kindsys props: %w", err)
}

kindDefinition := kindsys.Def[kindsys.CoreProperties]{
V: cueInstance,
Properties: props,
}

boundKind, err := kindsys.BindCore(themaRuntime, kindDefinition)
if err != nil {
return nil, fmt.Errorf("could not bind kind definition to kind: %w", err)
}

rawLatestSchemaAsCue := boundKind.Lineage().Latest().Underlying()
latestSchemaAsCue := rawLatestSchemaAsCue.LookupPath(cue.MakePath(cue.Hid("_#schema", "github.com/grafana/thema")))

schemaAst, err := simplecue.GenerateAST(latestSchemaAsCue, simplecue.Config{
Package: pkg, // TODO: extract from input schema/folder?
})
if err != nil {
return nil, err
}

allSchemas = append(allSchemas, schemaAst)
}

return allSchemas, nil
}
Loading

0 comments on commit 02891ef

Please sign in to comment.