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