• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Convert makefile containing device configuration to Starlark file
16// The conversion can handle the following constructs in a makefile:
17//   - comments
18//   - simple variable assignments
19//   - $(call init-product,<file>)
20//   - $(call inherit-product-if-exists
21//   - if directives
22//
23// All other constructs are carried over to the output starlark file as comments.
24package mk2rbc
25
26import (
27	"bytes"
28	"fmt"
29	"io"
30	"io/fs"
31	"io/ioutil"
32	"os"
33	"path/filepath"
34	"regexp"
35	"sort"
36	"strconv"
37	"strings"
38	"text/scanner"
39
40	mkparser "android/soong/androidmk/parser"
41)
42
43const (
44	annotationCommentPrefix = "RBC#"
45	baseUri                 = "//build/make/core:product_config.rbc"
46	// The name of the struct exported by the product_config.rbc
47	// that contains the functions and variables available to
48	// product configuration Starlark files.
49	baseName = "rblf"
50
51	soongNsPrefix = "SOONG_CONFIG_"
52
53	// And here are the functions and variables:
54	cfnGetCfg         = baseName + ".cfg"
55	cfnMain           = baseName + ".product_configuration"
56	cfnBoardMain      = baseName + ".board_configuration"
57	cfnPrintVars      = baseName + ".printvars"
58	cfnInherit        = baseName + ".inherit"
59	cfnSetListDefault = baseName + ".setdefault"
60)
61
62const (
63	soongConfigAppend = "soong_config_append"
64	soongConfigAssign = "soong_config_set"
65)
66
67var knownFunctions = map[string]interface {
68	parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr
69}{
70	"abspath":                              &simpleCallParser{name: baseName + ".abspath", returnType: starlarkTypeString},
71	"add-product-dex-preopt-module-config": &simpleCallParser{name: baseName + ".add_product_dex_preopt_module_config", returnType: starlarkTypeString, addHandle: true},
72	"add_soong_config_namespace":           &simpleCallParser{name: baseName + ".soong_config_namespace", returnType: starlarkTypeVoid, addGlobals: true},
73	"add_soong_config_var_value":           &simpleCallParser{name: baseName + ".soong_config_set", returnType: starlarkTypeVoid, addGlobals: true},
74	soongConfigAssign:                      &simpleCallParser{name: baseName + ".soong_config_set", returnType: starlarkTypeVoid, addGlobals: true},
75	soongConfigAppend:                      &simpleCallParser{name: baseName + ".soong_config_append", returnType: starlarkTypeVoid, addGlobals: true},
76	"soong_config_get":                     &simpleCallParser{name: baseName + ".soong_config_get", returnType: starlarkTypeString, addGlobals: true},
77	"add-to-product-copy-files-if-exists":  &simpleCallParser{name: baseName + ".copy_if_exists", returnType: starlarkTypeList},
78	"addprefix":                            &simpleCallParser{name: baseName + ".addprefix", returnType: starlarkTypeList},
79	"addsuffix":                            &simpleCallParser{name: baseName + ".addsuffix", returnType: starlarkTypeList},
80	"and":                                  &andOrParser{isAnd: true},
81	"clear-var-list":                       &simpleCallParser{name: baseName + ".clear_var_list", returnType: starlarkTypeVoid, addGlobals: true, addHandle: true},
82	"copy-files":                           &simpleCallParser{name: baseName + ".copy_files", returnType: starlarkTypeList},
83	"dir":                                  &simpleCallParser{name: baseName + ".dir", returnType: starlarkTypeString},
84	"dist-for-goals":                       &simpleCallParser{name: baseName + ".mkdist_for_goals", returnType: starlarkTypeVoid, addGlobals: true},
85	"enforce-product-packages-exist":       &simpleCallParser{name: baseName + ".enforce_product_packages_exist", returnType: starlarkTypeVoid, addHandle: true},
86	"error":                                &makeControlFuncParser{name: baseName + ".mkerror"},
87	"findstring":                           &simpleCallParser{name: baseName + ".findstring", returnType: starlarkTypeInt},
88	"find-copy-subdir-files":               &simpleCallParser{name: baseName + ".find_and_copy", returnType: starlarkTypeList},
89	"filter":                               &simpleCallParser{name: baseName + ".filter", returnType: starlarkTypeList},
90	"filter-out":                           &simpleCallParser{name: baseName + ".filter_out", returnType: starlarkTypeList},
91	"firstword":                            &simpleCallParser{name: baseName + ".first_word", returnType: starlarkTypeString},
92	"foreach":                              &foreachCallParser{},
93	"if":                                   &ifCallParser{},
94	"info":                                 &makeControlFuncParser{name: baseName + ".mkinfo"},
95	"is-board-platform":                    &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
96	"is-board-platform2":                   &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
97	"is-board-platform-in-list":            &simpleCallParser{name: baseName + ".board_platform_in", returnType: starlarkTypeBool, addGlobals: true},
98	"is-board-platform-in-list2":           &simpleCallParser{name: baseName + ".board_platform_in", returnType: starlarkTypeBool, addGlobals: true},
99	"is-product-in-list":                   &isProductInListCallParser{},
100	"is-vendor-board-platform":             &isVendorBoardPlatformCallParser{},
101	"is-vendor-board-qcom":                 &isVendorBoardQcomCallParser{},
102	"lastword":                             &simpleCallParser{name: baseName + ".last_word", returnType: starlarkTypeString},
103	"notdir":                               &simpleCallParser{name: baseName + ".notdir", returnType: starlarkTypeString},
104	"math_max":                             &mathMaxOrMinCallParser{function: "max"},
105	"math_min":                             &mathMaxOrMinCallParser{function: "min"},
106	"math_gt_or_eq":                        &mathComparisonCallParser{op: ">="},
107	"math_gt":                              &mathComparisonCallParser{op: ">"},
108	"math_lt":                              &mathComparisonCallParser{op: "<"},
109	"my-dir":                               &myDirCallParser{},
110	"or":                                   &andOrParser{isAnd: false},
111	"patsubst":                             &substCallParser{fname: "patsubst"},
112	"product-copy-files-by-pattern":        &simpleCallParser{name: baseName + ".product_copy_files_by_pattern", returnType: starlarkTypeList},
113	"require-artifacts-in-path":            &simpleCallParser{name: baseName + ".require_artifacts_in_path", returnType: starlarkTypeVoid, addHandle: true},
114	"require-artifacts-in-path-relaxed":    &simpleCallParser{name: baseName + ".require_artifacts_in_path_relaxed", returnType: starlarkTypeVoid, addHandle: true},
115	// TODO(asmundak): remove it once all calls are removed from configuration makefiles. see b/183161002
116	"shell":    &shellCallParser{},
117	"sort":     &simpleCallParser{name: baseName + ".mksort", returnType: starlarkTypeList},
118	"strip":    &simpleCallParser{name: baseName + ".mkstrip", returnType: starlarkTypeString},
119	"subst":    &substCallParser{fname: "subst"},
120	"to-lower": &lowerUpperParser{isUpper: false},
121	"to-upper": &lowerUpperParser{isUpper: true},
122	"warning":  &makeControlFuncParser{name: baseName + ".mkwarning"},
123	"word":     &wordCallParser{},
124	"words":    &wordsCallParser{},
125	"wildcard": &simpleCallParser{name: baseName + ".expand_wildcard", returnType: starlarkTypeList},
126}
127
128// The same as knownFunctions, but returns a []starlarkNode instead of a starlarkExpr
129var knownNodeFunctions = map[string]interface {
130	parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode
131}{
132	"eval":                      &evalNodeParser{},
133	"if":                        &ifCallNodeParser{},
134	"inherit-product":           &inheritProductCallParser{loadAlways: true},
135	"inherit-product-if-exists": &inheritProductCallParser{loadAlways: false},
136	"foreach":                   &foreachCallNodeParser{},
137}
138
139// These look like variables, but are actually functions, and would give
140// undefined variable errors if we converted them as variables. Instead,
141// emit an error instead of converting them.
142var unsupportedFunctions = map[string]bool{
143	"local-generated-sources-dir": true,
144	"local-intermediates-dir":     true,
145}
146
147// These are functions that we don't implement conversions for, but
148// we allow seeing their definitions in the product config files.
149var ignoredDefines = map[string]bool{
150	"find-word-in-list":                   true, // internal macro
151	"get-vendor-board-platforms":          true, // internal macro, used by is-board-platform, etc.
152	"is-android-codename":                 true, // unused by product config
153	"is-android-codename-in-list":         true, // unused by product config
154	"is-chipset-in-board-platform":        true, // unused by product config
155	"is-chipset-prefix-in-board-platform": true, // unused by product config
156	"is-not-board-platform":               true, // defined but never used
157	"is-platform-sdk-version-at-least":    true, // unused by product config
158	"match-prefix":                        true, // internal macro
159	"match-word":                          true, // internal macro
160	"match-word-in-list":                  true, // internal macro
161	"tb-modules":                          true, // defined in hardware/amlogic/tb_modules/tb_detect.mk, unused
162}
163
164var identifierFullMatchRegex = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
165
166func RelativeToCwd(path string) (string, error) {
167	cwd, err := os.Getwd()
168	if err != nil {
169		return "", err
170	}
171	path, err = filepath.Rel(cwd, path)
172	if err != nil {
173		return "", err
174	}
175	if strings.HasPrefix(path, "../") {
176		return "", fmt.Errorf("Could not make path relative to current working directory: " + path)
177	}
178	return path, nil
179}
180
181// Conversion request parameters
182type Request struct {
183	MkFile          string    // file to convert
184	Reader          io.Reader // if set, read input from this stream instead
185	OutputSuffix    string    // generated Starlark files suffix
186	OutputDir       string    // if set, root of the output hierarchy
187	ErrorLogger     ErrorLogger
188	TracedVariables []string // trace assignment to these variables
189	TraceCalls      bool
190	SourceFS        fs.FS
191	MakefileFinder  MakefileFinder
192}
193
194// ErrorLogger prints errors and gathers error statistics.
195// Its NewError function is called on every error encountered during the conversion.
196type ErrorLogger interface {
197	NewError(el ErrorLocation, node mkparser.Node, text string, args ...interface{})
198}
199
200type ErrorLocation struct {
201	MkFile string
202	MkLine int
203}
204
205func (el ErrorLocation) String() string {
206	return fmt.Sprintf("%s:%d", el.MkFile, el.MkLine)
207}
208
209// Derives module name for a given file. It is base name
210// (file name without suffix), with some characters replaced to make it a Starlark identifier
211func moduleNameForFile(mkFile string) string {
212	base := strings.TrimSuffix(filepath.Base(mkFile), filepath.Ext(mkFile))
213	// TODO(asmundak): what else can be in the product file names?
214	return strings.NewReplacer("-", "_", ".", "_").Replace(base)
215
216}
217
218func cloneMakeString(mkString *mkparser.MakeString) *mkparser.MakeString {
219	r := &mkparser.MakeString{StringPos: mkString.StringPos}
220	r.Strings = append(r.Strings, mkString.Strings...)
221	r.Variables = append(r.Variables, mkString.Variables...)
222	return r
223}
224
225func isMakeControlFunc(s string) bool {
226	return s == "error" || s == "warning" || s == "info"
227}
228
229// varAssignmentScope points to the last assignment for each variable
230// in the current block. It is used during the parsing to chain
231// the assignments to a variable together.
232type varAssignmentScope struct {
233	outer *varAssignmentScope
234	vars  map[string]bool
235}
236
237// Starlark output generation context
238type generationContext struct {
239	buf            strings.Builder
240	starScript     *StarlarkScript
241	indentLevel    int
242	inAssignment   bool
243	tracedCount    int
244	varAssignments *varAssignmentScope
245}
246
247func NewGenerateContext(ss *StarlarkScript) *generationContext {
248	return &generationContext{
249		starScript: ss,
250		varAssignments: &varAssignmentScope{
251			outer: nil,
252			vars:  make(map[string]bool),
253		},
254	}
255}
256
257func (gctx *generationContext) pushVariableAssignments() {
258	va := &varAssignmentScope{
259		outer: gctx.varAssignments,
260		vars:  make(map[string]bool),
261	}
262	gctx.varAssignments = va
263}
264
265func (gctx *generationContext) popVariableAssignments() {
266	gctx.varAssignments = gctx.varAssignments.outer
267}
268
269func (gctx *generationContext) hasBeenAssigned(v variable) bool {
270	for va := gctx.varAssignments; va != nil; va = va.outer {
271		if _, ok := va.vars[v.name()]; ok {
272			return true
273		}
274	}
275	return false
276}
277
278func (gctx *generationContext) setHasBeenAssigned(v variable) {
279	gctx.varAssignments.vars[v.name()] = true
280}
281
282// emit returns generated script
283func (gctx *generationContext) emit() string {
284	ss := gctx.starScript
285
286	// The emitted code has the following layout:
287	//    <initial comments>
288	//    preamble, i.e.,
289	//      load statement for the runtime support
290	//      load statement for each unique submodule pulled in by this one
291	//    def init(g, handle):
292	//      cfg = rblf.cfg(handle)
293	//      <statements>
294	//      <warning if conversion was not clean>
295
296	iNode := len(ss.nodes)
297	for i, node := range ss.nodes {
298		if _, ok := node.(*commentNode); !ok {
299			iNode = i
300			break
301		}
302		node.emit(gctx)
303	}
304
305	gctx.emitPreamble()
306
307	gctx.newLine()
308	// The arguments passed to the init function are the global dictionary
309	// ('g') and the product configuration dictionary ('cfg')
310	gctx.write("def init(g, handle):")
311	gctx.indentLevel++
312	if gctx.starScript.traceCalls {
313		gctx.newLine()
314		gctx.writef(`print(">%s")`, gctx.starScript.mkFile)
315	}
316	gctx.newLine()
317	gctx.writef("cfg = %s(handle)", cfnGetCfg)
318	for _, node := range ss.nodes[iNode:] {
319		node.emit(gctx)
320	}
321
322	if gctx.starScript.traceCalls {
323		gctx.newLine()
324		gctx.writef(`print("<%s")`, gctx.starScript.mkFile)
325	}
326	gctx.indentLevel--
327	gctx.write("\n")
328	return gctx.buf.String()
329}
330
331func (gctx *generationContext) emitPreamble() {
332	gctx.newLine()
333	gctx.writef("load(%q, %q)", baseUri, baseName)
334	// Emit exactly one load statement for each URI.
335	loadedSubConfigs := make(map[string]string)
336	for _, mi := range gctx.starScript.inherited {
337		uri := mi.path
338		if strings.HasPrefix(uri, "/") && !strings.HasPrefix(uri, "//") {
339			var err error
340			uri, err = RelativeToCwd(uri)
341			if err != nil {
342				panic(err)
343			}
344			uri = "//" + uri
345		}
346		if m, ok := loadedSubConfigs[uri]; ok {
347			// No need to emit load statement, but fix module name.
348			mi.moduleLocalName = m
349			continue
350		}
351		if mi.optional || mi.missing {
352			uri += "|init"
353		}
354		gctx.newLine()
355		gctx.writef("load(%q, %s = \"init\")", uri, mi.entryName())
356		loadedSubConfigs[uri] = mi.moduleLocalName
357	}
358	gctx.write("\n")
359}
360
361func (gctx *generationContext) emitPass() {
362	gctx.newLine()
363	gctx.write("pass")
364}
365
366func (gctx *generationContext) write(ss ...string) {
367	for _, s := range ss {
368		gctx.buf.WriteString(s)
369	}
370}
371
372func (gctx *generationContext) writef(format string, args ...interface{}) {
373	gctx.write(fmt.Sprintf(format, args...))
374}
375
376func (gctx *generationContext) newLine() {
377	if gctx.buf.Len() == 0 {
378		return
379	}
380	gctx.write("\n")
381	gctx.writef("%*s", 2*gctx.indentLevel, "")
382}
383
384func (gctx *generationContext) emitConversionError(el ErrorLocation, message string) {
385	gctx.writef(`rblf.mk2rbc_error("%s", %q)`, el, message)
386}
387
388func (gctx *generationContext) emitLoadCheck(im inheritedModule) {
389	if !im.needsLoadCheck() {
390		return
391	}
392	gctx.newLine()
393	gctx.writef("if not %s:", im.entryName())
394	gctx.indentLevel++
395	gctx.newLine()
396	gctx.write(`rblf.mkerror("`, gctx.starScript.mkFile, `", "Cannot find %s" % (`)
397	im.pathExpr().emit(gctx)
398	gctx.write("))")
399	gctx.indentLevel--
400}
401
402type knownVariable struct {
403	name      string
404	class     varClass
405	valueType starlarkType
406}
407
408type knownVariables map[string]knownVariable
409
410func (pcv knownVariables) NewVariable(name string, varClass varClass, valueType starlarkType) {
411	v, exists := pcv[name]
412	if !exists {
413		pcv[name] = knownVariable{name, varClass, valueType}
414		return
415	}
416	// Conflict resolution:
417	//    * config class trumps everything
418	//    * any type trumps unknown type
419	match := varClass == v.class
420	if !match {
421		if varClass == VarClassConfig {
422			v.class = VarClassConfig
423			match = true
424		} else if v.class == VarClassConfig {
425			match = true
426		}
427	}
428	if valueType != v.valueType {
429		if valueType != starlarkTypeUnknown {
430			if v.valueType == starlarkTypeUnknown {
431				v.valueType = valueType
432			} else {
433				match = false
434			}
435		}
436	}
437	if !match {
438		fmt.Fprintf(os.Stderr, "cannot redefine %s as %v/%v (already defined as %v/%v)\n",
439			name, varClass, valueType, v.class, v.valueType)
440	}
441}
442
443// All known product variables.
444var KnownVariables = make(knownVariables)
445
446func init() {
447	for _, kv := range []string{
448		// Kernel-related variables that we know are lists.
449		"BOARD_VENDOR_KERNEL_MODULES",
450		"BOARD_VENDOR_RAMDISK_KERNEL_MODULES",
451		"BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD",
452		"BOARD_RECOVERY_KERNEL_MODULES",
453		// Other variables we knwo are lists
454		"ART_APEX_JARS",
455	} {
456		KnownVariables.NewVariable(kv, VarClassSoong, starlarkTypeList)
457	}
458}
459
460// Information about the generated Starlark script.
461type StarlarkScript struct {
462	mkFile         string
463	moduleName     string
464	mkPos          scanner.Position
465	nodes          []starlarkNode
466	inherited      []*moduleInfo
467	hasErrors      bool
468	traceCalls     bool // print enter/exit each init function
469	sourceFS       fs.FS
470	makefileFinder MakefileFinder
471	nodeLocator    func(pos mkparser.Pos) int
472}
473
474// parseContext holds the script we are generating and all the ephemeral data
475// needed during the parsing.
476type parseContext struct {
477	script           *StarlarkScript
478	nodes            []mkparser.Node // Makefile as parsed by mkparser
479	currentNodeIndex int             // Node in it we are processing
480	ifNestLevel      int
481	moduleNameCount  map[string]int // count of imported modules with given basename
482	fatalError       error
483	outputSuffix     string
484	errorLogger      ErrorLogger
485	tracedVariables  map[string]bool // variables to be traced in the generated script
486	variables        map[string]variable
487	outputDir        string
488	dependentModules map[string]*moduleInfo
489	soongNamespaces  map[string]map[string]bool
490	includeTops      []string
491	typeHints        map[string]starlarkType
492	atTopOfMakefile  bool
493}
494
495func newParseContext(ss *StarlarkScript, nodes []mkparser.Node) *parseContext {
496	predefined := []struct{ name, value string }{
497		{"SRC_TARGET_DIR", filepath.Join("build", "make", "target")},
498		{"LOCAL_PATH", filepath.Dir(ss.mkFile)},
499		{"MAKEFILE_LIST", ss.mkFile},
500		{"TOPDIR", ""}, // TOPDIR is just set to an empty string in cleanbuild.mk and core.mk
501		// TODO(asmundak): maybe read it from build/make/core/envsetup.mk?
502		{"TARGET_COPY_OUT_SYSTEM", "system"},
503		{"TARGET_COPY_OUT_SYSTEM_OTHER", "system_other"},
504		{"TARGET_COPY_OUT_DATA", "data"},
505		{"TARGET_COPY_OUT_ASAN", filepath.Join("data", "asan")},
506		{"TARGET_COPY_OUT_OEM", "oem"},
507		{"TARGET_COPY_OUT_RAMDISK", "ramdisk"},
508		{"TARGET_COPY_OUT_DEBUG_RAMDISK", "debug_ramdisk"},
509		{"TARGET_COPY_OUT_VENDOR_DEBUG_RAMDISK", "vendor_debug_ramdisk"},
510		{"TARGET_COPY_OUT_TEST_HARNESS_RAMDISK", "test_harness_ramdisk"},
511		{"TARGET_COPY_OUT_ROOT", "root"},
512		{"TARGET_COPY_OUT_RECOVERY", "recovery"},
513		{"TARGET_COPY_OUT_VENDOR_RAMDISK", "vendor_ramdisk"},
514		// TODO(asmundak): to process internal config files, we need the following variables:
515		//    TARGET_VENDOR
516		//    target_base_product
517		//
518
519		// the following utility variables are set in build/make/common/core.mk:
520		{"empty", ""},
521		{"space", " "},
522		{"comma", ","},
523		{"newline", "\n"},
524		{"pound", "#"},
525		{"backslash", "\\"},
526	}
527	ctx := &parseContext{
528		script:           ss,
529		nodes:            nodes,
530		currentNodeIndex: 0,
531		ifNestLevel:      0,
532		moduleNameCount:  make(map[string]int),
533		variables:        make(map[string]variable),
534		dependentModules: make(map[string]*moduleInfo),
535		soongNamespaces:  make(map[string]map[string]bool),
536		includeTops:      []string{},
537		typeHints:        make(map[string]starlarkType),
538		atTopOfMakefile:  true,
539	}
540	for _, item := range predefined {
541		ctx.variables[item.name] = &predefinedVariable{
542			baseVariable: baseVariable{nam: item.name, typ: starlarkTypeString},
543			value:        &stringLiteralExpr{item.value},
544		}
545	}
546
547	return ctx
548}
549
550func (ctx *parseContext) hasNodes() bool {
551	return ctx.currentNodeIndex < len(ctx.nodes)
552}
553
554func (ctx *parseContext) getNode() mkparser.Node {
555	if !ctx.hasNodes() {
556		return nil
557	}
558	node := ctx.nodes[ctx.currentNodeIndex]
559	ctx.currentNodeIndex++
560	return node
561}
562
563func (ctx *parseContext) backNode() {
564	if ctx.currentNodeIndex <= 0 {
565		panic("Cannot back off")
566	}
567	ctx.currentNodeIndex--
568}
569
570func (ctx *parseContext) handleAssignment(a *mkparser.Assignment) []starlarkNode {
571	// Handle only simple variables
572	if !a.Name.Const() || a.Target != nil {
573		return []starlarkNode{ctx.newBadNode(a, "Only simple variables are handled")}
574	}
575	name := a.Name.Strings[0]
576	// The `override` directive
577	//      override FOO :=
578	// is parsed as an assignment to a variable named `override FOO`.
579	// There are very few places where `override` is used, just flag it.
580	if strings.HasPrefix(name, "override ") {
581		return []starlarkNode{ctx.newBadNode(a, "cannot handle override directive")}
582	}
583	if name == ".KATI_READONLY" {
584		// Skip assignments to .KATI_READONLY. If it was in the output file, it
585		// would be an error because it would be sorted before the definition of
586		// the variable it's trying to make readonly.
587		return []starlarkNode{}
588	}
589
590	// Soong configuration
591	if strings.HasPrefix(name, soongNsPrefix) {
592		return ctx.handleSoongNsAssignment(strings.TrimPrefix(name, soongNsPrefix), a)
593	}
594	lhs := ctx.addVariable(name)
595	if lhs == nil {
596		return []starlarkNode{ctx.newBadNode(a, "unknown variable %s", name)}
597	}
598	_, isTraced := ctx.tracedVariables[lhs.name()]
599	asgn := &assignmentNode{lhs: lhs, mkValue: a.Value, isTraced: isTraced, location: ctx.errorLocation(a)}
600	if lhs.valueType() == starlarkTypeUnknown {
601		// Try to divine variable type from the RHS
602		asgn.value = ctx.parseMakeString(a, a.Value)
603		inferred_type := asgn.value.typ()
604		if inferred_type != starlarkTypeUnknown {
605			lhs.setValueType(inferred_type)
606		}
607	}
608	if lhs.valueType() == starlarkTypeList {
609		xConcat, xBad := ctx.buildConcatExpr(a)
610		if xBad != nil {
611			asgn.value = xBad
612		} else {
613			switch len(xConcat.items) {
614			case 0:
615				asgn.value = &listExpr{}
616			case 1:
617				asgn.value = xConcat.items[0]
618			default:
619				asgn.value = xConcat
620			}
621		}
622	} else {
623		asgn.value = ctx.parseMakeString(a, a.Value)
624	}
625
626	if asgn.lhs.valueType() == starlarkTypeString &&
627		asgn.value.typ() != starlarkTypeUnknown &&
628		asgn.value.typ() != starlarkTypeString {
629		asgn.value = &toStringExpr{expr: asgn.value}
630	}
631
632	switch a.Type {
633	case "=", ":=":
634		asgn.flavor = asgnSet
635	case "+=":
636		asgn.flavor = asgnAppend
637	case "?=":
638		if _, ok := lhs.(*productConfigVariable); ok {
639			// Make sets all product configuration variables to empty strings before running product
640			// config makefiles. ?= will have no effect on a variable that has been assigned before,
641			// even if assigned to an empty string. So just skip emitting any code for this
642			// assignment.
643			return nil
644		}
645		asgn.flavor = asgnMaybeSet
646	default:
647		panic(fmt.Errorf("unexpected assignment type %s", a.Type))
648	}
649
650	return []starlarkNode{asgn}
651}
652
653func (ctx *parseContext) handleSoongNsAssignment(name string, asgn *mkparser.Assignment) []starlarkNode {
654	val := ctx.parseMakeString(asgn, asgn.Value)
655	if xBad, ok := val.(*badExpr); ok {
656		return []starlarkNode{&exprNode{expr: xBad}}
657	}
658
659	// Unfortunately, Soong namespaces can be set up by directly setting corresponding Make
660	// variables instead of via add_soong_config_namespace + add_soong_config_var_value.
661	// Try to divine the call from the assignment as follows:
662	if name == "NAMESPACES" {
663		// Upon seeng
664		//      SOONG_CONFIG_NAMESPACES += foo
665		//    remember that there is a namespace `foo` and act as we saw
666		//      $(call add_soong_config_namespace,foo)
667		s, ok := maybeString(val)
668		if !ok {
669			return []starlarkNode{ctx.newBadNode(asgn, "cannot handle variables in SOONG_CONFIG_NAMESPACES assignment, please use add_soong_config_namespace instead")}
670		}
671		result := make([]starlarkNode, 0)
672		for _, ns := range strings.Fields(s) {
673			ctx.addSoongNamespace(ns)
674			result = append(result, &exprNode{&callExpr{
675				name:       baseName + ".soong_config_namespace",
676				args:       []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{ns}},
677				returnType: starlarkTypeVoid,
678			}})
679		}
680		return result
681	} else {
682		// Upon seeing
683		//      SOONG_CONFIG_x_y = v
684		// find a namespace called `x` and act as if we encountered
685		//      $(call soong_config_set,x,y,v)
686		// or check that `x_y` is a namespace, and then add the RHS of this assignment as variables in
687		// it.
688		// Emit an error in the ambiguous situation (namespaces `foo_bar` with a variable `baz`
689		// and `foo` with a variable `bar_baz`.
690		namespaceName := ""
691		if ctx.hasSoongNamespace(name) {
692			namespaceName = name
693		}
694		var varName string
695		for pos, ch := range name {
696			if !(ch == '_' && ctx.hasSoongNamespace(name[0:pos])) {
697				continue
698			}
699			if namespaceName != "" {
700				return []starlarkNode{ctx.newBadNode(asgn, "ambiguous soong namespace (may be either `%s` or  `%s`)", namespaceName, name[0:pos])}
701			}
702			namespaceName = name[0:pos]
703			varName = name[pos+1:]
704		}
705		if namespaceName == "" {
706			return []starlarkNode{ctx.newBadNode(asgn, "cannot figure out Soong namespace, please use add_soong_config_var_value macro instead")}
707		}
708		if varName == "" {
709			// Remember variables in this namespace
710			s, ok := maybeString(val)
711			if !ok {
712				return []starlarkNode{ctx.newBadNode(asgn, "cannot handle variables in SOONG_CONFIG_ assignment, please use add_soong_config_var_value instead")}
713			}
714			ctx.updateSoongNamespace(asgn.Type != "+=", namespaceName, strings.Fields(s))
715			return []starlarkNode{}
716		}
717
718		// Finally, handle assignment to a namespace variable
719		if !ctx.hasNamespaceVar(namespaceName, varName) {
720			return []starlarkNode{ctx.newBadNode(asgn, "no %s variable in %s namespace, please use add_soong_config_var_value instead", varName, namespaceName)}
721		}
722		fname := baseName + "." + soongConfigAssign
723		if asgn.Type == "+=" {
724			fname = baseName + "." + soongConfigAppend
725		}
726		return []starlarkNode{&exprNode{&callExpr{
727			name:       fname,
728			args:       []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{namespaceName}, &stringLiteralExpr{varName}, val},
729			returnType: starlarkTypeVoid,
730		}}}
731	}
732}
733
734func (ctx *parseContext) buildConcatExpr(a *mkparser.Assignment) (*concatExpr, *badExpr) {
735	xConcat := &concatExpr{}
736	var xItemList *listExpr
737	addToItemList := func(x ...starlarkExpr) {
738		if xItemList == nil {
739			xItemList = &listExpr{[]starlarkExpr{}}
740		}
741		xItemList.items = append(xItemList.items, x...)
742	}
743	finishItemList := func() {
744		if xItemList != nil {
745			xConcat.items = append(xConcat.items, xItemList)
746			xItemList = nil
747		}
748	}
749
750	items := a.Value.Words()
751	for _, item := range items {
752		// A function call in RHS is supposed to return a list, all other item
753		// expressions return individual elements.
754		switch x := ctx.parseMakeString(a, item).(type) {
755		case *badExpr:
756			return nil, x
757		case *stringLiteralExpr:
758			addToItemList(maybeConvertToStringList(x).(*listExpr).items...)
759		default:
760			switch x.typ() {
761			case starlarkTypeList:
762				finishItemList()
763				xConcat.items = append(xConcat.items, x)
764			case starlarkTypeString:
765				finishItemList()
766				xConcat.items = append(xConcat.items, &callExpr{
767					object:     x,
768					name:       "split",
769					args:       nil,
770					returnType: starlarkTypeList,
771				})
772			default:
773				addToItemList(x)
774			}
775		}
776	}
777	if xItemList != nil {
778		xConcat.items = append(xConcat.items, xItemList)
779	}
780	return xConcat, nil
781}
782
783func (ctx *parseContext) newDependentModule(path string, optional bool) *moduleInfo {
784	modulePath := ctx.loadedModulePath(path)
785	if mi, ok := ctx.dependentModules[modulePath]; ok {
786		mi.optional = mi.optional && optional
787		return mi
788	}
789	moduleName := moduleNameForFile(path)
790	moduleLocalName := "_" + moduleName
791	n, found := ctx.moduleNameCount[moduleName]
792	if found {
793		moduleLocalName += fmt.Sprintf("%d", n)
794	}
795	ctx.moduleNameCount[moduleName] = n + 1
796	_, err := fs.Stat(ctx.script.sourceFS, path)
797	mi := &moduleInfo{
798		path:            modulePath,
799		originalPath:    path,
800		moduleLocalName: moduleLocalName,
801		optional:        optional,
802		missing:         err != nil,
803	}
804	ctx.dependentModules[modulePath] = mi
805	ctx.script.inherited = append(ctx.script.inherited, mi)
806	return mi
807}
808
809func (ctx *parseContext) handleSubConfig(
810	v mkparser.Node, pathExpr starlarkExpr, loadAlways bool, processModule func(inheritedModule) starlarkNode) []starlarkNode {
811
812	// Allow seeing $(sort $(wildcard realPathExpr)) or $(wildcard realPathExpr)
813	// because those are functionally the same as not having the sort/wildcard calls.
814	if ce, ok := pathExpr.(*callExpr); ok && ce.name == "rblf.mksort" && len(ce.args) == 1 {
815		if ce2, ok2 := ce.args[0].(*callExpr); ok2 && ce2.name == "rblf.expand_wildcard" && len(ce2.args) == 1 {
816			pathExpr = ce2.args[0]
817		}
818	} else if ce2, ok2 := pathExpr.(*callExpr); ok2 && ce2.name == "rblf.expand_wildcard" && len(ce2.args) == 1 {
819		pathExpr = ce2.args[0]
820	}
821
822	// In a simple case, the name of a module to inherit/include is known statically.
823	if path, ok := maybeString(pathExpr); ok {
824		// Note that even if this directive loads a module unconditionally, a module may be
825		// absent without causing any harm if this directive is inside an if/else block.
826		moduleShouldExist := loadAlways && ctx.ifNestLevel == 0
827		if strings.Contains(path, "*") {
828			if paths, err := fs.Glob(ctx.script.sourceFS, path); err == nil {
829				sort.Strings(paths)
830				result := make([]starlarkNode, 0)
831				for _, p := range paths {
832					mi := ctx.newDependentModule(p, !moduleShouldExist)
833					result = append(result, processModule(inheritedStaticModule{mi, loadAlways}))
834				}
835				return result
836			} else {
837				return []starlarkNode{ctx.newBadNode(v, "cannot glob wildcard argument")}
838			}
839		} else {
840			mi := ctx.newDependentModule(path, !moduleShouldExist)
841			return []starlarkNode{processModule(inheritedStaticModule{mi, loadAlways})}
842		}
843	}
844
845	// If module path references variables (e.g., $(v1)/foo/$(v2)/device-config.mk), find all the paths in the
846	// source tree that may be a match and the corresponding variable values. For instance, if the source tree
847	// contains vendor1/foo/abc/dev.mk and vendor2/foo/def/dev.mk, the first one will be inherited when
848	// (v1, v2) == ('vendor1', 'abc'), and the second one when (v1, v2) == ('vendor2', 'def').
849	// We then emit the code that loads all of them, e.g.:
850	//    load("//vendor1/foo/abc:dev.rbc", _dev1_init="init")
851	//    load("//vendor2/foo/def/dev.rbc", _dev2_init="init")
852	// And then inherit it as follows:
853	//    _e = {
854	//       "vendor1/foo/abc/dev.mk": ("vendor1/foo/abc/dev", _dev1_init),
855	//       "vendor2/foo/def/dev.mk": ("vendor2/foo/def/dev", _dev_init2) }.get("%s/foo/%s/dev.mk" % (v1, v2))
856	//    if _e:
857	//       rblf.inherit(handle, _e[0], _e[1])
858	//
859	var matchingPaths []string
860	var needsWarning = false
861	if interpolate, ok := pathExpr.(*interpolateExpr); ok {
862		pathPattern := []string{interpolate.chunks[0]}
863		for _, chunk := range interpolate.chunks[1:] {
864			if chunk != "" {
865				pathPattern = append(pathPattern, chunk)
866			}
867		}
868		if len(pathPattern) == 1 {
869			pathPattern = append(pathPattern, "")
870		}
871		matchingPaths = ctx.findMatchingPaths(pathPattern)
872		needsWarning = pathPattern[0] == "" && len(ctx.includeTops) == 0
873	} else if len(ctx.includeTops) > 0 {
874		matchingPaths = append(matchingPaths, ctx.findMatchingPaths([]string{"", ""})...)
875	} else {
876		return []starlarkNode{ctx.newBadNode(v, "inherit-product/include argument is too complex")}
877	}
878
879	// Safeguard against $(call inherit-product,$(PRODUCT_PATH))
880	const maxMatchingFiles = 150
881	if len(matchingPaths) > maxMatchingFiles {
882		return []starlarkNode{ctx.newBadNode(v, "there are >%d files matching the pattern, please rewrite it", maxMatchingFiles)}
883	}
884
885	res := inheritedDynamicModule{pathExpr, []*moduleInfo{}, loadAlways, ctx.errorLocation(v), needsWarning}
886	for _, p := range matchingPaths {
887		// A product configuration files discovered dynamically may attempt to inherit
888		// from another one which does not exist in this source tree. Prevent load errors
889		// by always loading the dynamic files as optional.
890		res.candidateModules = append(res.candidateModules, ctx.newDependentModule(p, true))
891	}
892	return []starlarkNode{processModule(res)}
893}
894
895func (ctx *parseContext) findMatchingPaths(pattern []string) []string {
896	files := ctx.script.makefileFinder.Find(".")
897	if len(pattern) == 0 {
898		return files
899	}
900
901	// Create regular expression from the pattern
902	regexString := "^" + regexp.QuoteMeta(pattern[0])
903	for _, s := range pattern[1:] {
904		regexString += ".*" + regexp.QuoteMeta(s)
905	}
906	regexString += "$"
907	rex := regexp.MustCompile(regexString)
908
909	includeTopRegexString := ""
910	if len(ctx.includeTops) > 0 {
911		for i, top := range ctx.includeTops {
912			if i > 0 {
913				includeTopRegexString += "|"
914			}
915			includeTopRegexString += "^" + regexp.QuoteMeta(top)
916		}
917	} else {
918		includeTopRegexString = ".*"
919	}
920
921	includeTopRegex := regexp.MustCompile(includeTopRegexString)
922
923	// Now match
924	var res []string
925	for _, p := range files {
926		if rex.MatchString(p) && includeTopRegex.MatchString(p) {
927			res = append(res, p)
928		}
929	}
930	return res
931}
932
933type inheritProductCallParser struct {
934	loadAlways bool
935}
936
937func (p *inheritProductCallParser) parse(ctx *parseContext, v mkparser.Node, args *mkparser.MakeString) []starlarkNode {
938	args.TrimLeftSpaces()
939	args.TrimRightSpaces()
940	pathExpr := ctx.parseMakeString(v, args)
941	if _, ok := pathExpr.(*badExpr); ok {
942		return []starlarkNode{ctx.newBadNode(v, "Unable to parse argument to inherit")}
943	}
944	return ctx.handleSubConfig(v, pathExpr, p.loadAlways, func(im inheritedModule) starlarkNode {
945		return &inheritNode{im, p.loadAlways}
946	})
947}
948
949func (ctx *parseContext) handleInclude(v *mkparser.Directive) []starlarkNode {
950	loadAlways := v.Name[0] != '-'
951	v.Args.TrimRightSpaces()
952	v.Args.TrimLeftSpaces()
953	return ctx.handleSubConfig(v, ctx.parseMakeString(v, v.Args), loadAlways, func(im inheritedModule) starlarkNode {
954		return &includeNode{im, loadAlways}
955	})
956}
957
958func (ctx *parseContext) handleVariable(v *mkparser.Variable) []starlarkNode {
959	// Handle:
960	//   $(call inherit-product,...)
961	//   $(call inherit-product-if-exists,...)
962	//   $(info xxx)
963	//   $(warning xxx)
964	//   $(error xxx)
965	//   $(call other-custom-functions,...)
966
967	if name, args, ok := ctx.maybeParseFunctionCall(v, v.Name); ok {
968		if kf, ok := knownNodeFunctions[name]; ok {
969			return kf.parse(ctx, v, args)
970		}
971	}
972
973	return []starlarkNode{&exprNode{expr: ctx.parseReference(v, v.Name)}}
974}
975
976func (ctx *parseContext) maybeHandleDefine(directive *mkparser.Directive) starlarkNode {
977	macro_name := strings.Fields(directive.Args.Strings[0])[0]
978	// Ignore the macros that we handle
979	_, ignored := ignoredDefines[macro_name]
980	_, known := knownFunctions[macro_name]
981	if !ignored && !known {
982		return ctx.newBadNode(directive, "define is not supported: %s", macro_name)
983	}
984	return nil
985}
986
987func (ctx *parseContext) handleIfBlock(ifDirective *mkparser.Directive) starlarkNode {
988	ssSwitch := &switchNode{
989		ssCases: []*switchCase{ctx.processBranch(ifDirective)},
990	}
991	for ctx.hasNodes() && ctx.fatalError == nil {
992		node := ctx.getNode()
993		switch x := node.(type) {
994		case *mkparser.Directive:
995			switch x.Name {
996			case "else", "elifdef", "elifndef", "elifeq", "elifneq":
997				ssSwitch.ssCases = append(ssSwitch.ssCases, ctx.processBranch(x))
998			case "endif":
999				return ssSwitch
1000			default:
1001				return ctx.newBadNode(node, "unexpected directive %s", x.Name)
1002			}
1003		default:
1004			return ctx.newBadNode(ifDirective, "unexpected statement")
1005		}
1006	}
1007	if ctx.fatalError == nil {
1008		ctx.fatalError = fmt.Errorf("no matching endif for %s", ifDirective.Dump())
1009	}
1010	return ctx.newBadNode(ifDirective, "no matching endif for %s", ifDirective.Dump())
1011}
1012
1013// processBranch processes a single branch (if/elseif/else) until the next directive
1014// on the same level.
1015func (ctx *parseContext) processBranch(check *mkparser.Directive) *switchCase {
1016	block := &switchCase{gate: ctx.parseCondition(check)}
1017	defer func() {
1018		ctx.ifNestLevel--
1019	}()
1020	ctx.ifNestLevel++
1021
1022	for ctx.hasNodes() {
1023		node := ctx.getNode()
1024		if d, ok := node.(*mkparser.Directive); ok {
1025			switch d.Name {
1026			case "else", "elifdef", "elifndef", "elifeq", "elifneq", "endif":
1027				ctx.backNode()
1028				return block
1029			}
1030		}
1031		block.nodes = append(block.nodes, ctx.handleSimpleStatement(node)...)
1032	}
1033	ctx.fatalError = fmt.Errorf("no matching endif for %s", check.Dump())
1034	return block
1035}
1036
1037func (ctx *parseContext) parseCondition(check *mkparser.Directive) starlarkNode {
1038	switch check.Name {
1039	case "ifdef", "ifndef", "elifdef", "elifndef":
1040		if !check.Args.Const() {
1041			return ctx.newBadNode(check, "ifdef variable ref too complex: %s", check.Args.Dump())
1042		}
1043		v := NewVariableRefExpr(ctx.addVariable(check.Args.Strings[0]))
1044		if strings.HasSuffix(check.Name, "ndef") {
1045			v = &notExpr{v}
1046		}
1047		return &ifNode{
1048			isElif: strings.HasPrefix(check.Name, "elif"),
1049			expr:   v,
1050		}
1051	case "ifeq", "ifneq", "elifeq", "elifneq":
1052		return &ifNode{
1053			isElif: strings.HasPrefix(check.Name, "elif"),
1054			expr:   ctx.parseCompare(check),
1055		}
1056	case "else":
1057		return &elseNode{}
1058	default:
1059		panic(fmt.Errorf("%s: unknown directive: %s", ctx.script.mkFile, check.Dump()))
1060	}
1061}
1062
1063func (ctx *parseContext) newBadExpr(node mkparser.Node, text string, args ...interface{}) starlarkExpr {
1064	if ctx.errorLogger != nil {
1065		ctx.errorLogger.NewError(ctx.errorLocation(node), node, text, args...)
1066	}
1067	ctx.script.hasErrors = true
1068	return &badExpr{errorLocation: ctx.errorLocation(node), message: fmt.Sprintf(text, args...)}
1069}
1070
1071// records that the given node failed to be converted and includes an explanatory message
1072func (ctx *parseContext) newBadNode(failedNode mkparser.Node, message string, args ...interface{}) starlarkNode {
1073	return &exprNode{ctx.newBadExpr(failedNode, message, args...)}
1074}
1075
1076func (ctx *parseContext) parseCompare(cond *mkparser.Directive) starlarkExpr {
1077	// Strip outer parentheses
1078	mkArg := cloneMakeString(cond.Args)
1079	mkArg.Strings[0] = strings.TrimLeft(mkArg.Strings[0], "( ")
1080	n := len(mkArg.Strings)
1081	mkArg.Strings[n-1] = strings.TrimRight(mkArg.Strings[n-1], ") ")
1082	args := mkArg.Split(",")
1083	// TODO(asmundak): handle the case where the arguments are in quotes and space-separated
1084	if len(args) != 2 {
1085		return ctx.newBadExpr(cond, "ifeq/ifneq len(args) != 2 %s", cond.Dump())
1086	}
1087	args[0].TrimRightSpaces()
1088	args[1].TrimLeftSpaces()
1089
1090	isEq := !strings.HasSuffix(cond.Name, "neq")
1091	xLeft := ctx.parseMakeString(cond, args[0])
1092	xRight := ctx.parseMakeString(cond, args[1])
1093	if bad, ok := xLeft.(*badExpr); ok {
1094		return bad
1095	}
1096	if bad, ok := xRight.(*badExpr); ok {
1097		return bad
1098	}
1099
1100	if expr, ok := ctx.parseCompareSpecialCases(cond, xLeft, xRight); ok {
1101		return expr
1102	}
1103
1104	var stringOperand string
1105	var otherOperand starlarkExpr
1106	if s, ok := maybeString(xLeft); ok {
1107		stringOperand = s
1108		otherOperand = xRight
1109	} else if s, ok := maybeString(xRight); ok {
1110		stringOperand = s
1111		otherOperand = xLeft
1112	}
1113
1114	// If we've identified one of the operands as being a string literal, check
1115	// for some special cases we can do to simplify the resulting expression.
1116	if otherOperand != nil {
1117		if stringOperand == "" {
1118			if isEq {
1119				return negateExpr(otherOperand)
1120			} else {
1121				return otherOperand
1122			}
1123		}
1124		if stringOperand == "true" && otherOperand.typ() == starlarkTypeBool {
1125			if !isEq {
1126				return negateExpr(otherOperand)
1127			} else {
1128				return otherOperand
1129			}
1130		}
1131		if otherOperand.typ() == starlarkTypeList {
1132			fields := strings.Fields(stringOperand)
1133			elements := make([]starlarkExpr, len(fields))
1134			for i, s := range fields {
1135				elements[i] = &stringLiteralExpr{literal: s}
1136			}
1137			return &eqExpr{
1138				left:  otherOperand,
1139				right: &listExpr{elements},
1140				isEq:  isEq,
1141			}
1142		}
1143		if intOperand, err := strconv.Atoi(strings.TrimSpace(stringOperand)); err == nil && otherOperand.typ() == starlarkTypeInt {
1144			return &eqExpr{
1145				left:  otherOperand,
1146				right: &intLiteralExpr{literal: intOperand},
1147				isEq:  isEq,
1148			}
1149		}
1150	}
1151
1152	return &eqExpr{left: xLeft, right: xRight, isEq: isEq}
1153}
1154
1155// Given an if statement's directive and the left/right starlarkExprs,
1156// check if the starlarkExprs are one of a few hardcoded special cases
1157// that can be converted to a simpler equality expression than simply comparing
1158// the two.
1159func (ctx *parseContext) parseCompareSpecialCases(directive *mkparser.Directive, left starlarkExpr,
1160	right starlarkExpr) (starlarkExpr, bool) {
1161	isEq := !strings.HasSuffix(directive.Name, "neq")
1162
1163	// All the special cases require a call on one side and a
1164	// string literal/variable on the other. Turn the left/right variables into
1165	// call/value variables, and return false if that's not possible.
1166	var value starlarkExpr = nil
1167	call, ok := left.(*callExpr)
1168	if ok {
1169		switch right.(type) {
1170		case *stringLiteralExpr, *variableRefExpr:
1171			value = right
1172		}
1173	} else {
1174		call, _ = right.(*callExpr)
1175		switch left.(type) {
1176		case *stringLiteralExpr, *variableRefExpr:
1177			value = left
1178		}
1179	}
1180
1181	if call == nil || value == nil {
1182		return nil, false
1183	}
1184
1185	switch call.name {
1186	case baseName + ".filter":
1187		return ctx.parseCompareFilterFuncResult(directive, call, value, isEq)
1188	case baseName + ".findstring":
1189		return ctx.parseCheckFindstringFuncResult(directive, call, value, !isEq), true
1190	case baseName + ".strip":
1191		return ctx.parseCompareStripFuncResult(directive, call, value, !isEq), true
1192	}
1193	return nil, false
1194}
1195
1196func (ctx *parseContext) parseCompareFilterFuncResult(cond *mkparser.Directive,
1197	filterFuncCall *callExpr, xValue starlarkExpr, negate bool) (starlarkExpr, bool) {
1198	// We handle:
1199	// *  ifeq/ifneq (,$(filter v1 v2 ..., EXPR) becomes if EXPR not in/in ["v1", "v2", ...]
1200	// *  ifeq/ifneq (,$(filter EXPR, v1 v2 ...) becomes if EXPR not in/in ["v1", "v2", ...]
1201	if x, ok := xValue.(*stringLiteralExpr); !ok || x.literal != "" {
1202		return nil, false
1203	}
1204	xPattern := filterFuncCall.args[0]
1205	xText := filterFuncCall.args[1]
1206	var xInList *stringLiteralExpr
1207	var expr starlarkExpr
1208	var ok bool
1209	if xInList, ok = xPattern.(*stringLiteralExpr); ok && !strings.ContainsRune(xInList.literal, '%') && xText.typ() == starlarkTypeList {
1210		expr = xText
1211	} else if xInList, ok = xText.(*stringLiteralExpr); ok {
1212		expr = xPattern
1213	} else {
1214		return nil, false
1215	}
1216	slExpr := newStringListExpr(strings.Fields(xInList.literal))
1217	// Generate simpler code for the common cases:
1218	if expr.typ() == starlarkTypeList {
1219		if len(slExpr.items) == 1 {
1220			// Checking that a string belongs to list
1221			return &inExpr{isNot: negate, list: expr, expr: slExpr.items[0]}, true
1222		} else {
1223			return nil, false
1224		}
1225	} else if len(slExpr.items) == 1 {
1226		return &eqExpr{left: expr, right: slExpr.items[0], isEq: !negate}, true
1227	} else {
1228		return &inExpr{isNot: negate, list: newStringListExpr(strings.Fields(xInList.literal)), expr: expr}, true
1229	}
1230}
1231
1232func (ctx *parseContext) parseCheckFindstringFuncResult(directive *mkparser.Directive,
1233	xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
1234	if isEmptyString(xValue) {
1235		return &eqExpr{
1236			left: &callExpr{
1237				object:     xCall.args[1],
1238				name:       "find",
1239				args:       []starlarkExpr{xCall.args[0]},
1240				returnType: starlarkTypeInt,
1241			},
1242			right: &intLiteralExpr{-1},
1243			isEq:  !negate,
1244		}
1245	} else if s, ok := maybeString(xValue); ok {
1246		if s2, ok := maybeString(xCall.args[0]); ok && s == s2 {
1247			return &eqExpr{
1248				left: &callExpr{
1249					object:     xCall.args[1],
1250					name:       "find",
1251					args:       []starlarkExpr{xCall.args[0]},
1252					returnType: starlarkTypeInt,
1253				},
1254				right: &intLiteralExpr{-1},
1255				isEq:  negate,
1256			}
1257		}
1258	}
1259	return ctx.newBadExpr(directive, "$(findstring) can only be compared to nothing or its first argument")
1260}
1261
1262func (ctx *parseContext) parseCompareStripFuncResult(directive *mkparser.Directive,
1263	xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
1264	if _, ok := xValue.(*stringLiteralExpr); !ok {
1265		return ctx.newBadExpr(directive, "strip result can be compared only to string: %s", xValue)
1266	}
1267	return &eqExpr{
1268		left: &callExpr{
1269			name:       "strip",
1270			args:       xCall.args,
1271			returnType: starlarkTypeString,
1272		},
1273		right: xValue, isEq: !negate}
1274}
1275
1276func (ctx *parseContext) maybeParseFunctionCall(node mkparser.Node, ref *mkparser.MakeString) (name string, args *mkparser.MakeString, ok bool) {
1277	ref.TrimLeftSpaces()
1278	ref.TrimRightSpaces()
1279
1280	words := ref.SplitN(" ", 2)
1281	if !words[0].Const() {
1282		return "", nil, false
1283	}
1284
1285	name = words[0].Dump()
1286	args = mkparser.SimpleMakeString("", words[0].Pos())
1287	if len(words) >= 2 {
1288		args = words[1]
1289	}
1290	args.TrimLeftSpaces()
1291	if name == "call" {
1292		words = args.SplitN(",", 2)
1293		if words[0].Empty() || !words[0].Const() {
1294			return "", nil, false
1295		}
1296		name = words[0].Dump()
1297		if len(words) < 2 {
1298			args = mkparser.SimpleMakeString("", words[0].Pos())
1299		} else {
1300			args = words[1]
1301		}
1302	}
1303	ok = true
1304	return
1305}
1306
1307// parses $(...), returning an expression
1308func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeString) starlarkExpr {
1309	ref.TrimLeftSpaces()
1310	ref.TrimRightSpaces()
1311	refDump := ref.Dump()
1312
1313	// Handle only the case where the first (or only) word is constant
1314	words := ref.SplitN(" ", 2)
1315	if !words[0].Const() {
1316		if len(words) == 1 {
1317			expr := ctx.parseMakeString(node, ref)
1318			return &callExpr{
1319				object: &identifierExpr{"cfg"},
1320				name:   "get",
1321				args: []starlarkExpr{
1322					expr,
1323					&callExpr{
1324						object: &identifierExpr{"g"},
1325						name:   "get",
1326						args: []starlarkExpr{
1327							expr,
1328							&stringLiteralExpr{literal: ""},
1329						},
1330						returnType: starlarkTypeUnknown,
1331					},
1332				},
1333				returnType: starlarkTypeUnknown,
1334			}
1335		} else {
1336			return ctx.newBadExpr(node, "reference is too complex: %s", refDump)
1337		}
1338	}
1339
1340	if name, _, ok := ctx.maybeParseFunctionCall(node, ref); ok {
1341		if _, unsupported := unsupportedFunctions[name]; unsupported {
1342			return ctx.newBadExpr(node, "%s is not supported", refDump)
1343		}
1344	}
1345
1346	// If it is a single word, it can be a simple variable
1347	// reference or a function call
1348	if len(words) == 1 && !isMakeControlFunc(refDump) && refDump != "shell" && refDump != "eval" {
1349		if strings.HasPrefix(refDump, soongNsPrefix) {
1350			// TODO (asmundak): if we find many, maybe handle them.
1351			return ctx.newBadExpr(node, "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: %s", refDump)
1352		}
1353		// Handle substitution references: https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html
1354		if strings.Contains(refDump, ":") {
1355			parts := strings.SplitN(refDump, ":", 2)
1356			substParts := strings.SplitN(parts[1], "=", 2)
1357			if len(substParts) < 2 || strings.Count(substParts[0], "%") > 1 {
1358				return ctx.newBadExpr(node, "Invalid substitution reference")
1359			}
1360			if !strings.Contains(substParts[0], "%") {
1361				if strings.Contains(substParts[1], "%") {
1362					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.")
1363				}
1364				substParts[0] = "%" + substParts[0]
1365				substParts[1] = "%" + substParts[1]
1366			}
1367			v := ctx.addVariable(parts[0])
1368			if v == nil {
1369				return ctx.newBadExpr(node, "unknown variable %s", refDump)
1370			}
1371			return &callExpr{
1372				name:       baseName + ".mkpatsubst",
1373				returnType: starlarkTypeString,
1374				args: []starlarkExpr{
1375					&stringLiteralExpr{literal: substParts[0]},
1376					&stringLiteralExpr{literal: substParts[1]},
1377					NewVariableRefExpr(v),
1378				},
1379			}
1380		}
1381		if v := ctx.addVariable(refDump); v != nil {
1382			return NewVariableRefExpr(v)
1383		}
1384		return ctx.newBadExpr(node, "unknown variable %s", refDump)
1385	}
1386
1387	if name, args, ok := ctx.maybeParseFunctionCall(node, ref); ok {
1388		if kf, found := knownFunctions[name]; found {
1389			return kf.parse(ctx, node, args)
1390		} else {
1391			return ctx.newBadExpr(node, "cannot handle invoking %s", name)
1392		}
1393	}
1394	return ctx.newBadExpr(node, "cannot handle %s", refDump)
1395}
1396
1397type simpleCallParser struct {
1398	name       string
1399	returnType starlarkType
1400	addGlobals bool
1401	addHandle  bool
1402}
1403
1404func (p *simpleCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1405	expr := &callExpr{name: p.name, returnType: p.returnType}
1406	if p.addGlobals {
1407		expr.args = append(expr.args, &globalsExpr{})
1408	}
1409	if p.addHandle {
1410		expr.args = append(expr.args, &identifierExpr{name: "handle"})
1411	}
1412	for _, arg := range args.Split(",") {
1413		arg.TrimLeftSpaces()
1414		arg.TrimRightSpaces()
1415		x := ctx.parseMakeString(node, arg)
1416		if xBad, ok := x.(*badExpr); ok {
1417			return xBad
1418		}
1419		expr.args = append(expr.args, x)
1420	}
1421	return expr
1422}
1423
1424type makeControlFuncParser struct {
1425	name string
1426}
1427
1428func (p *makeControlFuncParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1429	// Make control functions need special treatment as everything
1430	// after the name is a single text argument
1431	x := ctx.parseMakeString(node, args)
1432	if xBad, ok := x.(*badExpr); ok {
1433		return xBad
1434	}
1435	return &callExpr{
1436		name: p.name,
1437		args: []starlarkExpr{
1438			&stringLiteralExpr{ctx.script.mkFile},
1439			x,
1440		},
1441		returnType: starlarkTypeUnknown,
1442	}
1443}
1444
1445type shellCallParser struct{}
1446
1447func (p *shellCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1448	// Shell functions need special treatment as everything
1449	// after the name is a single text argument
1450	x := ctx.parseMakeString(node, args)
1451	if xBad, ok := x.(*badExpr); ok {
1452		return xBad
1453	}
1454	return &callExpr{
1455		name:       baseName + ".shell",
1456		args:       []starlarkExpr{x},
1457		returnType: starlarkTypeUnknown,
1458	}
1459}
1460
1461type myDirCallParser struct{}
1462
1463func (p *myDirCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1464	if !args.Empty() {
1465		return ctx.newBadExpr(node, "my-dir function cannot have any arguments passed to it.")
1466	}
1467	return &stringLiteralExpr{literal: filepath.Dir(ctx.script.mkFile)}
1468}
1469
1470type andOrParser struct {
1471	isAnd bool
1472}
1473
1474func (p *andOrParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1475	if args.Empty() {
1476		return ctx.newBadExpr(node, "and/or function must have at least 1 argument")
1477	}
1478	op := "or"
1479	if p.isAnd {
1480		op = "and"
1481	}
1482
1483	argsParsed := make([]starlarkExpr, 0)
1484
1485	for _, arg := range args.Split(",") {
1486		arg.TrimLeftSpaces()
1487		arg.TrimRightSpaces()
1488		x := ctx.parseMakeString(node, arg)
1489		if xBad, ok := x.(*badExpr); ok {
1490			return xBad
1491		}
1492		argsParsed = append(argsParsed, x)
1493	}
1494	typ := starlarkTypeUnknown
1495	for _, arg := range argsParsed {
1496		if typ != arg.typ() && arg.typ() != starlarkTypeUnknown && typ != starlarkTypeUnknown {
1497			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())
1498		}
1499		if arg.typ() != starlarkTypeUnknown {
1500			typ = arg.typ()
1501		}
1502	}
1503	result := argsParsed[0]
1504	for _, arg := range argsParsed[1:] {
1505		result = &binaryOpExpr{
1506			left:       result,
1507			right:      arg,
1508			op:         op,
1509			returnType: typ,
1510		}
1511	}
1512	return result
1513}
1514
1515type isProductInListCallParser struct{}
1516
1517func (p *isProductInListCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1518	if args.Empty() {
1519		return ctx.newBadExpr(node, "is-product-in-list requires an argument")
1520	}
1521	return &inExpr{
1522		expr:  NewVariableRefExpr(ctx.addVariable("TARGET_PRODUCT")),
1523		list:  maybeConvertToStringList(ctx.parseMakeString(node, args)),
1524		isNot: false,
1525	}
1526}
1527
1528type isVendorBoardPlatformCallParser struct{}
1529
1530func (p *isVendorBoardPlatformCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1531	if args.Empty() || !identifierFullMatchRegex.MatchString(args.Dump()) {
1532		return ctx.newBadExpr(node, "cannot handle non-constant argument to is-vendor-board-platform")
1533	}
1534	return &inExpr{
1535		expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM")),
1536		list:  NewVariableRefExpr(ctx.addVariable(args.Dump() + "_BOARD_PLATFORMS")),
1537		isNot: false,
1538	}
1539}
1540
1541type isVendorBoardQcomCallParser struct{}
1542
1543func (p *isVendorBoardQcomCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1544	if !args.Empty() {
1545		return ctx.newBadExpr(node, "is-vendor-board-qcom does not accept any arguments")
1546	}
1547	return &inExpr{
1548		expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM")),
1549		list:  NewVariableRefExpr(ctx.addVariable("QCOM_BOARD_PLATFORMS")),
1550		isNot: false,
1551	}
1552}
1553
1554type substCallParser struct {
1555	fname string
1556}
1557
1558func (p *substCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1559	words := args.Split(",")
1560	if len(words) != 3 {
1561		return ctx.newBadExpr(node, "%s function should have 3 arguments", p.fname)
1562	}
1563	from := ctx.parseMakeString(node, words[0])
1564	if xBad, ok := from.(*badExpr); ok {
1565		return xBad
1566	}
1567	to := ctx.parseMakeString(node, words[1])
1568	if xBad, ok := to.(*badExpr); ok {
1569		return xBad
1570	}
1571	words[2].TrimLeftSpaces()
1572	words[2].TrimRightSpaces()
1573	obj := ctx.parseMakeString(node, words[2])
1574	typ := obj.typ()
1575	if typ == starlarkTypeString && p.fname == "subst" {
1576		// Optimization: if it's $(subst from, to, string), emit string.replace(from, to)
1577		return &callExpr{
1578			object:     obj,
1579			name:       "replace",
1580			args:       []starlarkExpr{from, to},
1581			returnType: typ,
1582		}
1583	}
1584	return &callExpr{
1585		name:       baseName + ".mk" + p.fname,
1586		args:       []starlarkExpr{from, to, obj},
1587		returnType: obj.typ(),
1588	}
1589}
1590
1591type ifCallParser struct{}
1592
1593func (p *ifCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1594	words := args.Split(",")
1595	if len(words) != 2 && len(words) != 3 {
1596		return ctx.newBadExpr(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))
1597	}
1598	condition := ctx.parseMakeString(node, words[0])
1599	ifTrue := ctx.parseMakeString(node, words[1])
1600	var ifFalse starlarkExpr
1601	if len(words) == 3 {
1602		ifFalse = ctx.parseMakeString(node, words[2])
1603	} else {
1604		switch ifTrue.typ() {
1605		case starlarkTypeList:
1606			ifFalse = &listExpr{items: []starlarkExpr{}}
1607		case starlarkTypeInt:
1608			ifFalse = &intLiteralExpr{literal: 0}
1609		case starlarkTypeBool:
1610			ifFalse = &boolLiteralExpr{literal: false}
1611		default:
1612			ifFalse = &stringLiteralExpr{literal: ""}
1613		}
1614	}
1615	return &ifExpr{
1616		condition,
1617		ifTrue,
1618		ifFalse,
1619	}
1620}
1621
1622type ifCallNodeParser struct{}
1623
1624func (p *ifCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
1625	words := args.Split(",")
1626	if len(words) != 2 && len(words) != 3 {
1627		return []starlarkNode{ctx.newBadNode(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))}
1628	}
1629
1630	ifn := &ifNode{expr: ctx.parseMakeString(node, words[0])}
1631	cases := []*switchCase{
1632		{
1633			gate:  ifn,
1634			nodes: ctx.parseNodeMakeString(node, words[1]),
1635		},
1636	}
1637	if len(words) == 3 {
1638		cases = append(cases, &switchCase{
1639			gate:  &elseNode{},
1640			nodes: ctx.parseNodeMakeString(node, words[2]),
1641		})
1642	}
1643	if len(cases) == 2 {
1644		if len(cases[1].nodes) == 0 {
1645			// Remove else branch if it has no contents
1646			cases = cases[:1]
1647		} else if len(cases[0].nodes) == 0 {
1648			// If the if branch has no contents but the else does,
1649			// move them to the if and negate its condition
1650			ifn.expr = negateExpr(ifn.expr)
1651			cases[0].nodes = cases[1].nodes
1652			cases = cases[:1]
1653		}
1654	}
1655
1656	return []starlarkNode{&switchNode{ssCases: cases}}
1657}
1658
1659type foreachCallParser struct{}
1660
1661func (p *foreachCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1662	words := args.Split(",")
1663	if len(words) != 3 {
1664		return ctx.newBadExpr(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))
1665	}
1666	if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
1667		return ctx.newBadExpr(node, "first argument to foreach function must be a simple string identifier")
1668	}
1669	loopVarName := words[0].Strings[0]
1670	list := ctx.parseMakeString(node, words[1])
1671	action := ctx.parseMakeString(node, words[2]).transform(func(expr starlarkExpr) starlarkExpr {
1672		if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
1673			return &identifierExpr{loopVarName}
1674		}
1675		return nil
1676	})
1677
1678	if list.typ() != starlarkTypeList {
1679		list = &callExpr{
1680			name:       baseName + ".words",
1681			returnType: starlarkTypeList,
1682			args:       []starlarkExpr{list},
1683		}
1684	}
1685
1686	var result starlarkExpr = &foreachExpr{
1687		varName: loopVarName,
1688		list:    list,
1689		action:  action,
1690	}
1691
1692	if action.typ() == starlarkTypeList {
1693		result = &callExpr{
1694			name:       baseName + ".flatten_2d_list",
1695			args:       []starlarkExpr{result},
1696			returnType: starlarkTypeList,
1697		}
1698	}
1699
1700	return result
1701}
1702
1703func transformNode(node starlarkNode, transformer func(expr starlarkExpr) starlarkExpr) {
1704	switch a := node.(type) {
1705	case *ifNode:
1706		a.expr = a.expr.transform(transformer)
1707	case *switchCase:
1708		transformNode(a.gate, transformer)
1709		for _, n := range a.nodes {
1710			transformNode(n, transformer)
1711		}
1712	case *switchNode:
1713		for _, n := range a.ssCases {
1714			transformNode(n, transformer)
1715		}
1716	case *exprNode:
1717		a.expr = a.expr.transform(transformer)
1718	case *assignmentNode:
1719		a.value = a.value.transform(transformer)
1720	case *foreachNode:
1721		a.list = a.list.transform(transformer)
1722		for _, n := range a.actions {
1723			transformNode(n, transformer)
1724		}
1725	case *inheritNode:
1726		if b, ok := a.module.(inheritedDynamicModule); ok {
1727			b.path = b.path.transform(transformer)
1728			a.module = b
1729		}
1730	case *includeNode:
1731		if b, ok := a.module.(inheritedDynamicModule); ok {
1732			b.path = b.path.transform(transformer)
1733			a.module = b
1734		}
1735	}
1736}
1737
1738type foreachCallNodeParser struct{}
1739
1740func (p *foreachCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
1741	words := args.Split(",")
1742	if len(words) != 3 {
1743		return []starlarkNode{ctx.newBadNode(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))}
1744	}
1745	if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
1746		return []starlarkNode{ctx.newBadNode(node, "first argument to foreach function must be a simple string identifier")}
1747	}
1748
1749	loopVarName := words[0].Strings[0]
1750
1751	list := ctx.parseMakeString(node, words[1])
1752	if list.typ() != starlarkTypeList {
1753		list = &callExpr{
1754			name:       baseName + ".words",
1755			returnType: starlarkTypeList,
1756			args:       []starlarkExpr{list},
1757		}
1758	}
1759
1760	actions := ctx.parseNodeMakeString(node, words[2])
1761	// TODO(colefaust): Replace transforming code with something more elegant
1762	for _, action := range actions {
1763		transformNode(action, func(expr starlarkExpr) starlarkExpr {
1764			if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
1765				return &identifierExpr{loopVarName}
1766			}
1767			return nil
1768		})
1769	}
1770
1771	return []starlarkNode{&foreachNode{
1772		varName: loopVarName,
1773		list:    list,
1774		actions: actions,
1775	}}
1776}
1777
1778type wordCallParser struct{}
1779
1780func (p *wordCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1781	words := args.Split(",")
1782	if len(words) != 2 {
1783		return ctx.newBadExpr(node, "word function should have 2 arguments")
1784	}
1785	var index = 0
1786	if words[0].Const() {
1787		if i, err := strconv.Atoi(strings.TrimSpace(words[0].Strings[0])); err == nil {
1788			index = i
1789		}
1790	}
1791	if index < 1 {
1792		return ctx.newBadExpr(node, "word index should be constant positive integer")
1793	}
1794	words[1].TrimLeftSpaces()
1795	words[1].TrimRightSpaces()
1796	array := ctx.parseMakeString(node, words[1])
1797	if bad, ok := array.(*badExpr); ok {
1798		return bad
1799	}
1800	if array.typ() != starlarkTypeList {
1801		array = &callExpr{
1802			name:       baseName + ".words",
1803			args:       []starlarkExpr{array},
1804			returnType: starlarkTypeList,
1805		}
1806	}
1807	return &indexExpr{array, &intLiteralExpr{index - 1}}
1808}
1809
1810type wordsCallParser struct{}
1811
1812func (p *wordsCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1813	args.TrimLeftSpaces()
1814	args.TrimRightSpaces()
1815	array := ctx.parseMakeString(node, args)
1816	if bad, ok := array.(*badExpr); ok {
1817		return bad
1818	}
1819	if array.typ() != starlarkTypeList {
1820		array = &callExpr{
1821			name:       baseName + ".words",
1822			args:       []starlarkExpr{array},
1823			returnType: starlarkTypeList,
1824		}
1825	}
1826	return &callExpr{
1827		name:       "len",
1828		args:       []starlarkExpr{array},
1829		returnType: starlarkTypeInt,
1830	}
1831}
1832
1833func parseIntegerArguments(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString, expectedArgs int) ([]starlarkExpr, error) {
1834	parsedArgs := make([]starlarkExpr, 0)
1835	for _, arg := range args.Split(",") {
1836		expr := ctx.parseMakeString(node, arg)
1837		if expr.typ() == starlarkTypeList {
1838			return nil, fmt.Errorf("argument to math argument has type list, which cannot be converted to int")
1839		}
1840		if s, ok := maybeString(expr); ok {
1841			intVal, err := strconv.Atoi(strings.TrimSpace(s))
1842			if err != nil {
1843				return nil, err
1844			}
1845			expr = &intLiteralExpr{literal: intVal}
1846		} else if expr.typ() != starlarkTypeInt {
1847			expr = &callExpr{
1848				name:       "int",
1849				args:       []starlarkExpr{expr},
1850				returnType: starlarkTypeInt,
1851			}
1852		}
1853		parsedArgs = append(parsedArgs, expr)
1854	}
1855	if len(parsedArgs) != expectedArgs {
1856		return nil, fmt.Errorf("function should have %d arguments", expectedArgs)
1857	}
1858	return parsedArgs, nil
1859}
1860
1861type mathComparisonCallParser struct {
1862	op string
1863}
1864
1865func (p *mathComparisonCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1866	parsedArgs, err := parseIntegerArguments(ctx, node, args, 2)
1867	if err != nil {
1868		return ctx.newBadExpr(node, err.Error())
1869	}
1870	return &binaryOpExpr{
1871		left:       parsedArgs[0],
1872		right:      parsedArgs[1],
1873		op:         p.op,
1874		returnType: starlarkTypeBool,
1875	}
1876}
1877
1878type mathMaxOrMinCallParser struct {
1879	function string
1880}
1881
1882func (p *mathMaxOrMinCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1883	parsedArgs, err := parseIntegerArguments(ctx, node, args, 2)
1884	if err != nil {
1885		return ctx.newBadExpr(node, err.Error())
1886	}
1887	return &callExpr{
1888		object:     nil,
1889		name:       p.function,
1890		args:       parsedArgs,
1891		returnType: starlarkTypeInt,
1892	}
1893}
1894
1895type evalNodeParser struct{}
1896
1897func (p *evalNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
1898	parser := mkparser.NewParser("Eval expression", strings.NewReader(args.Dump()))
1899	nodes, errs := parser.Parse()
1900	if errs != nil {
1901		return []starlarkNode{ctx.newBadNode(node, "Unable to parse eval statement")}
1902	}
1903
1904	if len(nodes) == 0 {
1905		return []starlarkNode{}
1906	} else if len(nodes) == 1 {
1907		// Replace the nodeLocator with one that just returns the location of
1908		// the $(eval) node. Otherwise, statements inside an $(eval) will show as
1909		// being on line 1 of the file, because they're on line 1 of
1910		// strings.NewReader(args.Dump())
1911		oldNodeLocator := ctx.script.nodeLocator
1912		ctx.script.nodeLocator = func(pos mkparser.Pos) int {
1913			return oldNodeLocator(node.Pos())
1914		}
1915		defer func() {
1916			ctx.script.nodeLocator = oldNodeLocator
1917		}()
1918
1919		switch n := nodes[0].(type) {
1920		case *mkparser.Assignment:
1921			if n.Name.Const() {
1922				return ctx.handleAssignment(n)
1923			}
1924		case *mkparser.Comment:
1925			return []starlarkNode{&commentNode{strings.TrimSpace("#" + n.Comment)}}
1926		case *mkparser.Directive:
1927			if n.Name == "include" || n.Name == "-include" {
1928				return ctx.handleInclude(n)
1929			}
1930		case *mkparser.Variable:
1931			// Technically inherit-product(-if-exists) don't need to be put inside
1932			// an eval, but some makefiles do it, presumably because they copy+pasted
1933			// from a $(eval include ...)
1934			if name, _, ok := ctx.maybeParseFunctionCall(n, n.Name); ok {
1935				if name == "inherit-product" || name == "inherit-product-if-exists" {
1936					return ctx.handleVariable(n)
1937				}
1938			}
1939		}
1940	}
1941
1942	return []starlarkNode{ctx.newBadNode(node, "Eval expression too complex; only assignments, comments, includes, and inherit-products are supported")}
1943}
1944
1945type lowerUpperParser struct {
1946	isUpper bool
1947}
1948
1949func (p *lowerUpperParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1950	fn := "lower"
1951	if p.isUpper {
1952		fn = "upper"
1953	}
1954	arg := ctx.parseMakeString(node, args)
1955
1956	return &callExpr{
1957		object:     arg,
1958		name:       fn,
1959		returnType: starlarkTypeString,
1960	}
1961}
1962
1963func (ctx *parseContext) parseMakeString(node mkparser.Node, mk *mkparser.MakeString) starlarkExpr {
1964	if mk.Const() {
1965		return &stringLiteralExpr{mk.Dump()}
1966	}
1967	if mkRef, ok := mk.SingleVariable(); ok {
1968		return ctx.parseReference(node, mkRef)
1969	}
1970	// If we reached here, it's neither string literal nor a simple variable,
1971	// we need a full-blown interpolation node that will generate
1972	// "a%b%c" % (X, Y) for a$(X)b$(Y)c
1973	parts := make([]starlarkExpr, len(mk.Variables)+len(mk.Strings))
1974	for i := 0; i < len(parts); i++ {
1975		if i%2 == 0 {
1976			parts[i] = &stringLiteralExpr{literal: mk.Strings[i/2]}
1977		} else {
1978			parts[i] = ctx.parseReference(node, mk.Variables[i/2].Name)
1979			if x, ok := parts[i].(*badExpr); ok {
1980				return x
1981			}
1982		}
1983	}
1984	return NewInterpolateExpr(parts)
1985}
1986
1987func (ctx *parseContext) parseNodeMakeString(node mkparser.Node, mk *mkparser.MakeString) []starlarkNode {
1988	// Discard any constant values in the make string, as they would be top level
1989	// string literals and do nothing.
1990	result := make([]starlarkNode, 0, len(mk.Variables))
1991	for i := range mk.Variables {
1992		result = append(result, ctx.handleVariable(&mk.Variables[i])...)
1993	}
1994	return result
1995}
1996
1997// Handles the statements whose treatment is the same in all contexts: comment,
1998// assignment, variable (which is a macro call in reality) and all constructs that
1999// do not handle in any context ('define directive and any unrecognized stuff).
2000func (ctx *parseContext) handleSimpleStatement(node mkparser.Node) []starlarkNode {
2001	var result []starlarkNode
2002	switch x := node.(type) {
2003	case *mkparser.Comment:
2004		if n, handled := ctx.maybeHandleAnnotation(x); handled && n != nil {
2005			result = []starlarkNode{n}
2006		} else if !handled {
2007			result = []starlarkNode{&commentNode{strings.TrimSpace("#" + x.Comment)}}
2008		}
2009	case *mkparser.Assignment:
2010		result = ctx.handleAssignment(x)
2011	case *mkparser.Variable:
2012		result = ctx.handleVariable(x)
2013	case *mkparser.Directive:
2014		switch x.Name {
2015		case "define":
2016			if res := ctx.maybeHandleDefine(x); res != nil {
2017				result = []starlarkNode{res}
2018			}
2019		case "include", "-include":
2020			result = ctx.handleInclude(x)
2021		case "ifeq", "ifneq", "ifdef", "ifndef":
2022			result = []starlarkNode{ctx.handleIfBlock(x)}
2023		default:
2024			result = []starlarkNode{ctx.newBadNode(x, "unexpected directive %s", x.Name)}
2025		}
2026	default:
2027		result = []starlarkNode{ctx.newBadNode(x, "unsupported line %s", strings.ReplaceAll(x.Dump(), "\n", "\n#"))}
2028	}
2029
2030	// Clear the includeTops after each non-comment statement
2031	// so that include annotations placed on certain statements don't apply
2032	// globally for the rest of the makefile was well.
2033	if _, wasComment := node.(*mkparser.Comment); !wasComment {
2034		ctx.atTopOfMakefile = false
2035		ctx.includeTops = []string{}
2036	}
2037
2038	if result == nil {
2039		result = []starlarkNode{}
2040	}
2041
2042	return result
2043}
2044
2045// The types allowed in a type_hint
2046var typeHintMap = map[string]starlarkType{
2047	"string": starlarkTypeString,
2048	"list":   starlarkTypeList,
2049}
2050
2051// Processes annotation. An annotation is a comment that starts with #RBC# and provides
2052// a conversion hint -- say, where to look for the dynamically calculated inherit/include
2053// paths. Returns true if the comment was a successfully-handled annotation.
2054func (ctx *parseContext) maybeHandleAnnotation(cnode *mkparser.Comment) (starlarkNode, bool) {
2055	maybeTrim := func(s, prefix string) (string, bool) {
2056		if strings.HasPrefix(s, prefix) {
2057			return strings.TrimSpace(strings.TrimPrefix(s, prefix)), true
2058		}
2059		return s, false
2060	}
2061	annotation, ok := maybeTrim(cnode.Comment, annotationCommentPrefix)
2062	if !ok {
2063		return nil, false
2064	}
2065	if p, ok := maybeTrim(annotation, "include_top"); ok {
2066		// Don't allow duplicate include tops, because then we will generate
2067		// invalid starlark code. (duplicate keys in the _entry dictionary)
2068		for _, top := range ctx.includeTops {
2069			if top == p {
2070				return nil, true
2071			}
2072		}
2073		ctx.includeTops = append(ctx.includeTops, p)
2074		return nil, true
2075	} else if p, ok := maybeTrim(annotation, "type_hint"); ok {
2076		// Type hints must come at the beginning the file, to avoid confusion
2077		// if a type hint was specified later and thus only takes effect for half
2078		// of the file.
2079		if !ctx.atTopOfMakefile {
2080			return ctx.newBadNode(cnode, "type_hint annotations must come before the first Makefile statement"), true
2081		}
2082
2083		parts := strings.Fields(p)
2084		if len(parts) <= 1 {
2085			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
2086		}
2087
2088		var varType starlarkType
2089		if varType, ok = typeHintMap[parts[0]]; !ok {
2090			varType = starlarkTypeUnknown
2091		}
2092		if varType == starlarkTypeUnknown {
2093			return ctx.newBadNode(cnode, "Invalid type_hint annotation. Only list/string types are accepted, found %s", parts[0]), true
2094		}
2095
2096		for _, name := range parts[1:] {
2097			// Don't allow duplicate type hints
2098			if _, ok := ctx.typeHints[name]; ok {
2099				return ctx.newBadNode(cnode, "Duplicate type hint for variable %s", name), true
2100			}
2101			ctx.typeHints[name] = varType
2102		}
2103		return nil, true
2104	}
2105	return ctx.newBadNode(cnode, "unsupported annotation %s", cnode.Comment), true
2106}
2107
2108func (ctx *parseContext) loadedModulePath(path string) string {
2109	// During the transition to Roboleaf some of the product configuration files
2110	// will be converted and checked in while the others will be generated on the fly
2111	// and run. The runner  (rbcrun application) accommodates this by allowing three
2112	// different ways to specify the loaded file location:
2113	//  1) load(":<file>",...) loads <file> from the same directory
2114	//  2) load("//path/relative/to/source/root:<file>", ...) loads <file> source tree
2115	//  3) load("/absolute/path/to/<file> absolute path
2116	// If the file being generated and the file it wants to load are in the same directory,
2117	// generate option 1.
2118	// Otherwise, if output directory is not specified, generate 2)
2119	// Finally, if output directory has been specified and the file being generated and
2120	// the file it wants to load from are in the different directories, generate 2) or 3):
2121	//  * if the file being loaded exists in the source tree, generate 2)
2122	//  * otherwise, generate 3)
2123	// Finally, figure out the loaded module path and name and create a node for it
2124	loadedModuleDir := filepath.Dir(path)
2125	base := filepath.Base(path)
2126	loadedModuleName := strings.TrimSuffix(base, filepath.Ext(base)) + ctx.outputSuffix
2127	if loadedModuleDir == filepath.Dir(ctx.script.mkFile) {
2128		return ":" + loadedModuleName
2129	}
2130	if ctx.outputDir == "" {
2131		return fmt.Sprintf("//%s:%s", loadedModuleDir, loadedModuleName)
2132	}
2133	if _, err := os.Stat(filepath.Join(loadedModuleDir, loadedModuleName)); err == nil {
2134		return fmt.Sprintf("//%s:%s", loadedModuleDir, loadedModuleName)
2135	}
2136	return filepath.Join(ctx.outputDir, loadedModuleDir, loadedModuleName)
2137}
2138
2139func (ctx *parseContext) addSoongNamespace(ns string) {
2140	if _, ok := ctx.soongNamespaces[ns]; ok {
2141		return
2142	}
2143	ctx.soongNamespaces[ns] = make(map[string]bool)
2144}
2145
2146func (ctx *parseContext) hasSoongNamespace(name string) bool {
2147	_, ok := ctx.soongNamespaces[name]
2148	return ok
2149}
2150
2151func (ctx *parseContext) updateSoongNamespace(replace bool, namespaceName string, varNames []string) {
2152	ctx.addSoongNamespace(namespaceName)
2153	vars := ctx.soongNamespaces[namespaceName]
2154	if replace {
2155		vars = make(map[string]bool)
2156		ctx.soongNamespaces[namespaceName] = vars
2157	}
2158	for _, v := range varNames {
2159		vars[v] = true
2160	}
2161}
2162
2163func (ctx *parseContext) hasNamespaceVar(namespaceName string, varName string) bool {
2164	vars, ok := ctx.soongNamespaces[namespaceName]
2165	if ok {
2166		_, ok = vars[varName]
2167	}
2168	return ok
2169}
2170
2171func (ctx *parseContext) errorLocation(node mkparser.Node) ErrorLocation {
2172	return ErrorLocation{ctx.script.mkFile, ctx.script.nodeLocator(node.Pos())}
2173}
2174
2175func (ss *StarlarkScript) String() string {
2176	return NewGenerateContext(ss).emit()
2177}
2178
2179func (ss *StarlarkScript) SubConfigFiles() []string {
2180
2181	var subs []string
2182	for _, src := range ss.inherited {
2183		subs = append(subs, src.originalPath)
2184	}
2185	return subs
2186}
2187
2188func (ss *StarlarkScript) HasErrors() bool {
2189	return ss.hasErrors
2190}
2191
2192// Convert reads and parses a makefile. If successful, parsed tree
2193// is returned and then can be passed to String() to get the generated
2194// Starlark file.
2195func Convert(req Request) (*StarlarkScript, error) {
2196	reader := req.Reader
2197	if reader == nil {
2198		mkContents, err := ioutil.ReadFile(req.MkFile)
2199		if err != nil {
2200			return nil, err
2201		}
2202		reader = bytes.NewBuffer(mkContents)
2203	}
2204	parser := mkparser.NewParser(req.MkFile, reader)
2205	nodes, errs := parser.Parse()
2206	if len(errs) > 0 {
2207		for _, e := range errs {
2208			fmt.Fprintln(os.Stderr, "ERROR:", e)
2209		}
2210		return nil, fmt.Errorf("bad makefile %s", req.MkFile)
2211	}
2212	starScript := &StarlarkScript{
2213		moduleName:     moduleNameForFile(req.MkFile),
2214		mkFile:         req.MkFile,
2215		traceCalls:     req.TraceCalls,
2216		sourceFS:       req.SourceFS,
2217		makefileFinder: req.MakefileFinder,
2218		nodeLocator:    func(pos mkparser.Pos) int { return parser.Unpack(pos).Line },
2219		nodes:          make([]starlarkNode, 0),
2220	}
2221	ctx := newParseContext(starScript, nodes)
2222	ctx.outputSuffix = req.OutputSuffix
2223	ctx.outputDir = req.OutputDir
2224	ctx.errorLogger = req.ErrorLogger
2225	if len(req.TracedVariables) > 0 {
2226		ctx.tracedVariables = make(map[string]bool)
2227		for _, v := range req.TracedVariables {
2228			ctx.tracedVariables[v] = true
2229		}
2230	}
2231	for ctx.hasNodes() && ctx.fatalError == nil {
2232		starScript.nodes = append(starScript.nodes, ctx.handleSimpleStatement(ctx.getNode())...)
2233	}
2234	if ctx.fatalError != nil {
2235		return nil, ctx.fatalError
2236	}
2237	return starScript, nil
2238}
2239
2240func Launcher(mainModuleUri, inputVariablesUri, mainModuleName string) string {
2241	var buf bytes.Buffer
2242	fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
2243	fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
2244	fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
2245	fmt.Fprintf(&buf, "%s(%s(%q, init, input_variables_init))\n", cfnPrintVars, cfnMain, mainModuleName)
2246	return buf.String()
2247}
2248
2249func BoardLauncher(mainModuleUri string, inputVariablesUri string) string {
2250	var buf bytes.Buffer
2251	fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
2252	fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
2253	fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
2254	fmt.Fprintf(&buf, "%s(%s(init, input_variables_init))\n", cfnPrintVars, cfnBoardMain)
2255	return buf.String()
2256}
2257
2258func MakePath2ModuleName(mkPath string) string {
2259	return strings.TrimSuffix(mkPath, filepath.Ext(mkPath))
2260}
2261