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

Quelle  mk2rbc.go   Sprache: unbekannt

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

// Copyright 2021 Google LLC
//
// 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.

// The application to convert product configuration makefiles to Starlark.
// Converts either given list of files (and optionally the dependent files
// of the same kind), or all all product configuration makefiles in the
// given source tree.
// Previous version of a converted file can be backed up.
// Optionally prints detailed statistics at the end.
package main

import (
 "bufio"
 "flag"
 "fmt"
 "io/ioutil"
 "os"
 "os/exec"
 "path/filepath"
 "regexp"
 "runtime/debug"
 "runtime/pprof"
 "sort"
 "strings"
 "time"

 "android/soong/androidmk/parser"
 "android/soong/mk2rbc"
)

var (
 // TODO(asmundak): remove this option once there is a consensus on suffix
 suffix   = flag.String("suffix", ".rbc", "generated files' suffix")
 dryRun   = flag.Bool("dry_run", false, "dry run")
 recurse  = flag.Bool("convert_dependents", false, "convert all dependent files")
 mode     = flag.String("mode", "", `"backup" to back up existing files, "write" to overwrite them`)
 errstat  = flag.Bool("error_stat", false, "print error statistics")
 traceVar = flag.String("trace", "", "comma-separated list of variables to trace")
 // TODO(asmundak): this option is for debugging
 allInSource           = flag.Bool("all", false, "convert all product config makefiles in the tree under //")
 outputTop             = flag.String("outdir", "", "write output files into this directory hierarchy")
 launcher              = flag.String("launcher", "", "generated launcher path.")
 boardlauncher         = flag.String("boardlauncher", "", "generated board configuration launcher path.")
 printProductConfigMap = flag.Bool("print_product_config_map", false, "print product config map and exit")
 cpuProfile            = flag.String("cpu_profile", "", "write cpu profile to file")
 traceCalls            = flag.Bool("trace_calls", false, "trace function calls")
 inputVariables        = flag.String("input_variables", "", "starlark file containing product config and global variables")
 makefileList          = flag.String("makefile_list", "", "path to a list of all makefiles in the source tree, generated by soong's finder. If not provided, mk2rbc will find the makefiles itself (more slowly than if this flag was provided)")
)

func init() {
 // Simplistic flag aliasing: works, but the usage string is ugly and
 // both flag and its alias can be present on the command line
 flagAlias := func(target string, alias string) {
  if f := flag.Lookup(target); f != nil {
   flag.Var(f.Value, alias, "alias for --"+f.Name)
   return
  }
  quit("cannot alias unknown flag " + target)
 }
 flagAlias("suffix", "s")
 flagAlias("dry_run", "n")
 flagAlias("convert_dependents", "r")
 flagAlias("error_stat", "e")
}

var backupSuffix string
var tracedVariables []string
var errorLogger = errorSink{data: make(map[string]datum)}
var makefileFinder mk2rbc.MakefileFinder

