Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Android/build/build/soong/mk2rbc/   (Android Betriebssystem Version 17©)  Datei vom 26.5.2026 mit Größe 74 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.

// Convert makefile containing device configuration to Starlark file
// The conversion can handle the following constructs in a makefile:
//   - comments
//   - simple variable assignments
//   - $(call init-product,<file>)
//   - $(call inherit-product-if-exists
//   - if directives
//
// All other constructs are carried over to the output starlark file as comments.
package mk2rbc

import (
 "bytes"
 "fmt"
 "io"
 "io/fs"
 "io/ioutil"
 "os"
 "path/filepath"
 "regexp"
 "sort"
 "strconv"
 "strings"
 "text/scanner"

 mkparser "android/soong/androidmk/parser"
)

const (
 annotationCommentPrefix = "RBC#"
 baseUri                 = "//build/make/core:product_config.rbc"
 // The name of the struct exported by the product_config.rbc
 // that contains the functions and variables available to
 // product configuration Starlark files.
 baseName = "rblf"

 soongNsPrefix = "SOONG_CONFIG_"

 // And here are the functions and variables:
 cfnGetCfg         = baseName + ".cfg"
 cfnMain           = baseName + ".product_configuration"
 cfnBoardMain      = baseName + ".board_configuration"
 cfnPrintVars      = baseName + ".printvars"
 cfnInherit        = baseName + ".inherit"
 cfnSetListDefault = baseName + ".setdefault"
)

const (
 soongConfigAppend = "soong_config_append"
 soongConfigAssign = "soong_config_set"
)

var knownFunctions = map[string]interface {
 parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr
}{
 "abspath":                              &simpleCallParser{name: baseName + ".abspath", returnType: starlarkTypeString},
 "add-product-dex-preopt-module-config": &simpleCallParser{name: baseName + ".add_product_dex_preopt_module_config", returnType: starlarkTypeString, addHandle: true},
 "add_soong_config_namespace":           &simpleCallParser{name: baseName + ".soong_config_namespace", returnType: starlarkTypeVoid, addGlobals: true},
 "add_soong_config_var_value":           &simpleCallParser{name: baseName + ".soong_config_set", returnType: starlarkTypeVoid, addGlobals: true},
 soongConfigAssign:                      &simpleCallParser{name: baseName + ".soong_config_set", returnType: starlarkTypeVoid, addGlobals: true},
 "soong_config_set_bool":                &simpleCallParser{name: baseName + ".soong_config_set_bool", returnType: starlarkTypeVoid, addGlobals: true},
 soongConfigAppend:                      &simpleCallParser{name: baseName + ".soong_config_append", returnType: starlarkTypeVoid, addGlobals: true},
 "soong_config_get":                     &simpleCallParser{name: baseName + ".soong_config_get", returnType: starlarkTypeString, addGlobals: true},
 "add-to-product-copy-files-if-exists":  &simpleCallParser{name: baseName + ".copy_if_exists", returnType: starlarkTypeList},
 "addprefix":                            &simpleCallParser{name: baseName + ".addprefix", returnType: starlarkTypeList},
 "addsuffix":                            &simpleCallParser{name: baseName + ".addsuffix", returnType: starlarkTypeList},
 "and":                                  &andOrParser{isAnd: true},
 "clear-var-list":                       &simpleCallParser{name: baseName + ".clear_var_list", returnType: starlarkTypeVoid, addGlobals: true, addHandle: true},
 "copy-files":                           &simpleCallParser{name: baseName + ".copy_files", returnType: starlarkTypeList},
 "dir":                                  &simpleCallParser{name: baseName + ".dir", returnType: starlarkTypeString},
 "dist-for-goals":                       &simpleCallParser{name: baseName + ".mkdist_for_goals", returnType: starlarkTypeVoid, addGlobals: true},
 "enforce-product-packages-exist":       &simpleCallParser{name: baseName + ".enforce_product_packages_exist", returnType: starlarkTypeVoid, addHandle: true},
 "error":                                &makeControlFuncParser{name: baseName + ".mkerror"},
 "findstring":                           &simpleCallParser{name: baseName + ".findstring", returnType: starlarkTypeInt},
 "find-copy-subdir-files":               &simpleCallParser{name: baseName + ".find_and_copy", returnType: starlarkTypeList},
 "filter":                               &simpleCallParser{name: baseName + ".filter", returnType: starlarkTypeList},
 "filter-out":                           &simpleCallParser{name: baseName + ".filter_out", returnType: starlarkTypeList},
 "firstword":                            &simpleCallParser{name: baseName + ".first_word", returnType: starlarkTypeString},
 "foreach":                              &foreachCallParser{},
 "if":                                   &ifCallParser{},
 "info":                                 &makeControlFuncParser{name: baseName + ".mkinfo"},
 "is-board-platform":                    &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
 "is-board-platform2":                   &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
 "is-board-platform-in-list":            &simpleCallParser{name: baseName + ".board_platform_in", returnType: starlarkTypeBool, addGlobals: true},
 "is-board-platform-in-list2":           &simpleCallParser{name: baseName + ".board_platform_in", returnType: starlarkTypeBool, addGlobals: true},
 "is-product-in-list":                   &isProductInListCallParser{},
 "is-vendor-board-platform":             &isVendorBoardPlatformCallParser{},
 "is-vendor-board-qcom":                 &isVendorBoardQcomCallParser{},
 "lastword":                             &simpleCallParser{name: baseName + ".last_word", returnType: starlarkTypeString},
 "notdir":                               &simpleCallParser{name: baseName + ".notdir", returnType: starlarkTypeString},
 "math_max":                             &mathMaxOrMinCallParser{function: "max"},
 "math_min":                             &mathMaxOrMinCallParser{function: "min"},
 "math_gt_or_eq":                        &mathComparisonCallParser{op: ">="},
 "math_gt":                              &mathComparisonCallParser{op: ">"},
 "math_lt":                              &mathComparisonCallParser{op: "<"},
 "my-dir":                               &myDirCallParser{},
 "or":                                   &andOrParser{isAnd: false},
 "patsubst":                             &substCallParser{fname: "patsubst"},
 "product-copy-files-by-pattern":        &simpleCallParser{name: baseName + ".product_copy_files_by_pattern", returnType: starlarkTypeList},
 "require-artifacts-in-path":            &simpleCallParser{name: baseName + ".require_artifacts_in_path", returnType: starlarkTypeVoid, addHandle: true},
 "require-artifacts-in-path-relaxed":    &simpleCallParser{name: baseName + ".require_artifacts_in_path_relaxed", returnType: starlarkTypeVoid, addHandle: true},
 // TODO(asmundak): remove it once all calls are removed from configuration makefiles. see b/183161002
 "shell":    &shellCallParser{},
 "sort":     &simpleCallParser{name: baseName + ".mksort", returnType: starlarkTypeList},
 "strip":    &simpleCallParser{name: baseName + ".mkstrip", returnType: starlarkTypeString},
 "subst":    &substCallParser{fname: "subst"},
 "to-lower": &lowerUpperParser{isUpper: false},
 "to-upper": &lowerUpperParser{isUpper: true},
 "warning":  &makeControlFuncParser{name: baseName + ".mkwarning"},
 "word":     &wordCallParser{},
 "words":    &wordsCallParser{},
 "wildcard": &simpleCallParser{name: baseName + ".expand_wildcard", returnType: starlarkTypeList},
}

// The same as knownFunctions, but returns a []starlarkNode instead of a starlarkExpr
var knownNodeFunctions = map[string]interface {
 parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode
}{
 "eval":                      &evalNodeParser{},
 "if":                        &ifCallNodeParser{},
 "inherit-product":           &inheritProductCallParser{loadAlways: true},
 "inherit-product-if-exists": &inheritProductCallParser{loadAlways: false},
 "foreach":                   &foreachCallNodeParser{},
}

// These look like variables, but are actually functions, and would give
// undefined variable errors if we converted them as variables. Instead,
// emit an error instead of converting them.
var unsupportedFunctions = map[string]bool{
 "local-generated-sources-dir": true,
 "local-intermediates-dir":     true,
}

// These are functions that we don't implement conversions for, but
// we allow seeing their definitions in the product config files.
var ignoredDefines = map[string]bool{
 "find-word-in-list":                   true, // internal macro
 "get-vendor-board-platforms":          true, // internal macro, used by is-board-platform, etc.
 "is-android-codename":                 true, // unused by product config
 "is-android-codename-in-list":         true, // unused by product config
 "is-chipset-in-board-platform":        true, // unused by product config
 "is-chipset-prefix-in-board-platform": true, // unused by product config
 "is-not-board-platform":               true, // defined but never used
 "is-platform-sdk-version-at-least":    true, // unused by product config
 "match-prefix":                        true, // internal macro
 "match-word":                          true, // internal macro
 "match-word-in-list":                  true, // internal macro
 "tb-modules":                          true, // defined in hardware/amlogic/tb_modules/tb_detect.mk, unused
}

var identifierFullMatchRegex = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")

func RelativeToCwd(path string) (string, error) {
 cwd, err := os.Getwd()
 if err != nil {
  return "", err
 }
 path, err = filepath.Rel(cwd, path)
 if err != nil {
  return "", err
 }
 if strings.HasPrefix(path, "../") {
  return "", fmt.Errorf("Could not make path relative to current working directory: " + path)
 }
 return path, nil
}

// Conversion request parameters
type Request struct {
 MkFile          string    // file to convert
 Reader          io.Reader // if set, read input from this stream instead
 OutputSuffix    string    // generated Starlark files suffix
 OutputDir       string    // if set, root of the output hierarchy
 ErrorLogger     ErrorLogger
 TracedVariables []string // trace assignment to these variables
 TraceCalls      bool
 SourceFS        fs.FS
 MakefileFinder  MakefileFinder
}

// ErrorLogger prints errors and gathers error statistics.
// Its NewError function is called on every error encountered during the conversion.
type ErrorLogger interface {
 NewError(el ErrorLocation, node mkparser.Node, text string, args ...interface{})
}

type ErrorLocation struct {
 MkFile string
 MkLine int
}

