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