f035d405d8
While supporting $(eval) in the general case is impossible, as it would require emitting code at runtime, it is possible to handle some special cases that are common throughout the code base. Specifically, an assignement expression (where the left hand side is constant) can be converted without needing to evaluate strings as code, as its whole right hand side is treated as one string. However, this eval with an assignemnt can only be used as a statement, not an expression. So it requires the eval to be either a top-level expression, or nested within other expressions that can be converted to statements such as $(foreach) or $(if). Bug: 226974242 Test: go test Change-Id: Ifc52ef9ab7d62a69251918fcde5463f80a98a542
321 lines
7.2 KiB
Go
321 lines
7.2 KiB
Go
// 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 interpolateExpr
|
|
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
|
|
previous *assignmentNode
|
|
}
|
|
|
|
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, true)
|
|
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++
|
|
hasStatements := false
|
|
for _, node := range cb.nodes {
|
|
if _, ok := node.(*commentNode); !ok {
|
|
hasStatements = true
|
|
}
|
|
node.emit(gctx)
|
|
}
|
|
if !hasStatements {
|
|
gctx.emitPass()
|
|
}
|
|
gctx.indentLevel--
|
|
}
|
|
|
|
// 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.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--
|
|
}
|