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 414func setVariable(file *bpFile, plusequals bool, prefix, name string, value bpparser.Expression, local bool) error { 415 if prefix != "" { 416 name = prefix + "." + name 417 } 418 419 pos := file.bpPos 420 421 var oldValue *bpparser.Expression 422 if local { 423 oldProp := file.localAssignments[name] 424 if oldProp != nil { 425 oldValue = &oldProp.Value 426 } 427 } else { 428 oldValue = file.globalAssignments[name] 429 } 430 431 if local { 432 if oldValue != nil && plusequals { 433 val, err := addValues(*oldValue, value) 434 if err != nil { 435 return fmt.Errorf("unsupported addition: %s", err.Error()) 436 } 437 val.(*bpparser.Operator).OperatorPos = pos 438 *oldValue = val 439 } else { 440 names := strings.Split(name, ".") 441 if file.module == nil { 442 file.warnf("No 'include $(CLEAR_VARS)' detected before first assignment; clearing vars now") 443 resetModule(file) 444 } 445 container := &file.module.Properties 446 447 for i, n := range names[:len(names)-1] { 448 fqn := strings.Join(names[0:i+1], ".") 449 prop := file.localAssignments[fqn] 450 if prop == nil { 451 prop = &bpparser.Property{ 452 Name: n, 453 NamePos: pos, 454 Value: &bpparser.Map{ 455 Properties: []*bpparser.Property{}, 456 }, 457 } 458 file.localAssignments[fqn] = prop 459 *container = append(*container, prop) 460 } 461 container = &prop.Value.(*bpparser.Map).Properties 462 } 463 464 prop := &bpparser.Property{ 465 Name: names[len(names)-1], 466 NamePos: pos, 467 Value: value, 468 } 469 file.localAssignments[name] = prop 470 *container = append(*container, prop) 471 } 472 } else { 473 if oldValue != nil && plusequals { 474 a := &bpparser.Assignment{ 475 Name: name, 476 NamePos: pos, 477 Value: value, 478 OrigValue: value, 479 EqualsPos: pos, 480 Assigner: "+=", 481 } 482 file.defs = append(file.defs, a) 483 } else { 484 if _, ok := file.globalAssignments[name]; ok { 485 return fmt.Errorf("cannot assign a variable multiple times: \"%s\"", name) 486 } 487 a := &bpparser.Assignment{ 488 Name: name, 489 NamePos: pos, 490 Value: value, 491 OrigValue: value, 492 EqualsPos: pos, 493 Assigner: "=", 494 } 495 file.globalAssignments[name] = &a.Value 496 file.defs = append(file.defs, a) 497 } 498 } 499 return nil 500} 501