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 = ¬Expr{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