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