diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 9180358ab68..8c486bb9b9d 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -129,6 +129,10 @@ jobs: run: | make gogenerate git diff --exit-code || (echo 'Generated code is out of date, please run "make gogenerate" and commit the changes in this PR.' && exit 1) + - name: check-generate-fmt + run: | + make check-generate-fmt + git checkout . - name: Generate proto files run: | make genproto diff --git a/Makefile b/Makefile index 3d02f2b9839..7067fe98af6 100644 --- a/Makefile +++ b/Makefile @@ -107,6 +107,22 @@ gogenerate: @$(MAKE) for-all-target TARGET="generate" $(MAKE) fmt +.PHONY: check-generate-fmt +check-generate-fmt: + cd cmd/mdatagen && $(GOCMD) install . + @TMP_DIR=$$(mktemp -d); \ + echo '#!/bin/sh' > $$TMP_DIR/mdatagen; \ + echo 'exec "$$(go env GOPATH)/bin/mdatagen" --noformat "$$@"' >> $$TMP_DIR/mdatagen; \ + chmod +x $$TMP_DIR/mdatagen; \ + PATH="$$TMP_DIR:$$PATH" $(MAKE) for-all-target TARGET="generate"; \ + rm -rf $$TMP_DIR + @UNFORMATTED=$$(gofmt -l .); \ + if [ -n "$$UNFORMATTED" ]; then \ + echo "The following generated files are not gofmt-formatted (fix the template):"; \ + echo "$$UNFORMATTED"; \ + exit 1; \ + fi + .PHONY: govulncheck govulncheck: @$(MAKE) for-all-target TARGET="vulncheck" diff --git a/cmd/mdatagen/internal/command.go b/cmd/mdatagen/internal/command.go index 3e2d45fd259..55803bfab83 100644 --- a/cmd/mdatagen/internal/command.go +++ b/cmd/mdatagen/internal/command.go @@ -58,19 +58,21 @@ func NewCommand() (*cobra.Command, error) { if err != nil { return nil, err } + var noFormat bool rootCmd := &cobra.Command{ Use: "mdatagen", Version: ver, SilenceUsage: true, Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, args []string) error { - return run(args[0]) + return run(args[0], noFormat) }, } + rootCmd.Flags().BoolVar(&noFormat, "noformat", noFormat, "skip gofmt formatting of generated Go files") return rootCmd, nil } -func run(ymlPath string) error { +func run(ymlPath string, noformat bool) error { if ymlPath == "" { return errors.New("argument must be metadata.yaml file") } @@ -104,7 +106,7 @@ func run(ymlPath string) error { if !slices.Contains(nonComponents, md.Status.Class) { toGenerate[filepath.Join(tmplDir, "status.go.tmpl")] = filepath.Join(codeDir, "generated_status.go") err = generateFile(filepath.Join(tmplDir, "component_test.go.tmpl"), - filepath.Join(ymlDir, "generated_component_test.go"), md, packageName) + filepath.Join(ymlDir, "generated_component_test.go"), md, packageName, noformat) if err != nil { return err } @@ -124,7 +126,7 @@ func run(ymlPath string) error { } err = generateFile(filepath.Join(tmplDir, "package_test.go.tmpl"), - filepath.Join(ymlDir, "generated_package_test.go"), md, packageName) + filepath.Join(ymlDir, "generated_package_test.go"), md, packageName, noformat) if err != nil { return err } @@ -225,12 +227,12 @@ func run(ymlPath string) error { } for tmpl, dst := range toGenerate { - if err := generateFile(tmpl, dst, md, md.GeneratedPackageName); err != nil { + if err := generateFile(tmpl, dst, md, md.GeneratedPackageName, noformat); err != nil { return err } } - if err := generateConfigFiles(md, ymlDir); err != nil { + if err := generateConfigFiles(md, ymlDir, noformat); err != nil { return fmt.Errorf("failed to generate config files: %w", err) } @@ -416,8 +418,8 @@ func executeTemplate(tmplFile string, md Metadata, goPackage string, fns templat return buf.Bytes(), nil } -func generateFile(tmplFile, outputFile string, md Metadata, goPackage string) error { - return generateFileWithFns(tmplFile, outputFile, md, goPackage, getTemplateFuncMap(md)) +func generateFile(tmplFile, outputFile string, md Metadata, goPackage string, noformat bool) error { + return generateFileWithFns(tmplFile, outputFile, md, goPackage, getTemplateFuncMap(md), noformat) } func inlineReplace(tmplFile, outputFile string, md Metadata, start, end, goPackage string) error { @@ -449,7 +451,7 @@ func inlineReplace(tmplFile, outputFile string, md Metadata, start, end, goPacka return nil } -func generateFileWithFns(tmplFile, outputFile string, md Metadata, goPackage string, fns template.FuncMap) error { +func generateFileWithFns(tmplFile, outputFile string, md Metadata, goPackage string, fns template.FuncMap, noformat bool) error { if err := os.Remove(outputFile); err != nil && !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("unable to remove generated file %q: %w", outputFile, err) } @@ -459,7 +461,7 @@ func generateFileWithFns(tmplFile, outputFile string, md Metadata, goPackage str return err } var formatErr error - if strings.HasSuffix(outputFile, ".go") { + if strings.HasSuffix(outputFile, ".go") && !noformat { if formatted, err := format.Source(result); err == nil { result = formatted } else { @@ -534,7 +536,7 @@ func validateYAMLKeyOrder(raw []byte) error { return nil } -func generateConfigFiles(md Metadata, mdDir string) error { +func generateConfigFiles(md Metadata, mdDir string, noformat bool) error { if md.Config != nil { resolver := cfggen.NewResolver(md.PackageName, md.Status.Class, md.Type, mdDir) resolvedSchema, err := resolver.ResolveSchema(md.Config) @@ -547,14 +549,14 @@ func generateConfigFiles(md Metadata, mdDir string) error { return fmt.Errorf("failed to write config schema: %w", err) } - if err := generateConfigGoStruct(md, mdDir); err != nil { + if err := generateConfigGoStruct(md, mdDir, noformat); err != nil { return fmt.Errorf("failed to generate config Go struct: %w", err) } } return nil } -func generateConfigGoStruct(md Metadata, outputDir string) error { +func generateConfigGoStruct(md Metadata, outputDir string, noformat bool) error { rootPkg, err := helpers.RootPackage(outputDir) if err != nil { return fmt.Errorf("unable to determine root package: %w", err) @@ -565,7 +567,7 @@ func generateConfigGoStruct(md Metadata, outputDir string) error { dstFile := filepath.Join(outputDir, "generated_config.go") fns := cfggen.WithCfgFns(getTemplateFuncMap(md), rootPkg, md.PackageName) - return generateFileWithFns(tmplFile, dstFile, md, packageName, fns) + return generateFileWithFns(tmplFile, dstFile, md, packageName, fns, noformat) } func joinCamelCase(parts []string, exported bool) string { diff --git a/cmd/mdatagen/internal/command_test.go b/cmd/mdatagen/internal/command_test.go index b7c0a069c63..a45601e3e0a 100644 --- a/cmd/mdatagen/internal/command_test.go +++ b/cmd/mdatagen/internal/command_test.go @@ -305,7 +305,7 @@ foo require.NoError(t, os.WriteFile(filepath.Join(tmpdir, generatedPackageDir, "generated_telemetry_test.go"), []byte("test"), 0o600)) require.NoError(t, os.WriteFile(filepath.Join(tmpdir, generatedPackageDir, "generated_component_test.go"), []byte("test"), 0o600)) - err = run(metadataFile) + err = run(metadataFile, false) if tt.wantOrderErr { require.Error(t, err) require.Contains(t, err.Error(), "metadata.yaml ordering check failed") @@ -528,7 +528,7 @@ func TestGenerateConfigFiles(t *testing.T) { gitInit(t, root) require.NoError(t, os.WriteFile(filepath.Join(root, "go.mod"), []byte("module testmodule\n"), 0o600)) - err := generateConfigFiles(tt.md, tmpdir) + err := generateConfigFiles(tt.md, tmpdir, false) if tt.wantErr { require.Error(t, err) return @@ -551,7 +551,7 @@ func TestGenerateConfigGoStruct_RootPackageError(t *testing.T) { Status: &Status{Class: "receiver"}, Config: &cfggen.ConfigMetadata{Type: "object"}, } - err := generateConfigGoStruct(md, t.TempDir()) + err := generateConfigGoStruct(md, t.TempDir(), false) require.Error(t, err) require.Contains(t, err.Error(), "unable to determine root package") } @@ -564,7 +564,7 @@ func TestGenerateConfigFiles_GoStructError(t *testing.T) { Status: &Status{Class: "receiver"}, Config: &cfggen.ConfigMetadata{Type: "object"}, } - err := generateConfigFiles(md, t.TempDir()) + err := generateConfigFiles(md, t.TempDir(), false) require.Error(t, err) require.Contains(t, err.Error(), "failed to generate config Go struct") } @@ -580,7 +580,7 @@ func TestGenerateConfigFiles_WriteError(t *testing.T) { Type: "object", }, } - err := generateConfigFiles(md, "/nonexistent/path/that/does/not/exist") + err := generateConfigFiles(md, "/nonexistent/path/that/does/not/exist", false) require.Error(t, err) require.Contains(t, err.Error(), "failed to write config schema") } @@ -607,7 +607,7 @@ func TestRun(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := run(tt.args.ymlPath) + err := run(tt.args.ymlPath, false) if !tt.wantErr { require.NoError(t, err, "run()") } else { @@ -977,7 +977,7 @@ const ( t.Run(tt.name, func(t *testing.T) { tmpdir := t.TempDir() err := generateFile("templates/status.go.tmpl", - filepath.Join(tmpdir, "generated_status.go"), tt.md, "metadata") + filepath.Join(tmpdir, "generated_status.go"), tt.md, "metadata", false) require.NoError(t, err) actual, err := os.ReadFile(filepath.Clean(filepath.Join(tmpdir, "generated_status.go"))) require.NoError(t, err) @@ -1063,7 +1063,7 @@ func Tracer(settings component.TelemetrySettings) trace.Tracer { t.Run(tt.name, func(t *testing.T) { tmpdir := t.TempDir() err := generateFile("templates/telemetry.go.tmpl", - filepath.Join(tmpdir, "generated_telemetry.go"), tt.md, "metadata") + filepath.Join(tmpdir, "generated_telemetry.go"), tt.md, "metadata", false) require.NoError(t, err) actual, err := os.ReadFile(filepath.Clean(filepath.Join(tmpdir, "generated_telemetry.go"))) require.NoError(t, err)