func main() {
 flag.Usage = func() {
  cmd := filepath.Base(os.Args[0])
  fmt.Fprintf(flag.CommandLine.Output(),
   "Usage: %[1]s flags file...\n", cmd)
  flag.PrintDefaults()
 }
 flag.Parse()

 if _, err := os.Stat("build/soong/mk2rbc"); err != nil {
  quit("Must be run from the root of the android tree. (build/soong/mk2rbc does not exist)")
 }

 // Delouse
 if *suffix == ".mk" {
  quit("cannot use .mk as generated file suffix")
 }
 if *suffix == "" {
  quit("suffix cannot be empty")
 }
 if *outputTop != "" {
  if err := os.MkdirAll(*outputTop, os.ModeDir+os.ModePerm); err != nil {
   quit(err)
  }
  s, err := filepath.Abs(*outputTop)
  if err != nil {
   quit(err)
  }
  *outputTop = s
 }
 if *allInSource && len(flag.Args()) > 0 {
  quit("file list cannot be specified when -all is present")
 }
 if *allInSource && *launcher != "" {
  quit("--all and --launcher are mutually exclusive")
 }

 // Flag-driven adjustments
 if (*suffix)[0] != '.' {
  *suffix = "." + *suffix
 }
 if *mode == "backup" {
  backupSuffix = time.Now().Format("20060102150405")
 }
 if *traceVar != "" {
  tracedVariables = strings.Split(*traceVar, ",")
 }

 if *cpuProfile != "" {
  f, err := os.Create(*cpuProfile)
  if err != nil {
   quit(err)
  }
  pprof.StartCPUProfile(f)
  defer pprof.StopCPUProfile()
 }

 if *makefileList != "" {
  makefileFinder = &FileListMakefileFinder{
   cachedMakefiles: nil,
   filePath:        *makefileList,
  }
 } else {
  makefileFinder = &FindCommandMakefileFinder{}
 }

 // Find out global variables
 getConfigVariables()
 getSoongVariables()

 if *printProductConfigMap {
  productConfigMap := buildProductConfigMap()
  var products []string
  for p := range productConfigMap {
   products = append(products, p)
  }
  sort.Strings(products)
  for _, p := range products {
   fmt.Println(p, productConfigMap[p])
  }
  os.Exit(0)
 }

 // Convert!
 files := flag.Args()
 if *allInSource {
  productConfigMap := buildProductConfigMap()
  for _, path := range productConfigMap {
   files = append(files, path)
  }
 }
 ok := true
 for _, mkFile := range files {
  ok = convertOne(mkFile, []string{}) && ok
 }

 if *launcher != "" {
  if len(files) != 1 {
   quit(fmt.Errorf("a launcher can be generated only for a single product"))
  }
  if *inputVariables == "" {
   quit(fmt.Errorf("the product launcher requires an input variables file"))
  }
  if !convertOne(*inputVariables, []string{}) {
   quit(fmt.Errorf("the product launcher input variables file failed to convert"))
  }

  err := writeGenerated(*launcher, mk2rbc.Launcher(outputModulePath(files[0]), outputModulePath(*inputVariables),
   mk2rbc.MakePath2ModuleName(files[0])))
  if err != nil {
   fmt.Fprintf(os.Stderr, "%s: %s", files[0], err)
   ok = false
  }
 }
 if *boardlauncher != "" {
  if len(files) != 1 {
   quit(fmt.Errorf("a launcher can be generated only for a single product"))
  }
  if *inputVariables == "" {
   quit(fmt.Errorf("the board launcher requires an input variables file"))
  }
  if !convertOne(*inputVariables, []string{}) {
   quit(fmt.Errorf("the board launcher input variables file failed to convert"))
  }
  err := writeGenerated(*boardlauncher, mk2rbc.BoardLauncher(
   outputModulePath(files[0]), outputModulePath(*inputVariables)))
  if err != nil {
   fmt.Fprintf(os.Stderr, "%s: %s", files[0], err)
   ok = false
  }
 }

 if *errstat {
  errorLogger.printStatistics()
  printStats()
 }
 if !ok {
  os.Exit(1)
 }
}

func quit(s interface{}) {
 fmt.Fprintln(os.Stderr, s)
 os.Exit(2)
}

func buildProductConfigMap() map[string]string {
 const androidProductsMk = "AndroidProducts.mk"
 // Build the list of AndroidProducts.mk files: it's
 // build/make/target/product/AndroidProducts.mk + device/**/AndroidProducts.mk plus + vendor/**/AndroidProducts.mk
 targetAndroidProductsFile := filepath.Join("build", "make", "target", "product", androidProductsMk)
 if _, err := os.Stat(targetAndroidProductsFile); err != nil {
  fmt.Fprintf(os.Stderr, "%s: %s\n", targetAndroidProductsFile, err)
 }
 productConfigMap := make(map[string]string)
 if err := mk2rbc.UpdateProductConfigMap(productConfigMap, targetAndroidProductsFile); err != nil {
  fmt.Fprintf(os.Stderr, "%s: %s\n", targetAndroidProductsFile, err)
 }
 for _, t := range []string{"device", "vendor"} {
  _ = filepath.WalkDir(t,
   func(path string, d os.DirEntry, err error) error {
    if err != nil || d.IsDir() || filepath.Base(path) != androidProductsMk {
     return nil
    }
    if err2 := mk2rbc.UpdateProductConfigMap(productConfigMap, path); err2 != nil {
     fmt.Fprintf(os.Stderr, "%s: %s\n", path, err)
     // Keep going, we want to find all such errors in a single run
    }
    return nil
   })
 }
 return productConfigMap
}

