platform_build_soong/mk2rbc/variable.go
Cole Faust 8e15f69709 Remove ?= assignements to product variables
In make, all product variables are assigned to an empty string before
including the product config makefiles. In starlark, we don't do that
just so the memory usage is smaller. This means that in make, using
?= to assign a product config variable has no effect. Replicate this
behavior in starlark by not emitting any code for ?= assignments of
product variables.

Fixes: 304324003
Test: go test
Change-Id: Id31531506ac9e372a1bea92ce9ae8e17ae0ca70c
2023-10-09 12:26:21 -07:00

318 lines
8.8 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)
emitSet(gctx *generationContext, asgn *assignmentNode)
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() {
gctx.writef("cfg[%q] = ", pcv.nam)
asgn.value.emitListVarCopy(gctx)
}
emitAppend := func() {
gctx.writef("cfg[%q] += ", pcv.nam)
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()
}
// If we are not sure variable has been assigned before, emit setdefault
needsSetDefault := !gctx.hasBeenAssigned(&pcv) && !pcv.isPreset() && asgn.isSelfReferential()
switch asgn.flavor {
case asgnSet:
if needsSetDefault {
emitSetDefault()
}
emitAssignment()
case asgnAppend:
if needsSetDefault {
emitSetDefault()
}
emitAppend()
case asgnMaybeSet:
// In mk2rbc.go we never emit a maybeSet assignment for product config variables, because
// they are set to empty strings before running product config.
panic("Should never get here")
default:
panic("Unknown assignment flavor")
}
gctx.setHasBeenAssigned(&pcv)
}
func (pcv productConfigVariable) emitGet(gctx *generationContext) {
if gctx.hasBeenAssigned(&pcv) || pcv.isPreset() {
gctx.writef("cfg[%q]", pcv.nam)
} else {
gctx.writef("cfg.get(%q, %s)", pcv.nam, pcv.defaultValueString())
}
}
type otherGlobalVariable struct {
baseVariable
}
func (scv otherGlobalVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
emitAssignment := func() {
gctx.writef("g[%q] = ", scv.nam)
asgn.value.emitListVarCopy(gctx)
}
emitAppend := func() {
gctx.writef("g[%q] += ", scv.nam)
value := asgn.value
if scv.valueType() == starlarkTypeString {
gctx.writef(`" " + `)
value = &toStringExpr{expr: value}
}
value.emit(gctx)
}
// If we are not sure variable has been assigned before, emit setdefault
needsSetDefault := !gctx.hasBeenAssigned(&scv) && !scv.isPreset() && asgn.isSelfReferential()
switch asgn.flavor {
case asgnSet:
if needsSetDefault {
gctx.writef("g.setdefault(%q, %s)", scv.name(), scv.defaultValueString())
gctx.newLine()
}
emitAssignment()
case asgnAppend:
if needsSetDefault {
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()
if needsSetDefault {
gctx.writef("g.setdefault(%q, %s)", scv.name(), scv.defaultValueString())
gctx.newLine()
}
emitAssignment()
gctx.indentLevel--
}
gctx.setHasBeenAssigned(&scv)
}
func (scv otherGlobalVariable) emitGet(gctx *generationContext) {
if gctx.hasBeenAssigned(&scv) || scv.isPreset() {
gctx.writef("g[%q]", scv.nam)
} else {
gctx.writef("g.get(%q, %s)", scv.nam, scv.defaultValueString())
}
}
type localVariable struct {
baseVariable
}
func (lv localVariable) String() string {
return "_" + lv.nam
}
func (lv localVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
switch asgn.flavor {
case asgnSet, asgnMaybeSet:
gctx.writef("%s = ", lv)
asgn.value.emitListVarCopy(gctx)
case asgnAppend:
gctx.writef("%s += ", lv)
value := asgn.value
if lv.valueType() == starlarkTypeString {
gctx.writef(`" " + `)
value = &toStringExpr{expr: value}
}
value.emit(gctx)
}
}
func (lv localVariable) emitGet(gctx *generationContext) {
gctx.writef("%s", lv)
}
type predefinedVariable struct {
baseVariable
value starlarkExpr
}
func (pv predefinedVariable) emitGet(gctx *generationContext) {
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()))
}
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
// Heuristics: local variables that contribute to corresponding config variables
if cfgVarName, found := localProductConfigVariables[name]; found && vt == starlarkTypeUnknown {
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
}