func (el ErrorLocation) String() string {
 return fmt.Sprintf("%s:%d", el.MkFile, el.MkLine)
}

// Derives module name for a given file. It is base name
// (file name without suffix), with some characters replaced to make it a Starlark identifier
func moduleNameForFile(mkFile string) string {
 base := strings.TrimSuffix(filepath.Base(mkFile), filepath.Ext(mkFile))
 // TODO(asmundak): what else can be in the product file names?
 return strings.NewReplacer("-", "_", ".", "_").Replace(base)

}

func cloneMakeString(mkString *mkparser.MakeString) *mkparser.MakeString {
 r := &mkparser.MakeString{StringPos: mkString.StringPos}
 r.Strings = append(r.Strings, mkString.Strings...)
 r.Variables = append(r.Variables, mkString.Variables...)
 return r
}

func isMakeControlFunc(s string) bool {
 return s == "error" || s == "warning" || s == "info"
}

// varAssignmentScope points to the last assignment for each variable
// in the current block. It is used during the parsing to chain
// the assignments to a variable together.
type varAssignmentScope struct {
 outer *varAssignmentScope
 vars  map[string]bool
}

// Starlark output generation context
type generationContext struct {
 buf            strings.Builder
 starScript     *StarlarkScript
 indentLevel    int
 inAssignment   bool
 tracedCount    int
 varAssignments *varAssignmentScope
}

func NewGenerateContext(ss *StarlarkScript) *generationContext {
 return &generationContext{
  starScript: ss,
  varAssignments: &varAssignmentScope{
   outer: nil,
   vars:  make(map[string]bool),
  },
 }
}

func (gctx *generationContext) pushVariableAssignments() {
 va := &varAssignmentScope{
  outer: gctx.varAssignments,
  vars:  make(map[string]bool),
 }
 gctx.varAssignments = va
}

func (gctx *generationContext) popVariableAssignments() {
 gctx.varAssignments = gctx.varAssignments.outer
}

func (gctx *generationContext) hasBeenAssigned(v variable) bool {
 for va := gctx.varAssignments; va != nil; va = va.outer {
  if _, ok := va.vars[v.name()]; ok {
   return true
  }
 }
 return false
}

func (gctx *generationContext) setHasBeenAssigned(v variable) {
 gctx.varAssignments.vars[v.name()] = true
}

// emit returns generated script
func (gctx *generationContext) emit() string {
 ss := gctx.starScript

 // The emitted code has the following layout:
 //    <initial comments>
 //    preamble, i.e.,
 //      load statement for the runtime support
 //      load statement for each unique submodule pulled in by this one
 //    def init(g, handle):
 //      cfg = rblf.cfg(handle)
 //      <statements>
 //      <warning if conversion was not clean>

 iNode := len(ss.nodes)
 for i, node := range ss.nodes {
  if _, ok := node.(*commentNode); !ok {
   iNode = i
   break
  }
  node.emit(gctx)
 }

 gctx.emitPreamble()

 gctx.newLine()
 // The arguments passed to the init function are the global dictionary
 // ('g') and the product configuration dictionary ('cfg')
 gctx.write("def init(g, handle):")
 gctx.indentLevel++
 if gctx.starScript.traceCalls {
  gctx.newLine()
  gctx.writef(`print(">%s")`, gctx.starScript.mkFile)
 }
 gctx.newLine()
 gctx.writef("cfg = %s(handle)", cfnGetCfg)
 for _, node := range ss.nodes[iNode:] {
  node.emit(gctx)
 }

 if gctx.starScript.traceCalls {
  gctx.newLine()
  gctx.writef(`print("<%s")`, gctx.starScript.mkFile)
 }
 gctx.indentLevel--
 gctx.write("\n")
 return gctx.buf.String()
}

func (gctx *generationContext) emitPreamble() {
 gctx.newLine()
 gctx.writef("load(%q, %q)", baseUri, baseName)
 // Emit exactly one load statement for each URI.
 loadedSubConfigs := make(map[string]string)
 for _, mi := range gctx.starScript.inherited {
  uri := mi.path
  if strings.HasPrefix(uri, "/") && !strings.HasPrefix(uri, "//") {
   var err error
   uri, err = RelativeToCwd(uri)
   if err != nil {
    panic(err)
   }
   uri = "//" + uri
  }
  if m, ok := loadedSubConfigs[uri]; ok {
   // No need to emit load statement, but fix module name.
   mi.moduleLocalName = m
   continue
  }
  if mi.optional || mi.missing {
   uri += "|init"
  }
  gctx.newLine()
  gctx.writef("load(%q, %s = \"init\")", uri, mi.entryName())
  loadedSubConfigs[uri] = mi.moduleLocalName
 }
 gctx.write("\n")
}

func (gctx *generationContext) emitPass() {
 gctx.newLine()
 gctx.write("pass")
}

func (gctx *generationContext) write(ss ...string) {
 for _, s := range ss {
  gctx.buf.WriteString(s)
 }
}

func (gctx *generationContext) writef(format string, args ...interface{}) {
 gctx.write(fmt.Sprintf(format, args...))
}

func (gctx *generationContext) newLine() {
 if gctx.buf.Len() == 0 {
  return
 }
 gctx.write("\n")
 gctx.writef("%*s", 2*gctx.indentLevel, "")
}

func (gctx *generationContext) emitConversionError(el ErrorLocation, message string) {
 gctx.writef(`rblf.mk2rbc_error("%s", %q)`, el, message)
}

func (gctx *generationContext) emitLoadCheck(im inheritedModule) {
 if !im.needsLoadCheck() {
  return
 }
 gctx.newLine()
 gctx.writef("if not %s:", im.entryName())
 gctx.indentLevel++
 gctx.newLine()
 gctx.write(`rblf.mkerror("`, gctx.starScript.mkFile, `", "Cannot find %s" % (`)
 im.pathExpr().emit(gctx)
 gctx.write("))")
 gctx.indentLevel--
}

type knownVariable struct {
 name      string
 class     varClass
 valueType starlarkType
}

type knownVariables map[string]knownVariable

func (pcv knownVariables) NewVariable(name string, varClass varClass, valueType starlarkType) {
 v, exists := pcv[name]
 if !exists {
  pcv[name] = knownVariable{name, varClass, valueType}
  return
 }
 // Conflict resolution:
 //    * config class trumps everything
 //    * any type trumps unknown type
 match := varClass == v.class
 if !match {
  if varClass == VarClassConfig {
   v.class = VarClassConfig
   match = true
  } else if v.class == VarClassConfig {
   match = true
  }
 }
 if valueType != v.valueType {
  if valueType != starlarkTypeUnknown {
   if v.valueType == starlarkTypeUnknown {
    v.valueType = valueType
   } else {
    match = false
   }
  }
 }
 if !match {
  fmt.Fprintf(os.Stderr, "cannot redefine %s as %v/%v (already defined as %v/%v)\n",
   name, varClass, valueType, v.class, v.valueType)
 }
}

// All known product variables.
var KnownVariables = make(knownVariables)

func init() {
 for _, kv := range []string{
  // Kernel-related variables that we know are lists.
  "BOARD_VENDOR_KERNEL_MODULES",
  "BOARD_VENDOR_RAMDISK_KERNEL_MODULES",
  "BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD",
  "BOARD_RECOVERY_KERNEL_MODULES",
  // Other variables we knwo are lists
  "ART_APEX_JARS",
 } {
  KnownVariables.NewVariable(kv, VarClassSoong, starlarkTypeList)
 }
}

// Information about the generated Starlark script.
type StarlarkScript struct {
 mkFile         string
 moduleName     string
 mkPos          scanner.Position
 nodes          []starlarkNode
 inherited      []*moduleInfo
 hasErrors      bool
 traceCalls     bool // print enter/exit each init function
 sourceFS       fs.FS
 makefileFinder MakefileFinder
 nodeLocator    func(pos mkparser.Pos) int
}

// parseContext holds the script we are generating and all the ephemeral data
// needed during the parsing.
type parseContext struct {
 script           *StarlarkScript
 nodes            []mkparser.Node // Makefile as parsed by mkparser
 currentNodeIndex int             // Node in it we are processing
 ifNestLevel      int
 moduleNameCount  map[string]int // count of imported modules with given basename
 fatalError       error
 outputSuffix     string
 errorLogger      ErrorLogger
 tracedVariables  map[string]bool // variables to be traced in the generated script
 variables        map[string]variable
 outputDir        string
 dependentModules map[string]*moduleInfo
 soongNamespaces  map[string]map[string]bool
 includeTops      []string
 typeHints        map[string]starlarkType
 atTopOfMakefile  bool
}

func newParseContext(ss *StarlarkScript, nodes []mkparser.Node) *parseContext {
 predefined := []struct{ name, value string }{
  {"SRC_TARGET_DIR", filepath.Join("build", "make", "target")},
  {"LOCAL_PATH", filepath.Dir(ss.mkFile)},
  {"MAKEFILE_LIST", ss.mkFile},
  {"TOPDIR", ""}, // TOPDIR is just set to an empty string in cleanbuild.mk and core.mk
  // TODO(asmundak): maybe read it from build/make/core/envsetup.mk?
  {"TARGET_COPY_OUT_SYSTEM", "system"},
  {"TARGET_COPY_OUT_SYSTEM_OTHER", "system_other"},
  {"TARGET_COPY_OUT_DATA", "data"},
  {"TARGET_COPY_OUT_ASAN", filepath.Join("data", "asan")},
  {"TARGET_COPY_OUT_OEM", "oem"},
  {"TARGET_COPY_OUT_RAMDISK", "ramdisk"},
  {"TARGET_COPY_OUT_DEBUG_RAMDISK", "debug_ramdisk"},
  {"TARGET_COPY_OUT_VENDOR_DEBUG_RAMDISK", "vendor_debug_ramdisk"},
  {"TARGET_COPY_OUT_TEST_HARNESS_RAMDISK", "test_harness_ramdisk"},
  {"TARGET_COPY_OUT_ROOT", "root"},
  {"TARGET_COPY_OUT_RECOVERY", "recovery"},
  {"TARGET_COPY_OUT_VENDOR_RAMDISK", "vendor_ramdisk"},
  // TODO(asmundak): to process internal config files, we need the following variables:
  //    TARGET_VENDOR
  //    target_base_product
  //

  // the following utility variables are set in build/make/common/core.mk:
  {"empty", ""},
  {"space", " "},
  {"comma", ","},
  {"newline", "\n"},
  {"pound", "#"},
  {"backslash", "\\"},
 }
 ctx := &parseContext{
  script:           ss,
  nodes:            nodes,
  currentNodeIndex: 0,
  ifNestLevel:      0,
  moduleNameCount:  make(map[string]int),
  variables:        make(map[string]variable),
  dependentModules: make(map[string]*moduleInfo),
  soongNamespaces:  make(map[string]map[string]bool),
  includeTops:      []string{},
  typeHints:        make(map[string]starlarkType),
  atTopOfMakefile:  true,
 }
 for _, item := range predefined {
  ctx.variables[item.name] = &predefinedVariable{
   baseVariable: baseVariable{nam: item.name, typ: starlarkTypeString},
   value:        &stringLiteralExpr{item.value},
  }
 }

 return ctx
}

