blob: eb036cfabfdd5e291eb3d8481c6ca58cb3469b0d [file] [log] [blame]
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001// 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// All other constructs are carried over to the output starlark file as comments.
23//
24package mk2rbc
25
26import (
27 "bytes"
28 "fmt"
29 "io"
Sasha Smundak6609ba72021-07-22 18:32:56 -070030 "io/fs"
Sasha Smundakb051c4e2020-11-05 20:45:07 -080031 "io/ioutil"
32 "os"
33 "path/filepath"
34 "regexp"
35 "strconv"
36 "strings"
37 "text/scanner"
38
39 mkparser "android/soong/androidmk/parser"
40)
41
42const (
Sasha Smundak6d852dd2021-09-27 20:34:39 -070043 annotationCommentPrefix = "RBC#"
44 baseUri = "//build/make/core:product_config.rbc"
Sasha Smundakb051c4e2020-11-05 20:45:07 -080045 // The name of the struct exported by the product_config.rbc
46 // that contains the functions and variables available to
47 // product configuration Starlark files.
48 baseName = "rblf"
49
Sasha Smundak65b547e2021-09-17 15:35:41 -070050 soongNsPrefix = "SOONG_CONFIG_"
51
Sasha Smundakb051c4e2020-11-05 20:45:07 -080052 // And here are the functions and variables:
53 cfnGetCfg = baseName + ".cfg"
54 cfnMain = baseName + ".product_configuration"
Cole Faust6ed7cb42021-10-07 17:08:46 -070055 cfnBoardMain = baseName + ".board_configuration"
Sasha Smundakb051c4e2020-11-05 20:45:07 -080056 cfnPrintVars = baseName + ".printvars"
57 cfnWarning = baseName + ".warning"
58 cfnLocalAppend = baseName + ".local_append"
59 cfnLocalSetDefault = baseName + ".local_set_default"
60 cfnInherit = baseName + ".inherit"
61 cfnSetListDefault = baseName + ".setdefault"
62)
63
64const (
Cole Faust9ebf6e42021-12-13 14:08:34 -080065 soongConfigAppend = "soong_config_append"
66 soongConfigAssign = "soong_config_set"
Sasha Smundakb051c4e2020-11-05 20:45:07 -080067)
68
Cole Faust9ebf6e42021-12-13 14:08:34 -080069var knownFunctions = map[string]interface {
70 parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr
Sasha Smundakb051c4e2020-11-05 20:45:07 -080071}{
Cole Faust9ebf6e42021-12-13 14:08:34 -080072 "abspath": &simpleCallParser{name: baseName + ".abspath", returnType: starlarkTypeString, addGlobals: false},
73 "add_soong_config_namespace": &simpleCallParser{name: baseName + ".soong_config_namespace", returnType: starlarkTypeVoid, addGlobals: true},
74 "add_soong_config_var_value": &simpleCallParser{name: baseName + ".soong_config_set", returnType: starlarkTypeVoid, addGlobals: true},
75 soongConfigAssign: &simpleCallParser{name: baseName + ".soong_config_set", returnType: starlarkTypeVoid, addGlobals: true},
76 soongConfigAppend: &simpleCallParser{name: baseName + ".soong_config_append", returnType: starlarkTypeVoid, addGlobals: true},
77 "soong_config_get": &simpleCallParser{name: baseName + ".soong_config_get", returnType: starlarkTypeString, addGlobals: true},
78 "add-to-product-copy-files-if-exists": &simpleCallParser{name: baseName + ".copy_if_exists", returnType: starlarkTypeList, addGlobals: false},
79 "addprefix": &simpleCallParser{name: baseName + ".addprefix", returnType: starlarkTypeList, addGlobals: false},
80 "addsuffix": &simpleCallParser{name: baseName + ".addsuffix", returnType: starlarkTypeList, addGlobals: false},
81 "copy-files": &simpleCallParser{name: baseName + ".copy_files", returnType: starlarkTypeList, addGlobals: false},
82 "dir": &simpleCallParser{name: baseName + ".dir", returnType: starlarkTypeList, addGlobals: false},
83 "dist-for-goals": &simpleCallParser{name: baseName + ".mkdist_for_goals", returnType: starlarkTypeVoid, addGlobals: true},
84 "enforce-product-packages-exist": &simpleCallParser{name: baseName + ".enforce_product_packages_exist", returnType: starlarkTypeVoid, addGlobals: false},
85 "error": &makeControlFuncParser{name: baseName + ".mkerror"},
86 "findstring": &simpleCallParser{name: baseName + ".findstring", returnType: starlarkTypeInt, addGlobals: false},
87 "find-copy-subdir-files": &simpleCallParser{name: baseName + ".find_and_copy", returnType: starlarkTypeList, addGlobals: false},
88 "filter": &simpleCallParser{name: baseName + ".filter", returnType: starlarkTypeList, addGlobals: false},
89 "filter-out": &simpleCallParser{name: baseName + ".filter_out", returnType: starlarkTypeList, addGlobals: false},
90 "firstword": &firstOrLastwordCallParser{isLastWord: false},
91 "foreach": &foreachCallPaser{},
92 "if": &ifCallParser{},
93 "info": &makeControlFuncParser{name: baseName + ".mkinfo"},
Cole Faustb2e0b602022-01-07 15:46:58 -080094 "is-board-platform": &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
Cole Faust9ebf6e42021-12-13 14:08:34 -080095 "is-board-platform2": &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
Cole Faustb2e0b602022-01-07 15:46:58 -080096 "is-board-platform-in-list": &simpleCallParser{name: baseName + ".board_platform_in", returnType: starlarkTypeBool, addGlobals: true},
Cole Faust9ebf6e42021-12-13 14:08:34 -080097 "is-board-platform-in-list2": &simpleCallParser{name: baseName + ".board_platform_in", returnType: starlarkTypeBool, addGlobals: true},
98 "is-product-in-list": &isProductInListCallParser{},
99 "is-vendor-board-platform": &isVendorBoardPlatformCallParser{},
100 "is-vendor-board-qcom": &isVendorBoardQcomCallParser{},
101 "lastword": &firstOrLastwordCallParser{isLastWord: true},
102 "notdir": &simpleCallParser{name: baseName + ".notdir", returnType: starlarkTypeString, addGlobals: false},
103 "my-dir": &myDirCallParser{},
104 "patsubst": &substCallParser{fname: "patsubst"},
105 "product-copy-files-by-pattern": &simpleCallParser{name: baseName + ".product_copy_files_by_pattern", returnType: starlarkTypeList, addGlobals: false},
106 "require-artifacts-in-path": &simpleCallParser{name: baseName + ".require_artifacts_in_path", returnType: starlarkTypeVoid, addGlobals: false},
107 "require-artifacts-in-path-relaxed": &simpleCallParser{name: baseName + ".require_artifacts_in_path_relaxed", returnType: starlarkTypeVoid, addGlobals: false},
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800108 // TODO(asmundak): remove it once all calls are removed from configuration makefiles. see b/183161002
Cole Faust9ebf6e42021-12-13 14:08:34 -0800109 "shell": &shellCallParser{},
110 "strip": &simpleCallParser{name: baseName + ".mkstrip", returnType: starlarkTypeString, addGlobals: false},
111 "subst": &substCallParser{fname: "subst"},
112 "warning": &makeControlFuncParser{name: baseName + ".mkwarning"},
113 "word": &wordCallParser{},
114 "wildcard": &simpleCallParser{name: baseName + ".expand_wildcard", returnType: starlarkTypeList, addGlobals: false},
115}
116
117// These are functions that we don't implement conversions for, but
118// we allow seeing their definitions in the product config files.
119var ignoredDefines = map[string]bool{
120 "find-word-in-list": true, // internal macro
121 "get-vendor-board-platforms": true, // internal macro, used by is-board-platform, etc.
122 "is-android-codename": true, // unused by product config
123 "is-android-codename-in-list": true, // unused by product config
124 "is-chipset-in-board-platform": true, // unused by product config
125 "is-chipset-prefix-in-board-platform": true, // unused by product config
126 "is-not-board-platform": true, // defined but never used
127 "is-platform-sdk-version-at-least": true, // unused by product config
128 "match-prefix": true, // internal macro
129 "match-word": true, // internal macro
130 "match-word-in-list": true, // internal macro
131 "tb-modules": true, // defined in hardware/amlogic/tb_modules/tb_detect.mk, unused
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800132}
133
Cole Faustb0d32ab2021-12-09 14:00:59 -0800134var identifierFullMatchRegex = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800135
136// Conversion request parameters
137type Request struct {
Sasha Smundak422b6142021-11-11 18:31:59 -0800138 MkFile string // file to convert
139 Reader io.Reader // if set, read input from this stream instead
140 RootDir string // root directory path used to resolve included files
141 OutputSuffix string // generated Starlark files suffix
142 OutputDir string // if set, root of the output hierarchy
143 ErrorLogger ErrorLogger
144 TracedVariables []string // trace assignment to these variables
145 TraceCalls bool
146 SourceFS fs.FS
147 MakefileFinder MakefileFinder
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800148}
149
Sasha Smundak7d934b92021-11-10 12:20:01 -0800150// ErrorLogger prints errors and gathers error statistics.
151// Its NewError function is called on every error encountered during the conversion.
152type ErrorLogger interface {
Sasha Smundak422b6142021-11-11 18:31:59 -0800153 NewError(el ErrorLocation, node mkparser.Node, text string, args ...interface{})
154}
155
156type ErrorLocation struct {
157 MkFile string
158 MkLine int
159}
160
161func (el ErrorLocation) String() string {
162 return fmt.Sprintf("%s:%d", el.MkFile, el.MkLine)
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800163}
164
165// Derives module name for a given file. It is base name
166// (file name without suffix), with some characters replaced to make it a Starlark identifier
167func moduleNameForFile(mkFile string) string {
168 base := strings.TrimSuffix(filepath.Base(mkFile), filepath.Ext(mkFile))
169 // TODO(asmundak): what else can be in the product file names?
Sasha Smundak6609ba72021-07-22 18:32:56 -0700170 return strings.NewReplacer("-", "_", ".", "_").Replace(base)
171
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800172}
173
174func cloneMakeString(mkString *mkparser.MakeString) *mkparser.MakeString {
175 r := &mkparser.MakeString{StringPos: mkString.StringPos}
176 r.Strings = append(r.Strings, mkString.Strings...)
177 r.Variables = append(r.Variables, mkString.Variables...)
178 return r
179}
180
181func isMakeControlFunc(s string) bool {
182 return s == "error" || s == "warning" || s == "info"
183}
184
185// Starlark output generation context
186type generationContext struct {
187 buf strings.Builder
188 starScript *StarlarkScript
189 indentLevel int
190 inAssignment bool
191 tracedCount int
192}
193
194func NewGenerateContext(ss *StarlarkScript) *generationContext {
195 return &generationContext{starScript: ss}
196}
197
198// emit returns generated script
199func (gctx *generationContext) emit() string {
200 ss := gctx.starScript
201
202 // The emitted code has the following layout:
203 // <initial comments>
204 // preamble, i.e.,
205 // load statement for the runtime support
206 // load statement for each unique submodule pulled in by this one
207 // def init(g, handle):
208 // cfg = rblf.cfg(handle)
209 // <statements>
210 // <warning if conversion was not clean>
211
212 iNode := len(ss.nodes)
213 for i, node := range ss.nodes {
214 if _, ok := node.(*commentNode); !ok {
215 iNode = i
216 break
217 }
218 node.emit(gctx)
219 }
220
221 gctx.emitPreamble()
222
223 gctx.newLine()
224 // The arguments passed to the init function are the global dictionary
225 // ('g') and the product configuration dictionary ('cfg')
226 gctx.write("def init(g, handle):")
227 gctx.indentLevel++
228 if gctx.starScript.traceCalls {
229 gctx.newLine()
230 gctx.writef(`print(">%s")`, gctx.starScript.mkFile)
231 }
232 gctx.newLine()
233 gctx.writef("cfg = %s(handle)", cfnGetCfg)
234 for _, node := range ss.nodes[iNode:] {
235 node.emit(gctx)
236 }
237
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800238 if gctx.starScript.traceCalls {
239 gctx.newLine()
240 gctx.writef(`print("<%s")`, gctx.starScript.mkFile)
241 }
242 gctx.indentLevel--
243 gctx.write("\n")
244 return gctx.buf.String()
245}
246
247func (gctx *generationContext) emitPreamble() {
248 gctx.newLine()
249 gctx.writef("load(%q, %q)", baseUri, baseName)
250 // Emit exactly one load statement for each URI.
251 loadedSubConfigs := make(map[string]string)
252 for _, sc := range gctx.starScript.inherited {
253 uri := sc.path
254 if m, ok := loadedSubConfigs[uri]; ok {
255 // No need to emit load statement, but fix module name.
256 sc.moduleLocalName = m
257 continue
258 }
Sasha Smundak6609ba72021-07-22 18:32:56 -0700259 if sc.optional {
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800260 uri += "|init"
261 }
262 gctx.newLine()
263 gctx.writef("load(%q, %s = \"init\")", uri, sc.entryName())
264 loadedSubConfigs[uri] = sc.moduleLocalName
265 }
266 gctx.write("\n")
267}
268
269func (gctx *generationContext) emitPass() {
270 gctx.newLine()
271 gctx.write("pass")
272}
273
274func (gctx *generationContext) write(ss ...string) {
275 for _, s := range ss {
276 gctx.buf.WriteString(s)
277 }
278}
279
280func (gctx *generationContext) writef(format string, args ...interface{}) {
281 gctx.write(fmt.Sprintf(format, args...))
282}
283
284func (gctx *generationContext) newLine() {
285 if gctx.buf.Len() == 0 {
286 return
287 }
288 gctx.write("\n")
289 gctx.writef("%*s", 2*gctx.indentLevel, "")
290}
291
Sasha Smundak422b6142021-11-11 18:31:59 -0800292func (gctx *generationContext) emitConversionError(el ErrorLocation, message string) {
293 gctx.writef(`rblf.mk2rbc_error("%s", %q)`, el, message)
294}
295
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800296type knownVariable struct {
297 name string
298 class varClass
299 valueType starlarkType
300}
301
302type knownVariables map[string]knownVariable
303
304func (pcv knownVariables) NewVariable(name string, varClass varClass, valueType starlarkType) {
305 v, exists := pcv[name]
306 if !exists {
307 pcv[name] = knownVariable{name, varClass, valueType}
308 return
309 }
310 // Conflict resolution:
311 // * config class trumps everything
312 // * any type trumps unknown type
313 match := varClass == v.class
314 if !match {
315 if varClass == VarClassConfig {
316 v.class = VarClassConfig
317 match = true
318 } else if v.class == VarClassConfig {
319 match = true
320 }
321 }
322 if valueType != v.valueType {
323 if valueType != starlarkTypeUnknown {
324 if v.valueType == starlarkTypeUnknown {
325 v.valueType = valueType
326 } else {
327 match = false
328 }
329 }
330 }
331 if !match {
332 fmt.Fprintf(os.Stderr, "cannot redefine %s as %v/%v (already defined as %v/%v)\n",
333 name, varClass, valueType, v.class, v.valueType)
334 }
335}
336
337// All known product variables.
338var KnownVariables = make(knownVariables)
339
340func init() {
341 for _, kv := range []string{
342 // Kernel-related variables that we know are lists.
343 "BOARD_VENDOR_KERNEL_MODULES",
344 "BOARD_VENDOR_RAMDISK_KERNEL_MODULES",
345 "BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD",
346 "BOARD_RECOVERY_KERNEL_MODULES",
347 // Other variables we knwo are lists
348 "ART_APEX_JARS",
349 } {
350 KnownVariables.NewVariable(kv, VarClassSoong, starlarkTypeList)
351 }
352}
353
354type nodeReceiver interface {
355 newNode(node starlarkNode)
356}
357
358// Information about the generated Starlark script.
359type StarlarkScript struct {
Sasha Smundak422b6142021-11-11 18:31:59 -0800360 mkFile string
361 moduleName string
362 mkPos scanner.Position
363 nodes []starlarkNode
364 inherited []*moduleInfo
365 hasErrors bool
366 topDir string
367 traceCalls bool // print enter/exit each init function
368 sourceFS fs.FS
369 makefileFinder MakefileFinder
370 nodeLocator func(pos mkparser.Pos) int
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800371}
372
373func (ss *StarlarkScript) newNode(node starlarkNode) {
374 ss.nodes = append(ss.nodes, node)
375}
376
377// varAssignmentScope points to the last assignment for each variable
378// in the current block. It is used during the parsing to chain
379// the assignments to a variable together.
380type varAssignmentScope struct {
381 outer *varAssignmentScope
382 vars map[string]*assignmentNode
383}
384
385// parseContext holds the script we are generating and all the ephemeral data
386// needed during the parsing.
387type parseContext struct {
388 script *StarlarkScript
389 nodes []mkparser.Node // Makefile as parsed by mkparser
390 currentNodeIndex int // Node in it we are processing
391 ifNestLevel int
392 moduleNameCount map[string]int // count of imported modules with given basename
393 fatalError error
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800394 outputSuffix string
Sasha Smundak7d934b92021-11-10 12:20:01 -0800395 errorLogger ErrorLogger
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800396 tracedVariables map[string]bool // variables to be traced in the generated script
397 variables map[string]variable
398 varAssignments *varAssignmentScope
399 receiver nodeReceiver // receptacle for the generated starlarkNode's
400 receiverStack []nodeReceiver
401 outputDir string
Sasha Smundak6609ba72021-07-22 18:32:56 -0700402 dependentModules map[string]*moduleInfo
Sasha Smundak3deb9682021-07-26 18:42:25 -0700403 soongNamespaces map[string]map[string]bool
Sasha Smundak6d852dd2021-09-27 20:34:39 -0700404 includeTops []string
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800405}
406
407func newParseContext(ss *StarlarkScript, nodes []mkparser.Node) *parseContext {
Sasha Smundak6609ba72021-07-22 18:32:56 -0700408 topdir, _ := filepath.Split(filepath.Join(ss.topDir, "foo"))
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800409 predefined := []struct{ name, value string }{
410 {"SRC_TARGET_DIR", filepath.Join("build", "make", "target")},
411 {"LOCAL_PATH", filepath.Dir(ss.mkFile)},
Sasha Smundak6609ba72021-07-22 18:32:56 -0700412 {"TOPDIR", topdir},
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800413 // TODO(asmundak): maybe read it from build/make/core/envsetup.mk?
414 {"TARGET_COPY_OUT_SYSTEM", "system"},
415 {"TARGET_COPY_OUT_SYSTEM_OTHER", "system_other"},
416 {"TARGET_COPY_OUT_DATA", "data"},
417 {"TARGET_COPY_OUT_ASAN", filepath.Join("data", "asan")},
418 {"TARGET_COPY_OUT_OEM", "oem"},
419 {"TARGET_COPY_OUT_RAMDISK", "ramdisk"},
420 {"TARGET_COPY_OUT_DEBUG_RAMDISK", "debug_ramdisk"},
421 {"TARGET_COPY_OUT_VENDOR_DEBUG_RAMDISK", "vendor_debug_ramdisk"},
422 {"TARGET_COPY_OUT_TEST_HARNESS_RAMDISK", "test_harness_ramdisk"},
423 {"TARGET_COPY_OUT_ROOT", "root"},
424 {"TARGET_COPY_OUT_RECOVERY", "recovery"},
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800425 {"TARGET_COPY_OUT_VENDOR_RAMDISK", "vendor_ramdisk"},
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800426 // TODO(asmundak): to process internal config files, we need the following variables:
427 // BOARD_CONFIG_VENDOR_PATH
428 // TARGET_VENDOR
429 // target_base_product
430 //
431
432 // the following utility variables are set in build/make/common/core.mk:
433 {"empty", ""},
434 {"space", " "},
435 {"comma", ","},
436 {"newline", "\n"},
437 {"pound", "#"},
438 {"backslash", "\\"},
439 }
440 ctx := &parseContext{
441 script: ss,
442 nodes: nodes,
443 currentNodeIndex: 0,
444 ifNestLevel: 0,
445 moduleNameCount: make(map[string]int),
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800446 variables: make(map[string]variable),
Sasha Smundak6609ba72021-07-22 18:32:56 -0700447 dependentModules: make(map[string]*moduleInfo),
Sasha Smundak3deb9682021-07-26 18:42:25 -0700448 soongNamespaces: make(map[string]map[string]bool),
Sasha Smundak6d852dd2021-09-27 20:34:39 -0700449 includeTops: []string{"vendor/google-devices"},
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800450 }
451 ctx.pushVarAssignments()
452 for _, item := range predefined {
453 ctx.variables[item.name] = &predefinedVariable{
454 baseVariable: baseVariable{nam: item.name, typ: starlarkTypeString},
455 value: &stringLiteralExpr{item.value},
456 }
457 }
458
459 return ctx
460}
461
462func (ctx *parseContext) lastAssignment(name string) *assignmentNode {
463 for va := ctx.varAssignments; va != nil; va = va.outer {
464 if v, ok := va.vars[name]; ok {
465 return v
466 }
467 }
468 return nil
469}
470
471func (ctx *parseContext) setLastAssignment(name string, asgn *assignmentNode) {
472 ctx.varAssignments.vars[name] = asgn
473}
474
475func (ctx *parseContext) pushVarAssignments() {
476 va := &varAssignmentScope{
477 outer: ctx.varAssignments,
478 vars: make(map[string]*assignmentNode),
479 }
480 ctx.varAssignments = va
481}
482
483func (ctx *parseContext) popVarAssignments() {
484 ctx.varAssignments = ctx.varAssignments.outer
485}
486
487func (ctx *parseContext) pushReceiver(rcv nodeReceiver) {
488 ctx.receiverStack = append(ctx.receiverStack, ctx.receiver)
489 ctx.receiver = rcv
490}
491
492func (ctx *parseContext) popReceiver() {
493 last := len(ctx.receiverStack) - 1
494 if last < 0 {
495 panic(fmt.Errorf("popReceiver: receiver stack empty"))
496 }
497 ctx.receiver = ctx.receiverStack[last]
498 ctx.receiverStack = ctx.receiverStack[0:last]
499}
500
501func (ctx *parseContext) hasNodes() bool {
502 return ctx.currentNodeIndex < len(ctx.nodes)
503}
504
505func (ctx *parseContext) getNode() mkparser.Node {
506 if !ctx.hasNodes() {
507 return nil
508 }
509 node := ctx.nodes[ctx.currentNodeIndex]
510 ctx.currentNodeIndex++
511 return node
512}
513
514func (ctx *parseContext) backNode() {
515 if ctx.currentNodeIndex <= 0 {
516 panic("Cannot back off")
517 }
518 ctx.currentNodeIndex--
519}
520
521func (ctx *parseContext) handleAssignment(a *mkparser.Assignment) {
522 // Handle only simple variables
523 if !a.Name.Const() {
524 ctx.errorf(a, "Only simple variables are handled")
525 return
526 }
527 name := a.Name.Strings[0]
Sasha Smundakea3bc3a2021-11-10 13:06:42 -0800528 // The `override` directive
529 // override FOO :=
530 // is parsed as an assignment to a variable named `override FOO`.
531 // There are very few places where `override` is used, just flag it.
532 if strings.HasPrefix(name, "override ") {
533 ctx.errorf(a, "cannot handle override directive")
534 }
535
Cole Faustc00184e2021-11-08 12:08:57 -0800536 // Soong configuration
Sasha Smundak3deb9682021-07-26 18:42:25 -0700537 if strings.HasPrefix(name, soongNsPrefix) {
538 ctx.handleSoongNsAssignment(strings.TrimPrefix(name, soongNsPrefix), a)
539 return
540 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800541 lhs := ctx.addVariable(name)
542 if lhs == nil {
543 ctx.errorf(a, "unknown variable %s", name)
544 return
545 }
546 _, isTraced := ctx.tracedVariables[name]
Sasha Smundak422b6142021-11-11 18:31:59 -0800547 asgn := &assignmentNode{lhs: lhs, mkValue: a.Value, isTraced: isTraced, location: ctx.errorLocation(a)}
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800548 if lhs.valueType() == starlarkTypeUnknown {
549 // Try to divine variable type from the RHS
550 asgn.value = ctx.parseMakeString(a, a.Value)
551 if xBad, ok := asgn.value.(*badExpr); ok {
552 ctx.wrapBadExpr(xBad)
553 return
554 }
555 inferred_type := asgn.value.typ()
556 if inferred_type != starlarkTypeUnknown {
Sasha Smundak9d011ab2021-07-09 16:00:57 -0700557 lhs.setValueType(inferred_type)
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800558 }
559 }
560 if lhs.valueType() == starlarkTypeList {
561 xConcat := ctx.buildConcatExpr(a)
562 if xConcat == nil {
563 return
564 }
565 switch len(xConcat.items) {
566 case 0:
567 asgn.value = &listExpr{}
568 case 1:
569 asgn.value = xConcat.items[0]
570 default:
571 asgn.value = xConcat
572 }
573 } else {
574 asgn.value = ctx.parseMakeString(a, a.Value)
575 if xBad, ok := asgn.value.(*badExpr); ok {
576 ctx.wrapBadExpr(xBad)
577 return
578 }
579 }
580
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800581 asgn.previous = ctx.lastAssignment(name)
582 ctx.setLastAssignment(name, asgn)
583 switch a.Type {
584 case "=", ":=":
585 asgn.flavor = asgnSet
586 case "+=":
587 if asgn.previous == nil && !asgn.lhs.isPreset() {
588 asgn.flavor = asgnMaybeAppend
589 } else {
590 asgn.flavor = asgnAppend
591 }
592 case "?=":
593 asgn.flavor = asgnMaybeSet
594 default:
595 panic(fmt.Errorf("unexpected assignment type %s", a.Type))
596 }
597
598 ctx.receiver.newNode(asgn)
599}
600
Sasha Smundak3deb9682021-07-26 18:42:25 -0700601func (ctx *parseContext) handleSoongNsAssignment(name string, asgn *mkparser.Assignment) {
602 val := ctx.parseMakeString(asgn, asgn.Value)
603 if xBad, ok := val.(*badExpr); ok {
604 ctx.wrapBadExpr(xBad)
605 return
606 }
Sasha Smundak3deb9682021-07-26 18:42:25 -0700607
608 // Unfortunately, Soong namespaces can be set up by directly setting corresponding Make
609 // variables instead of via add_soong_config_namespace + add_soong_config_var_value.
610 // Try to divine the call from the assignment as follows:
611 if name == "NAMESPACES" {
612 // Upon seeng
613 // SOONG_CONFIG_NAMESPACES += foo
614 // remember that there is a namespace `foo` and act as we saw
615 // $(call add_soong_config_namespace,foo)
616 s, ok := maybeString(val)
617 if !ok {
618 ctx.errorf(asgn, "cannot handle variables in SOONG_CONFIG_NAMESPACES assignment, please use add_soong_config_namespace instead")
619 return
620 }
621 for _, ns := range strings.Fields(s) {
622 ctx.addSoongNamespace(ns)
623 ctx.receiver.newNode(&exprNode{&callExpr{
Cole Faust9ebf6e42021-12-13 14:08:34 -0800624 name: baseName + ".soong_config_namespace",
625 args: []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{ns}},
Sasha Smundak3deb9682021-07-26 18:42:25 -0700626 returnType: starlarkTypeVoid,
627 }})
628 }
629 } else {
630 // Upon seeing
631 // SOONG_CONFIG_x_y = v
632 // find a namespace called `x` and act as if we encountered
Cole Faustc00184e2021-11-08 12:08:57 -0800633 // $(call soong_config_set,x,y,v)
Sasha Smundak3deb9682021-07-26 18:42:25 -0700634 // or check that `x_y` is a namespace, and then add the RHS of this assignment as variables in
635 // it.
636 // Emit an error in the ambiguous situation (namespaces `foo_bar` with a variable `baz`
637 // and `foo` with a variable `bar_baz`.
638 namespaceName := ""
639 if ctx.hasSoongNamespace(name) {
640 namespaceName = name
641 }
642 var varName string
643 for pos, ch := range name {
644 if !(ch == '_' && ctx.hasSoongNamespace(name[0:pos])) {
645 continue
646 }
647 if namespaceName != "" {
648 ctx.errorf(asgn, "ambiguous soong namespace (may be either `%s` or `%s`)", namespaceName, name[0:pos])
649 return
650 }
651 namespaceName = name[0:pos]
652 varName = name[pos+1:]
653 }
654 if namespaceName == "" {
655 ctx.errorf(asgn, "cannot figure out Soong namespace, please use add_soong_config_var_value macro instead")
656 return
657 }
658 if varName == "" {
659 // Remember variables in this namespace
660 s, ok := maybeString(val)
661 if !ok {
662 ctx.errorf(asgn, "cannot handle variables in SOONG_CONFIG_ assignment, please use add_soong_config_var_value instead")
663 return
664 }
665 ctx.updateSoongNamespace(asgn.Type != "+=", namespaceName, strings.Fields(s))
666 return
667 }
668
669 // Finally, handle assignment to a namespace variable
670 if !ctx.hasNamespaceVar(namespaceName, varName) {
671 ctx.errorf(asgn, "no %s variable in %s namespace, please use add_soong_config_var_value instead", varName, namespaceName)
672 return
673 }
Cole Faust9ebf6e42021-12-13 14:08:34 -0800674 fname := baseName + "." + soongConfigAssign
Sasha Smundak65b547e2021-09-17 15:35:41 -0700675 if asgn.Type == "+=" {
Cole Faust9ebf6e42021-12-13 14:08:34 -0800676 fname = baseName + "." + soongConfigAppend
Sasha Smundak65b547e2021-09-17 15:35:41 -0700677 }
Sasha Smundak3deb9682021-07-26 18:42:25 -0700678 ctx.receiver.newNode(&exprNode{&callExpr{
Sasha Smundak65b547e2021-09-17 15:35:41 -0700679 name: fname,
Cole Faust9ebf6e42021-12-13 14:08:34 -0800680 args: []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{namespaceName}, &stringLiteralExpr{varName}, val},
Sasha Smundak3deb9682021-07-26 18:42:25 -0700681 returnType: starlarkTypeVoid,
682 }})
683 }
684}
685
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800686func (ctx *parseContext) buildConcatExpr(a *mkparser.Assignment) *concatExpr {
687 xConcat := &concatExpr{}
688 var xItemList *listExpr
689 addToItemList := func(x ...starlarkExpr) {
690 if xItemList == nil {
691 xItemList = &listExpr{[]starlarkExpr{}}
692 }
693 xItemList.items = append(xItemList.items, x...)
694 }
695 finishItemList := func() {
696 if xItemList != nil {
697 xConcat.items = append(xConcat.items, xItemList)
698 xItemList = nil
699 }
700 }
701
702 items := a.Value.Words()
703 for _, item := range items {
704 // A function call in RHS is supposed to return a list, all other item
705 // expressions return individual elements.
706 switch x := ctx.parseMakeString(a, item).(type) {
707 case *badExpr:
708 ctx.wrapBadExpr(x)
709 return nil
710 case *stringLiteralExpr:
711 addToItemList(maybeConvertToStringList(x).(*listExpr).items...)
712 default:
713 switch x.typ() {
714 case starlarkTypeList:
715 finishItemList()
716 xConcat.items = append(xConcat.items, x)
717 case starlarkTypeString:
718 finishItemList()
719 xConcat.items = append(xConcat.items, &callExpr{
720 object: x,
721 name: "split",
722 args: nil,
723 returnType: starlarkTypeList,
724 })
725 default:
726 addToItemList(x)
727 }
728 }
729 }
730 if xItemList != nil {
731 xConcat.items = append(xConcat.items, xItemList)
732 }
733 return xConcat
734}
735
Sasha Smundak6609ba72021-07-22 18:32:56 -0700736func (ctx *parseContext) newDependentModule(path string, optional bool) *moduleInfo {
737 modulePath := ctx.loadedModulePath(path)
738 if mi, ok := ctx.dependentModules[modulePath]; ok {
Sasha Smundak868c5e32021-09-23 16:20:58 -0700739 mi.optional = mi.optional && optional
Sasha Smundak6609ba72021-07-22 18:32:56 -0700740 return mi
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800741 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800742 moduleName := moduleNameForFile(path)
743 moduleLocalName := "_" + moduleName
744 n, found := ctx.moduleNameCount[moduleName]
745 if found {
746 moduleLocalName += fmt.Sprintf("%d", n)
747 }
748 ctx.moduleNameCount[moduleName] = n + 1
Sasha Smundak6609ba72021-07-22 18:32:56 -0700749 mi := &moduleInfo{
750 path: modulePath,
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800751 originalPath: path,
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800752 moduleLocalName: moduleLocalName,
Sasha Smundak6609ba72021-07-22 18:32:56 -0700753 optional: optional,
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800754 }
Sasha Smundak6609ba72021-07-22 18:32:56 -0700755 ctx.dependentModules[modulePath] = mi
756 ctx.script.inherited = append(ctx.script.inherited, mi)
757 return mi
758}
759
760func (ctx *parseContext) handleSubConfig(
761 v mkparser.Node, pathExpr starlarkExpr, loadAlways bool, processModule func(inheritedModule)) {
Sasha Smundak6609ba72021-07-22 18:32:56 -0700762
763 // In a simple case, the name of a module to inherit/include is known statically.
764 if path, ok := maybeString(pathExpr); ok {
Sasha Smundak868c5e32021-09-23 16:20:58 -0700765 // Note that even if this directive loads a module unconditionally, a module may be
766 // absent without causing any harm if this directive is inside an if/else block.
767 moduleShouldExist := loadAlways && ctx.ifNestLevel == 0
Sasha Smundak6609ba72021-07-22 18:32:56 -0700768 if strings.Contains(path, "*") {
769 if paths, err := fs.Glob(ctx.script.sourceFS, path); err == nil {
770 for _, p := range paths {
Sasha Smundak868c5e32021-09-23 16:20:58 -0700771 mi := ctx.newDependentModule(p, !moduleShouldExist)
772 processModule(inheritedStaticModule{mi, loadAlways})
Sasha Smundak6609ba72021-07-22 18:32:56 -0700773 }
774 } else {
775 ctx.errorf(v, "cannot glob wildcard argument")
776 }
777 } else {
Sasha Smundak868c5e32021-09-23 16:20:58 -0700778 mi := ctx.newDependentModule(path, !moduleShouldExist)
779 processModule(inheritedStaticModule{mi, loadAlways})
Sasha Smundak6609ba72021-07-22 18:32:56 -0700780 }
781 return
782 }
783
784 // If module path references variables (e.g., $(v1)/foo/$(v2)/device-config.mk), find all the paths in the
785 // source tree that may be a match and the corresponding variable values. For instance, if the source tree
786 // contains vendor1/foo/abc/dev.mk and vendor2/foo/def/dev.mk, the first one will be inherited when
787 // (v1, v2) == ('vendor1', 'abc'), and the second one when (v1, v2) == ('vendor2', 'def').
788 // We then emit the code that loads all of them, e.g.:
789 // load("//vendor1/foo/abc:dev.rbc", _dev1_init="init")
790 // load("//vendor2/foo/def/dev.rbc", _dev2_init="init")
791 // And then inherit it as follows:
792 // _e = {
793 // "vendor1/foo/abc/dev.mk": ("vendor1/foo/abc/dev", _dev1_init),
794 // "vendor2/foo/def/dev.mk": ("vendor2/foo/def/dev", _dev_init2) }.get("%s/foo/%s/dev.mk" % (v1, v2))
795 // if _e:
796 // rblf.inherit(handle, _e[0], _e[1])
797 //
798 var matchingPaths []string
799 varPath, ok := pathExpr.(*interpolateExpr)
800 if !ok {
801 ctx.errorf(v, "inherit-product/include argument is too complex")
802 return
803 }
804
805 pathPattern := []string{varPath.chunks[0]}
806 for _, chunk := range varPath.chunks[1:] {
807 if chunk != "" {
808 pathPattern = append(pathPattern, chunk)
809 }
810 }
Sasha Smundak6d852dd2021-09-27 20:34:39 -0700811 if pathPattern[0] == "" {
812 // If pattern starts from the top. restrict it to the directories where
813 // we know inherit-product uses dynamically calculated path.
814 for _, p := range ctx.includeTops {
815 pathPattern[0] = p
816 matchingPaths = append(matchingPaths, ctx.findMatchingPaths(pathPattern)...)
Sasha Smundak6609ba72021-07-22 18:32:56 -0700817 }
Sasha Smundak6d852dd2021-09-27 20:34:39 -0700818 } else {
819 matchingPaths = ctx.findMatchingPaths(pathPattern)
Sasha Smundak6609ba72021-07-22 18:32:56 -0700820 }
821 // Safeguard against $(call inherit-product,$(PRODUCT_PATH))
Sasha Smundak90be8c52021-08-03 11:06:10 -0700822 const maxMatchingFiles = 150
Sasha Smundak6609ba72021-07-22 18:32:56 -0700823 if len(matchingPaths) > maxMatchingFiles {
824 ctx.errorf(v, "there are >%d files matching the pattern, please rewrite it", maxMatchingFiles)
825 return
826 }
827 res := inheritedDynamicModule{*varPath, []*moduleInfo{}, loadAlways}
828 for _, p := range matchingPaths {
829 // A product configuration files discovered dynamically may attempt to inherit
830 // from another one which does not exist in this source tree. Prevent load errors
831 // by always loading the dynamic files as optional.
832 res.candidateModules = append(res.candidateModules, ctx.newDependentModule(p, true))
833 }
834 processModule(res)
835}
836
837func (ctx *parseContext) findMatchingPaths(pattern []string) []string {
838 files := ctx.script.makefileFinder.Find(ctx.script.topDir)
839 if len(pattern) == 0 {
840 return files
841 }
842
843 // Create regular expression from the pattern
844 s_regexp := "^" + regexp.QuoteMeta(pattern[0])
845 for _, s := range pattern[1:] {
846 s_regexp += ".*" + regexp.QuoteMeta(s)
847 }
848 s_regexp += "$"
849 rex := regexp.MustCompile(s_regexp)
850
851 // Now match
852 var res []string
853 for _, p := range files {
854 if rex.MatchString(p) {
855 res = append(res, p)
856 }
857 }
858 return res
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800859}
860
Cole Faust9ebf6e42021-12-13 14:08:34 -0800861func (ctx *parseContext) handleInheritModule(v mkparser.Node, args *mkparser.MakeString, loadAlways bool) {
862 args.TrimLeftSpaces()
863 args.TrimRightSpaces()
864 pathExpr := ctx.parseMakeString(v, args)
865 if _, ok := pathExpr.(*badExpr); ok {
866 ctx.errorf(v, "Unable to parse argument to inherit")
867 }
Sasha Smundak6609ba72021-07-22 18:32:56 -0700868 ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) {
Sasha Smundak868c5e32021-09-23 16:20:58 -0700869 ctx.receiver.newNode(&inheritNode{im, loadAlways})
Sasha Smundak6609ba72021-07-22 18:32:56 -0700870 })
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800871}
872
873func (ctx *parseContext) handleInclude(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) {
Sasha Smundak6609ba72021-07-22 18:32:56 -0700874 ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) {
Sasha Smundak868c5e32021-09-23 16:20:58 -0700875 ctx.receiver.newNode(&includeNode{im, loadAlways})
Sasha Smundak6609ba72021-07-22 18:32:56 -0700876 })
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800877}
878
879func (ctx *parseContext) handleVariable(v *mkparser.Variable) {
880 // Handle:
881 // $(call inherit-product,...)
882 // $(call inherit-product-if-exists,...)
883 // $(info xxx)
884 // $(warning xxx)
885 // $(error xxx)
Cole Faust9ebf6e42021-12-13 14:08:34 -0800886 // $(call other-custom-functions,...)
887
888 // inherit-product(-if-exists) gets converted to a series of statements,
889 // not just a single expression like parseReference returns. So handle it
890 // separately at the beginning here.
891 if strings.HasPrefix(v.Name.Dump(), "call inherit-product,") {
892 args := v.Name.Clone()
893 args.ReplaceLiteral("call inherit-product,", "")
894 ctx.handleInheritModule(v, args, true)
895 return
896 }
897 if strings.HasPrefix(v.Name.Dump(), "call inherit-product-if-exists,") {
898 args := v.Name.Clone()
899 args.ReplaceLiteral("call inherit-product-if-exists,", "")
900 ctx.handleInheritModule(v, args, false)
901 return
902 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800903 expr := ctx.parseReference(v, v.Name)
904 switch x := expr.(type) {
905 case *callExpr:
Cole Faust9ebf6e42021-12-13 14:08:34 -0800906 ctx.receiver.newNode(&exprNode{expr})
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800907 case *badExpr:
908 ctx.wrapBadExpr(x)
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800909 default:
910 ctx.errorf(v, "cannot handle %s", v.Dump())
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800911 }
912}
913
914func (ctx *parseContext) handleDefine(directive *mkparser.Directive) {
Sasha Smundakf3e072a2021-07-14 12:50:28 -0700915 macro_name := strings.Fields(directive.Args.Strings[0])[0]
916 // Ignore the macros that we handle
Cole Faust9ebf6e42021-12-13 14:08:34 -0800917 _, ignored := ignoredDefines[macro_name]
918 _, known := knownFunctions[macro_name]
919 if !ignored && !known {
Sasha Smundakf3e072a2021-07-14 12:50:28 -0700920 ctx.errorf(directive, "define is not supported: %s", macro_name)
921 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800922}
923
924func (ctx *parseContext) handleIfBlock(ifDirective *mkparser.Directive) {
925 ssSwitch := &switchNode{}
926 ctx.pushReceiver(ssSwitch)
927 for ctx.processBranch(ifDirective); ctx.hasNodes() && ctx.fatalError == nil; {
928 node := ctx.getNode()
929 switch x := node.(type) {
930 case *mkparser.Directive:
931 switch x.Name {
932 case "else", "elifdef", "elifndef", "elifeq", "elifneq":
933 ctx.processBranch(x)
934 case "endif":
935 ctx.popReceiver()
936 ctx.receiver.newNode(ssSwitch)
937 return
938 default:
939 ctx.errorf(node, "unexpected directive %s", x.Name)
940 }
941 default:
942 ctx.errorf(ifDirective, "unexpected statement")
943 }
944 }
945 if ctx.fatalError == nil {
946 ctx.fatalError = fmt.Errorf("no matching endif for %s", ifDirective.Dump())
947 }
948 ctx.popReceiver()
949}
950
951// processBranch processes a single branch (if/elseif/else) until the next directive
952// on the same level.
953func (ctx *parseContext) processBranch(check *mkparser.Directive) {
954 block := switchCase{gate: ctx.parseCondition(check)}
955 defer func() {
956 ctx.popVarAssignments()
957 ctx.ifNestLevel--
958
959 }()
960 ctx.pushVarAssignments()
961 ctx.ifNestLevel++
962
963 ctx.pushReceiver(&block)
964 for ctx.hasNodes() {
965 node := ctx.getNode()
Cole Faust591a1fe2021-11-08 15:37:57 -0800966 if d, ok := node.(*mkparser.Directive); ok {
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800967 switch d.Name {
968 case "else", "elifdef", "elifndef", "elifeq", "elifneq", "endif":
969 ctx.popReceiver()
970 ctx.receiver.newNode(&block)
971 ctx.backNode()
972 return
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800973 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800974 }
Cole Faust591a1fe2021-11-08 15:37:57 -0800975 ctx.handleSimpleStatement(node)
Sasha Smundakb051c4e2020-11-05 20:45:07 -0800976 }
977 ctx.fatalError = fmt.Errorf("no matching endif for %s", check.Dump())
978 ctx.popReceiver()
979}
980
981func (ctx *parseContext) newIfDefinedNode(check *mkparser.Directive) (starlarkExpr, bool) {
982 if !check.Args.Const() {
983 return ctx.newBadExpr(check, "ifdef variable ref too complex: %s", check.Args.Dump()), false
984 }
985 v := ctx.addVariable(check.Args.Strings[0])
986 return &variableDefinedExpr{v}, true
987}
988
989func (ctx *parseContext) parseCondition(check *mkparser.Directive) starlarkNode {
990 switch check.Name {
991 case "ifdef", "ifndef", "elifdef", "elifndef":
992 v, ok := ctx.newIfDefinedNode(check)
993 if ok && strings.HasSuffix(check.Name, "ndef") {
994 v = &notExpr{v}
995 }
996 return &ifNode{
997 isElif: strings.HasPrefix(check.Name, "elif"),
998 expr: v,
999 }
1000 case "ifeq", "ifneq", "elifeq", "elifneq":
1001 return &ifNode{
1002 isElif: strings.HasPrefix(check.Name, "elif"),
1003 expr: ctx.parseCompare(check),
1004 }
1005 case "else":
1006 return &elseNode{}
1007 default:
1008 panic(fmt.Errorf("%s: unknown directive: %s", ctx.script.mkFile, check.Dump()))
1009 }
1010}
1011
1012func (ctx *parseContext) newBadExpr(node mkparser.Node, text string, args ...interface{}) starlarkExpr {
1013 message := fmt.Sprintf(text, args...)
1014 if ctx.errorLogger != nil {
Sasha Smundak422b6142021-11-11 18:31:59 -08001015 ctx.errorLogger.NewError(ctx.errorLocation(node), node, text, args...)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001016 }
1017 ctx.script.hasErrors = true
Sasha Smundak422b6142021-11-11 18:31:59 -08001018 return &badExpr{errorLocation: ctx.errorLocation(node), message: message}
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001019}
1020
1021func (ctx *parseContext) parseCompare(cond *mkparser.Directive) starlarkExpr {
1022 // Strip outer parentheses
1023 mkArg := cloneMakeString(cond.Args)
1024 mkArg.Strings[0] = strings.TrimLeft(mkArg.Strings[0], "( ")
1025 n := len(mkArg.Strings)
1026 mkArg.Strings[n-1] = strings.TrimRight(mkArg.Strings[n-1], ") ")
1027 args := mkArg.Split(",")
1028 // TODO(asmundak): handle the case where the arguments are in quotes and space-separated
1029 if len(args) != 2 {
1030 return ctx.newBadExpr(cond, "ifeq/ifneq len(args) != 2 %s", cond.Dump())
1031 }
1032 args[0].TrimRightSpaces()
1033 args[1].TrimLeftSpaces()
1034
1035 isEq := !strings.HasSuffix(cond.Name, "neq")
Cole Faustf8320212021-11-10 15:05:07 -08001036 xLeft := ctx.parseMakeString(cond, args[0])
1037 xRight := ctx.parseMakeString(cond, args[1])
1038 if bad, ok := xLeft.(*badExpr); ok {
1039 return bad
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001040 }
Cole Faustf8320212021-11-10 15:05:07 -08001041 if bad, ok := xRight.(*badExpr); ok {
1042 return bad
1043 }
1044
1045 if expr, ok := ctx.parseCompareSpecialCases(cond, xLeft, xRight); ok {
1046 return expr
1047 }
1048
Cole Faust9ebf6e42021-12-13 14:08:34 -08001049 var stringOperand string
1050 var otherOperand starlarkExpr
1051 if s, ok := maybeString(xLeft); ok {
1052 stringOperand = s
1053 otherOperand = xRight
1054 } else if s, ok := maybeString(xRight); ok {
1055 stringOperand = s
1056 otherOperand = xLeft
1057 }
1058
1059 not := func(expr starlarkExpr) starlarkExpr {
1060 switch typedExpr := expr.(type) {
1061 case *inExpr:
1062 typedExpr.isNot = !typedExpr.isNot
1063 return typedExpr
1064 case *eqExpr:
1065 typedExpr.isEq = !typedExpr.isEq
1066 return typedExpr
1067 default:
1068 return &notExpr{expr: expr}
1069 }
1070 }
1071
1072 // If we've identified one of the operands as being a string literal, check
1073 // for some special cases we can do to simplify the resulting expression.
1074 if otherOperand != nil {
1075 if stringOperand == "" {
1076 if isEq {
1077 return not(otherOperand)
1078 } else {
1079 return otherOperand
1080 }
1081 }
1082 if stringOperand == "true" && otherOperand.typ() == starlarkTypeBool {
1083 if !isEq {
1084 return not(otherOperand)
1085 } else {
1086 return otherOperand
1087 }
1088 }
1089 }
1090
Cole Faustf8320212021-11-10 15:05:07 -08001091 return &eqExpr{left: xLeft, right: xRight, isEq: isEq}
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001092}
1093
Cole Faustf8320212021-11-10 15:05:07 -08001094// Given an if statement's directive and the left/right starlarkExprs,
1095// check if the starlarkExprs are one of a few hardcoded special cases
1096// that can be converted to a simpler equalify expression than simply comparing
1097// the two.
1098func (ctx *parseContext) parseCompareSpecialCases(directive *mkparser.Directive, left starlarkExpr,
1099 right starlarkExpr) (starlarkExpr, bool) {
1100 isEq := !strings.HasSuffix(directive.Name, "neq")
1101
1102 // All the special cases require a call on one side and a
1103 // string literal/variable on the other. Turn the left/right variables into
1104 // call/value variables, and return false if that's not possible.
1105 var value starlarkExpr = nil
1106 call, ok := left.(*callExpr)
1107 if ok {
1108 switch right.(type) {
1109 case *stringLiteralExpr, *variableRefExpr:
1110 value = right
1111 }
1112 } else {
1113 call, _ = right.(*callExpr)
1114 switch left.(type) {
1115 case *stringLiteralExpr, *variableRefExpr:
1116 value = left
1117 }
1118 }
1119
1120 if call == nil || value == nil {
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001121 return nil, false
1122 }
Cole Faustf8320212021-11-10 15:05:07 -08001123
Cole Faustf8320212021-11-10 15:05:07 -08001124 switch call.name {
Cole Faust9ebf6e42021-12-13 14:08:34 -08001125 case baseName + ".filter", baseName + ".filter-out":
Cole Faustf8320212021-11-10 15:05:07 -08001126 return ctx.parseCompareFilterFuncResult(directive, call, value, isEq), true
Cole Faust9ebf6e42021-12-13 14:08:34 -08001127 case baseName + ".expand_wildcard":
Cole Faustf8320212021-11-10 15:05:07 -08001128 return ctx.parseCompareWildcardFuncResult(directive, call, value, !isEq), true
Cole Faust9ebf6e42021-12-13 14:08:34 -08001129 case baseName + ".findstring":
Cole Faustf8320212021-11-10 15:05:07 -08001130 return ctx.parseCheckFindstringFuncResult(directive, call, value, !isEq), true
Cole Faust9ebf6e42021-12-13 14:08:34 -08001131 case baseName + ".strip":
Cole Faustf8320212021-11-10 15:05:07 -08001132 return ctx.parseCompareStripFuncResult(directive, call, value, !isEq), true
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001133 }
Cole Faustf8320212021-11-10 15:05:07 -08001134 return nil, false
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001135}
1136
1137func (ctx *parseContext) parseCompareFilterFuncResult(cond *mkparser.Directive,
1138 filterFuncCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
1139 // We handle:
Sasha Smundak0554d762021-07-08 18:26:12 -07001140 // * ifeq/ifneq (,$(filter v1 v2 ..., EXPR) becomes if EXPR not in/in ["v1", "v2", ...]
1141 // * ifeq/ifneq (,$(filter EXPR, v1 v2 ...) becomes if EXPR not in/in ["v1", "v2", ...]
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001142 // * ifeq/ifneq ($(VAR),$(filter $(VAR), v1 v2 ...) becomes if VAR in/not in ["v1", "v2"]
1143 // TODO(Asmundak): check the last case works for filter-out, too.
1144 xPattern := filterFuncCall.args[0]
1145 xText := filterFuncCall.args[1]
1146 var xInList *stringLiteralExpr
Sasha Smundak0554d762021-07-08 18:26:12 -07001147 var expr starlarkExpr
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001148 var ok bool
1149 switch x := xValue.(type) {
1150 case *stringLiteralExpr:
1151 if x.literal != "" {
1152 return ctx.newBadExpr(cond, "filter comparison to non-empty value: %s", xValue)
1153 }
1154 // Either pattern or text should be const, and the
1155 // non-const one should be varRefExpr
Sasha Smundak5f463be2021-09-15 18:43:36 -07001156 if xInList, ok = xPattern.(*stringLiteralExpr); ok && !strings.ContainsRune(xInList.literal, '%') && xText.typ() == starlarkTypeList {
Sasha Smundak0554d762021-07-08 18:26:12 -07001157 expr = xText
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001158 } else if xInList, ok = xText.(*stringLiteralExpr); ok {
Sasha Smundak0554d762021-07-08 18:26:12 -07001159 expr = xPattern
1160 } else {
Sasha Smundak5f463be2021-09-15 18:43:36 -07001161 expr = &callExpr{
Sasha Smundak0554d762021-07-08 18:26:12 -07001162 object: nil,
1163 name: filterFuncCall.name,
1164 args: filterFuncCall.args,
1165 returnType: starlarkTypeBool,
1166 }
Sasha Smundak5f463be2021-09-15 18:43:36 -07001167 if negate {
1168 expr = &notExpr{expr: expr}
1169 }
1170 return expr
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001171 }
1172 case *variableRefExpr:
1173 if v, ok := xPattern.(*variableRefExpr); ok {
1174 if xInList, ok = xText.(*stringLiteralExpr); ok && v.ref.name() == x.ref.name() {
1175 // ifeq/ifneq ($(VAR),$(filter $(VAR), v1 v2 ...), flip negate,
1176 // it's the opposite to what is done when comparing to empty.
Sasha Smundak0554d762021-07-08 18:26:12 -07001177 expr = xPattern
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001178 negate = !negate
1179 }
1180 }
1181 }
Sasha Smundak0554d762021-07-08 18:26:12 -07001182 if expr != nil && xInList != nil {
1183 slExpr := newStringListExpr(strings.Fields(xInList.literal))
1184 // Generate simpler code for the common cases:
1185 if expr.typ() == starlarkTypeList {
1186 if len(slExpr.items) == 1 {
1187 // Checking that a string belongs to list
1188 return &inExpr{isNot: negate, list: expr, expr: slExpr.items[0]}
1189 } else {
1190 // TODO(asmundak):
1191 panic("TBD")
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001192 }
Sasha Smundak0554d762021-07-08 18:26:12 -07001193 } else if len(slExpr.items) == 1 {
1194 return &eqExpr{left: expr, right: slExpr.items[0], isEq: !negate}
1195 } else {
1196 return &inExpr{isNot: negate, list: newStringListExpr(strings.Fields(xInList.literal)), expr: expr}
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001197 }
1198 }
1199 return ctx.newBadExpr(cond, "filter arguments are too complex: %s", cond.Dump())
1200}
1201
1202func (ctx *parseContext) parseCompareWildcardFuncResult(directive *mkparser.Directive,
1203 xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
Sasha Smundak0554d762021-07-08 18:26:12 -07001204 if !isEmptyString(xValue) {
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001205 return ctx.newBadExpr(directive, "wildcard result can be compared only to empty: %s", xValue)
1206 }
Cole Faust9ebf6e42021-12-13 14:08:34 -08001207 callFunc := baseName + ".file_wildcard_exists"
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001208 if s, ok := xCall.args[0].(*stringLiteralExpr); ok && !strings.ContainsAny(s.literal, "*?{[") {
Cole Faust9ebf6e42021-12-13 14:08:34 -08001209 callFunc = baseName + ".file_exists"
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001210 }
1211 var cc starlarkExpr = &callExpr{name: callFunc, args: xCall.args, returnType: starlarkTypeBool}
1212 if !negate {
1213 cc = &notExpr{cc}
1214 }
1215 return cc
1216}
1217
1218func (ctx *parseContext) parseCheckFindstringFuncResult(directive *mkparser.Directive,
1219 xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
Sasha Smundak0554d762021-07-08 18:26:12 -07001220 if isEmptyString(xValue) {
1221 return &eqExpr{
1222 left: &callExpr{
1223 object: xCall.args[1],
1224 name: "find",
1225 args: []starlarkExpr{xCall.args[0]},
1226 returnType: starlarkTypeInt,
1227 },
1228 right: &intLiteralExpr{-1},
1229 isEq: !negate,
1230 }
Cole Faust0e9418c2021-12-13 16:33:25 -08001231 } else if s, ok := maybeString(xValue); ok {
1232 if s2, ok := maybeString(xCall.args[0]); ok && s == s2 {
1233 return &eqExpr{
1234 left: &callExpr{
1235 object: xCall.args[1],
1236 name: "find",
1237 args: []starlarkExpr{xCall.args[0]},
1238 returnType: starlarkTypeInt,
1239 },
1240 right: &intLiteralExpr{-1},
1241 isEq: negate,
1242 }
1243 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001244 }
Cole Faust0e9418c2021-12-13 16:33:25 -08001245 return ctx.newBadExpr(directive, "$(findstring) can only be compared to nothing or its first argument")
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001246}
1247
1248func (ctx *parseContext) parseCompareStripFuncResult(directive *mkparser.Directive,
1249 xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
1250 if _, ok := xValue.(*stringLiteralExpr); !ok {
1251 return ctx.newBadExpr(directive, "strip result can be compared only to string: %s", xValue)
1252 }
1253 return &eqExpr{
1254 left: &callExpr{
1255 name: "strip",
1256 args: xCall.args,
1257 returnType: starlarkTypeString,
1258 },
1259 right: xValue, isEq: !negate}
1260}
1261
1262// parses $(...), returning an expression
1263func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeString) starlarkExpr {
1264 ref.TrimLeftSpaces()
1265 ref.TrimRightSpaces()
1266 refDump := ref.Dump()
1267
1268 // Handle only the case where the first (or only) word is constant
1269 words := ref.SplitN(" ", 2)
1270 if !words[0].Const() {
1271 return ctx.newBadExpr(node, "reference is too complex: %s", refDump)
1272 }
1273
1274 // If it is a single word, it can be a simple variable
1275 // reference or a function call
Cole Faust9ebf6e42021-12-13 14:08:34 -08001276 if len(words) == 1 && !isMakeControlFunc(refDump) && refDump != "shell" {
Sasha Smundak65b547e2021-09-17 15:35:41 -07001277 if strings.HasPrefix(refDump, soongNsPrefix) {
1278 // TODO (asmundak): if we find many, maybe handle them.
Cole Faustc00184e2021-11-08 12:08:57 -08001279 return ctx.newBadExpr(node, "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: %s", refDump)
Sasha Smundak65b547e2021-09-17 15:35:41 -07001280 }
Cole Faustc36c9622021-12-07 15:20:45 -08001281 // Handle substitution references: https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html
1282 if strings.Contains(refDump, ":") {
1283 parts := strings.SplitN(refDump, ":", 2)
1284 substParts := strings.SplitN(parts[1], "=", 2)
1285 if len(substParts) < 2 || strings.Count(substParts[0], "%") > 1 {
1286 return ctx.newBadExpr(node, "Invalid substitution reference")
1287 }
1288 if !strings.Contains(substParts[0], "%") {
1289 if strings.Contains(substParts[1], "%") {
1290 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.")
1291 }
1292 substParts[0] = "%" + substParts[0]
1293 substParts[1] = "%" + substParts[1]
1294 }
1295 v := ctx.addVariable(parts[0])
1296 if v == nil {
1297 return ctx.newBadExpr(node, "unknown variable %s", refDump)
1298 }
1299 return &callExpr{
Cole Faust9ebf6e42021-12-13 14:08:34 -08001300 name: baseName + ".mkpatsubst",
1301 returnType: starlarkTypeString,
Cole Faustc36c9622021-12-07 15:20:45 -08001302 args: []starlarkExpr{
1303 &stringLiteralExpr{literal: substParts[0]},
1304 &stringLiteralExpr{literal: substParts[1]},
Cole Faustfc438682021-12-14 12:46:32 -08001305 NewVariableRefExpr(v, ctx.lastAssignment(v.name()) != nil),
Cole Faustc36c9622021-12-07 15:20:45 -08001306 },
1307 }
1308 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001309 if v := ctx.addVariable(refDump); v != nil {
Cole Faustfc438682021-12-14 12:46:32 -08001310 return NewVariableRefExpr(v, ctx.lastAssignment(v.name()) != nil)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001311 }
1312 return ctx.newBadExpr(node, "unknown variable %s", refDump)
1313 }
1314
1315 expr := &callExpr{name: words[0].Dump(), returnType: starlarkTypeUnknown}
Cole Faust9ebf6e42021-12-13 14:08:34 -08001316 args := mkparser.SimpleMakeString("", words[0].Pos())
1317 if len(words) >= 2 {
1318 args = words[1]
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001319 }
Cole Faust9ebf6e42021-12-13 14:08:34 -08001320 args.TrimLeftSpaces()
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001321 if expr.name == "call" {
1322 words = args.SplitN(",", 2)
1323 if words[0].Empty() || !words[0].Const() {
Sasha Smundakf2c9f8b2021-07-27 10:44:48 -07001324 return ctx.newBadExpr(node, "cannot handle %s", refDump)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001325 }
1326 expr.name = words[0].Dump()
1327 if len(words) < 2 {
Sasha Smundak6609ba72021-07-22 18:32:56 -07001328 args = &mkparser.MakeString{}
1329 } else {
1330 args = words[1]
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001331 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001332 }
1333 if kf, found := knownFunctions[expr.name]; found {
Cole Faust9ebf6e42021-12-13 14:08:34 -08001334 return kf.parse(ctx, node, args)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001335 } else {
1336 return ctx.newBadExpr(node, "cannot handle invoking %s", expr.name)
1337 }
Cole Faust9ebf6e42021-12-13 14:08:34 -08001338}
1339
1340type simpleCallParser struct {
1341 name string
1342 returnType starlarkType
1343 addGlobals bool
1344}
1345
1346func (p *simpleCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1347 expr := &callExpr{name: p.name, returnType: p.returnType}
1348 if p.addGlobals {
1349 expr.args = append(expr.args, &globalsExpr{})
1350 }
1351 for _, arg := range args.Split(",") {
1352 arg.TrimLeftSpaces()
1353 arg.TrimRightSpaces()
1354 x := ctx.parseMakeString(node, arg)
1355 if xBad, ok := x.(*badExpr); ok {
1356 return xBad
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001357 }
Cole Faust9ebf6e42021-12-13 14:08:34 -08001358 expr.args = append(expr.args, x)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001359 }
1360 return expr
1361}
1362
Cole Faust9ebf6e42021-12-13 14:08:34 -08001363type makeControlFuncParser struct {
1364 name string
1365}
1366
1367func (p *makeControlFuncParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1368 // Make control functions need special treatment as everything
1369 // after the name is a single text argument
1370 x := ctx.parseMakeString(node, args)
1371 if xBad, ok := x.(*badExpr); ok {
1372 return xBad
1373 }
1374 return &callExpr{
1375 name: p.name,
1376 args: []starlarkExpr{
1377 &stringLiteralExpr{ctx.script.mkFile},
1378 x,
1379 },
1380 returnType: starlarkTypeUnknown,
1381 }
1382}
1383
1384type shellCallParser struct{}
1385
1386func (p *shellCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1387 // Shell functions need special treatment as everything
1388 // after the name is a single text argument
1389 x := ctx.parseMakeString(node, args)
1390 if xBad, ok := x.(*badExpr); ok {
1391 return xBad
1392 }
1393 return &callExpr{
1394 name: baseName + ".shell",
1395 args: []starlarkExpr{x},
1396 returnType: starlarkTypeUnknown,
1397 }
1398}
1399
1400type myDirCallParser struct{}
1401
1402func (p *myDirCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1403 if !args.Empty() {
1404 return ctx.newBadExpr(node, "my-dir function cannot have any arguments passed to it.")
1405 }
1406 return &variableRefExpr{ctx.addVariable("LOCAL_PATH"), true}
1407}
1408
Cole Faust9ebf6e42021-12-13 14:08:34 -08001409type isProductInListCallParser struct{}
1410
1411func (p *isProductInListCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1412 if args.Empty() {
1413 return ctx.newBadExpr(node, "is-product-in-list requires an argument")
1414 }
1415 return &inExpr{
1416 expr: &variableRefExpr{ctx.addVariable("TARGET_PRODUCT"), true},
1417 list: maybeConvertToStringList(ctx.parseMakeString(node, args)),
1418 isNot: false,
1419 }
1420}
1421
1422type isVendorBoardPlatformCallParser struct{}
1423
1424func (p *isVendorBoardPlatformCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1425 if args.Empty() || !identifierFullMatchRegex.MatchString(args.Dump()) {
1426 return ctx.newBadExpr(node, "cannot handle non-constant argument to is-vendor-board-platform")
1427 }
1428 return &inExpr{
1429 expr: &variableRefExpr{ctx.addVariable("TARGET_BOARD_PLATFORM"), false},
1430 list: &variableRefExpr{ctx.addVariable(args.Dump() + "_BOARD_PLATFORMS"), true},
1431 isNot: false,
1432 }
1433}
1434
1435type isVendorBoardQcomCallParser struct{}
1436
1437func (p *isVendorBoardQcomCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
1438 if !args.Empty() {
1439 return ctx.newBadExpr(node, "is-vendor-board-qcom does not accept any arguments")
1440 }
1441 return &inExpr{
1442 expr: &variableRefExpr{ctx.addVariable("TARGET_BOARD_PLATFORM"), false},
1443 list: &variableRefExpr{ctx.addVariable("QCOM_BOARD_PLATFORMS"), true},
1444 isNot: false,
1445 }
1446}
1447
1448type substCallParser struct {
1449 fname string
1450}
1451
1452func (p *substCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001453 words := args.Split(",")
1454 if len(words) != 3 {
Cole Faust9ebf6e42021-12-13 14:08:34 -08001455 return ctx.newBadExpr(node, "%s function should have 3 arguments", p.fname)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001456 }
Sasha Smundak35434ed2021-11-05 16:29:56 -07001457 from := ctx.parseMakeString(node, words[0])
1458 if xBad, ok := from.(*badExpr); ok {
1459 return xBad
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001460 }
Sasha Smundak35434ed2021-11-05 16:29:56 -07001461 to := ctx.parseMakeString(node, words[1])
1462 if xBad, ok := to.(*badExpr); ok {
1463 return xBad
1464 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001465 words[2].TrimLeftSpaces()
1466 words[2].TrimRightSpaces()
1467 obj := ctx.parseMakeString(node, words[2])
Sasha Smundak9d011ab2021-07-09 16:00:57 -07001468 typ := obj.typ()
Cole Faust9ebf6e42021-12-13 14:08:34 -08001469 if typ == starlarkTypeString && p.fname == "subst" {
Sasha Smundak94b41c72021-07-12 18:30:42 -07001470 // Optimization: if it's $(subst from, to, string), emit string.replace(from, to)
Sasha Smundak9d011ab2021-07-09 16:00:57 -07001471 return &callExpr{
1472 object: obj,
1473 name: "replace",
Sasha Smundak35434ed2021-11-05 16:29:56 -07001474 args: []starlarkExpr{from, to},
Sasha Smundak9d011ab2021-07-09 16:00:57 -07001475 returnType: typ,
1476 }
1477 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001478 return &callExpr{
Cole Faust9ebf6e42021-12-13 14:08:34 -08001479 name: baseName + ".mk" + p.fname,
Sasha Smundak35434ed2021-11-05 16:29:56 -07001480 args: []starlarkExpr{from, to, obj},
Sasha Smundak9d011ab2021-07-09 16:00:57 -07001481 returnType: obj.typ(),
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001482 }
1483}
1484
Cole Faust9ebf6e42021-12-13 14:08:34 -08001485type ifCallParser struct{}
1486
1487func (p *ifCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
Cole Faust4eadba72021-12-07 11:54:52 -08001488 words := args.Split(",")
1489 if len(words) != 2 && len(words) != 3 {
1490 return ctx.newBadExpr(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))
1491 }
1492 condition := ctx.parseMakeString(node, words[0])
1493 ifTrue := ctx.parseMakeString(node, words[1])
1494 var ifFalse starlarkExpr
1495 if len(words) == 3 {
1496 ifFalse = ctx.parseMakeString(node, words[2])
1497 } else {
1498 switch ifTrue.typ() {
1499 case starlarkTypeList:
1500 ifFalse = &listExpr{items: []starlarkExpr{}}
1501 case starlarkTypeInt:
1502 ifFalse = &intLiteralExpr{literal: 0}
1503 case starlarkTypeBool:
1504 ifFalse = &boolLiteralExpr{literal: false}
1505 default:
1506 ifFalse = &stringLiteralExpr{literal: ""}
1507 }
1508 }
1509 return &ifExpr{
1510 condition,
1511 ifTrue,
1512 ifFalse,
1513 }
1514}
1515
Cole Faust9ebf6e42021-12-13 14:08:34 -08001516type foreachCallPaser struct{}
1517
1518func (p *foreachCallPaser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
Cole Faustb0d32ab2021-12-09 14:00:59 -08001519 words := args.Split(",")
1520 if len(words) != 3 {
1521 return ctx.newBadExpr(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))
1522 }
1523 if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
1524 return ctx.newBadExpr(node, "first argument to foreach function must be a simple string identifier")
1525 }
1526 loopVarName := words[0].Strings[0]
1527 list := ctx.parseMakeString(node, words[1])
1528 action := ctx.parseMakeString(node, words[2]).transform(func(expr starlarkExpr) starlarkExpr {
1529 if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
1530 return &identifierExpr{loopVarName}
1531 }
1532 return nil
1533 })
1534
1535 if list.typ() != starlarkTypeList {
1536 list = &callExpr{
Cole Faust9ebf6e42021-12-13 14:08:34 -08001537 name: baseName + ".words",
1538 returnType: starlarkTypeList,
Cole Faustb0d32ab2021-12-09 14:00:59 -08001539 args: []starlarkExpr{list},
1540 }
1541 }
1542
1543 return &foreachExpr{
1544 varName: loopVarName,
1545 list: list,
1546 action: action,
1547 }
1548}
1549
Cole Faust9ebf6e42021-12-13 14:08:34 -08001550type wordCallParser struct{}
1551
1552func (p *wordCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001553 words := args.Split(",")
1554 if len(words) != 2 {
1555 return ctx.newBadExpr(node, "word function should have 2 arguments")
1556 }
1557 var index uint64 = 0
1558 if words[0].Const() {
1559 index, _ = strconv.ParseUint(strings.TrimSpace(words[0].Strings[0]), 10, 64)
1560 }
1561 if index < 1 {
1562 return ctx.newBadExpr(node, "word index should be constant positive integer")
1563 }
1564 words[1].TrimLeftSpaces()
1565 words[1].TrimRightSpaces()
1566 array := ctx.parseMakeString(node, words[1])
1567 if xBad, ok := array.(*badExpr); ok {
1568 return xBad
1569 }
1570 if array.typ() != starlarkTypeList {
1571 array = &callExpr{object: array, name: "split", returnType: starlarkTypeList}
1572 }
Cole Faustb0d32ab2021-12-09 14:00:59 -08001573 return &indexExpr{array, &intLiteralExpr{int(index - 1)}}
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001574}
1575
Cole Faust9ebf6e42021-12-13 14:08:34 -08001576type firstOrLastwordCallParser struct {
1577 isLastWord bool
1578}
1579
1580func (p *firstOrLastwordCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
Sasha Smundak16e07732021-07-23 11:38:23 -07001581 arg := ctx.parseMakeString(node, args)
1582 if bad, ok := arg.(*badExpr); ok {
1583 return bad
1584 }
1585 index := &intLiteralExpr{0}
Cole Faust9ebf6e42021-12-13 14:08:34 -08001586 if p.isLastWord {
Sasha Smundak16e07732021-07-23 11:38:23 -07001587 if v, ok := arg.(*variableRefExpr); ok && v.ref.name() == "MAKEFILE_LIST" {
1588 return &stringLiteralExpr{ctx.script.mkFile}
1589 }
1590 index.literal = -1
1591 }
1592 if arg.typ() == starlarkTypeList {
1593 return &indexExpr{arg, index}
1594 }
1595 return &indexExpr{&callExpr{object: arg, name: "split", returnType: starlarkTypeList}, index}
1596}
1597
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001598func (ctx *parseContext) parseMakeString(node mkparser.Node, mk *mkparser.MakeString) starlarkExpr {
1599 if mk.Const() {
1600 return &stringLiteralExpr{mk.Dump()}
1601 }
1602 if mkRef, ok := mk.SingleVariable(); ok {
1603 return ctx.parseReference(node, mkRef)
1604 }
1605 // If we reached here, it's neither string literal nor a simple variable,
1606 // we need a full-blown interpolation node that will generate
1607 // "a%b%c" % (X, Y) for a$(X)b$(Y)c
Cole Faustfc438682021-12-14 12:46:32 -08001608 parts := make([]starlarkExpr, len(mk.Variables)+len(mk.Strings))
1609 for i := 0; i < len(parts); i++ {
1610 if i%2 == 0 {
1611 parts[i] = &stringLiteralExpr{literal: mk.Strings[i/2]}
1612 } else {
1613 parts[i] = ctx.parseReference(node, mk.Variables[i/2].Name)
1614 if x, ok := parts[i].(*badExpr); ok {
1615 return x
1616 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001617 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001618 }
Cole Faustfc438682021-12-14 12:46:32 -08001619 return NewInterpolateExpr(parts)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001620}
1621
1622// Handles the statements whose treatment is the same in all contexts: comment,
1623// assignment, variable (which is a macro call in reality) and all constructs that
1624// do not handle in any context ('define directive and any unrecognized stuff).
Cole Faust591a1fe2021-11-08 15:37:57 -08001625func (ctx *parseContext) handleSimpleStatement(node mkparser.Node) {
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001626 switch x := node.(type) {
1627 case *mkparser.Comment:
Sasha Smundak6d852dd2021-09-27 20:34:39 -07001628 ctx.maybeHandleAnnotation(x)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001629 ctx.insertComment("#" + x.Comment)
1630 case *mkparser.Assignment:
1631 ctx.handleAssignment(x)
1632 case *mkparser.Variable:
1633 ctx.handleVariable(x)
1634 case *mkparser.Directive:
1635 switch x.Name {
1636 case "define":
1637 ctx.handleDefine(x)
1638 case "include", "-include":
1639 ctx.handleInclude(node, ctx.parseMakeString(node, x.Args), x.Name[0] != '-')
Cole Faust591a1fe2021-11-08 15:37:57 -08001640 case "ifeq", "ifneq", "ifdef", "ifndef":
1641 ctx.handleIfBlock(x)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001642 default:
Cole Faust591a1fe2021-11-08 15:37:57 -08001643 ctx.errorf(x, "unexpected directive %s", x.Name)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001644 }
1645 default:
Sasha Smundak2afb9d72021-10-24 15:16:59 -07001646 ctx.errorf(x, "unsupported line %s", strings.ReplaceAll(x.Dump(), "\n", "\n#"))
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001647 }
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001648}
1649
Sasha Smundak6d852dd2021-09-27 20:34:39 -07001650// Processes annotation. An annotation is a comment that starts with #RBC# and provides
1651// a conversion hint -- say, where to look for the dynamically calculated inherit/include
1652// paths.
1653func (ctx *parseContext) maybeHandleAnnotation(cnode *mkparser.Comment) {
1654 maybeTrim := func(s, prefix string) (string, bool) {
1655 if strings.HasPrefix(s, prefix) {
1656 return strings.TrimSpace(strings.TrimPrefix(s, prefix)), true
1657 }
1658 return s, false
1659 }
1660 annotation, ok := maybeTrim(cnode.Comment, annotationCommentPrefix)
1661 if !ok {
1662 return
1663 }
1664 if p, ok := maybeTrim(annotation, "include_top"); ok {
Cole Faustf7ed5342021-12-21 14:15:12 -08001665 // Don't allow duplicate include tops, because then we will generate
1666 // invalid starlark code. (duplicate keys in the _entry dictionary)
1667 for _, top := range ctx.includeTops {
1668 if top == p {
1669 return
1670 }
1671 }
Sasha Smundak6d852dd2021-09-27 20:34:39 -07001672 ctx.includeTops = append(ctx.includeTops, p)
1673 return
1674 }
1675 ctx.errorf(cnode, "unsupported annotation %s", cnode.Comment)
1676
1677}
1678
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001679func (ctx *parseContext) insertComment(s string) {
1680 ctx.receiver.newNode(&commentNode{strings.TrimSpace(s)})
1681}
1682
1683func (ctx *parseContext) carryAsComment(failedNode mkparser.Node) {
1684 for _, line := range strings.Split(failedNode.Dump(), "\n") {
1685 ctx.insertComment("# " + line)
1686 }
1687}
1688
1689// records that the given node failed to be converted and includes an explanatory message
1690func (ctx *parseContext) errorf(failedNode mkparser.Node, message string, args ...interface{}) {
1691 if ctx.errorLogger != nil {
Sasha Smundak422b6142021-11-11 18:31:59 -08001692 ctx.errorLogger.NewError(ctx.errorLocation(failedNode), failedNode, message, args...)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001693 }
Sasha Smundak422b6142021-11-11 18:31:59 -08001694 ctx.receiver.newNode(&exprNode{ctx.newBadExpr(failedNode, message, args...)})
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001695 ctx.script.hasErrors = true
1696}
1697
1698func (ctx *parseContext) wrapBadExpr(xBad *badExpr) {
Sasha Smundak422b6142021-11-11 18:31:59 -08001699 ctx.receiver.newNode(&exprNode{xBad})
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001700}
1701
1702func (ctx *parseContext) loadedModulePath(path string) string {
1703 // During the transition to Roboleaf some of the product configuration files
1704 // will be converted and checked in while the others will be generated on the fly
1705 // and run. The runner (rbcrun application) accommodates this by allowing three
1706 // different ways to specify the loaded file location:
1707 // 1) load(":<file>",...) loads <file> from the same directory
1708 // 2) load("//path/relative/to/source/root:<file>", ...) loads <file> source tree
1709 // 3) load("/absolute/path/to/<file> absolute path
1710 // If the file being generated and the file it wants to load are in the same directory,
1711 // generate option 1.
1712 // Otherwise, if output directory is not specified, generate 2)
1713 // Finally, if output directory has been specified and the file being generated and
1714 // the file it wants to load from are in the different directories, generate 2) or 3):
1715 // * if the file being loaded exists in the source tree, generate 2)
1716 // * otherwise, generate 3)
1717 // Finally, figure out the loaded module path and name and create a node for it
1718 loadedModuleDir := filepath.Dir(path)
1719 base := filepath.Base(path)
1720 loadedModuleName := strings.TrimSuffix(base, filepath.Ext(base)) + ctx.outputSuffix
1721 if loadedModuleDir == filepath.Dir(ctx.script.mkFile) {
1722 return ":" + loadedModuleName
1723 }
1724 if ctx.outputDir == "" {
1725 return fmt.Sprintf("//%s:%s", loadedModuleDir, loadedModuleName)
1726 }
1727 if _, err := os.Stat(filepath.Join(loadedModuleDir, loadedModuleName)); err == nil {
1728 return fmt.Sprintf("//%s:%s", loadedModuleDir, loadedModuleName)
1729 }
1730 return filepath.Join(ctx.outputDir, loadedModuleDir, loadedModuleName)
1731}
1732
Sasha Smundak3deb9682021-07-26 18:42:25 -07001733func (ctx *parseContext) addSoongNamespace(ns string) {
1734 if _, ok := ctx.soongNamespaces[ns]; ok {
1735 return
1736 }
1737 ctx.soongNamespaces[ns] = make(map[string]bool)
1738}
1739
1740func (ctx *parseContext) hasSoongNamespace(name string) bool {
1741 _, ok := ctx.soongNamespaces[name]
1742 return ok
1743}
1744
1745func (ctx *parseContext) updateSoongNamespace(replace bool, namespaceName string, varNames []string) {
1746 ctx.addSoongNamespace(namespaceName)
1747 vars := ctx.soongNamespaces[namespaceName]
1748 if replace {
1749 vars = make(map[string]bool)
1750 ctx.soongNamespaces[namespaceName] = vars
1751 }
1752 for _, v := range varNames {
1753 vars[v] = true
1754 }
1755}
1756
1757func (ctx *parseContext) hasNamespaceVar(namespaceName string, varName string) bool {
1758 vars, ok := ctx.soongNamespaces[namespaceName]
1759 if ok {
1760 _, ok = vars[varName]
1761 }
1762 return ok
1763}
1764
Sasha Smundak422b6142021-11-11 18:31:59 -08001765func (ctx *parseContext) errorLocation(node mkparser.Node) ErrorLocation {
1766 return ErrorLocation{ctx.script.mkFile, ctx.script.nodeLocator(node.Pos())}
1767}
1768
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001769func (ss *StarlarkScript) String() string {
1770 return NewGenerateContext(ss).emit()
1771}
1772
1773func (ss *StarlarkScript) SubConfigFiles() []string {
Sasha Smundak6609ba72021-07-22 18:32:56 -07001774
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001775 var subs []string
1776 for _, src := range ss.inherited {
1777 subs = append(subs, src.originalPath)
1778 }
1779 return subs
1780}
1781
1782func (ss *StarlarkScript) HasErrors() bool {
1783 return ss.hasErrors
1784}
1785
1786// Convert reads and parses a makefile. If successful, parsed tree
1787// is returned and then can be passed to String() to get the generated
1788// Starlark file.
1789func Convert(req Request) (*StarlarkScript, error) {
1790 reader := req.Reader
1791 if reader == nil {
1792 mkContents, err := ioutil.ReadFile(req.MkFile)
1793 if err != nil {
1794 return nil, err
1795 }
1796 reader = bytes.NewBuffer(mkContents)
1797 }
1798 parser := mkparser.NewParser(req.MkFile, reader)
1799 nodes, errs := parser.Parse()
1800 if len(errs) > 0 {
1801 for _, e := range errs {
1802 fmt.Fprintln(os.Stderr, "ERROR:", e)
1803 }
1804 return nil, fmt.Errorf("bad makefile %s", req.MkFile)
1805 }
1806 starScript := &StarlarkScript{
Sasha Smundak422b6142021-11-11 18:31:59 -08001807 moduleName: moduleNameForFile(req.MkFile),
1808 mkFile: req.MkFile,
1809 topDir: req.RootDir,
1810 traceCalls: req.TraceCalls,
1811 sourceFS: req.SourceFS,
1812 makefileFinder: req.MakefileFinder,
1813 nodeLocator: func(pos mkparser.Pos) int { return parser.Unpack(pos).Line },
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001814 }
1815 ctx := newParseContext(starScript, nodes)
1816 ctx.outputSuffix = req.OutputSuffix
1817 ctx.outputDir = req.OutputDir
1818 ctx.errorLogger = req.ErrorLogger
1819 if len(req.TracedVariables) > 0 {
1820 ctx.tracedVariables = make(map[string]bool)
1821 for _, v := range req.TracedVariables {
1822 ctx.tracedVariables[v] = true
1823 }
1824 }
1825 ctx.pushReceiver(starScript)
1826 for ctx.hasNodes() && ctx.fatalError == nil {
Cole Faust591a1fe2021-11-08 15:37:57 -08001827 ctx.handleSimpleStatement(ctx.getNode())
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001828 }
1829 if ctx.fatalError != nil {
1830 return nil, ctx.fatalError
1831 }
1832 return starScript, nil
1833}
1834
Cole Faust864028a2021-12-01 13:43:17 -08001835func Launcher(mainModuleUri, inputVariablesUri, mainModuleName string) string {
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001836 var buf bytes.Buffer
1837 fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
Cole Faust864028a2021-12-01 13:43:17 -08001838 fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
Sasha Smundakd7d07ad2021-09-10 15:42:34 -07001839 fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
Cole Faust864028a2021-12-01 13:43:17 -08001840 fmt.Fprintf(&buf, "%s(%s(%q, init, input_variables_init))\n", cfnPrintVars, cfnMain, mainModuleName)
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001841 return buf.String()
1842}
1843
Cole Faust6ed7cb42021-10-07 17:08:46 -07001844func BoardLauncher(mainModuleUri string, inputVariablesUri string) string {
1845 var buf bytes.Buffer
1846 fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
1847 fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
1848 fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
1849 fmt.Fprintf(&buf, "globals, cfg, globals_base = %s(init, input_variables_init)\n", cfnBoardMain)
1850 fmt.Fprintf(&buf, "# TODO: Some product config variables need to be printed, but most are readonly so we can't just print cfg here.\n")
Cole Faust3c1868b2021-11-22 16:34:11 -08001851 fmt.Fprintf(&buf, "%s((globals, cfg, globals_base))\n", cfnPrintVars)
Cole Faust6ed7cb42021-10-07 17:08:46 -07001852 return buf.String()
1853}
1854
Sasha Smundakb051c4e2020-11-05 20:45:07 -08001855func MakePath2ModuleName(mkPath string) string {
1856 return strings.TrimSuffix(mkPath, filepath.Ext(mkPath))
1857}