func getConfigVariables() {
 path := filepath.Join("build", "make", "core", "product.mk")
 if err := mk2rbc.FindConfigVariables(path, mk2rbc.KnownVariables); err != nil {
  quit(err)
 }
}

// Implements mkparser.Scope, to be used by mkparser.Value.Value()
type fileNameScope struct {
 mk2rbc.ScopeBase
}

func (s fileNameScope) Get(name string) string {
 if name != "BUILD_SYSTEM" {
  return fmt.Sprintf("$(%s)", name)
 }
 return filepath.Join("build", "make", "core")
}

func getSoongVariables() {
 path := filepath.Join("build", "make", "core", "soong_config.mk")
 err := mk2rbc.FindSoongVariables(path, fileNameScope{}, mk2rbc.KnownVariables)
 if err != nil {
  quit(err)
 }
}

var converted = make(map[string]*mk2rbc.StarlarkScript)

//goland:noinspection RegExpRepeatedSpace
var cpNormalizer = regexp.MustCompile(
 "#  Copyright \\(C\\) 20.. The Android Open Source Project")

const cpNormalizedCopyright = "#  Copyright (C) 20xx The Android Open Source Project"
const copyright = `#
#  Copyright (C) 20xx The Android Open Source Project
#
#  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.
#
`

// Convert a single file.
// Write the result either to the same directory, to the same place in
// the output hierarchy, or to the stdout.
// Optionally, recursively convert the files this one includes by
// $(call inherit-product) or an include statement.
func convertOne(mkFile string, loadStack []string) (ok bool) {
 if v, ok := converted[mkFile]; ok {
  if v == nil {
   fmt.Fprintf(os.Stderr, "Cycle in load graph:\n%s\n%s\n\n", strings.Join(loadStack, "\n"), mkFile)
   return false
  }
  return true
 }
 converted[mkFile] = nil
 defer func() {
  if r := recover(); r != nil {
   ok = false
   fmt.Fprintf(os.Stderr, "%s: panic while converting: %s\n%s\n", mkFile, r, debug.Stack())
  }
 }()

 mk2starRequest := mk2rbc.Request{
  MkFile:          mkFile,
  Reader:          nil,
  OutputDir:       *outputTop,
  OutputSuffix:    *suffix,
  TracedVariables: tracedVariables,
  TraceCalls:      *traceCalls,
  SourceFS:        os.DirFS("."),
  MakefileFinder:  makefileFinder,
  ErrorLogger:     errorLogger,
 }
 ss, err := mk2rbc.Convert(mk2starRequest)
 if err != nil {
  fmt.Fprintln(os.Stderr, mkFile, ": ", err)
  return false
 }
 script := ss.String()
 outputPath := outputFilePath(mkFile)

 if *dryRun {
  fmt.Printf("==== %s ====\n", outputPath)
  // Print generated script after removing the copyright header
  outText := cpNormalizer.ReplaceAllString(script, cpNormalizedCopyright)
  fmt.Println(strings.TrimPrefix(outText, copyright))
 } else {
  if err := maybeBackup(outputPath); err != nil {
   fmt.Fprintln(os.Stderr, err)
   return false
  }
  if err := writeGenerated(outputPath, script); err != nil {
   fmt.Fprintln(os.Stderr, err)
   return false
  }
 }
 loadStack = append(loadStack, mkFile)
 ok = true
 if *recurse {
  for _, sub := range ss.SubConfigFiles() {
   // File may be absent if it is a conditional load
   if _, err := os.Stat(sub); os.IsNotExist(err) {
    continue
   }
   ok = convertOne(sub, loadStack) && ok
  }
 }
 converted[mkFile] = ss
 return ok
}

