|  | // Copyright 2021 Google LLC | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | package mk2rbc | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "strings" | 
|  |  | 
|  | mkparser "android/soong/androidmk/parser" | 
|  | ) | 
|  |  | 
|  | // A parsed node for which starlark code will be generated | 
|  | // by calling emit(). | 
|  | type starlarkNode interface { | 
|  | emit(ctx *generationContext) | 
|  | } | 
|  |  | 
|  | // Types used to keep processed makefile data: | 
|  | type commentNode struct { | 
|  | text string | 
|  | } | 
|  |  | 
|  | func (c *commentNode) emit(gctx *generationContext) { | 
|  | chunks := strings.Split(c.text, "\\\n") | 
|  | gctx.newLine() | 
|  | gctx.write(chunks[0]) // It has '#' at the beginning already. | 
|  | for _, chunk := range chunks[1:] { | 
|  | gctx.newLine() | 
|  | gctx.write("#", chunk) | 
|  | } | 
|  | } | 
|  |  | 
|  | type moduleInfo struct { | 
|  | path            string // Converted Starlark file path | 
|  | originalPath    string // Makefile file path | 
|  | moduleLocalName string | 
|  | optional        bool | 
|  | missing         bool // a module may not exist if a module that depends on it is loaded dynamically | 
|  | } | 
|  |  | 
|  | func (im moduleInfo) entryName() string { | 
|  | return im.moduleLocalName + "_init" | 
|  | } | 
|  |  | 
|  | func (mi moduleInfo) name() string { | 
|  | return fmt.Sprintf("%q", MakePath2ModuleName(mi.originalPath)) | 
|  | } | 
|  |  | 
|  | type inheritedModule interface { | 
|  | name() string | 
|  | entryName() string | 
|  | emitSelect(gctx *generationContext) | 
|  | pathExpr() starlarkExpr | 
|  | needsLoadCheck() bool | 
|  | } | 
|  |  | 
|  | type inheritedStaticModule struct { | 
|  | *moduleInfo | 
|  | loadAlways bool | 
|  | } | 
|  |  | 
|  | func (im inheritedStaticModule) emitSelect(_ *generationContext) { | 
|  | } | 
|  |  | 
|  | func (im inheritedStaticModule) pathExpr() starlarkExpr { | 
|  | return &stringLiteralExpr{im.path} | 
|  | } | 
|  |  | 
|  | func (im inheritedStaticModule) needsLoadCheck() bool { | 
|  | return im.missing | 
|  | } | 
|  |  | 
|  | type inheritedDynamicModule struct { | 
|  | path             starlarkExpr | 
|  | candidateModules []*moduleInfo | 
|  | loadAlways       bool | 
|  | location         ErrorLocation | 
|  | needsWarning     bool | 
|  | } | 
|  |  | 
|  | func (i inheritedDynamicModule) name() string { | 
|  | return "_varmod" | 
|  | } | 
|  |  | 
|  | func (i inheritedDynamicModule) entryName() string { | 
|  | return i.name() + "_init" | 
|  | } | 
|  |  | 
|  | func (i inheritedDynamicModule) emitSelect(gctx *generationContext) { | 
|  | if i.needsWarning { | 
|  | gctx.newLine() | 
|  | gctx.writef("%s.mkwarning(%q, %q)", baseName, i.location, "Please avoid starting an include path with a variable. See https://source.android.com/setup/build/bazel/product_config/issues/includes for details.") | 
|  | } | 
|  | gctx.newLine() | 
|  | gctx.writef("_entry = {") | 
|  | gctx.indentLevel++ | 
|  | for _, mi := range i.candidateModules { | 
|  | gctx.newLine() | 
|  | gctx.writef(`"%s": (%s, %s),`, mi.originalPath, mi.name(), mi.entryName()) | 
|  | } | 
|  | gctx.indentLevel-- | 
|  | gctx.newLine() | 
|  | gctx.write("}.get(") | 
|  | i.path.emit(gctx) | 
|  | gctx.write(")") | 
|  | gctx.newLine() | 
|  | gctx.writef("(%s, %s) = _entry if _entry else (None, None)", i.name(), i.entryName()) | 
|  | } | 
|  |  | 
|  | func (i inheritedDynamicModule) pathExpr() starlarkExpr { | 
|  | return i.path | 
|  | } | 
|  |  | 
|  | func (i inheritedDynamicModule) needsLoadCheck() bool { | 
|  | return true | 
|  | } | 
|  |  | 
|  | type inheritNode struct { | 
|  | module     inheritedModule | 
|  | loadAlways bool | 
|  | } | 
|  |  | 
|  | func (inn *inheritNode) emit(gctx *generationContext) { | 
|  | // Unconditional case: | 
|  | //    maybe check that loaded | 
|  | //    rblf.inherit(handle, <module>, module_init) | 
|  | // Conditional case: | 
|  | //    if <module>_init != None: | 
|  | //      same as above | 
|  | inn.module.emitSelect(gctx) | 
|  | name := inn.module.name() | 
|  | entry := inn.module.entryName() | 
|  | if inn.loadAlways { | 
|  | gctx.emitLoadCheck(inn.module) | 
|  | gctx.newLine() | 
|  | gctx.writef("%s(handle, %s, %s)", cfnInherit, name, entry) | 
|  | return | 
|  | } | 
|  |  | 
|  | gctx.newLine() | 
|  | gctx.writef("if %s:", entry) | 
|  | gctx.indentLevel++ | 
|  | gctx.newLine() | 
|  | gctx.writef("%s(handle, %s, %s)", cfnInherit, name, entry) | 
|  | gctx.indentLevel-- | 
|  | } | 
|  |  | 
|  | type includeNode struct { | 
|  | module     inheritedModule | 
|  | loadAlways bool | 
|  | } | 
|  |  | 
|  | func (inn *includeNode) emit(gctx *generationContext) { | 
|  | inn.module.emitSelect(gctx) | 
|  | entry := inn.module.entryName() | 
|  | if inn.loadAlways { | 
|  | gctx.emitLoadCheck(inn.module) | 
|  | gctx.newLine() | 
|  | gctx.writef("%s(g, handle)", entry) | 
|  | return | 
|  | } | 
|  |  | 
|  | gctx.newLine() | 
|  | gctx.writef("if %s != None:", entry) | 
|  | gctx.indentLevel++ | 
|  | gctx.newLine() | 
|  | gctx.writef("%s(g, handle)", entry) | 
|  | gctx.indentLevel-- | 
|  | } | 
|  |  | 
|  | type assignmentFlavor int | 
|  |  | 
|  | const ( | 
|  | // Assignment flavors | 
|  | asgnSet      assignmentFlavor = iota // := or = | 
|  | asgnMaybeSet assignmentFlavor = iota // ?= | 
|  | asgnAppend   assignmentFlavor = iota // += | 
|  | ) | 
|  |  | 
|  | type assignmentNode struct { | 
|  | lhs      variable | 
|  | value    starlarkExpr | 
|  | mkValue  *mkparser.MakeString | 
|  | flavor   assignmentFlavor | 
|  | location ErrorLocation | 
|  | isTraced bool | 
|  | } | 
|  |  | 
|  | func (asgn *assignmentNode) emit(gctx *generationContext) { | 
|  | gctx.newLine() | 
|  | gctx.inAssignment = true | 
|  | asgn.lhs.emitSet(gctx, asgn) | 
|  | gctx.inAssignment = false | 
|  |  | 
|  | if asgn.isTraced { | 
|  | gctx.newLine() | 
|  | gctx.tracedCount++ | 
|  | gctx.writef(`print("%s.%d: %s := ", `, gctx.starScript.mkFile, gctx.tracedCount, asgn.lhs.name()) | 
|  | asgn.lhs.emitGet(gctx) | 
|  | gctx.writef(")") | 
|  | } | 
|  | } | 
|  |  | 
|  | func (asgn *assignmentNode) isSelfReferential() bool { | 
|  | if asgn.flavor == asgnAppend { | 
|  | return true | 
|  | } | 
|  | isSelfReferential := false | 
|  | asgn.value.transform(func(expr starlarkExpr) starlarkExpr { | 
|  | if ref, ok := expr.(*variableRefExpr); ok && ref.ref.name() == asgn.lhs.name() { | 
|  | isSelfReferential = true | 
|  | } | 
|  | return nil | 
|  | }) | 
|  | return isSelfReferential | 
|  | } | 
|  |  | 
|  | type exprNode struct { | 
|  | expr starlarkExpr | 
|  | } | 
|  |  | 
|  | func (exn *exprNode) emit(gctx *generationContext) { | 
|  | gctx.newLine() | 
|  | exn.expr.emit(gctx) | 
|  | } | 
|  |  | 
|  | type ifNode struct { | 
|  | isElif bool // true if this is 'elif' statement | 
|  | expr   starlarkExpr | 
|  | } | 
|  |  | 
|  | func (in *ifNode) emit(gctx *generationContext) { | 
|  | ifElif := "if " | 
|  | if in.isElif { | 
|  | ifElif = "elif " | 
|  | } | 
|  |  | 
|  | gctx.newLine() | 
|  | gctx.write(ifElif) | 
|  | in.expr.emit(gctx) | 
|  | gctx.write(":") | 
|  | } | 
|  |  | 
|  | type elseNode struct{} | 
|  |  | 
|  | func (br *elseNode) emit(gctx *generationContext) { | 
|  | gctx.newLine() | 
|  | gctx.write("else:") | 
|  | } | 
|  |  | 
|  | // switchCase represents as single if/elseif/else branch. All the necessary | 
|  | // info about flavor (if/elseif/else) is supposed to be kept in `gate`. | 
|  | type switchCase struct { | 
|  | gate  starlarkNode | 
|  | nodes []starlarkNode | 
|  | } | 
|  |  | 
|  | func (cb *switchCase) emit(gctx *generationContext) { | 
|  | cb.gate.emit(gctx) | 
|  | gctx.indentLevel++ | 
|  | gctx.pushVariableAssignments() | 
|  | hasStatements := false | 
|  | for _, node := range cb.nodes { | 
|  | if _, ok := node.(*commentNode); !ok { | 
|  | hasStatements = true | 
|  | } | 
|  | node.emit(gctx) | 
|  | } | 
|  | if !hasStatements { | 
|  | gctx.emitPass() | 
|  | } | 
|  | gctx.indentLevel-- | 
|  | gctx.popVariableAssignments() | 
|  | } | 
|  |  | 
|  | // A single complete if ... elseif ... else ... endif sequences | 
|  | type switchNode struct { | 
|  | ssCases []*switchCase | 
|  | } | 
|  |  | 
|  | func (ssw *switchNode) emit(gctx *generationContext) { | 
|  | for _, ssCase := range ssw.ssCases { | 
|  | ssCase.emit(gctx) | 
|  | } | 
|  | } | 
|  |  | 
|  | type foreachNode struct { | 
|  | varName string | 
|  | list    starlarkExpr | 
|  | actions []starlarkNode | 
|  | } | 
|  |  | 
|  | func (f *foreachNode) emit(gctx *generationContext) { | 
|  | gctx.pushVariableAssignments() | 
|  | gctx.newLine() | 
|  | gctx.writef("for %s in ", f.varName) | 
|  | f.list.emit(gctx) | 
|  | gctx.write(":") | 
|  | gctx.indentLevel++ | 
|  | hasStatements := false | 
|  | for _, a := range f.actions { | 
|  | if _, ok := a.(*commentNode); !ok { | 
|  | hasStatements = true | 
|  | } | 
|  | a.emit(gctx) | 
|  | } | 
|  | if !hasStatements { | 
|  | gctx.emitPass() | 
|  | } | 
|  | gctx.indentLevel-- | 
|  | gctx.popVariableAssignments() | 
|  | } |