Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Android/build/build/blueprint/gobtools/codegen/   (Android Betriebssystem Version 17©)  Datei vom 26.5.2026 mit Größe 15 kB image not shown  

Quelle  gob_gen.go   Sprache: unbekannt

 
Spracherkennung für: .go vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

// Copyright 2025 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
 "bytes"
 "errors"
 "flag"
 "fmt"
 "go/ast"
 "go/format"
 "go/parser"
 "go/scanner"
 "go/token"
 "maps"
 "os"
 "path"
 "path/filepath"
 "slices"
 "sort"
 "strings"
)

// This file provides functionality to auto-generate custom Encode and Decode
// method for Go structs.
//
// # Auto-Generation Trigger
//
// The generation process is triggered for structs annotated with the comment:
//   // @auto-generate: gob
// within a given Go source file.
//
// # Generated Methods
//
// 1. Encode(buf *bytes.Buffer) error:
//    Encodes each field within the struct into a stream of bytes.
//
// 2. Decode(buf *bytes.Reader) error:
//    Decodes from a stream of byte and populates each field within the struct.
//
// # Supported Data Types
//
// The generator can handle the following data types within the annotated structs:
//   - Most of the primitive types (e.g., int, string, bool)
//   - Pointers to supported types
//   - Maps (keys and values must be supported types)
//   - Slices of supported types
//   - Type aliases
//   - Interfaces.
//   - Other user defined structs (which should have generated Encode and Decode methods)

var verify = flag.Bool("verify", false, "verify existing outputs")

const genGobAnnotation = "@auto-generate: gob"
const blueprintPkgPrefix = "github.com/google/blueprint"
const blueprintPkgPath = "build/blueprint"
const soongPkgPrefix = "android/soong"
const soongPkgPath = "build/soong"
const gobtoolsImport = `"github.com/google/blueprint/gobtools"`
const proptoolsImport = `"github.com/google/blueprint/proptools"`
const reflectImport = `"reflect"`
const valueIsNil = -1
const codeGenComment = "// Code generated by go run gob_gen.go; DO NOT EDIT."

type typeDefTypes int

const (
 Struct typeDefTypes = iota
 Alias
 Interface
 Slice
 Array
 Map
 Pointer
 Ident
 Unknown
)

type structMap map[string]*ast.TypeSpec

type gobGen struct {
 pkgStructs map[string]structMap
 importPkgs map[string]string
 curPackage string
 sourceDir  string
 fieldId    int
 imports    map[string]bool
}

func newGobGen() *gobGen {
 return &gobGen{
  pkgStructs: make(map[string]structMap),
  importPkgs: make(map[string]string),
  imports:    make(map[string]bool),
 }
}

func main() {
 flag.Usage = func() {
  fmt.Fprintf(os.Stderr, "Usage: %s [sources]\n", os.Args[0])
  flag.PrintDefaults()
 }
 flag.Parse()
 sources := slices.Clone(flag.Args())

 if f := os.Getenv("GOFILE"); f != "" {
  sources = append(sources, f)
 }

 if len(sources) == 0 {
  flag.Usage()
 }

 g := newGobGen()
 curDir, _ := os.Getwd()
 if *verify {
  // verify mode, the current directory is the base of the source tree.
  g.sourceDir = curDir
 } else {
  var err error
  if g.sourceDir, err = getTop(curDir); err != nil {
   panic(err)
  }
 }

 var out []byte
 var outputFile string
 var err error
 for _, s := range sources {
  out, outputFile, err = g.generate(s, out, *verify)
  if err != nil {
   fmt.Fprintf(os.Stderr, "failed to generate output for %s: %s\n", s, err)
   os.Exit(1)
  }
 }

 if *verify {
  if len(out) == 0 {
   err := expectNotExist(outputFile)
   if err != nil {
    fmt.Fprintf(os.Stderr, "verification error: %s\n", err)
    os.Exit(1)
   }
  } else {
   if err := expectContents(outputFile, out); err != nil {
    fmt.Fprintf(os.Stderr, "verification error: %s\n", err)
    os.Exit(1)
   }
   if !slices.Contains(sources, outputFile) {
    fmt.Fprintf(os.Stderr, "verification error: generated file %s is not in srcs\n", outputFile)
    os.Exit(1)
   }
  }
 } else if len(out) > 0 {
  err = os.WriteFile(outputFile, out, 0666)
  if err != nil {
   fmt.Fprintf(os.Stderr, "failed to write output to %s: %s\n", outputFile, err)
   os.Exit(1)
  }
 }
}