func (ctx *parseContext) hasNodes() bool {
 return ctx.currentNodeIndex < len(ctx.nodes)
}

func (ctx *parseContext) getNode() mkparser.Node {
 if !ctx.hasNodes() {
  return nil
 }
 node := ctx.nodes[ctx.currentNodeIndex]
 ctx.currentNodeIndex++
 return node
}

func (ctx *parseContext) backNode() {
 if ctx.currentNodeIndex <= 0 {
  panic("Cannot back off")
 }
 ctx.currentNodeIndex--
}

func (ctx *parseContext) handleAssignment(a *mkparser.Assignment) []starlarkNode {
 // Handle only simple variables
 if !a.Name.Const() || a.Target != nil {
  return []starlarkNode{ctx.newBadNode(a, "Only simple variables are handled")}
 }
 name := a.Name.Strings[0]
 // The `override` directive
 //      override FOO :=
 // is parsed as an assignment to a variable named `override FOO`.
 // There are very few places where `override` is used, just flag it.
 if strings.HasPrefix(name, "override ") {
  return []starlarkNode{ctx.newBadNode(a, "cannot handle override directive")}
 }
 if name == ".KATI_READONLY" {
  // Skip assignments to .KATI_READONLY. If it was in the output file, it
  // would be an error because it would be sorted before the definition of
  // the variable it's trying to make readonly.
  return []starlarkNode{}
 }

 // Soong configuration
 if strings.HasPrefix(name, soongNsPrefix) {
  return ctx.handleSoongNsAssignment(strings.TrimPrefix(name, soongNsPrefix), a)
 }
 lhs := ctx.addVariable(name)
 if lhs == nil {
  return []starlarkNode{ctx.newBadNode(a, "unknown variable %s", name)}
 }
 _, isTraced := ctx.tracedVariables[lhs.name()]
 asgn := &assignmentNode{lhs: lhs, mkValue: a.Value, isTraced: isTraced, location: ctx.errorLocation(a)}
 if lhs.valueType() == starlarkTypeUnknown {
  // Try to divine variable type from the RHS
  asgn.value = ctx.parseMakeString(a, a.Value)
  inferred_type := asgn.value.typ()
  if inferred_type != starlarkTypeUnknown {
   lhs.setValueType(inferred_type)
  }
 }
 if lhs.valueType() == starlarkTypeList {
  xConcat, xBad := ctx.buildConcatExpr(a)
  if xBad != nil {
   asgn.value = xBad
  } else {
   switch len(xConcat.items) {
   case 0:
    asgn.value = &listExpr{}
   case 1:
    asgn.value = xConcat.items[0]
   default:
    asgn.value = xConcat
   }
  }
 } else {
  asgn.value = ctx.parseMakeString(a, a.Value)
 }

 if asgn.lhs.valueType() == starlarkTypeString &&
  asgn.value.typ() != starlarkTypeUnknown &&
  asgn.value.typ() != starlarkTypeString {
  asgn.value = &toStringExpr{expr: asgn.value}
 }

 switch a.Type {
 case "=", ":=":
  asgn.flavor = asgnSet
 case "+=":
  asgn.flavor = asgnAppend
 case "?=":
  if _, ok := lhs.(*productConfigVariable); ok {
   // Make sets all product configuration variables to empty strings before running product
   // config makefiles. ?= will have no effect on a variable that has been assigned before,
   // even if assigned to an empty string. So just skip emitting any code for this
   // assignment.
   return nil
  }
  asgn.flavor = asgnMaybeSet
 default:
  panic(fmt.Errorf("unexpected assignment type %s", a.Type))
 }

 return []starlarkNode{asgn}
}

func (ctx *parseContext) handleSoongNsAssignment(name string, asgn *mkparser.Assignment) []starlarkNode {
 val := ctx.parseMakeString(asgn, asgn.Value)
 if xBad, ok := val.(*badExpr); ok {
  return []starlarkNode{&exprNode{expr: xBad}}
 }

 // Unfortunately, Soong namespaces can be set up by directly setting corresponding Make
 // variables instead of via add_soong_config_namespace + add_soong_config_var_value.
 // Try to divine the call from the assignment as follows:
 if name == "NAMESPACES" {
  // Upon seeng
  //      SOONG_CONFIG_NAMESPACES += foo
  //    remember that there is a namespace `foo` and act as we saw
  //      $(call add_soong_config_namespace,foo)
  s, ok := maybeString(val)
  if !ok {
   return []starlarkNode{ctx.newBadNode(asgn, "cannot handle variables in SOONG_CONFIG_NAMESPACES assignment, please use add_soong_config_namespace instead")}
  }
  result := make([]starlarkNode, 0)
  for _, ns := range strings.Fields(s) {
   ctx.addSoongNamespace(ns)
   result = append(result, &exprNode{&callExpr{
    name:       baseName + ".soong_config_namespace",
    args:       []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{ns}},
    returnType: starlarkTypeVoid,
   }})
  }
  return result
 } else {
  // Upon seeing
  //      SOONG_CONFIG_x_y = v
  // find a namespace called `x` and act as if we encountered
  //      $(call soong_config_set,x,y,v)
  // or check that `x_y` is a namespace, and then add the RHS of this assignment as variables in
  // it.
  // Emit an error in the ambiguous situation (namespaces `foo_bar` with a variable `baz`
  // and `foo` with a variable `bar_baz`.
  namespaceName := ""
  if ctx.hasSoongNamespace(name) {
   namespaceName = name
  }
  var varName string
  for pos, ch := range name {
   if !(ch == '_' && ctx.hasSoongNamespace(name[0:pos])) {
    continue
   }
   if namespaceName != "" {
    return []starlarkNode{ctx.newBadNode(asgn, "ambiguous soong namespace (may be either `%s` or  `%s`)", namespaceName, name[0:pos])}
   }
   namespaceName = name[0:pos]
   varName = name[pos+1:]
  }
  if namespaceName == "" {
   return []starlarkNode{ctx.newBadNode(asgn, "cannot figure out Soong namespace, please use add_soong_config_var_value macro instead")}
  }
  if varName == "" {
   // Remember variables in this namespace
   s, ok := maybeString(val)
   if !ok {
    return []starlarkNode{ctx.newBadNode(asgn, "cannot handle variables in SOONG_CONFIG_ assignment, please use add_soong_config_var_value instead")}
   }
   ctx.updateSoongNamespace(asgn.Type != "+=", namespaceName, strings.Fields(s))
   return []starlarkNode{}
  }

  // Finally, handle assignment to a namespace variable
  if !ctx.hasNamespaceVar(namespaceName, varName) {
   return []starlarkNode{ctx.newBadNode(asgn, "no %s variable in %s namespace, please use add_soong_config_var_value instead", varName, namespaceName)}
  }
  fname := baseName + "." + soongConfigAssign
  if asgn.Type == "+=" {
   fname = baseName + "." + soongConfigAppend
  }
  return []starlarkNode{&exprNode{&callExpr{
   name:       fname,
   args:       []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{namespaceName}, &stringLiteralExpr{varName}, val},
   returnType: starlarkTypeVoid,
  }}}
 }
}

func (ctx *parseContext) buildConcatExpr(a *mkparser.Assignment) (*concatExpr, *badExpr) {
 xConcat := &concatExpr{}
 var xItemList *listExpr
 addToItemList := func(x ...starlarkExpr) {
  if xItemList == nil {
   xItemList = &listExpr{[]starlarkExpr{}}
  }
  xItemList.items = append(xItemList.items, x...)
 }
 finishItemList := func() {
  if xItemList != nil {
   xConcat.items = append(xConcat.items, xItemList)
   xItemList = nil
  }
 }

 items := a.Value.Words()
 for _, item := range items {
  // A function call in RHS is supposed to return a list, all other item
  // expressions return individual elements.
  switch x := ctx.parseMakeString(a, item).(type) {
  case *badExpr:
   return nil, x
  case *stringLiteralExpr:
   addToItemList(maybeConvertToStringList(x).(*listExpr).items...)
  default:
   switch x.typ() {
   case starlarkTypeList:
    finishItemList()
    xConcat.items = append(xConcat.items, x)
   case starlarkTypeString:
    finishItemList()
    xConcat.items = append(xConcat.items, &callExpr{
     object:     x,
     name:       "split",
     args:       nil,
     returnType: starlarkTypeList,
    })
   default:
    addToItemList(x)
   }
  }
 }
 if xItemList != nil {
  xConcat.items = append(xConcat.items, xItemList)
 }
 return xConcat, nil
}

func (ctx *parseContext) newDependentModule(path string, optional bool) *moduleInfo {
 modulePath := ctx.loadedModulePath(path)
 if mi, ok := ctx.dependentModules[modulePath]; ok {
  mi.optional = mi.optional && optional
  return mi
 }
 moduleName := moduleNameForFile(path)
 moduleLocalName := "_" + moduleName
 n, found := ctx.moduleNameCount[moduleName]
 if found {
  moduleLocalName += fmt.Sprintf("%d", n)
 }
 ctx.moduleNameCount[moduleName] = n + 1
 _, err := fs.Stat(ctx.script.sourceFS, path)
 mi := &moduleInfo{
  path:            modulePath,
  originalPath:    path,
  moduleLocalName: moduleLocalName,
  optional:        optional,
  missing:         err != nil,
 }
 ctx.dependentModules[modulePath] = mi
 ctx.script.inherited = append(ctx.script.inherited, mi)
 return mi
}

