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