const (
 beginMarkPrefix = "// begin of "
 endMarkPrefix   = "// end of "
)

// mergeSections merges the newly generated section with the old ones in alphabetical order.
func mergeSections(origContent []byte, newContent string) []byte {
 // Combine old and new sections. This ensures all sections and imports are processed together.
 combinedContent := string(origContent) + "\n" + newContent

 // Perform a pass over the combined content to parse everything
 var packageLine, genComment string
 allImportsSet := make(map[string]struct{})
 sectionsMap := make(map[string]string)

 inImportBlock := false
 inSectionBlock := false
 var currentSectionMarker string
 var currentSectionContent []string

 for _, line := range strings.Split(combinedContent, "\n") {
  trimmedLine := strings.TrimSpace(line)

  // Capture the first valid header line found
  if packageLine == "" && strings.HasPrefix(trimmedLine, "package ") {
   packageLine = trimmedLine
   continue
  }
  if genComment == "" && strings.HasPrefix(trimmedLine, codeGenComment) {
   genComment = trimmedLine
   continue
  }

  // Collect all unique imports
  if strings.HasPrefix(trimmedLine, "import (") {
   inImportBlock = true
   continue
  }
  if inImportBlock {
   if trimmedLine == ")" {
    inImportBlock = false
   } else if trimmedLine != "" {
    allImportsSet[trimmedLine] = struct{}{}
   }
   continue
  }
  if strings.HasPrefix(trimmedLine, "import ") {
   path := strings.Trim(trimmedLine[len("import "):], `"`)
   allImportsSet[`"`+path+`"`] = struct{}{}
   continue
  }

  // Parse generated sections. The map naturally handles updates by overwriting keys.
  if strings.HasPrefix(trimmedLine, beginMarkPrefix) {
   inSectionBlock = true
   currentSectionMarker = trimmedLine
   currentSectionContent = nil // Reset for new section
   continue
  }
  if inSectionBlock {
   if strings.HasPrefix(trimmedLine, endMarkPrefix) {
    inSectionBlock = false
    sectionsMap[currentSectionMarker] = strings.Join(currentSectionContent, "\n")
   } else {
    // Don't append blank lines at the start of the content
    if len(currentSectionContent) > 0 || trimmedLine != "" {
     currentSectionContent = append(currentSectionContent, line)
    }
   }
  }
 }

 // Rebuild the entire file from the collected parts.
 var output strings.Builder

 // Write header
 if genComment != "" {
  output.WriteString(genComment + "\n\n")
 }
 if packageLine != "" {
  output.WriteString(packageLine + "\n\n")
 }

 // Write sorted imports
 if len(allImportsSet) > 0 {
  var allImports []string
  for imp := range allImportsSet {
   allImports = append(allImports, imp)
  }
  sort.Strings(allImports)

  output.WriteString("import (\n")
  for _, imp := range allImports {
   output.WriteString("\t" + imp + "\n")
  }
  output.WriteString(")\n")
 }

 // Get all section markers and sort them alphabetically
 var sortedMarkers []string
 for marker := range sectionsMap {
  sortedMarkers = append(sortedMarkers, marker)
 }
 sort.Strings(sortedMarkers)

 // Write all sections back in sorted order
 for _, marker := range sortedMarkers {
  content := strings.TrimSpace(sectionsMap[marker])
  endMarker := strings.Replace(marker, beginMarkPrefix, endMarkPrefix, 1)

  output.WriteString("\n" + marker + "\n")
  if content != "" {
   output.WriteString(content + "\n\n")
  }
  output.WriteString(endMarker + "\n")
 }

 finalString := strings.TrimRight(output.String(), "\n") + "\n"
 return []byte(finalString)
}

// getTop finds the root of the source tree.
// It mimics the behavior of the build/make/envsetup.sh.
func getTop(curDir string) (string, error) {
 const topFile = "build/make/core/envsetup.mk"

 // Start searching from the current directory.
 dir := curDir
 for {
  // Check for the existence of the marker file in the current directory.
  pathToCheck := filepath.Join(dir, topFile)
  if info, err := os.Stat(pathToCheck); err == nil && !info.IsDir() {
   // Found it. Resolve symlinks and return the path.
   return filepath.EvalSymlinks(dir)
  }

  // Get the parent directory.
  parentDir := filepath.Dir(dir)

  // If the parent is the same as the current directory, we've hit the root.
  if parentDir == dir {
   break
  }

  // Move up to the parent directory for the next iteration.
  dir = parentDir
 }

 // If the loop completes without finding the file, report error.
 panic(fmt.Errorf("failed to find the root of the source tree: %s", curDir))
}