func (ctx *parseContext) handleSubConfig(
 v mkparser.Node, pathExpr starlarkExpr, loadAlways bool, processModule func(inheritedModule) starlarkNode) []starlarkNode {

 // Allow seeing $(sort $(wildcard realPathExpr)) or $(wildcard realPathExpr)
 // because those are functionally the same as not having the sort/wildcard calls.
 if ce, ok := pathExpr.(*callExpr); ok && ce.name == "rblf.mksort" && len(ce.args) == 1 {
  if ce2, ok2 := ce.args[0].(*callExpr); ok2 && ce2.name == "rblf.expand_wildcard" && len(ce2.args) == 1 {
   pathExpr = ce2.args[0]
  }
 } else if ce2, ok2 := pathExpr.(*callExpr); ok2 && ce2.name == "rblf.expand_wildcard" && len(ce2.args) == 1 {
  pathExpr = ce2.args[0]
 }

 // In a simple case, the name of a module to inherit/include is known statically.
 if path, ok := maybeString(pathExpr); ok {
  // Note that even if this directive loads a module unconditionally, a module may be
  // absent without causing any harm if this directive is inside an if/else block.
  moduleShouldExist := loadAlways && ctx.ifNestLevel == 0
  if strings.Contains(path, "*") {
   if paths, err := fs.Glob(ctx.script.sourceFS, path); err == nil {
    sort.Strings(paths)
    result := make([]starlarkNode, 0)
    for _, p := range paths {
     mi := ctx.newDependentModule(p, !moduleShouldExist)
     result = append(result, processModule(inheritedStaticModule{mi, loadAlways}))
    }
    return result
   } else {
    return []starlarkNode{ctx.newBadNode(v, "cannot glob wildcard argument")}
   }
  } else {
   mi := ctx.newDependentModule(path, !moduleShouldExist)
   return []starlarkNode{processModule(inheritedStaticModule{mi, loadAlways})}
  }
 }

 // If module path references variables (e.g., $(v1)/foo/$(v2)/device-config.mk), find all the paths in the
 // source tree that may be a match and the corresponding variable values. For instance, if the source tree
 // contains vendor1/foo/abc/dev.mk and vendor2/foo/def/dev.mk, the first one will be inherited when
 // (v1, v2) == ('vendor1', 'abc'), and the second one when (v1, v2) == ('vendor2', 'def').
 // We then emit the code that loads all of them, e.g.:
 //    load("//vendor1/foo/abc:dev.rbc", _dev1_init="init")
 //    load("//vendor2/foo/def/dev.rbc", _dev2_init="init")
 // And then inherit it as follows:
 //    _e = {
 //       "vendor1/foo/abc/dev.mk": ("vendor1/foo/abc/dev", _dev1_init),
 //       "vendor2/foo/def/dev.mk": ("vendor2/foo/def/dev", _dev_init2) }.get("%s/foo/%s/dev.mk" % (v1, v2))
 //    if _e:
 //       rblf.inherit(handle, _e[0], _e[1])
 //
 var matchingPaths []string
 var needsWarning = false
 if interpolate, ok := pathExpr.(*interpolateExpr); ok {
  pathPattern := []string{interpolate.chunks[0]}
  for _, chunk := range interpolate.chunks[1:] {
   if chunk != "" {
    pathPattern = append(pathPattern, chunk)
   }
  }
  if len(pathPattern) == 1 {
   pathPattern = append(pathPattern, "")
  }
  matchingPaths = ctx.findMatchingPaths(pathPattern)
  needsWarning = pathPattern[0] == "" && len(ctx.includeTops) == 0
 } else if len(ctx.includeTops) > 0 {
  matchingPaths = append(matchingPaths, ctx.findMatchingPaths([]string{"", ""})...)
 } else {
  return []starlarkNode{ctx.newBadNode(v, "inherit-product/include argument is too complex")}
 }

 // Safeguard against $(call inherit-product,$(PRODUCT_PATH))
 const maxMatchingFiles = 150
 if len(matchingPaths) > maxMatchingFiles {
  return []starlarkNode{ctx.newBadNode(v, "there are >%d files matching the pattern, please rewrite it", maxMatchingFiles)}
 }

 res := inheritedDynamicModule{pathExpr, []*moduleInfo{}, loadAlways, ctx.errorLocation(v), needsWarning}
 for _, p := range matchingPaths {
  // A product configuration files discovered dynamically may attempt to inherit
  // from another one which does not exist in this source tree. Prevent load errors
  // by always loading the dynamic files as optional.
  res.candidateModules = append(res.candidateModules, ctx.newDependentModule(p, true))
 }
 return []starlarkNode{processModule(res)}
}

func (ctx *parseContext) findMatchingPaths(pattern []string) []string {
 files := ctx.script.makefileFinder.Find(".")
 if len(pattern) == 0 {
  return files
 }

 // Create regular expression from the pattern
 regexString := "^" + regexp.QuoteMeta(pattern[0])
 for _, s := range pattern[1:] {
  regexString += ".*" + regexp.QuoteMeta(s)
 }
 regexString += "$"
 rex := regexp.MustCompile(regexString)

 includeTopRegexString := ""
 if len(ctx.includeTops) > 0 {
  for i, top := range ctx.includeTops {
   if i > 0 {
    includeTopRegexString += "|"
   }
   includeTopRegexString += "^" + regexp.QuoteMeta(top)
  }
 } else {
  includeTopRegexString = ".*"
 }

 includeTopRegex := regexp.MustCompile(includeTopRegexString)

 // Now match
 var res []string
 for _, p := range files {
  if rex.MatchString(p) && includeTopRegex.MatchString(p) {
   res = append(res, p)
  }
 }
 return res
}

type inheritProductCallParser struct {
 loadAlways bool
}

func (p *inheritProductCallParser) parse(ctx *parseContext, v mkparser.Node, args *mkparser.MakeString) []starlarkNode {
 args.TrimLeftSpaces()
 args.TrimRightSpaces()
 pathExpr := ctx.parseMakeString(v, args)
 if _, ok := pathExpr.(*badExpr); ok {
  return []starlarkNode{ctx.newBadNode(v, "Unable to parse argument to inherit")}
 }
 return ctx.handleSubConfig(v, pathExpr, p.loadAlways, func(im inheritedModule) starlarkNode {
  return &inheritNode{im, p.loadAlways}
 })
}

func (ctx *parseContext) handleInclude(v *mkparser.Directive) []starlarkNode {
 loadAlways := v.Name[0] != '-'
 v.Args.TrimRightSpaces()
 v.Args.TrimLeftSpaces()
 return ctx.handleSubConfig(v, ctx.parseMakeString(v, v.Args), loadAlways, func(im inheritedModule) starlarkNode {
  return &includeNode{im, loadAlways}
 })
}

func (ctx *parseContext) handleVariable(v *mkparser.Variable) []starlarkNode {
 // Handle:
 //   $(call inherit-product,...)
 //   $(call inherit-product-if-exists,...)
 //   $(info xxx)
 //   $(warning xxx)
 //   $(error xxx)
 //   $(call other-custom-functions,...)

 if name, args, ok := ctx.maybeParseFunctionCall(v, v.Name); ok {
  if kf, ok := knownNodeFunctions[name]; ok {
   return kf.parse(ctx, v, args)
  }
 }

 return []starlarkNode{&exprNode{expr: ctx.parseReference(v, v.Name)}}
}

func (ctx *parseContext) maybeHandleDefine(directive *mkparser.Directive) starlarkNode {
 macro_name := strings.Fields(directive.Args.Strings[0])[0]
 // Ignore the macros that we handle
 _, ignored := ignoredDefines[macro_name]
 _, known := knownFunctions[macro_name]
 if !ignored && !known {
  return ctx.newBadNode(directive, "define is not supported: %s", macro_name)
 }
 return nil
}

func (ctx *parseContext) handleIfBlock(ifDirective *mkparser.Directive) starlarkNode {
 ssSwitch := &switchNode{
  ssCases: []*switchCase{ctx.processBranch(ifDirective)},
 }
 for ctx.hasNodes() && ctx.fatalError == nil {
  node := ctx.getNode()
  switch x := node.(type) {
  case *mkparser.Directive:
   switch x.Name {
   case "else", "elifdef", "elifndef", "elifeq", "elifneq":
    ssSwitch.ssCases = append(ssSwitch.ssCases, ctx.processBranch(x))
   case "endif":
    return ssSwitch
   default:
    return ctx.newBadNode(node, "unexpected directive %s", x.Name)
   }
  default:
   return ctx.newBadNode(ifDirective, "unexpected statement")
  }
 }
 if ctx.fatalError == nil {
  ctx.fatalError = fmt.Errorf("no matching endif for %s", ifDirective.Dump())
 }
 return ctx.newBadNode(ifDirective, "no matching endif for %s", ifDirective.Dump())
}

// processBranch processes a single branch (if/elseif/else) until the next directive
// on the same level.
func (ctx *parseContext) processBranch(check *mkparser.Directive) *switchCase {
 block := &switchCase{gate: ctx.parseCondition(check)}
 defer func() {
  ctx.ifNestLevel--
 }()
 ctx.ifNestLevel++

 for ctx.hasNodes() {
  node := ctx.getNode()
  if d, ok := node.(*mkparser.Directive); ok {
   switch d.Name {
   case "else", "elifdef", "elifndef", "elifeq", "elifneq", "endif":
    ctx.backNode()
    return block
   }
  }
  block.nodes = append(block.nodes, ctx.handleSimpleStatement(node)...)
 }
 ctx.fatalError = fmt.Errorf("no matching endif for %s", check.Dump())
 return block
}

