platform_build_soong/mk2rbc/variable.go
Cole Faust f92c9f2809 Add type hints to mk2rbc
Type hints have the format #RBC# type_hint MY_VAR list
and must be specified at the top of the Makefile. Setting
one will cause that variable to have that type for the
remainder of the Makefile. This can be used where mk2rbc's
type inference detects the wrong type and it must be
manually changed.

Bug: 224601891
Test: go test
Change-Id: I6db2c50056d0298227e1d2801a522adf8bbd1df8
2022-03-15 12:52:20 -07:00

341 lines
9.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"
)
type variable interface {
name() string
emitGet(gctx *generationContext, isDefined bool)
emitSet(gctx *generationContext, asgn *assignmentNode)
emitDefined(gctx *generationContext)
valueType() starlarkType
setValueType(t starlarkType)
defaultValueString() string
isPreset() bool
}
type baseVariable struct {
nam string
typ starlarkType
preset bool // true if it has been initialized at startup
}
func (v baseVariable) name() string {
return v.nam
}
func (v baseVariable) valueType() starlarkType {
return v.typ
}
func (v *baseVariable) setValueType(t starlarkType) {
v.typ = t
}
func (v baseVariable) isPreset() bool {
return v.preset
}
var defaultValuesByType = map[starlarkType]string{
starlarkTypeUnknown: `""`,
starlarkTypeList: "[]",
starlarkTypeString: `""`,
starlarkTypeInt: "0",
starlarkTypeBool: "False",
starlarkTypeVoid: "None",
}
func (v baseVariable) defaultValueString() string {
if v, ok := defaultValuesByType[v.valueType()]; ok {
return v
}
panic(fmt.Errorf("%s has unknown type %q", v.name(), v.valueType()))
}
type productConfigVariable struct {
baseVariable
}
func (pcv productConfigVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
emitAssignment := func() {
pcv.emitGet(gctx, true)
gctx.write(" = ")
asgn.value.emitListVarCopy(gctx)
}
emitAppend := func() {
pcv.emitGet(gctx, true)
gctx.write(" += ")
value := asgn.value
if pcv.valueType() == starlarkTypeString {
gctx.writef(`" " + `)
value = &toStringExpr{expr: value}
}
value.emit(gctx)
}
emitSetDefault := func() {
if pcv.typ == starlarkTypeList {
gctx.writef("%s(handle, %q)", cfnSetListDefault, pcv.name())
} else {
gctx.writef("cfg.setdefault(%q, %s)", pcv.name(), pcv.defaultValueString())
}
gctx.newLine()
}
switch asgn.flavor {
case asgnSet:
isSelfReferential := false
asgn.value.transform(func(expr starlarkExpr) starlarkExpr {
if ref, ok := expr.(*variableRefExpr); ok && ref.ref.name() == pcv.name() {
isSelfReferential = true
}
return nil
})
if isSelfReferential {
emitSetDefault()
}
emitAssignment()
case asgnAppend:
emitAppend()
case asgnMaybeAppend:
// If we are not sure variable has been assigned before, emit setdefault
emitSetDefault()
emitAppend()
case asgnMaybeSet:
gctx.writef("if cfg.get(%q) == None:", pcv.nam)
gctx.indentLevel++
gctx.newLine()
emitAssignment()
gctx.indentLevel--
}
}
func (pcv productConfigVariable) emitGet(gctx *generationContext, isDefined bool) {
if isDefined || pcv.isPreset() {
gctx.writef("cfg[%q]", pcv.nam)
} else {
gctx.writef("cfg.get(%q, %s)", pcv.nam, pcv.defaultValueString())
}
}
func (pcv productConfigVariable) emitDefined(gctx *generationContext) {
gctx.writef("g.get(%q) != None", pcv.name())
}
type otherGlobalVariable struct {
baseVariable
}
func (scv otherGlobalVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
emitAssignment := func() {
scv.emitGet(gctx, true)
gctx.write(" = ")
asgn.value.emitListVarCopy(gctx)
}
emitAppend := func() {
scv.emitGet(gctx, true)
gctx.write(" += ")
value := asgn.value
if scv.valueType() == starlarkTypeString {
gctx.writef(`" " + `)
value = &toStringExpr{expr: value}
}
value.emit(gctx)
}
switch asgn.flavor {
case asgnSet:
emitAssignment()
case asgnAppend:
emitAppend()
case asgnMaybeAppend:
// If we are not sure variable has been assigned before, emit setdefault
gctx.writef("g.setdefault(%q, %s)", scv.name(), scv.defaultValueString())
gctx.newLine()
emitAppend()
case asgnMaybeSet:
gctx.writef("if g.get(%q) == None:", scv.nam)
gctx.indentLevel++
gctx.newLine()
emitAssignment()
gctx.indentLevel--
}
}
func (scv otherGlobalVariable) emitGet(gctx *generationContext, isDefined bool) {
if isDefined || scv.isPreset() {
gctx.writef("g[%q]", scv.nam)
} else {
gctx.writef("g.get(%q, %s)", scv.nam, scv.defaultValueString())
}
}
func (scv otherGlobalVariable) emitDefined(gctx *generationContext) {
gctx.writef("g.get(%q) != None", scv.name())
}
type localVariable struct {
baseVariable
}
func (lv localVariable) emitDefined(gctx *generationContext) {
gctx.writef(lv.String())
}
func (lv localVariable) String() string {
return "_" + lv.nam
}
func (lv localVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
switch asgn.flavor {
case asgnSet:
gctx.writef("%s = ", lv)
asgn.value.emitListVarCopy(gctx)
case asgnAppend:
lv.emitGet(gctx, false)
gctx.write(" += ")
value := asgn.value
if lv.valueType() == starlarkTypeString {
gctx.writef(`" " + `)
value = &toStringExpr{expr: value}
}
value.emit(gctx)
case asgnMaybeAppend:
gctx.writef("%s(%q, ", cfnLocalAppend, lv)
asgn.value.emit(gctx)
gctx.write(")")
case asgnMaybeSet:
gctx.writef("%s(%q, ", cfnLocalSetDefault, lv)
asgn.value.emit(gctx)
gctx.write(")")
}
}
func (lv localVariable) emitGet(gctx *generationContext, _ bool) {
gctx.writef("%s", lv)
}
type predefinedVariable struct {
baseVariable
value starlarkExpr
}
func (pv predefinedVariable) emitGet(gctx *generationContext, _ bool) {
pv.value.emit(gctx)
}
func (pv predefinedVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
if expectedValue, ok1 := maybeString(pv.value); ok1 {
actualValue, ok2 := maybeString(asgn.value)
if ok2 {
if actualValue == expectedValue {
return
}
gctx.emitConversionError(asgn.location,
fmt.Sprintf("cannot set predefined variable %s to %q, its value should be %q",
pv.name(), actualValue, expectedValue))
gctx.starScript.hasErrors = true
return
}
}
panic(fmt.Errorf("cannot set predefined variable %s to %q", pv.name(), asgn.mkValue.Dump()))
}
func (pv predefinedVariable) emitDefined(gctx *generationContext) {
gctx.write("True")
}
var localProductConfigVariables = map[string]string{
"LOCAL_AUDIO_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
"LOCAL_AUDIO_PRODUCT_COPY_FILES": "PRODUCT_COPY_FILES",
"LOCAL_AUDIO_DEVICE_PACKAGE_OVERLAYS": "DEVICE_PACKAGE_OVERLAYS",
"LOCAL_DUMPSTATE_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
"LOCAL_GATEKEEPER_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
"LOCAL_HEALTH_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
"LOCAL_SENSOR_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
"LOCAL_KEYMASTER_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
"LOCAL_KEYMINT_PRODUCT_PACKAGE": "PRODUCT_PACKAGES",
}
var presetVariables = map[string]bool{
"BUILD_ID": true,
"HOST_ARCH": true,
"HOST_OS": true,
"HOST_BUILD_TYPE": true,
"OUT_DIR": true,
"PLATFORM_VERSION_CODENAME": true,
"PLATFORM_VERSION": true,
"TARGET_ARCH": true,
"TARGET_ARCH_VARIANT": true,
"TARGET_BUILD_TYPE": true,
"TARGET_BUILD_VARIANT": true,
"TARGET_PRODUCT": true,
}
// addVariable returns a variable with a given name. A variable is
// added if it does not exist yet.
func (ctx *parseContext) addVariable(name string) variable {
// Get the hintType before potentially changing the variable name
var hintType starlarkType
var ok bool
if hintType, ok = ctx.typeHints[name]; !ok {
hintType = starlarkTypeUnknown
}
// Heuristics: if variable's name is all lowercase, consider it local
// string variable.
isLocalVariable := name == strings.ToLower(name)
// Local variables can't have special characters in them, because they
// will be used as starlark identifiers
if isLocalVariable {
name = strings.ReplaceAll(strings.TrimSpace(name), "-", "_")
}
v, found := ctx.variables[name]
if !found {
if vi, found := KnownVariables[name]; found {
_, preset := presetVariables[name]
switch vi.class {
case VarClassConfig:
v = &productConfigVariable{baseVariable{nam: name, typ: vi.valueType, preset: preset}}
case VarClassSoong:
v = &otherGlobalVariable{baseVariable{nam: name, typ: vi.valueType, preset: preset}}
}
} else if isLocalVariable {
v = &localVariable{baseVariable{nam: name, typ: hintType}}
} else {
vt := hintType
if strings.HasPrefix(name, "LOCAL_") && vt == starlarkTypeUnknown {
// Heuristics: local variables that contribute to corresponding config variables
if cfgVarName, found := localProductConfigVariables[name]; found {
vi, found2 := KnownVariables[cfgVarName]
if !found2 {
panic(fmt.Errorf("unknown config variable %s for %s", cfgVarName, name))
}
vt = vi.valueType
}
}
if strings.HasSuffix(name, "_LIST") && vt == starlarkTypeUnknown {
// Heuristics: Variables with "_LIST" suffix are lists
vt = starlarkTypeList
}
v = &otherGlobalVariable{baseVariable{nam: name, typ: vt}}
}
ctx.variables[name] = v
}
return v
}