func (g *gobGen) findPackagePath(pkgName string) string {
 var pkgDir string
 baseDir := strings.TrimPrefix(g.importPkgs[pkgName], blueprintPkgPrefix)
 if baseDir != g.importPkgs[pkgName] {
  pkgDir = filepath.Join(g.sourceDir, blueprintPkgPath, baseDir)
 } else {
  baseDir = strings.TrimPrefix(g.importPkgs[pkgName], soongPkgPrefix)
  if baseDir != g.importPkgs[pkgName] {
   pkgDir = filepath.Join(g.sourceDir, soongPkgPath, baseDir)
  }
 }
 if pkgDir == "" {
  panic(fmt.Errorf("failed to find the package path: %s %s", pkgName, g.importPkgs[pkgName]))
 }

 return pkgDir
}

func (g *gobGen) importPackage(pkgName string, pkgDir string) {
 if _, ok := g.pkgStructs[pkgName]; ok {
  return
 }
 includePattern := "*.go"
 excludePattern := "*_test.go"

 fullPattern := filepath.Join(pkgDir, includePattern)

 matches, err := filepath.Glob(fullPattern)
 if err != nil {
  panic(fmt.Errorf("error matching include pattern: %v", err))
 }

 if len(matches) == 0 {
  panic(fmt.Errorf("No files found matching pattern '%s' in directory '%s'\n", includePattern, pkgDir))
  return
 }

 for _, match := range matches {
  ok, err := filepath.Match(excludePattern, match)
  if err != nil {
   panic(fmt.Errorf("error matching exclude pattern: %v", err))
  }
  if ok {
   continue
  }
  node, err := parseFile(match)
  if err != nil {
   panic(fmt.Errorf("failed to parse file: %s", match))
  }
  g.loadTypes(node)
 }
}

func parseFile(source string) (*ast.File, error) {
 fset := token.NewFileSet()
 node, err := parser.ParseFile(fset, source, nil, parser.ParseComments)
 if err != nil {
  // ParseFile might return multiple errors in the form of a scanner.ErrorList.  By default printing the error
  // only shows the first error.  Verification may happen very early during the build, so this may be the first
  // time syntax errors are reported.  Use scanner.PrintError to convert them into a single error that contains
  // all the error lines to make the errors more actionable.
  if errorList, ok := err.(scanner.ErrorList); ok {
   var buf bytes.Buffer
   scanner.PrintError(&buf, errorList)
   err = errors.New(buf.String())
  }
  return nil, fmt.Errorf("failed to parse:\n%w", err)
 }
 return node, nil
}

func (g *gobGen) generateRegistry(structDecl *ast.TypeSpec, codeBody *strings.Builder, initCodeBody *strings.Builder) {
 structName := structDecl.Name.Name
 typeId := structName + "GobRegId"
 codeBody.WriteString(fmt.Sprintf("\tvar %s int16\n", typeId))
 codeBody.WriteString("func (r " + structName + ") GetTypeId() int16 {\n")
 codeBody.WriteString(fmt.Sprintf("\treturn %s\n", typeId))
 codeBody.WriteString("}\n\n")

 initCodeBody.WriteString(fmt.Sprintf("\t%s = gobtools.RegisterType(func() gobtools.CustomDec { return new(%s) })\n", typeId, structName))
 g.imports[gobtoolsImport] = true
}