func (ctx *parseContext) parseCondition(check *mkparser.Directive) starlarkNode {
 switch check.Name {
 case "ifdef", "ifndef", "elifdef", "elifndef":
  if !check.Args.Const() {
   return ctx.newBadNode(check, "ifdef variable ref too complex: %s", check.Args.Dump())
  }
  v := NewVariableRefExpr(ctx.addVariable(check.Args.Strings[0]))
  if strings.HasSuffix(check.Name, "ndef") {
   v = ¬Expr{v}
  }
  return &ifNode{
   isElif: strings.HasPrefix(check.Name, "elif"),
   expr:   v,
  }
 case "ifeq", "ifneq", "elifeq", "elifneq":
  return &ifNode{
   isElif: strings.HasPrefix(check.Name, "elif"),
   expr:   ctx.parseCompare(check),
  }
 case "else":
  return &elseNode{}
 default:
  panic(fmt.Errorf("%s: unknown directive: %s", ctx.script.mkFile, check.Dump()))
 }
}

func (ctx *parseContext) newBadExpr(node mkparser.Node, text string, args ...interface{}) starlarkExpr {
 if ctx.errorLogger != nil {
  ctx.errorLogger.NewError(ctx.errorLocation(node), node, text, args...)
 }
 ctx.script.hasErrors = true
 return &badExpr{errorLocation: ctx.errorLocation(node), message: fmt.Sprintf(text, args...)}
}

// records that the given node failed to be converted and includes an explanatory message
func (ctx *parseContext) newBadNode(failedNode mkparser.Node, message string, args ...interface{}) starlarkNode {
 return &exprNode{ctx.newBadExpr(failedNode, message, args...)}
}

func (ctx *parseContext) parseCompare(cond *mkparser.Directive) starlarkExpr {
 // Strip outer parentheses
 mkArg := cloneMakeString(cond.Args)
 mkArg.Strings[0] = strings.TrimLeft(mkArg.Strings[0], "( ")
 n := len(mkArg.Strings)
 mkArg.Strings[n-1] = strings.TrimRight(mkArg.Strings[n-1], ") ")
 args := mkArg.Split(",")
 // TODO(asmundak): handle the case where the arguments are in quotes and space-separated
 if len(args) != 2 {
  return ctx.newBadExpr(cond, "ifeq/ifneq len(args) != 2 %s", cond.Dump())
 }
 args[0].TrimRightSpaces()
 args[1].TrimLeftSpaces()

 isEq := !strings.HasSuffix(cond.Name, "neq")
 xLeft := ctx.parseMakeString(cond, args[0])
 xRight := ctx.parseMakeString(cond, args[1])
 if bad, ok := xLeft.(*badExpr); ok {
  return bad
 }
 if bad, ok := xRight.(*badExpr); ok {
  return bad
 }

 if expr, ok := ctx.parseCompareSpecialCases(cond, xLeft, xRight); ok {
  return expr
 }

 var stringOperand string
 var otherOperand starlarkExpr
 if s, ok := maybeString(xLeft); ok {
  stringOperand = s
  otherOperand = xRight
 } else if s, ok := maybeString(xRight); ok {
  stringOperand = s
  otherOperand = xLeft
 }

 // If we've identified one of the operands as being a string literal, check
 // for some special cases we can do to simplify the resulting expression.
 if otherOperand != nil {
  if stringOperand == "" {
   if isEq {
    return negateExpr(otherOperand)
   } else {
    return otherOperand
   }
  }
  if stringOperand == "true" && otherOperand.typ() == starlarkTypeBool {
   if !isEq {
    return negateExpr(otherOperand)
   } else {
    return otherOperand
   }
  }
  if otherOperand.typ() == starlarkTypeList {
   fields := strings.Fields(stringOperand)
   elements := make([]starlarkExpr, len(fields))
   for i, s := range fields {
    elements[i] = &stringLiteralExpr{literal: s}
   }
   return &eqExpr{
    left:  otherOperand,
    right: &listExpr{elements},
    isEq:  isEq,
   }
  }
  if intOperand, err := strconv.Atoi(strings.TrimSpace(stringOperand)); err == nil && otherOperand.typ() == starlarkTypeInt {
   return &eqExpr{
    left:  otherOperand,
    right: &intLiteralExpr{literal: intOperand},
    isEq:  isEq,
   }
  }
 }

 return &eqExpr{left: xLeft, right: xRight, isEq: isEq}
}

// Given an if statement's directive and the left/right starlarkExprs,
// check if the starlarkExprs are one of a few hardcoded special cases
// that can be converted to a simpler equality expression than simply comparing
// the two.
func (ctx *parseContext) parseCompareSpecialCases(directive *mkparser.Directive, left starlarkExpr,
 right starlarkExpr) (starlarkExpr, bool) {
 isEq := !strings.HasSuffix(directive.Name, "neq")

 // All the special cases require a call on one side and a
 // string literal/variable on the other. Turn the left/right variables into
 // call/value variables, and return false if that's not possible.
 var value starlarkExpr = nil
 call, ok := left.(*callExpr)
 if ok {
  switch right.(type) {
  case *stringLiteralExpr, *variableRefExpr:
   value = right
  }
 } else {
  call, _ = right.(*callExpr)
  switch left.(type) {
  case *stringLiteralExpr, *variableRefExpr:
   value = left
  }
 }

 if call == nil || value == nil {
  return nil, false
 }

 switch call.name {
 case baseName + ".filter":
  return ctx.parseCompareFilterFuncResult(directive, call, value, isEq)
 case baseName + ".findstring":
  return ctx.parseCheckFindstringFuncResult(directive, call, value, !isEq), true
 case baseName + ".strip":
  return ctx.parseCompareStripFuncResult(directive, call, value, !isEq), true
 }
 return nil, false
}

func (ctx *parseContext) parseCompareFilterFuncResult(cond *mkparser.Directive,
 filterFuncCall *callExpr, xValue starlarkExpr, negate bool) (starlarkExpr, bool) {
 // We handle:
 // *  ifeq/ifneq (,$(filter v1 v2 ..., EXPR) becomes if EXPR not in/in ["v1", "v2", ...]
 // *  ifeq/ifneq (,$(filter EXPR, v1 v2 ...) becomes if EXPR not in/in ["v1", "v2", ...]
 if x, ok := xValue.(*stringLiteralExpr); !ok || x.literal != "" {
  return nil, false
 }
 xPattern := filterFuncCall.args[0]
 xText := filterFuncCall.args[1]
 var xInList *stringLiteralExpr
 var expr starlarkExpr
 var ok bool
 if xInList, ok = xPattern.(*stringLiteralExpr); ok && !strings.ContainsRune(xInList.literal, '%') && xText.typ() == starlarkTypeList {
  expr = xText
 } else if xInList, ok = xText.(*stringLiteralExpr); ok {
  expr = xPattern
 } else {
  return nil, false
 }
 slExpr := newStringListExpr(strings.Fields(xInList.literal))
 // Generate simpler code for the common cases:
 if expr.typ() == starlarkTypeList {
  if len(slExpr.items) == 1 {
   // Checking that a string belongs to list
   return &inExpr{isNot: negate, list: expr, expr: slExpr.items[0]}, true
  } else {
   return nil, false
  }
 } else if len(slExpr.items) == 1 {
  return &eqExpr{left: expr, right: slExpr.items[0], isEq: !negate}, true
 } else {
  return &inExpr{isNot: negate, list: newStringListExpr(strings.Fields(xInList.literal)), expr: expr}, true
 }
}

func (ctx *parseContext) parseCheckFindstringFuncResult(directive *mkparser.Directive,
 xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
 if isEmptyString(xValue) {
  return &eqExpr{
   left: &callExpr{
    object:     xCall.args[1],
    name:       "find",
    args:       []starlarkExpr{xCall.args[0]},
    returnType: starlarkTypeInt,
   },
   right: &intLiteralExpr{-1},
   isEq:  !negate,
  }
 } else if s, ok := maybeString(xValue); ok {
  if s2, ok := maybeString(xCall.args[0]); ok && s == s2 {
   return &eqExpr{
    left: &callExpr{
     object:     xCall.args[1],
     name:       "find",
     args:       []starlarkExpr{xCall.args[0]},
     returnType: starlarkTypeInt,
    },
    right: &intLiteralExpr{-1},
    isEq:  negate,
   }
  }
 }
 return ctx.newBadExpr(directive, "$(findstring) can only be compared to nothing or its first argument")
}

func (ctx *parseContext) parseCompareStripFuncResult(directive *mkparser.Directive,
 xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
 if _, ok := xValue.(*stringLiteralExpr); !ok {
  return ctx.newBadExpr(directive, "strip result can be compared only to string: %s", xValue)
 }
 return &eqExpr{
  left: &callExpr{
   name:       "strip",
   args:       xCall.args,
   returnType: starlarkTypeString,
  },
  right: xValue, isEq: !negate}
}

func (ctx *parseContext) maybeParseFunctionCall(node mkparser.Node, ref *mkparser.MakeString) (name string, args *mkparser.MakeString, ok bool) {
 ref.TrimLeftSpaces()
 ref.TrimRightSpaces()

 words := ref.SplitN(" ", 2)
 if !words[0].Const() {
  return "", nil, false
 }

 name = words[0].Dump()
 args = mkparser.SimpleMakeString("", words[0].Pos())
 if len(words) >= 2 {
  args = words[1]
 }
 args.TrimLeftSpaces()
 if name == "call" {
  words = args.SplitN(",", 2)
  if words[0].Empty() || !words[0].Const() {
   return "", nil, false
  }
  name = words[0].Dump()
  if len(words) < 2 {
   args = mkparser.SimpleMakeString("", words[0].Pos())
  } else {
   args = words[1]
  }
 }
 ok = true
 return
}

