1// Copyright 2021 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package mk2rbc 16 17import ( 18 "fmt" 19 "strings" 20) 21 22type variable interface { 23 name() string 24 emitGet(gctx *generationContext) 25 emitSet(gctx *generationContext, asgn *assignmentNode) 26 valueType() starlarkType 27 setValueType(t starlarkType) 28 defaultValueString() string 29 isPreset() bool 30} 31 32type baseVariable struct { 33 nam string 34 typ starlarkType 35 preset bool // true if it has been initialized at startup 36} 37 38func (v baseVariable) name() string { 39 return v.nam 40} 41 42func (v baseVariable) valueType() starlarkType { 43 return v.typ 44} 45 46func (v *baseVariable) setValueType(t starlarkType) { 47 v.typ = t 48} 49 50func (v baseVariable) isPreset() bool { 51 return v.preset 52} 53 54var defaultValuesByType = map[starlarkType]string{ 55 starlarkTypeUnknown: `""`, 56 starlarkTypeList: "[]", 57 starlarkTypeString: `""`, 58 starlarkTypeInt: "0", 59 starlarkTypeBool: "False", 60 starlarkTypeVoid: "None", 61} 62 63func (v baseVariable) defaultValueString() string { 64 if v, ok := defaultValuesByType[v.valueType()]; ok { 65 return v 66 } 67 panic(fmt.Errorf("%s has unknown type %q", v.name(), v.valueType())) 68} 69 70type productConfigVariable struct { 71 baseVariable 72} 73 74func (pcv productConfigVariable) emitSet(gctx *generationContext, asgn *assignmentNode) { 75 emitAssignment := func() { 76 gctx.writef("cfg[%q] = ", pcv.nam) 77 asgn.value.emitListVarCopy(gctx) 78 } 79 emitAppend := func() { 80 gctx.writef("cfg[%q] += ", pcv.nam) 81 value := asgn.value 82 if pcv.valueType() == starlarkTypeString { 83 gctx.writef(`" " + `) 84 value = &toStringExpr{expr: value} 85 } 86 value.emit(gctx) 87 } 88 emitSetDefault := func() { 89 if pcv.typ == starlarkTypeList { 90 gctx.writef("%s(handle, %q)", cfnSetListDefault, pcv.name()) 91 } else { 92 gctx.writef("cfg.setdefault(%q, %s)", pcv.name(), pcv.defaultValueString()) 93 } 94 gctx.newLine() 95 } 96 97 // If we are not sure variable has been assigned before, emit setdefault 98 needsSetDefault := !gctx.hasBeenAssigned(&pcv) && !pcv.isPreset() && asgn.isSelfReferential() 99 100 switch asgn.flavor { 101 case asgnSet: 102 if needsSetDefault { 103 emitSetDefault() 104 } 105 emitAssignment() 106 case asgnAppend: 107 if needsSetDefault { 108 emitSetDefault() 109 } 110 emitAppend() 111 case asgnMaybeSet: 112 // In mk2rbc.go we never emit a maybeSet assignment for product config variables, because 113 // they are set to empty strings before running product config. 114 panic("Should never get here") 115 default: 116 panic("Unknown assignment flavor") 117 } 118 119 gctx.setHasBeenAssigned(&pcv) 120} 121 122func (pcv productConfigVariable) emitGet(gctx *generationContext) { 123 if gctx.hasBeenAssigned(&pcv) || pcv.isPreset() { 124 gctx.writef("cfg[%q]", pcv.nam) 125 } else { 126 gctx.writef("cfg.get(%q, %s)", pcv.nam, pcv.defaultValueString()) 127 } 128} 129 130type otherGlobalVariable struct { 131 baseVariable 132} 133 134func (scv otherGlobalVariable) emitSet(gctx *generationContext, asgn *assignmentNode) { 135 emitAssignment := func() { 136 gctx.writef("g[%q] = ", scv.nam) 137 asgn.value.emitListVarCopy(gctx) 138 } 139 140 emitAppend := func() { 141 gctx.writef("g[%q] += ", scv.nam) 142 value := asgn.value 143 if scv.valueType() == starlarkTypeString { 144 gctx.writef(`" " + `) 145 value = &toStringExpr{expr: value} 146 } 147 value.emit(gctx) 148 } 149 150 // If we are not sure variable has been assigned before, emit setdefault 151 needsSetDefault := !gctx.hasBeenAssigned(&scv) && !scv.isPreset() && asgn.isSelfReferential() 152 153 switch asgn.flavor { 154 case asgnSet: 155 if needsSetDefault { 156 gctx.writef("g.setdefault(%q, %s)", scv.name(), scv.defaultValueString()) 157 gctx.newLine() 158 } 159 emitAssignment() 160 case asgnAppend: 161 if needsSetDefault { 162 gctx.writef("g.setdefault(%q, %s)", scv.name(), scv.defaultValueString()) 163 gctx.newLine() 164 } 165 emitAppend() 166 case asgnMaybeSet: 167 gctx.writef("if g.get(%q) == None:", scv.nam) 168 gctx.indentLevel++ 169 gctx.newLine() 170 if needsSetDefault { 171 gctx.writef("g.setdefault(%q, %s)", scv.name(), scv.defaultValueString()) 172 gctx.newLine() 173 } 174 emitAssignment() 175 gctx.indentLevel-- 176 } 177 178 gctx.setHasBeenAssigned(&scv) 179} 180 181func (scv otherGlobalVariable) emitGet(gctx *generationContext) { 182 if gctx.hasBeenAssigned(&scv) || scv.isPreset() { 183 gctx.writef("g[%q]", scv.nam) 184 } else { 185 gctx.writef("g.get(%q, %s)", scv.nam, scv.defaultValueString()) 186 } 187} 188 189type localVariable struct { 190 baseVariable 191} 192 193func (lv localVariable) String() string { 194 return "_" + lv.nam 195} 196 197func (lv localVariable) emitSet(gctx *generationContext, asgn *assignmentNode) { 198 switch asgn.flavor { 199 case asgnSet, asgnMaybeSet: 200 gctx.writef("%s = ", lv) 201 asgn.value.emitListVarCopy(gctx) 202 case asgnAppend: 203 gctx.writef("%s += ", lv) 204 value := asgn.value 205 if lv.valueType() == starlarkTypeString { 206 gctx.writef(`" " + `) 207 value = &toStringExpr{expr: value} 208 } 209 value.emit(gctx) 210 } 211} 212 213func (lv localVariable) emitGet(gctx *generationContext) { 214 gctx.writef("%s", lv) 215} 216 217type predefinedVariable struct { 218 baseVariable 219 value starlarkExpr 220} 221 222func (pv predefinedVariable) emitGet(gctx *generationContext) { 223 pv.value.emit(gctx) 224} 225 226func (pv predefinedVariable) emitSet(gctx *generationContext, asgn *assignmentNode) { 227 if expectedValue, ok1 := maybeString(pv.value); ok1 { 228 actualValue, ok2 := maybeString(asgn.value) 229 if ok2 { 230 if actualValue == expectedValue { 231 return 232 } 233 gctx.emitConversionError(asgn.location, 234 fmt.Sprintf("cannot set predefined variable %s to %q, its value should be %q", 235 pv.name(), actualValue, expectedValue)) 236 gctx.starScript.hasErrors = true 237 return 238 } 239 } 240 panic(fmt.Errorf("cannot set predefined variable %s to %q", pv.name(), asgn.mkValue.Dump())) 241} 242 243var localProductConfigVariables = map[string]string{ 244 "LOCAL_AUDIO_PRODUCT_PACKAGE": "PRODUCT_PACKAGES", 245 "LOCAL_AUDIO_PRODUCT_COPY_FILES": "PRODUCT_COPY_FILES", 246 "LOCAL_AUDIO_DEVICE_PACKAGE_OVERLAYS": "DEVICE_PACKAGE_OVERLAYS", 247 "LOCAL_DUMPSTATE_PRODUCT_PACKAGE": "PRODUCT_PACKAGES", 248 "LOCAL_GATEKEEPER_PRODUCT_PACKAGE": "PRODUCT_PACKAGES", 249 "LOCAL_HEALTH_PRODUCT_PACKAGE": "PRODUCT_PACKAGES", 250 "LOCAL_SENSOR_PRODUCT_PACKAGE": "PRODUCT_PACKAGES", 251 "LOCAL_KEYMASTER_PRODUCT_PACKAGE": "PRODUCT_PACKAGES", 252 "LOCAL_KEYMINT_PRODUCT_PACKAGE": "PRODUCT_PACKAGES", 253} 254 255var presetVariables = map[string]bool{ 256 "BUILD_ID": true, 257 "HOST_ARCH": true, 258 "HOST_OS": true, 259 "HOST_BUILD_TYPE": true, 260 "OUT_DIR": true, 261 "PLATFORM_VERSION_CODENAME": true, 262 "PLATFORM_VERSION": true, 263 "TARGET_ARCH": true, 264 "TARGET_ARCH_VARIANT": true, 265 "TARGET_BUILD_TYPE": true, 266 "TARGET_BUILD_VARIANT": true, 267 "TARGET_PRODUCT": true, 268} 269 270// addVariable returns a variable with a given name. A variable is 271// added if it does not exist yet. 272func (ctx *parseContext) addVariable(name string) variable { 273 // Get the hintType before potentially changing the variable name 274 var hintType starlarkType 275 var ok bool 276 if hintType, ok = ctx.typeHints[name]; !ok { 277 hintType = starlarkTypeUnknown 278 } 279 // Heuristics: if variable's name is all lowercase, consider it local 280 // string variable. 281 isLocalVariable := name == strings.ToLower(name) 282 // Local variables can't have special characters in them, because they 283 // will be used as starlark identifiers 284 if isLocalVariable { 285 name = strings.ReplaceAll(strings.TrimSpace(name), "-", "_") 286 } 287 v, found := ctx.variables[name] 288 if !found { 289 if vi, found := KnownVariables[name]; found { 290 _, preset := presetVariables[name] 291 switch vi.class { 292 case VarClassConfig: 293 v = &productConfigVariable{baseVariable{nam: name, typ: vi.valueType, preset: preset}} 294 case VarClassSoong: 295 v = &otherGlobalVariable{baseVariable{nam: name, typ: vi.valueType, preset: preset}} 296 } 297 } else if isLocalVariable { 298 v = &localVariable{baseVariable{nam: name, typ: hintType}} 299 } else { 300 vt := hintType 301 // Heuristics: local variables that contribute to corresponding config variables 302 if cfgVarName, found := localProductConfigVariables[name]; found && vt == starlarkTypeUnknown { 303 vi, found2 := KnownVariables[cfgVarName] 304 if !found2 { 305 panic(fmt.Errorf("unknown config variable %s for %s", cfgVarName, name)) 306 } 307 vt = vi.valueType 308 } 309 if strings.HasSuffix(name, "_LIST") && vt == starlarkTypeUnknown { 310 // Heuristics: Variables with "_LIST" suffix are lists 311 vt = starlarkTypeList 312 } 313 v = &otherGlobalVariable{baseVariable{nam: name, typ: vt}} 314 } 315 ctx.variables[name] = v 316 } 317 return v 318} 319