platform_build_soong/mk2rbc/node.go
Cole Faust f035d405d8 Support converting simple $(eval) expressions
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
2022-03-31 12:44:59 -07:00

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--
}