// parses $(...), returning an expression
func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeString) starlarkExpr {
 ref.TrimLeftSpaces()
 ref.TrimRightSpaces()
 refDump := ref.Dump()

 // Handle only the case where the first (or only) word is constant
 words := ref.SplitN(" ", 2)
 if !words[0].Const() {
  if len(words) == 1 {
   expr := ctx.parseMakeString(node, ref)
   return &callExpr{
    object: &identifierExpr{"cfg"},
    name:   "get",
    args: []starlarkExpr{
     expr,
     &callExpr{
      object: &identifierExpr{"g"},
      name:   "get",
      args: []starlarkExpr{
       expr,
       &stringLiteralExpr{literal: ""},
      },
      returnType: starlarkTypeUnknown,
     },
    },
    returnType: starlarkTypeUnknown,
   }
  } else {
   return ctx.newBadExpr(node, "reference is too complex: %s", refDump)
  }
 }

 if name, _, ok := ctx.maybeParseFunctionCall(node, ref); ok {
  if _, unsupported := unsupportedFunctions[name]; unsupported {
   return ctx.newBadExpr(node, "%s is not supported", refDump)
  }
 }

 // If it is a single word, it can be a simple variable
 // reference or a function call
 if len(words) == 1 && !isMakeControlFunc(refDump) && refDump != "shell" && refDump != "eval" {
  if strings.HasPrefix(refDump, soongNsPrefix) {
   // TODO (asmundak): if we find many, maybe handle them.
   return ctx.newBadExpr(node, "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: %s", refDump)
  }
  // Handle substitution references: https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html
  if strings.Contains(refDump, ":") {
   parts := strings.SplitN(refDump, ":", 2)
   substParts := strings.SplitN(parts[1], "=", 2)
   if len(substParts) < 2 || strings.Count(substParts[0], "%") > 1 {
    return ctx.newBadExpr(node, "Invalid substitution reference")
   }
   if !strings.Contains(substParts[0], "%") {
    if strings.Contains(substParts[1], "%") {
     return ctx.newBadExpr(node, "A substitution reference must have a %% in the \"before\" part of the substitution if it has one in the \"after\" part.")
    }
    substParts[0] = "%" + substParts[0]
    substParts[1] = "%" + substParts[1]
   }
   v := ctx.addVariable(parts[0])
   if v == nil {
    return ctx.newBadExpr(node, "unknown variable %s", refDump)
   }
   return &callExpr{
    name:       baseName + ".mkpatsubst",
    returnType: starlarkTypeString,
    args: []starlarkExpr{
     &stringLiteralExpr{literal: substParts[0]},
     &stringLiteralExpr{literal: substParts[1]},
     NewVariableRefExpr(v),
    },
   }
  }
  if v := ctx.addVariable(refDump); v != nil {
   return NewVariableRefExpr(v)
  }
  return ctx.newBadExpr(node, "unknown variable %s", refDump)
 }

 if name, args, ok := ctx.maybeParseFunctionCall(node, ref); ok {
  if kf, found := knownFunctions[name]; found {
   return kf.parse(ctx, node, args)
  } else {
   return ctx.newBadExpr(node, "cannot handle invoking %s", name)
  }
 }
 return ctx.newBadExpr(node, "cannot handle %s", refDump)
}

type simpleCallParser struct {
 name       string
 returnType starlarkType
 addGlobals bool
 addHandle  bool
}

func (p *simpleCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 expr := &callExpr{name: p.name, returnType: p.returnType}
 if p.addGlobals {
  expr.args = append(expr.args, &globalsExpr{})
 }
 if p.addHandle {
  expr.args = append(expr.args, &identifierExpr{name: "handle"})
 }
 for _, arg := range args.Split(",") {
  arg.TrimLeftSpaces()
  arg.TrimRightSpaces()
  x := ctx.parseMakeString(node, arg)
  if xBad, ok := x.(*badExpr); ok {
   return xBad
  }
  expr.args = append(expr.args, x)
 }
 return expr
}

type makeControlFuncParser struct {
 name string
}

func (p *makeControlFuncParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 // Make control functions need special treatment as everything
 // after the name is a single text argument
 x := ctx.parseMakeString(node, args)
 if xBad, ok := x.(*badExpr); ok {
  return xBad
 }
 return &callExpr{
  name: p.name,
  args: []starlarkExpr{
   &stringLiteralExpr{ctx.script.mkFile},
   x,
  },
  returnType: starlarkTypeUnknown,
 }
}

type shellCallParser struct{}

func (p *shellCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 // Shell functions need special treatment as everything
 // after the name is a single text argument
 x := ctx.parseMakeString(node, args)
 if xBad, ok := x.(*badExpr); ok {
  return xBad
 }
 return &callExpr{
  name:       baseName + ".shell",
  args:       []starlarkExpr{x},
  returnType: starlarkTypeUnknown,
 }
}

type myDirCallParser struct{}

func (p *myDirCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 if !args.Empty() {
  return ctx.newBadExpr(node, "my-dir function cannot have any arguments passed to it.")
 }
 return &stringLiteralExpr{literal: filepath.Dir(ctx.script.mkFile)}
}

type andOrParser struct {
 isAnd bool
}

func (p *andOrParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 if args.Empty() {
  return ctx.newBadExpr(node, "and/or function must have at least 1 argument")
 }
 op := "or"
 if p.isAnd {
  op = "and"
 }

 argsParsed := make([]starlarkExpr, 0)

 for _, arg := range args.Split(",") {
  arg.TrimLeftSpaces()
  arg.TrimRightSpaces()
  x := ctx.parseMakeString(node, arg)
  if xBad, ok := x.(*badExpr); ok {
   return xBad
  }
  argsParsed = append(argsParsed, x)
 }
 typ := starlarkTypeUnknown
 for _, arg := range argsParsed {
  if typ != arg.typ() && arg.typ() != starlarkTypeUnknown && typ != starlarkTypeUnknown {
   return ctx.newBadExpr(node, "Expected all arguments to $(or) or $(and) to have the same type, found %q and %q", typ.String(), arg.typ().String())
  }
  if arg.typ() != starlarkTypeUnknown {
   typ = arg.typ()
  }
 }
 result := argsParsed[0]
 for _, arg := range argsParsed[1:] {
  result = &binaryOpExpr{
   left:       result,
   right:      arg,
   op:         op,
   returnType: typ,
  }
 }
 return result
}

type isProductInListCallParser struct{}

func (p *isProductInListCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 if args.Empty() {
  return ctx.newBadExpr(node, "is-product-in-list requires an argument")
 }
 return &inExpr{
  expr:  NewVariableRefExpr(ctx.addVariable("TARGET_PRODUCT")),
  list:  maybeConvertToStringList(ctx.parseMakeString(node, args)),
  isNot: false,
 }
}

type isVendorBoardPlatformCallParser struct{}

func (p *isVendorBoardPlatformCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 if args.Empty() || !identifierFullMatchRegex.MatchString(args.Dump()) {
  return ctx.newBadExpr(node, "cannot handle non-constant argument to is-vendor-board-platform")
 }
 return &inExpr{
  expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM")),
  list:  NewVariableRefExpr(ctx.addVariable(args.Dump() + "_BOARD_PLATFORMS")),
  isNot: false,
 }
}

type isVendorBoardQcomCallParser struct{}

func (p *isVendorBoardQcomCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 if !args.Empty() {
  return ctx.newBadExpr(node, "is-vendor-board-qcom does not accept any arguments")
 }
 return &inExpr{
  expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM")),
  list:  NewVariableRefExpr(ctx.addVariable("QCOM_BOARD_PLATFORMS")),
  isNot: false,
 }
}

type substCallParser struct {
 fname string
}

func (p *substCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 words := args.Split(",")
 if len(words) != 3 {
  return ctx.newBadExpr(node, "%s function should have 3 arguments", p.fname)
 }
 from := ctx.parseMakeString(node, words[0])
 if xBad, ok := from.(*badExpr); ok {
  return xBad
 }
 to := ctx.parseMakeString(node, words[1])
 if xBad, ok := to.(*badExpr); ok {
  return xBad
 }
 words[2].TrimLeftSpaces()
 words[2].TrimRightSpaces()
 obj := ctx.parseMakeString(node, words[2])
 typ := obj.typ()
 if typ == starlarkTypeString && p.fname == "subst" {
  // Optimization: if it's $(subst from, to, string), emit string.replace(from, to)
  return &callExpr{
   object:     obj,
   name:       "replace",
   args:       []starlarkExpr{from, to},
   returnType: typ,
  }
 }
 return &callExpr{
  name:       baseName + ".mk" + p.fname,
  args:       []starlarkExpr{from, to, obj},
  returnType: obj.typ(),
 }
}

type ifCallParser struct{}

func (p *ifCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 words := args.Split(",")
 if len(words) != 2 && len(words) != 3 {
  return ctx.newBadExpr(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))
 }
 condition := ctx.parseMakeString(node, words[0])
 ifTrue := ctx.parseMakeString(node, words[1])
 var ifFalse starlarkExpr
 if len(words) == 3 {
  ifFalse = ctx.parseMakeString(node, words[2])
 } else {
  switch ifTrue.typ() {
  case starlarkTypeList:
   ifFalse = &listExpr{items: []starlarkExpr{}}
  case starlarkTypeInt:
   ifFalse = &intLiteralExpr{literal: 0}
  case starlarkTypeBool:
   ifFalse = &boolLiteralExpr{literal: false}
  default:
   ifFalse = &stringLiteralExpr{literal: ""}
  }
 }
 return &ifExpr{
  condition,
  ifTrue,
  ifFalse,
 }
}

type ifCallNodeParser struct{}

func (p *ifCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
 words := args.Split(",")
 if len(words) != 2 && len(words) != 3 {
  return []starlarkNode{ctx.newBadNode(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))}
 }

 ifn := &ifNode{expr: ctx.parseMakeString(node, words[0])}
 cases := []*switchCase{
  {
   gate:  ifn,
   nodes: ctx.parseNodeMakeString(node, words[1]),
  },
 }
 if len(words) == 3 {
  cases = append(cases, &switchCase{
   gate:  &elseNode{},
   nodes: ctx.parseNodeMakeString(node, words[2]),
  })
 }
 if len(cases) == 2 {
  if len(cases[1].nodes) == 0 {
   // Remove else branch if it has no contents
   cases = cases[:1]
  } else if len(cases[0].nodes) == 0 {
   // If the if branch has no contents but the else does,
   // move them to the if and negate its condition
   ifn.expr = negateExpr(ifn.expr)
   cases[0].nodes = cases[1].nodes
   cases = cases[:1]
  }
 }

 return []starlarkNode{&switchNode{ssCases: cases}}
}

