Skip to content

Commit

Permalink
Merge pull request #91 from saltosystems/feature/validate-generated-c…
Browse files Browse the repository at this point in the history
…ode-matches-generator

sanity-check: validate generated code was not modified
  • Loading branch information
jagobagascon authored Apr 24, 2024
2 parents 03d032b + f0c662c commit c45669f
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 43 deletions.
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ include .go-builder/Makefile
prepare: $(prepare_targets)

.PHONY: sanity-check
sanity-check: $(sanity_check_targets)
sanity-check: $(sanity_check_targets) check-generated

.PHONY: build
build: $(build_targets)
Expand All @@ -28,6 +28,11 @@ gen-files:
rm -rf $(CURDIR)/windows
go generate github.com/saltosystems/winrt-go/...

.PHONY: check-generated
check-generated: export WINRT_GO_GEN_VALIDATE=1
check-generated:
go generate github.com/saltosystems/winrt-go/...

.PHONY: go-test
go-test:
go test github.com/saltosystems/winrt-go/...
1 change: 1 addition & 0 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func NewGenerateCommand(logger log.Logger) *subcommands.Command {
cfg := codegen.NewConfig()
fs := flag.NewFlagSet("winrt-go-gen", flag.ExitOnError)
_ = fs.String("config", "", "config file (optional)")
fs.BoolVar(&cfg.ValidateOnly, "validate", cfg.ValidateOnly, "validate the existing code instead of generating it")
fs.StringVar(&cfg.Class, "class", cfg.Class, "The class to generate. This should include the namespace and the class name, e.g. 'System.Runtime.InteropServices.WindowsRuntime.EventRegistrationToken'.")
fs.Func("method-filter", methodFilterUsage, func(m string) error {
cfg.AddMethodFilter(m)
Expand Down
110 changes: 69 additions & 41 deletions internal/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (

type generator struct {
class string
validateOnly bool
methodFilter *MethodFilter

logger log.Logger
Expand All @@ -45,6 +46,7 @@ func Generate(cfg *Config, logger log.Logger) error {

g := &generator{
class: cfg.Class,
validateOnly: cfg.ValidateOnly,
methodFilter: cfg.MethodFilter(),
logger: logger,
mdStore: mdStore,
Expand All @@ -71,63 +73,89 @@ func (g *generator) generate(typeDef *winmd.TypeDef) error {
return fmt.Errorf("%s.%s is not a WinRT class", typeDef.TypeNamespace, typeDef.TypeName)
}

// get data & execute templates
if err := g.loadCodeGenData(typeDef); err != nil {
return err
}

for _, fData := range g.genDataFiles {
if err := g.generateDataFile(fData, typeDef); err != nil {
return err
}
}
return nil
}

func (g *generator) generateDataFile(fData *genDataFile, typeDef *winmd.TypeDef) error {
// get templates
tmpl, err := getTemplates()
if err != nil {
return err
}

// get data & execute templates
if err := g.loadCodeGenData(typeDef); err != nil {
fData.Data.ComputeImports(typeDef)

var buf bytes.Buffer
if err := tmpl.ExecuteTemplate(&buf, "file.tmpl", fData.Data); err != nil {
return err
}

for _, fData := range g.genDataFiles {
fData.Data.ComputeImports(typeDef)
// use go imports to cleanup imports
goimported, err := imports.Process(fData.Filename, buf.Bytes(), nil)
if err != nil {
return err
}

var buf bytes.Buffer
if err := tmpl.ExecuteTemplate(&buf, "file.tmpl", fData.Data); err != nil {
return err
}
// format the output source code
formatted, err := format.Source(goimported)
if err != nil {
return err
}

// create file & write contents
filename := fData.Filename
parts := strings.Split(fData.Filename, "/")
folder := strings.Join(parts[:len(parts)-1], "/")
err = os.MkdirAll(folder, os.ModePerm)
if err != nil {
return err
}
file, err := os.Create(filepath.Clean(filename))
if err != nil {
return err
}
defer func() { _ = file.Close() }()
if g.validateOnly {
// validate existing file content
return g.validateFileContent(fData, formatted)
}

// use go imports to cleanup imports
goimported, err := imports.Process(filename, buf.Bytes(), nil)
if err != nil {
// write unimported source code to file as a debugging mechanism
_, _ = file.Write(buf.Bytes())
return err
}
// create file & write contents
return g.writeFile(fData, formatted)
}

// format the output source code
formatted, err := format.Source(goimported)
if err != nil {
// write unformatted source code to file as a debugging mechanism
_, _ = file.Write(goimported)
return err
}
func (g *generator) validateFileContent(fData *genDataFile, genContent []byte) error {
// validate existing content
existingContent, err := os.ReadFile(fData.Filename)
if err != nil {
return err
}

// and write it to file
_, err = file.Write(formatted)
if err != nil {
return err
}
// compare existing content to generated
_ = level.Debug(g.logger).Log("msg", "validating generated code", "filename", fData.Filename)
if string(existingContent) != string(genContent) {
return fmt.Errorf("file %s does not contain expected content", fData.Filename)
}
return nil
}

func (g *generator) writeFile(fData *genDataFile, content []byte) error {
parts := strings.Split(fData.Filename, "/")
folder := strings.Join(parts[:len(parts)-1], "/")
err := os.MkdirAll(folder, os.ModePerm)
if err != nil {
return err
}
file, err := os.Create(filepath.Clean(fData.Filename))
if err != nil {
return err
}
defer func() { _ = file.Close() }()

return err
// and write it to file
_, err = file.Write(content)
if err != nil {
return err
}

return nil
}

func (g *generator) loadCodeGenData(typeDef *winmd.TypeDef) error {
Expand Down
5 changes: 4 additions & 1 deletion internal/codegen/config.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package codegen

import "fmt"
import (
"fmt"
)

// Config is the configuration for the code generation.
type Config struct {
Debug bool
Class string
ValidateOnly bool
methodFilters []string
}

Expand Down

0 comments on commit c45669f

Please sign in to comment.