// 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_init) // Conditional case: // if _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() }