type foreachCallParser struct{}

func (p *foreachCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 words := args.Split(",")
 if len(words) != 3 {
  return ctx.newBadExpr(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))
 }
 if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
  return ctx.newBadExpr(node, "first argument to foreach function must be a simple string identifier")
 }
 loopVarName := words[0].Strings[0]
 list := ctx.parseMakeString(node, words[1])
 action := ctx.parseMakeString(node, words[2]).transform(func(expr starlarkExpr) starlarkExpr {
  if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
   return &identifierExpr{loopVarName}
  }
  return nil
 })

 if list.typ() != starlarkTypeList {
  list = &callExpr{
   name:       baseName + ".words",
   returnType: starlarkTypeList,
   args:       []starlarkExpr{list},
  }
 }

 var result starlarkExpr = &foreachExpr{
  varName: loopVarName,
  list:    list,
  action:  action,
 }

 if action.typ() == starlarkTypeList {
  result = &callExpr{
   name:       baseName + ".flatten_2d_list",
   args:       []starlarkExpr{result},
   returnType: starlarkTypeList,
  }
 }

 return result
}

func transformNode(node starlarkNode, transformer func(expr starlarkExpr) starlarkExpr) {
 switch a := node.(type) {
 case *ifNode:
  a.expr = a.expr.transform(transformer)
 case *switchCase:
  transformNode(a.gate, transformer)
  for _, n := range a.nodes {
   transformNode(n, transformer)
  }
 case *switchNode:
  for _, n := range a.ssCases {
   transformNode(n, transformer)
  }
 case *exprNode:
  a.expr = a.expr.transform(transformer)
 case *assignmentNode:
  a.value = a.value.transform(transformer)
 case *foreachNode:
  a.list = a.list.transform(transformer)
  for _, n := range a.actions {
   transformNode(n, transformer)
  }
 case *inheritNode:
  if b, ok := a.module.(inheritedDynamicModule); ok {
   b.path = b.path.transform(transformer)
   a.module = b
  }
 case *includeNode:
  if b, ok := a.module.(inheritedDynamicModule); ok {
   b.path = b.path.transform(transformer)
   a.module = b
  }
 }
}

type foreachCallNodeParser struct{}

func (p *foreachCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
 words := args.Split(",")
 if len(words) != 3 {
  return []starlarkNode{ctx.newBadNode(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))}
 }
 if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
  return []starlarkNode{ctx.newBadNode(node, "first argument to foreach function must be a simple string identifier")}
 }

 loopVarName := words[0].Strings[0]

 list := ctx.parseMakeString(node, words[1])
 if list.typ() != starlarkTypeList {
  list = &callExpr{
   name:       baseName + ".words",
   returnType: starlarkTypeList,
   args:       []starlarkExpr{list},
  }
 }

 actions := ctx.parseNodeMakeString(node, words[2])
 // TODO(colefaust): Replace transforming code with something more elegant
 for _, action := range actions {
  transformNode(action, func(expr starlarkExpr) starlarkExpr {
   if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
    return &identifierExpr{loopVarName}
   }
   return nil
  })
 }

 return []starlarkNode{&foreachNode{
  varName: loopVarName,
  list:    list,
  actions: actions,
 }}
}

type wordCallParser struct{}

func (p *wordCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 words := args.Split(",")
 if len(words) != 2 {
  return ctx.newBadExpr(node, "word function should have 2 arguments")
 }
 var index = 0
 if words[0].Const() {
  if i, err := strconv.Atoi(strings.TrimSpace(words[0].Strings[0])); err == nil {
   index = i
  }
 }
 if index < 1 {
  return ctx.newBadExpr(node, "word index should be constant positive integer")
 }
 words[1].TrimLeftSpaces()
 words[1].TrimRightSpaces()
 array := ctx.parseMakeString(node, words[1])
 if bad, ok := array.(*badExpr); ok {
  return bad
 }
 if array.typ() != starlarkTypeList {
  array = &callExpr{
   name:       baseName + ".words",
   args:       []starlarkExpr{array},
   returnType: starlarkTypeList,
  }
 }
 return &indexExpr{array, &intLiteralExpr{index - 1}}
}

type wordsCallParser struct{}

func (p *wordsCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 args.TrimLeftSpaces()
 args.TrimRightSpaces()
 array := ctx.parseMakeString(node, args)
 if bad, ok := array.(*badExpr); ok {
  return bad
 }
 if array.typ() != starlarkTypeList {
  array = &callExpr{
   name:       baseName + ".words",
   args:       []starlarkExpr{array},
   returnType: starlarkTypeList,
  }
 }
 return &callExpr{
  name:       "len",
  args:       []starlarkExpr{array},
  returnType: starlarkTypeInt,
 }
}

func parseIntegerArguments(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString, expectedArgs int) ([]starlarkExpr, error) {
 parsedArgs := make([]starlarkExpr, 0)
 for _, arg := range args.Split(",") {
  expr := ctx.parseMakeString(node, arg)
  if expr.typ() == starlarkTypeList {
   return nil, fmt.Errorf("argument to math argument has type list, which cannot be converted to int")
  }
  if s, ok := maybeString(expr); ok {
   intVal, err := strconv.Atoi(strings.TrimSpace(s))
   if err != nil {
    return nil, err
   }
   expr = &intLiteralExpr{literal: intVal}
  } else if expr.typ() != starlarkTypeInt {
   expr = &callExpr{
    name:       "int",
    args:       []starlarkExpr{expr},
    returnType: starlarkTypeInt,
   }
  }
  parsedArgs = append(parsedArgs, expr)
 }
 if len(parsedArgs) != expectedArgs {
  return nil, fmt.Errorf("function should have %d arguments", expectedArgs)
 }
 return parsedArgs, nil
}

type mathComparisonCallParser struct {
 op string
}

func (p *mathComparisonCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 parsedArgs, err := parseIntegerArguments(ctx, node, args, 2)
 if err != nil {
  return ctx.newBadExpr(node, err.Error())
 }
 return &binaryOpExpr{
  left:       parsedArgs[0],
  right:      parsedArgs[1],
  op:         p.op,
  returnType: starlarkTypeBool,
 }
}

type mathMaxOrMinCallParser struct {
 function string
}

func (p *mathMaxOrMinCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 parsedArgs, err := parseIntegerArguments(ctx, node, args, 2)
 if err != nil {
  return ctx.newBadExpr(node, err.Error())
 }
 return &callExpr{
  object:     nil,
  name:       p.function,
  args:       parsedArgs,
  returnType: starlarkTypeInt,
 }
}

type evalNodeParser struct{}

func (p *evalNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
 parser := mkparser.NewParser("Eval expression", strings.NewReader(args.Dump()))
 nodes, errs := parser.Parse()
 if errs != nil {
  return []starlarkNode{ctx.newBadNode(node, "Unable to parse eval statement")}
 }

 if len(nodes) == 0 {
  return []starlarkNode{}
 } else if len(nodes) == 1 {
  // Replace the nodeLocator with one that just returns the location of
  // the $(eval) node. Otherwise, statements inside an $(eval) will show as
  // being on line 1 of the file, because they're on line 1 of
  // strings.NewReader(args.Dump())
  oldNodeLocator := ctx.script.nodeLocator
  ctx.script.nodeLocator = func(pos mkparser.Pos) int {
   return oldNodeLocator(node.Pos())
  }
  defer func() {
   ctx.script.nodeLocator = oldNodeLocator
  }()

  switch n := nodes[0].(type) {
  case *mkparser.Assignment:
   if n.Name.Const() {
    return ctx.handleAssignment(n)
   }
  case *mkparser.Comment:
   return []starlarkNode{&commentNode{strings.TrimSpace("#" + n.Comment)}}
  case *mkparser.Directive:
   if n.Name == "include" || n.Name == "-include" {
    return ctx.handleInclude(n)
   }
  case *mkparser.Variable:
   // Technically inherit-product(-if-exists) don't need to be put inside
   // an eval, but some makefiles do it, presumably because they copy+pasted
   // from a $(eval include ...)
   if name, _, ok := ctx.maybeParseFunctionCall(n, n.Name); ok {
    if name == "inherit-product" || name == "inherit-product-if-exists" {
     return ctx.handleVariable(n)
    }
   }
  }
 }

 return []starlarkNode{ctx.newBadNode(node, "Eval expression too complex; only assignments, comments, includes, and inherit-products are supported")}
}

type lowerUpperParser struct {
 isUpper bool
}

func (p *lowerUpperParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 fn := "lower"
 if p.isUpper {
  fn = "upper"
 }
 arg := ctx.parseMakeString(node, args)

 return &callExpr{
  object:     arg,
  name:       fn,
  returnType: starlarkTypeString,
 }
}

func (ctx *parseContext) parseMakeString(node mkparser.Node, mk *mkparser.MakeString) starlarkExpr {
 if mk.Const() {
  return &stringLiteralExpr{mk.Dump()}
 }
 if mkRef, ok := mk.SingleVariable(); ok {
  return ctx.parseReference(node, mkRef)
 }
 // If we reached here, it's neither string literal nor a simple variable,
 // we need a full-blown interpolation node that will generate
 // "a%b%c" % (X, Y) for a$(X)b$(Y)c
 parts := make([]starlarkExpr, len(mk.Variables)+len(mk.Strings))
 for i := 0; i < len(parts); i++ {
  if i%2 == 0 {
   parts[i] = &stringLiteralExpr{literal: mk.Strings[i/2]}
  } else {
   parts[i] = ctx.parseReference(node, mk.Variables[i/2].Name)
   if x, ok := parts[i].(*badExpr); ok {
    return x
   }
  }
 }
 return NewInterpolateExpr(parts)
}

func (ctx *parseContext) parseNodeMakeString(node mkparser.Node, mk *mkparser.MakeString) []starlarkNode {
 // Discard any constant values in the make string, as they would be top level
 // string literals and do nothing.
 result := make([]starlarkNode, 0, len(mk.Variables))
 for i := range mk.Variables {
  result = append(result, ctx.handleVariable(&mk.Variables[i])...)
 }
 return result
}