func (g *gobGen) generate(source string, input []byte, verify bool) ([]byte, string, error) {
 node, err := parseFile(source) // Find the file containing the struct
 if err != nil {
  return nil, "", err
 }
 for _, p := range node.Imports {
  pkgPath := strings.ReplaceAll(p.Path.Value, "\"", "")
  if strings.HasPrefix(pkgPath, blueprintPkgPrefix) || strings.HasPrefix(pkgPath, soongPkgPrefix) {
   pkgName := path.Base(pkgPath)
   if _, ok := g.importPkgs[pkgName]; !ok {
    g.importPkgs[pkgName] = pkgPath
   }
  }
 }
 g.curPackage = node.Name.Name
 g.importPackage(g.curPackage, path.Dir(source))

 var b bytes.Buffer
 fmt.Fprintf(&b, "%s\n\n", codeGenComment)
 fmt.Fprintf(&b, "package %s\n", g.curPackage)
 fmt.Fprintln(&b, "import (")

 var codeBodies []*strings.Builder
 g.imports = make(map[string]bool)
 initCodeBody := &strings.Builder{}
 initCodeBody.WriteString("func init() {\n")
 structDecls := findStructs(node)
 for _, structDecl := range structDecls {
  g.fieldId = 0
  codeBody := &strings.Builder{}
  g.generateEncode(g.curPackage, structDecl, codeBody)
  codeBody.WriteString("\n")
  g.fieldId = 0
  g.generateHash(g.curPackage, structDecl, codeBody)
  codeBody.WriteString("\n")
  g.fieldId = 0
  g.generateDecode(g.curPackage, structDecl, codeBody)
  codeBodies = append(codeBodies, codeBody)
  g.generateRegistry(structDecl, codeBody, initCodeBody)
 }

 outputFile := filepath.Join(filepath.Dir(source), g.curPackage+"_enc.go")
 if len(codeBodies) == 0 {
  return input, outputFile, nil
 }

 initCodeBody.WriteString("}\n\n")

 fmt.Fprintln(&b, strings.Join(slices.Sorted(maps.Keys(g.imports)), "\n"))
 fmt.Fprintln(&b, ")")
 beginMark := beginMarkPrefix + filepath.Base(source)
 endMark := endMarkPrefix + filepath.Base(source)
 fmt.Fprintln(&b, beginMark)
 fmt.Fprintf(&b, initCodeBody.String())
 for _, codeBody := range codeBodies {
  fmt.Fprintf(&b, codeBody.String())
  fmt.Fprintln(&b)
 }
 fmt.Fprintln(&b, endMark)

 out, err := format.Source(b.Bytes())
 if err != nil {
  return nil, "", fmt.Errorf("source format error: %w", err)
 }

 if !verify {
  input, err = os.ReadFile(outputFile)
  if err != nil && !os.IsNotExist(err) {
   return nil, "", err
  }
 }
 out = mergeSections(input, string(out))
 return out, outputFile, nil
}

// expectContents verifies the that file contains the given bytes, returning an error that describes
// how to fix the problem if it does not.
func expectContents(file string, expected []byte) error {
 actual, err := os.ReadFile(file)
 if os.IsNotExist(err) {
  return fmt.Errorf("generated file %s does not exist, rerun `go generate` in %s",
   file, filepath.Dir(file))
 }
 if err != nil {
  return err
 }

 if len(expected) == 0 {
  return fmt.Errorf("found unexpected generated file %s, delete it", file)
 }

 if !bytes.Equal(actual, expected) {
  missing := missingSections(actual, expected)
  if len(missing) > 0 {
   return fmt.Errorf("some files were removed: %s, delete %s and rerun `go generate` in %s",
    strings.Join(missing, ", "), file, filepath.Dir(file))
  }
  return fmt.Errorf("generated file %s has out of date contents, rerun `go generate` in %s",
   file, filepath.Dir(file))
 }

 return nil
}

// parseSectionMarkers scans content and returns a set of all begin markers found.
func parseSectionMarkers(content []byte) map[string]struct{} {
 markers := make(map[string]struct{})
 lines := strings.Split(string(content), "\n")

 for _, line := range lines {
  trimmedLine := strings.TrimSpace(line)
  if strings.HasPrefix(trimmedLine, beginMarkPrefix) {
   markers[trimmedLine] = struct{}{}
  }
 }
 return markers
}

// missingSections finds the missing sections in new content from the original one.
// It returns missing sections.
func missingSections(origContent, newContent []byte) (missing []string) {
 // Step 1: Parse all markers from each input into sets.
 origSections := parseSectionMarkers(origContent)
 newSections := parseSectionMarkers(newContent)

 for marker := range origSections {
  if _, found := newSections[marker]; !found {
   missing = append(missing, strings.TrimPrefix(marker, beginMarkPrefix))
  }
 }

 return missing
}

// expectNotExist verifies that the file does not exist, returning an error that describes how to
// fix the problem if it does.
func expectNotExist(file string) error {
 _, err := os.Stat(file)
 if os.IsNotExist(err) {
  return nil
 }
 if err != nil {
  return err
 }
 return fmt.Errorf("expected %s to not exist, delete it", file)
}

[Dauer der Verarbeitung: 0.23 Sekunden, vorverarbeitet 2026-06-28]