diff --git a/Makefile b/Makefile index b734e3d..567dcaf 100644 --- a/Makefile +++ b/Makefile @@ -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) @@ -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/... diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 0d1f614..811293a 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -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) diff --git a/internal/codegen/codegen.go b/internal/codegen/codegen.go index fd33a79..0910a04 100644 --- a/internal/codegen/codegen.go +++ b/internal/codegen/codegen.go @@ -23,6 +23,7 @@ const ( type generator struct { class string + validateOnly bool methodFilter *MethodFilter logger log.Logger @@ -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, @@ -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 { diff --git a/internal/codegen/config.go b/internal/codegen/config.go index 0aaf838..1a5d65b 100644 --- a/internal/codegen/config.go +++ b/internal/codegen/config.go @@ -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 }