Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

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.24 Sekunden, vorverarbeitet 2026-06-28]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik