1// Copyright 2017 Google Inc. All rights reserved.
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 androidmk
16
17import (
18	"bytes"
19	"fmt"
20	"strings"
21	"text/scanner"
22
23	"android/soong/bpfix/bpfix"
24
25	mkparser "android/soong/androidmk/parser"
26
27	bpparser "github.com/google/blueprint/parser"
28)
29
30// TODO: non-expanded variables with expressions
31
32type bpFile struct {
33	comments          []*bpparser.CommentGroup
34	defs              []bpparser.Definition
35	localAssignments  map[string]*bpparser.Property
36	globalAssignments map[string]*bpparser.Expression
37	variableRenames   map[string]string
38	scope             mkparser.Scope
39	module            *bpparser.Module
40
41	mkPos scanner.Position // Position of the last handled line in the makefile
42	bpPos scanner.Position // Position of the last emitted line to the blueprint file
43
44	inModule bool
45}
46
47var invalidVariableStringToReplacement = map[string]string{
48	"-": "_dash_",
49}
50
51func (f *bpFile) insertComment(s string) {
52	f.comments = append(f.comments, &bpparser.CommentGroup{
53		Comments: []*bpparser.Comment{
54			&bpparser.Comment{
55				Comment: []string{s},
56				Slash:   f.bpPos,
57			},
58		},
59	})
60	f.bpPos.Offset += len(s)
61}
62
63func (f *bpFile) insertExtraComment(s string) {
64	f.insertComment(s)
65	f.bpPos.Line++
66}
67
68// records that the given node failed to be converted and includes an explanatory message
69func (f *bpFile) errorf(failedNode mkparser.Node, message string, args ...interface{}) {
70	orig := failedNode.Dump()
71	message = fmt.Sprintf(message, args...)
72	f.addErrorText(fmt.Sprintf("// ANDROIDMK TRANSLATION ERROR: %s", message))
73
74	lines := strings.Split(orig, "\n")
75	for _, l := range lines {
76		f.insertExtraComment("// " + l)
77	}
78}
79
80// records that something unexpected occurred
81func (f *bpFile) warnf(message string, args ...interface{}) {
82	message = fmt.Sprintf(message, args...)
83	f.addErrorText(fmt.Sprintf("// ANDROIDMK TRANSLATION WARNING: %s", message))
84}
85
86// adds the given error message as-is to the bottom of the (in-progress) file
87func (f *bpFile) addErrorText(message string) {
88	f.insertExtraComment(message)
89}
90
91func (f *bpFile) setMkPos(pos, end scanner.Position) {
92	// It is unusual but not forbidden for pos.Line to be smaller than f.mkPos.Line
93	// For example:
94	//
95	// if true                       # this line is emitted 1st
96	// if true                       # this line is emitted 2nd
97	// some-target: some-file        # this line is emitted 3rd
98	//         echo doing something  # this recipe is emitted 6th
99	// endif #some comment           # this endif is emitted 4th; this comment is part of the recipe
100	//         echo doing more stuff # this is part of the recipe
101	// endif                         # this endif is emitted 5th
102	//
103	// However, if pos.Line < f.mkPos.Line, we treat it as though it were equal
104	if pos.Line >= f.mkPos.Line {
105		f.bpPos.Line += (pos.Line - f.mkPos.Line)
106		f.mkPos = end
107	}
108
109}
110
111type conditional struct {
112	cond string
113	eq   bool
114}
115
116func ConvertFile(filename string, buffer *bytes.Buffer) (string, []error) {
117	p := mkparser.NewParser(filename, buffer)
118
119	nodes, errs := p.Parse()
120	if len(errs) > 0 {
121		return "", errs
122	}
123
124	file := &bpFile{
125		scope:             androidScope(),
126		localAssignments:  make(map[string]*bpparser.Property),
127		globalAssignments: make(map[string]*bpparser.Expression),
128		variableRenames:   make(map[string]string),
129	}
130
131	var conds []*conditional
132	var assignmentCond *conditional
133	var tree *bpparser.File
134
135	for _, node := range nodes {
136		file.setMkPos(p.Unpack(node.Pos()), p.Unpack(node.End()))
137
138		switch x := node.(type) {
139		case *mkparser.Comment:
140			file.insertComment("//" + x.Comment)
141		case *mkparser.Assignment:
142			handleAssignment(file, x, assignmentCond)
143		case *mkparser.Directive:
144			switch x.Name {
145			case "include", "-include":
146				module, ok := mapIncludePath(x.Args.Value(file.scope))
147				if !ok {
148					file.errorf(x, "unsupported include")
149					continue
150				}
151				switch module {
152				case clear_vars:
153					resetModule(file)
154				case include_ignored:
155					// subdirs are already automatically included in Soong
156					continue
157				default:
158					handleModuleConditionals(file, x, conds)
159					makeModule(file, module)
160				}
161			case "ifeq", "ifneq", "ifdef", "ifndef":
162				args := x.Args.Dump()
163				eq := x.Name == "ifeq" || x.Name == "ifdef"
164				if _, ok := conditionalTranslations[args]; ok {
165					newCond := conditional{args, eq}
166					conds = append(conds, &newCond)
167					if file.inModule {
168						if assignmentCond == nil {
169							assignmentCond = &newCond
170						} else {
171							file.errorf(x, "unsupported nested conditional in module")
172						}
173					}
174				} else {
175					file.errorf(x, "unsupported conditional")
176					conds = append(conds, nil)
177					continue
178				}
179			case "else":
180				if len(conds) == 0 {
181					file.errorf(x, "missing if before else")
182					continue
183				} else if conds[len(conds)-1] == nil {
184					file.errorf(x, "else from unsupported conditional")
185					continue
186				}
187				conds[len(conds)-1].eq = !conds[len(conds)-1].eq
188			case "endif":
189				if len(conds) == 0 {
190					file.errorf(x, "missing if before endif")
191					continue
192				} else if conds[len(conds)-1] == nil {
193					file.errorf(x, "endif from unsupported conditional")
194					conds = conds[:len(conds)-1]
195				} else {
196					if assignmentCond == conds[len(conds)-1] {
197						assignmentCond = nil
198					}
199					conds = conds[:len(conds)-1]
200				}
201			default:
202				file.errorf(x, "unsupported directive")
203				continue
204			}
205		default:
206			file.errorf(x, "unsupported line")
207		}
208	}
209
210	tree = &bpparser.File{
211		Defs:     file.defs,
212		Comments: file.comments,
213	}
214
215	// check for common supported but undesirable structures and clean them up
216	fixer := bpfix.NewFixer(tree)
217	fixedTree, fixerErr := fixer.Fix(bpfix.NewFixRequest().AddAll())
218	if fixerErr != nil {
219		errs = append(errs, fixerErr)
220	} else {
221		tree = fixedTree
222	}
223
224	out, err := bpparser.Print(tree)
225	if err != nil {
226		errs = append(errs, err)
227		return "", errs
228	}
229
230	return string(out), errs
231}
232
233func renameVariableWithInvalidCharacters(name string) string {
234	renamed := ""
235	for invalid, replacement := range invalidVariableStringToReplacement {
236		if strings.Contains(name, invalid) {
237			renamed = strings.ReplaceAll(name, invalid, replacement)
238		}
239	}
240
241	return renamed
242}
243
244func invalidVariableStrings() string {
245	invalidStrings := make([]string, 0, len(invalidVariableStringToReplacement))
246	for s := range invalidVariableStringToReplacement {
247		invalidStrings = append(invalidStrings, "\""+s+"\"")
248	}
249	return strings.Join(invalidStrings, ", ")
250}
251
252func handleAssignment(file *bpFile, assignment *mkparser.Assignment, c *conditional) {
253	if !assignment.Name.Const() {
254		file.errorf(assignment, "unsupported non-const variable name")
255		return
256	}
257
258	if assignment.Target != nil {
259		file.errorf(assignment, "unsupported target assignment")
260		return
261	}
262
263	name := assignment.Name.Value(nil)
264	prefix := ""
265
266	if newName := renameVariableWithInvalidCharacters(name); newName != "" {
267		file.warnf("Variable names cannot contain: %s. Renamed \"%s\" to \"%s\"", invalidVariableStrings(), name, newName)
268		file.variableRenames[name] = newName
269		name = newName
270	}
271
272	if strings.HasPrefix(name, "LOCAL_") {
273		for _, x := range propertyPrefixes {
274			if strings.HasSuffix(name, "_"+x.mk) {
275				name = strings.TrimSuffix(name, "_"+x.mk)
276				prefix = x.bp
277				break
278			}
279		}
280
281		if c != nil {
282			if prefix != "" {
283				file.errorf(assignment, "prefix assignment inside conditional, skipping conditional")
284			} else {
285				var ok bool
286				if prefix, ok = conditionalTranslations[c.cond][c.eq]; !ok {
287					panic("unknown conditional")
288				}
289			}
290		}
291	} else {
292		if c != nil {
293			eq := "eq"
294			if !c.eq {
295				eq = "neq"
296			}
297			file.errorf(assignment, "conditional %s %s on global assignment", eq, c.cond)
298		}
299	}
300
301	appendVariable := assignment.Type == "+="
302
303	var err error
304	if prop, ok := rewriteProperties[name]; ok {
305		err = prop(variableAssignmentContext{file, prefix, assignment.Value, appendVariable})
306	} else {
307		switch {
308		case name == "LOCAL_ARM_MODE":
309			// This is a hack to get the LOCAL_ARM_MODE value inside
310			// of an arch: { arm: {} } block.
311			armModeAssign := assignment
312			armModeAssign.Name = mkparser.SimpleMakeString("LOCAL_ARM_MODE_HACK_arm", assignment.Name.Pos())
313			handleAssignment(file, armModeAssign, c)
314		case strings.HasPrefix(name, "LOCAL_"):
315			file.errorf(assignment, "unsupported assignment to %s", name)
316			return
317		default:
318			var val bpparser.Expression
319			val, err = makeVariableToBlueprint(file, assignment.Value, bpparser.ListType)
320			if err == nil {
321				err = setVariable(file, appendVariable, prefix, name, val, false)
322			}
323		}
324	}
325	if err != nil {
326		file.errorf(assignment, err.Error())
327	}
328}
329
330func handleModuleConditionals(file *bpFile, directive *mkparser.Directive, conds []*conditional) {
331	for _, c := range conds {
332		if c == nil {
333			continue
334		}
335
336		if _, ok := conditionalTranslations[c.cond]; !ok {
337			panic("unknown conditional " + c.cond)
338		}
339
340		disabledPrefix := conditionalTranslations[c.cond][!c.eq]
341
342		// Create a fake assignment with enabled = false
343		val, err := makeVariableToBlueprint(file, mkparser.SimpleMakeString("false", mkparser.NoPos), bpparser.BoolType)
344		if err == nil {
345			err = setVariable(file, false, disabledPrefix, "enabled", val, true)
346		}
347		if err != nil {
348			file.errorf(directive, err.Error())
349		}
350	}
351}
352
353func makeModule(file *bpFile, t string) {
354	file.module.Type = t
355	file.module.TypePos = file.module.LBracePos
356	file.module.RBracePos = file.bpPos
357	file.defs = append(file.defs, file.module)
358	file.inModule = false
359}
360
361func resetModule(file *bpFile) {
362	file.module = &bpparser.Module{}
363	file.module.LBracePos = file.bpPos
364	file.localAssignments = make(map[string]*bpparser.Property)
365	file.inModule = true
366}
367
368func makeVariableToBlueprint(file *bpFile, val *mkparser.MakeString,
369	typ bpparser.Type) (bpparser.Expression, error) {
370
371	var exp bpparser.Expression
372	var err error
373	switch typ {
374	case bpparser.ListType:
375		exp, err = makeToListExpression(val, file)
376	case bpparser.StringType:
377		exp, err = makeToStringExpression(val, file)
378	case bpparser.BoolType:
379		exp, err = makeToBoolExpression(val, file)
380	default:
381		panic("unknown type")
382	}
383
384	if err != nil {
385		return nil, err
386	}
387
388	return exp, nil
389}
390
391func setVariable(file *bpFile, plusequals bool, prefix, name string, value bpparser.Expression, local bool) error {
392	if prefix != "" {
393		name = prefix + "." + name
394	}
395
396	pos := file.bpPos
397
398	var oldValue *bpparser.Expression
399	if local {
400		oldProp := file.localAssignments[name]
401		if oldProp != nil {
402			oldValue = &oldProp.Value
403		}
404	} else {
405		oldValue = file.globalAssignments[name]
406	}
407
408	if local {
409		if oldValue != nil && plusequals {
410			val, err := addValues(*oldValue, value)
411			if err != nil {
412				return fmt.Errorf("unsupported addition: %s", err.Error())
413			}
414			val.(*bpparser.Operator).OperatorPos = pos
415			*oldValue = val
416		} else {
417			names := strings.Split(name, ".")
418			if file.module == nil {
419				file.warnf("No 'include $(CLEAR_VARS)' detected before first assignment; clearing vars now")
420				resetModule(file)
421			}
422			container := &file.module.Properties
423
424			for i, n := range names[:len(names)-1] {
425				fqn := strings.Join(names[0:i+1], ".")
426				prop := file.localAssignments[fqn]
427				if prop == nil {
428					prop = &bpparser.Property{
429						Name:    n,
430						NamePos: pos,
431						Value: &bpparser.Map{
432							Properties: []*bpparser.Property{},
433						},
434					}
435					file.localAssignments[fqn] = prop
436					*container = append(*container, prop)
437				}
438				container = &prop.Value.(*bpparser.Map).Properties
439			}
440
441			prop := &bpparser.Property{
442				Name:    names[len(names)-1],
443				NamePos: pos,
444				Value:   value,
445			}
446			file.localAssignments[name] = prop
447			*container = append(*container, prop)
448		}
449	} else {
450		if oldValue != nil && plusequals {
451			a := &bpparser.Assignment{
452				Name:      name,
453				NamePos:   pos,
454				Value:     value,
455				OrigValue: value,
456				EqualsPos: pos,
457				Assigner:  "+=",
458			}
459			file.defs = append(file.defs, a)
460		} else {
461			if _, ok := file.globalAssignments[name]; ok {
462				return fmt.Errorf("cannot assign a variable multiple times: \"%s\"", name)
463			}
464			a := &bpparser.Assignment{
465				Name:      name,
466				NamePos:   pos,
467				Value:     value,
468				OrigValue: value,
469				EqualsPos: pos,
470				Assigner:  "=",
471			}
472			file.globalAssignments[name] = &a.Value
473			file.defs = append(file.defs, a)
474		}
475	}
476	return nil
477}
478