// Optionally saves the previous version of the generated file
func maybeBackup(filename string) error {
 stat, err := os.Stat(filename)
 if os.IsNotExist(err) {
  return nil
 }
 if !stat.Mode().IsRegular() {
  return fmt.Errorf("%s exists and is not a regular file", filename)
 }
 switch *mode {
 case "backup":
  return os.Rename(filename, filename+backupSuffix)
 case "write":
  return os.Remove(filename)
 default:
  return fmt.Errorf("%s already exists, use --mode option", filename)
 }
}

func outputFilePath(mkFile string) string {
 path := strings.TrimSuffix(mkFile, filepath.Ext(mkFile)) + *suffix
 if *outputTop != "" {
  path = filepath.Join(*outputTop, path)
 }
 return path
}

func outputModulePath(mkFile string) string {
 path := outputFilePath(mkFile)
 path, err := mk2rbc.RelativeToCwd(path)
 if err != nil {
  panic(err)
 }
 return "//" + path
}

func writeGenerated(path string, contents string) error {
 if err := os.MkdirAll(filepath.Dir(path), os.ModeDir|os.ModePerm); err != nil {
  return err
 }
 if err := ioutil.WriteFile(path, []byte(contents), 0644); err != nil {
  return err
 }
 return nil
}

func printStats() {
 var sortedFiles []string
 for p := range converted {
  sortedFiles = append(sortedFiles, p)
 }
 sort.Strings(sortedFiles)

 nOk, nPartial, nFailed := 000
 for _, f := range sortedFiles {
  if converted[f] == nil {
   nFailed++
  } else if converted[f].HasErrors() {
   nPartial++
  } else {
   nOk++
  }
 }
 if nPartial > 0 {
  fmt.Fprintf(os.Stderr, "Conversion was partially successful for:\n")
  for _, f := range sortedFiles {
   if ss := converted[f]; ss != nil && ss.HasErrors() {
    fmt.Fprintln(os.Stderr, "  ", f)
   }
  }
 }

 if nFailed > 0 {
  fmt.Fprintf(os.Stderr, "Conversion failed for files:\n")
  for _, f := range sortedFiles {
   if converted[f] == nil {
    fmt.Fprintln(os.Stderr, "  ", f)
   }
  }
 }
}

type datum struct {
 count          int
 formattingArgs []string
}

type errorSink struct {
 data map[string]datum
}

func (ebt errorSink) NewError(el mk2rbc.ErrorLocation, node parser.Node, message string, args ...interface{}) {
 fmt.Fprint(os.Stderr, el, ": ")
 fmt.Fprintf(os.Stderr, message, args...)
 fmt.Fprintln(os.Stderr)
 if !*errstat {
  return
 }

 v, exists := ebt.data[message]
 if exists {
  v.count++
 } else {
  v = datum{1, nil}
 }
 if strings.Contains(message, "%s") {
  var newArg1 string
  if len(args) == 0 {
   panic(fmt.Errorf(`%s has %%s but args are missing`, message))
  }
  newArg1 = fmt.Sprint(args[0])
  if message == "unsupported line" {
   newArg1 = node.Dump()
  } else if message == "unsupported directive %s" {
   if newArg1 == "include" || newArg1 == "-include" {
    newArg1 = node.Dump()
   }
  }
  v.formattingArgs = append(v.formattingArgs, newArg1)
 }
 ebt.data[message] = v
}

func (ebt errorSink) printStatistics() {
 if len(ebt.data) > 0 {
  fmt.Fprintln(os.Stderr, "Error counts:")
 }
 for message, data := range ebt.data {
  if len(data.formattingArgs) == 0 {
   fmt.Fprintf(os.Stderr, "%4d %s\n", data.count, message)
   continue
  }
  itemsByFreq, count := stringsWithFreq(data.formattingArgs, 30)
  fmt.Fprintf(os.Stderr, "%4d %s [%d unique items]:\n", data.count, message, count)
  fmt.Fprintln(os.Stderr, "      ", itemsByFreq)
 }
}

