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