Home | History | Annotate | Download | only in androidmk
      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 
     15 package main
     16 
     17 import (
     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 
     33 var 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 
     42 type 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 
     56 func (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 
     68 func (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
     74 func (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
     86 func (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
     92 func (f *bpFile) addErrorText(message string) {
     93 	f.insertExtraComment(message)
     94 }
     95 
     96 func (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 
    116 type conditional struct {
    117 	cond string
    118 	eq   bool
    119 }
    120 
    121 func 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 
    145 func 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":
    173 				val := x.Args.Value(file.scope)
    174 				switch {
    175 				case soongModuleTypes[val]:
    176 					handleModuleConditionals(file, x, conds)
    177 					makeModule(file, val)
    178 				case val == clear_vars:
    179 					resetModule(file)
    180 				case val == include_ignored:
    181 					// subdirs are already automatically included in Soong
    182 					continue
    183 				default:
    184 					file.errorf(x, "unsupported include")
    185 					continue
    186 				}
    187 			case "ifeq", "ifneq", "ifdef", "ifndef":
    188 				args := x.Args.Dump()
    189 				eq := x.Name == "ifeq" || x.Name == "ifdef"
    190 				if _, ok := conditionalTranslations[args]; ok {
    191 					newCond := conditional{args, eq}
    192 					conds = append(conds, &newCond)
    193 					if file.inModule {
    194 						if assignmentCond == nil {
    195 							assignmentCond = &newCond
    196 						} else {
    197 							file.errorf(x, "unsupported nested conditional in module")
    198 						}
    199 					}
    200 				} else {
    201 					file.errorf(x, "unsupported conditional")
    202 					conds = append(conds, nil)
    203 					continue
    204 				}
    205 			case "else":
    206 				if len(conds) == 0 {
    207 					file.errorf(x, "missing if before else")
    208 					continue
    209 				} else if conds[len(conds)-1] == nil {
    210 					file.errorf(x, "else from unsupported contitional")
    211 					continue
    212 				}
    213 				conds[len(conds)-1].eq = !conds[len(conds)-1].eq
    214 			case "endif":
    215 				if len(conds) == 0 {
    216 					file.errorf(x, "missing if before endif")
    217 					continue
    218 				} else if conds[len(conds)-1] == nil {
    219 					file.errorf(x, "endif from unsupported contitional")
    220 					conds = conds[:len(conds)-1]
    221 				} else {
    222 					if assignmentCond == conds[len(conds)-1] {
    223 						assignmentCond = nil
    224 					}
    225 					conds = conds[:len(conds)-1]
    226 				}
    227 			default:
    228 				file.errorf(x, "unsupported directive")
    229 				continue
    230 			}
    231 		default:
    232 			file.errorf(x, "unsupported line")
    233 		}
    234 	}
    235 
    236 	tree := &bpparser.File{
    237 		Defs:     file.defs,
    238 		Comments: file.comments,
    239 	}
    240 
    241 	// check for common supported but undesirable structures and clean them up
    242 	fixer := bpfix.NewFixer(tree)
    243 	tree, err := fixer.Fix(bpfix.NewFixRequest().AddAll())
    244 	if err != nil {
    245 		return "", []error{err}
    246 	}
    247 
    248 	out, err := bpparser.Print(tree)
    249 	if err != nil {
    250 		return "", []error{err}
    251 	}
    252 
    253 	return string(out), nil
    254 }
    255 
    256 func handleAssignment(file *bpFile, assignment *mkparser.Assignment, c *conditional) {
    257 	if !assignment.Name.Const() {
    258 		file.errorf(assignment, "unsupported non-const variable name")
    259 		return
    260 	}
    261 
    262 	if assignment.Target != nil {
    263 		file.errorf(assignment, "unsupported target assignment")
    264 		return
    265 	}
    266 
    267 	name := assignment.Name.Value(nil)
    268 	prefix := ""
    269 
    270 	if strings.HasPrefix(name, "LOCAL_") {
    271 		for _, x := range propertyPrefixes {
    272 			if strings.HasSuffix(name, "_"+x.mk) {
    273 				name = strings.TrimSuffix(name, "_"+x.mk)
    274 				prefix = x.bp
    275 				break
    276 			}
    277 		}
    278 
    279 		if c != nil {
    280 			if prefix != "" {
    281 				file.errorf(assignment, "prefix assignment inside conditional, skipping conditional")
    282 			} else {
    283 				var ok bool
    284 				if prefix, ok = conditionalTranslations[c.cond][c.eq]; !ok {
    285 					panic("unknown conditional")
    286 				}
    287 			}
    288 		}
    289 	} else {
    290 		if c != nil {
    291 			eq := "eq"
    292 			if !c.eq {
    293 				eq = "neq"
    294 			}
    295 			file.errorf(assignment, "conditional %s %s on global assignment", eq, c.cond)
    296 		}
    297 	}
    298 
    299 	appendVariable := assignment.Type == "+="
    300 
    301 	var err error
    302 	if prop, ok := rewriteProperties[name]; ok {
    303 		err = prop(variableAssignmentContext{file, prefix, assignment.Value, appendVariable})
    304 	} else {
    305 		switch {
    306 		case name == "LOCAL_ARM_MODE":
    307 			// This is a hack to get the LOCAL_ARM_MODE value inside
    308 			// of an arch: { arm: {} } block.
    309 			armModeAssign := assignment
    310 			armModeAssign.Name = mkparser.SimpleMakeString("LOCAL_ARM_MODE_HACK_arm", assignment.Name.Pos())
    311 			handleAssignment(file, armModeAssign, c)
    312 		case strings.HasPrefix(name, "LOCAL_"):
    313 			file.errorf(assignment, "unsupported assignment to %s", name)
    314 			return
    315 		default:
    316 			var val bpparser.Expression
    317 			val, err = makeVariableToBlueprint(file, assignment.Value, bpparser.ListType)
    318 			if err == nil {
    319 				err = setVariable(file, appendVariable, prefix, name, val, false)
    320 			}
    321 		}
    322 	}
    323 	if err != nil {
    324 		file.errorf(assignment, err.Error())
    325 	}
    326 }
    327 
    328 func handleModuleConditionals(file *bpFile, directive *mkparser.Directive, conds []*conditional) {
    329 	for _, c := range conds {
    330 		if c == nil {
    331 			continue
    332 		}
    333 
    334 		if _, ok := conditionalTranslations[c.cond]; !ok {
    335 			panic("unknown conditional " + c.cond)
    336 		}
    337 
    338 		disabledPrefix := conditionalTranslations[c.cond][!c.eq]
    339 
    340 		// Create a fake assignment with enabled = false
    341 		val, err := makeVariableToBlueprint(file, mkparser.SimpleMakeString("false", mkparser.NoPos), bpparser.BoolType)
    342 		if err == nil {
    343 			err = setVariable(file, false, disabledPrefix, "enabled", val, true)
    344 		}
    345 		if err != nil {
    346 			file.errorf(directive, err.Error())
    347 		}
    348 	}
    349 }
    350 
    351 func makeModule(file *bpFile, t string) {
    352 	file.module.Type = t
    353 	file.module.TypePos = file.module.LBracePos
    354 	file.module.RBracePos = file.bpPos
    355 	file.defs = append(file.defs, file.module)
    356 	file.inModule = false
    357 }
    358 
    359 func resetModule(file *bpFile) {
    360 	file.module = &bpparser.Module{}
    361 	file.module.LBracePos = file.bpPos
    362 	file.localAssignments = make(map[string]*bpparser.Property)
    363 	file.inModule = true
    364 }
    365 
    366 func makeVariableToBlueprint(file *bpFile, val *mkparser.MakeString,
    367 	typ bpparser.Type) (bpparser.Expression, error) {
    368 
    369 	var exp bpparser.Expression
    370 	var err error
    371 	switch typ {
    372 	case bpparser.ListType:
    373 		exp, err = makeToListExpression(val, file.scope)
    374 	case bpparser.StringType:
    375 		exp, err = makeToStringExpression(val, file.scope)
    376 	case bpparser.BoolType:
    377 		exp, err = makeToBoolExpression(val)
    378 	default:
    379 		panic("unknown type")
    380 	}
    381 
    382 	if err != nil {
    383 		return nil, err
    384 	}
    385 
    386 	return exp, nil
    387 }
    388 
    389 func setVariable(file *bpFile, plusequals bool, prefix, name string, value bpparser.Expression, local bool) error {
    390 
    391 	if prefix != "" {
    392 		name = prefix + "." + name
    393 	}
    394 
    395 	pos := file.bpPos
    396 
    397 	var oldValue *bpparser.Expression
    398 	if local {
    399 		oldProp := file.localAssignments[name]
    400 		if oldProp != nil {
    401 			oldValue = &oldProp.Value
    402 		}
    403 	} else {
    404 		oldValue = file.globalAssignments[name]
    405 	}
    406 
    407 	if local {
    408 		if oldValue != nil && plusequals {
    409 			val, err := addValues(*oldValue, value)
    410 			if err != nil {
    411 				return fmt.Errorf("unsupported addition: %s", err.Error())
    412 			}
    413 			val.(*bpparser.Operator).OperatorPos = pos
    414 			*oldValue = val
    415 		} else {
    416 			names := strings.Split(name, ".")
    417 			if file.module == nil {
    418 				file.warnf("No 'include $(CLEAR_VARS)' detected before first assignment; clearing vars now")
    419 				resetModule(file)
    420 			}
    421 			container := &file.module.Properties
    422 
    423 			for i, n := range names[:len(names)-1] {
    424 				fqn := strings.Join(names[0:i+1], ".")
    425 				prop := file.localAssignments[fqn]
    426 				if prop == nil {
    427 					prop = &bpparser.Property{
    428 						Name:    n,
    429 						NamePos: pos,
    430 						Value: &bpparser.Map{
    431 							Properties: []*bpparser.Property{},
    432 						},
    433 					}
    434 					file.localAssignments[fqn] = prop
    435 					*container = append(*container, prop)
    436 				}
    437 				container = &prop.Value.(*bpparser.Map).Properties
    438 			}
    439 
    440 			prop := &bpparser.Property{
    441 				Name:    names[len(names)-1],
    442 				NamePos: pos,
    443 				Value:   value,
    444 			}
    445 			file.localAssignments[name] = prop
    446 			*container = append(*container, prop)
    447 		}
    448 	} else {
    449 		if oldValue != nil && plusequals {
    450 			a := &bpparser.Assignment{
    451 				Name:      name,
    452 				NamePos:   pos,
    453 				Value:     value,
    454 				OrigValue: value,
    455 				EqualsPos: pos,
    456 				Assigner:  "+=",
    457 			}
    458 			file.defs = append(file.defs, a)
    459 		} else {
    460 			a := &bpparser.Assignment{
    461 				Name:      name,
    462 				NamePos:   pos,
    463 				Value:     value,
    464 				OrigValue: value,
    465 				EqualsPos: pos,
    466 				Assigner:  "=",
    467 			}
    468 			file.globalAssignments[name] = &a.Value
    469 			file.defs = append(file.defs, a)
    470 		}
    471 	}
    472 	return nil
    473 }
    474