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