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