// Handles the statements whose treatment is the same in all contexts: comment,
// assignment, variable (which is a macro call in reality) and all constructs that
// do not handle in any context ('define directive and any unrecognized stuff).
func (ctx *parseContext) handleSimpleStatement(node mkparser.Node) []starlarkNode {
 var result []starlarkNode
 switch x := node.(type) {
 case *mkparser.Comment:
  if n, handled := ctx.maybeHandleAnnotation(x); handled && n != nil {
   result = []starlarkNode{n}
  } else if !handled {
   result = []starlarkNode{&commentNode{strings.TrimSpace("#" + x.Comment)}}
  }
 case *mkparser.Assignment:
  result = ctx.handleAssignment(x)
 case *mkparser.Variable:
  result = ctx.handleVariable(x)
 case *mkparser.Directive:
  switch x.Name {
  case "define":
   if res := ctx.maybeHandleDefine(x); res != nil {
    result = []starlarkNode{res}
   }
  case "include", "-include":
   result = ctx.handleInclude(x)
  case "ifeq", "ifneq", "ifdef", "ifndef":
   result = []starlarkNode{ctx.handleIfBlock(x)}
  default:
   result = []starlarkNode{ctx.newBadNode(x, "unexpected directive %s", x.Name)}
  }
 default:
  result = []starlarkNode{ctx.newBadNode(x, "unsupported line %s", strings.ReplaceAll(x.Dump(), "\n", "\n#"))}
 }

 // Clear the includeTops after each non-comment statement
 // so that include annotations placed on certain statements don't apply
 // globally for the rest of the makefile was well.
 if _, wasComment := node.(*mkparser.Comment); !wasComment {
  ctx.atTopOfMakefile = false
  ctx.includeTops = []string{}
 }

 if result == nil {
  result = []starlarkNode{}
 }

 return result
}

// The types allowed in a type_hint
var typeHintMap = map[string]starlarkType{
 "string": starlarkTypeString,
 "list":   starlarkTypeList,
}

// Processes annotation. An annotation is a comment that starts with #RBC# and provides
// a conversion hint -- say, where to look for the dynamically calculated inherit/include
// paths. Returns true if the comment was a successfully-handled annotation.
func (ctx *parseContext) maybeHandleAnnotation(cnode *mkparser.Comment) (starlarkNode, bool) {
 maybeTrim := func(s, prefix string) (string, bool) {
  if strings.HasPrefix(s, prefix) {
   return strings.TrimSpace(strings.TrimPrefix(s, prefix)), true
  }
  return s, false
 }
 annotation, ok := maybeTrim(cnode.Comment, annotationCommentPrefix)
 if !ok {
  return nil, false
 }
 if p, ok := maybeTrim(annotation, "include_top"); ok {
  // Don't allow duplicate include tops, because then we will generate
  // invalid starlark code. (duplicate keys in the _entry dictionary)
  for _, top := range ctx.includeTops {
   if top == p {
    return nil, true
   }
  }
  ctx.includeTops = append(ctx.includeTops, p)
  return nil, true
 } else if p, ok := maybeTrim(annotation, "type_hint"); ok {
  // Type hints must come at the beginning the file, to avoid confusion
  // if a type hint was specified later and thus only takes effect for half
  // of the file.
  if !ctx.atTopOfMakefile {
   return ctx.newBadNode(cnode, "type_hint annotations must come before the first Makefile statement"), true
  }

  parts := strings.Fields(p)
  if len(parts) <= 1 {
   return ctx.newBadNode(cnode, "Invalid type_hint annotation: %s. Must be a variable type followed by a list of variables of that type", p), true
  }

  var varType starlarkType
  if varType, ok = typeHintMap[parts[0]]; !ok {
   varType = starlarkTypeUnknown
  }
  if varType == starlarkTypeUnknown {
   return ctx.newBadNode(cnode, "Invalid type_hint annotation. Only list/string types are accepted, found %s", parts[0]), true
  }

  for _, name := range parts[1:] {
   // Don't allow duplicate type hints
   if _, ok := ctx.typeHints[name]; ok {
    return ctx.newBadNode(cnode, "Duplicate type hint for variable %s", name), true
   }
   ctx.typeHints[name] = varType
  }
  return nil, true
 }
 return ctx.newBadNode(cnode, "unsupported annotation %s", cnode.Comment), true
}

func (ctx *parseContext) loadedModulePath(path string) string {
 // During the transition to Roboleaf some of the product configuration files
 // will be converted and checked in while the others will be generated on the fly
 // and run. The runner  (rbcrun application) accommodates this by allowing three
 // different ways to specify the loaded file location:
 //  1) load(":<file>",...) loads <file> from the same directory
 //  2) load("//path/relative/to/source/root:<file>", ...) loads <file> source tree
 //  3) load("/absolute/path/to/<file> absolute path
 // If the file being generated and the file it wants to load are in the same directory,
 // generate option 1.
 // Otherwise, if output directory is not specified, generate 2)
 // Finally, if output directory has been specified and the file being generated and
 // the file it wants to load from are in the different directories, generate 2) or 3):
 //  * if the file being loaded exists in the source tree, generate 2)
 //  * otherwise, generate 3)
 // Finally, figure out the loaded module path and name and create a node for it
 loadedModuleDir := filepath.Dir(path)
 base := filepath.Base(path)
 loadedModuleName := strings.TrimSuffix(base, filepath.Ext(base)) + ctx.outputSuffix
 if loadedModuleDir == filepath.Dir(ctx.script.mkFile) {
  return ":" + loadedModuleName
 }
 if ctx.outputDir == "" {
  return fmt.Sprintf("//%s:%s", loadedModuleDir, loadedModuleName)
 }
 if _, err := os.Stat(filepath.Join(loadedModuleDir, loadedModuleName)); err == nil {
  return fmt.Sprintf("//%s:%s", loadedModuleDir, loadedModuleName)
 }
 return filepath.Join(ctx.outputDir, loadedModuleDir, loadedModuleName)
}

func (ctx *parseContext) addSoongNamespace(ns string) {
 if _, ok := ctx.soongNamespaces[ns]; ok {
  return
 }
 ctx.soongNamespaces[ns] = make(map[string]bool)
}

func (ctx *parseContext) hasSoongNamespace(name string) bool {
 _, ok := ctx.soongNamespaces[name]
 return ok
}

func (ctx *parseContext) updateSoongNamespace(replace bool, namespaceName string, varNames []string) {
 ctx.addSoongNamespace(namespaceName)
 vars := ctx.soongNamespaces[namespaceName]
 if replace {
  vars = make(map[string]bool)
  ctx.soongNamespaces[namespaceName] = vars
 }
 for _, v := range varNames {
  vars[v] = true
 }
}

func (ctx *parseContext) hasNamespaceVar(namespaceName string, varName string) bool {
 vars, ok := ctx.soongNamespaces[namespaceName]
 if ok {
  _, ok = vars[varName]
 }
 return ok
}

func (ctx *parseContext) errorLocation(node mkparser.Node) ErrorLocation {
 return ErrorLocation{ctx.script.mkFile, ctx.script.nodeLocator(node.Pos())}
}

func (ss *StarlarkScript) String() string {
 return NewGenerateContext(ss).emit()
}

func (ss *StarlarkScript) SubConfigFiles() []string {

 var subs []string
 for _, src := range ss.inherited {
  subs = append(subs, src.originalPath)
 }
 return subs
}

func (ss *StarlarkScript) HasErrors() bool {
 return ss.hasErrors
}

// Convert reads and parses a makefile. If successful, parsed tree
// is returned and then can be passed to String() to get the generated
// Starlark file.
func Convert(req Request) (*StarlarkScript, error) {
 reader := req.Reader
 if reader == nil {
  mkContents, err := ioutil.ReadFile(req.MkFile)
  if err != nil {
   return nil, err
  }
  reader = bytes.NewBuffer(mkContents)
 }
 parser := mkparser.NewParser(req.MkFile, reader)
 nodes, errs := parser.Parse()
 if len(errs) > 0 {
  for _, e := range errs {
   fmt.Fprintln(os.Stderr, "ERROR:", e)
  }
  return nil, fmt.Errorf("bad makefile %s", req.MkFile)
 }
 starScript := &StarlarkScript{
  moduleName:     moduleNameForFile(req.MkFile),
  mkFile:         req.MkFile,
  traceCalls:     req.TraceCalls,
  sourceFS:       req.SourceFS,
  makefileFinder: req.MakefileFinder,
  nodeLocator:    func(pos mkparser.Pos) int { return parser.Unpack(pos).Line },
  nodes:          make([]starlarkNode, 0),
 }
 ctx := newParseContext(starScript, nodes)
 ctx.outputSuffix = req.OutputSuffix
 ctx.outputDir = req.OutputDir
 ctx.errorLogger = req.ErrorLogger
 if len(req.TracedVariables) > 0 {
  ctx.tracedVariables = make(map[string]bool)
  for _, v := range req.TracedVariables {
   ctx.tracedVariables[v] = true
  }
 }
 for ctx.hasNodes() && ctx.fatalError == nil {
  starScript.nodes = append(starScript.nodes, ctx.handleSimpleStatement(ctx.getNode())...)
 }
 if ctx.fatalError != nil {
  return nil, ctx.fatalError
 }
 return starScript, nil
}

func Launcher(mainModuleUri, inputVariablesUri, mainModuleName string) string {
 var buf bytes.Buffer
 fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
 fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
 fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
 fmt.Fprintf(&buf, "%s(%s(%q, init, input_variables_init))\n", cfnPrintVars, cfnMain, mainModuleName)
 return buf.String()
}

func BoardLauncher(mainModuleUri string, inputVariablesUri string) string {
 var buf bytes.Buffer
 fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
 fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
 fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
 fmt.Fprintf(&buf, "%s(%s(init, input_variables_init))\n", cfnPrintVars, cfnBoardMain)
 return buf.String()
}

func MakePath2ModuleName(mkPath string) string {
 return strings.TrimSuffix(mkPath, filepath.Ext(mkPath))
}

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