func stringsWithFreq(items []string, topN int) (string, int) {
 freq := make(map[string]int)
 for _, item := range items {
  freq[strings.TrimPrefix(strings.TrimSuffix(item, "]"), "[")]++
 }
 var sorted []string
 for item := range freq {
  sorted = append(sorted, item)
 }
 sort.Slice(sorted, func(i int, j int) bool {
  return freq[sorted[i]] > freq[sorted[j]]
 })
 sep := ""
 res := ""
 for i, item := range sorted {
  if i >= topN {
   res += " ..."
   break
  }
  count := freq[item]
  if count > 1 {
   res += fmt.Sprintf("%s%s(%d)", sep, item, count)
  } else {
   res += fmt.Sprintf("%s%s", sep, item)
  }
  sep = ", "
 }
 return res, len(sorted)
}

// FindCommandMakefileFinder is an implementation of mk2rbc.MakefileFinder that
// runs the unix find command to find all the makefiles in the source tree.
type FindCommandMakefileFinder struct {
 cachedRoot      string
 cachedMakefiles []string
}

func (l *FindCommandMakefileFinder) Find(root string) []string {
 if l.cachedMakefiles != nil && l.cachedRoot == root {
  return l.cachedMakefiles
 }

 // Return all *.mk files but not in hidden directories.

 // NOTE(asmundak): as it turns out, even the WalkDir (which is an _optimized_ directory tree walker)
 // is about twice slower than running `find` command (14s vs 6s on the internal Android source tree).
 common_args := []string{"!", "-type", "d", "-name", "*.mk", "!", "-path", "*/.*/*"}
 if root != "" {
  common_args = append([]string{root}, common_args...)
 }
 cmd := exec.Command("/usr/bin/find", common_args...)
 stdout, err := cmd.StdoutPipe()
 if err == nil {
  err = cmd.Start()
 }
 if err != nil {
  panic(fmt.Errorf("cannot get the output from %s: %s", cmd, err))
 }
 scanner := bufio.NewScanner(stdout)
 result := make([]string, 0)
 for scanner.Scan() {
  result = append(result, strings.TrimPrefix(scanner.Text(), "./"))
 }
 stdout.Close()
 err = scanner.Err()
 if err != nil {
  panic(fmt.Errorf("cannot get the output from %s: %s", cmd, err))
 }
 l.cachedRoot = root
 l.cachedMakefiles = result
 return l.cachedMakefiles
}

// FileListMakefileFinder is an implementation of mk2rbc.MakefileFinder that
// reads a file containing the list of makefiles in the android source tree.
// This file is generated by soong's finder, so that it can be computed while
// soong is already walking the source tree looking for other files. If the root
// to find makefiles under is not the root of the android source tree, it will
// fall back to using FindCommandMakefileFinder.
type FileListMakefileFinder struct {
 FindCommandMakefileFinder
 cachedMakefiles []string
 filePath        string
}

func (l *FileListMakefileFinder) Find(root string) []string {
 root, err1 := filepath.Abs(root)
 wd, err2 := os.Getwd()
 if root != wd || err1 != nil || err2 != nil {
  return l.FindCommandMakefileFinder.Find(root)
 }
 if l.cachedMakefiles != nil {
  return l.cachedMakefiles
 }

 file, err := os.Open(l.filePath)
 if err != nil {
  panic(fmt.Errorf("Cannot read makefile list: %s\n", err))
 }
 defer file.Close()

 result := make([]string, 0)
 scanner := bufio.NewScanner(file)
 for scanner.Scan() {
  line := scanner.Text()
  if len(line) > 0 {
   result = append(result, line)
  }
 }

 if err = scanner.Err(); err != nil {
  panic(fmt.Errorf("Cannot read makefile list: %s\n", err))
 }
 l.cachedMakefiles = result
 return l.cachedMakefiles
}

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