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 51func (f *bpFile) insertComment(s string) { 52 f.comments = append(f.comments, &bpparser.CommentGroup{ 53 Comments: []*bpparser.Comment{ 54 &bpparser.Comment{ 55 Comment: []string{s}, 56 Slash: f.bpPos, 57 }, 58 }, 59 }) 60 f.bpPos.Offset += len(s) 61} 62 63func (f *bpFile) insertExtraComment(s string) { 64 f.insertComment(s) 65 f.bpPos.Line++ 66} 67 68// records that the given node failed to be converted and includes an explanatory message 69func (f *bpFile) errorf(failedNode mkparser.Node, message string, args ...interface{}) { 70 orig := failedNode.Dump() 71 message = fmt.Sprintf(message, args...) 72 f.addErrorText(fmt.Sprintf("// ANDROIDMK TRANSLATION ERROR: %s", message)) 73 74 lines := strings.Split(orig, "\n") 75 for _, l := range lines { 76 f.insertExtraComment("// " + l) 77 } 78} 79 80// records that something unexpected occurred 81func (f *bpFile) warnf(message string, args ...interface{}) { 82 message = fmt.Sprintf(message, args...) 83 f.addErrorText(fmt.Sprintf("// ANDROIDMK TRANSLATION WARNING: %s", message)) 84} 85 86// adds the given error message as-is to the bottom of the (in-progress) file 87func (f *bpFile) addErrorText(message string) { 88 f.insertExtraComment(message) 89} 90 91func (f *bpFile) setMkPos(pos, end scanner.Position) { 92 // It is unusual but not forbidden for pos.Line to be smaller than f.mkPos.Line 93 // For example: 94 // 95 // if true # this line is emitted 1st 96 // if true # this line is emitted 2nd 97 // some-target: some-file # this line is emitted 3rd 98 // echo doing something # this recipe is emitted 6th 99 // endif #some comment # this endif is emitted 4th; this comment is part of the recipe 100 // echo doing more stuff # this is part of the recipe 101 // endif # this endif is emitted 5th 102 // 103 // However, if pos.Line < f.mkPos.Line, we treat it as though it were equal 104 if pos.Line >= f.mkPos.Line { 105 f.bpPos.Line += (pos.Line - f.mkPos.Line) 106 f.mkPos = end 107 } 108 109} 110 111type conditional struct { 112 cond string 113 eq bool 114} 115 116func ConvertFile(filename string, buffer *bytes.Buffer) (string, []error) { 117 p := mkparser.NewParser(filename, buffer) 118 119 nodes, errs := p.Parse() 120 if len(errs) > 0 { 121 return "", errs 122 } 123 124 file := &bpFile{ 125 scope: androidScope(), 126 localAssignments: make(map[string]*bpparser.Property), 127 globalAssignments: make(map[string]*bpparser.Expression), 128 variableRenames: make(map[string]string), 129 } 130 131 var conds []*conditional 132 var assignmentCond *conditional 133 var tree *bpparser.File 134 135 for _, node := range nodes { 136 file.setMkPos(p.Unpack(node.Pos()), p.Unpack(node.End())) 137 138 switch x := node.(type) { 139 case *mkparser.Comment: 140 file.insertComment("//" + x.Comment) 141 case *mkparser.Assignment: 142 handleAssignment(file, x, assignmentCond) 143 case *mkparser.Directive: 144 switch x.Name { 145 case "include", "-include": 146 module, ok := mapIncludePath(x.Args.Value(file.scope)) 147 if !ok { 148 file.errorf(x, "unsupported include") 149 continue 150 } 151 switch module { 152 case clear_vars: 153 resetModule(file) 154 case include_ignored: 155 // subdirs are already automatically included in Soong 156 continue 157 default: 158 handleModuleConditionals(file, x, conds) 159 makeModule(file, module) 160 } 161 case "ifeq", "ifneq", "ifdef", "ifndef": 162 args := x.Args.Dump() 163 eq := x.Name == "ifeq" || x.Name == "ifdef" 164 if _, ok := conditionalTranslations[args]; ok { 165 newCond := conditional{args, eq} 166 conds = append(conds, &newCond) 167 if file.inModule { 168 if assignmentCond == nil { 169 assignmentCond = &newCond 170 } else { 171 file.errorf(x, "unsupported nested conditional in module") 172 } 173 } 174 } else { 175 file.errorf(x, "unsupported conditional") 176 conds = append(conds, nil) 177 continue 178 } 179 case "else": 180 if len(conds) == 0 { 181 file.errorf(x, "missing if before else") 182 continue 183 } else if conds[len(conds)-1] == nil { 184 file.errorf(x, "else from unsupported conditional") 185 continue 186 } 187 conds[len(conds)-1].eq = !conds[len(conds)-1].eq 188 case "endif": 189 if len(conds) == 0 { 190 file.errorf(x, "missing if before endif") 191 continue 192 } else if conds[len(conds)-1] == nil { 193 file.errorf(x, "endif from unsupported conditional") 194 conds = conds[:len(conds)-1] 195 } else { 196 if assignmentCond == conds[len(conds)-1] { 197 assignmentCond = nil 198 } 199 conds = conds[:len(conds)-1] 200 } 201 default: 202 file.errorf(x, "unsupported directive") 203 continue 204 } 205 default: 206 file.errorf(x, "unsupported line") 207 } 208 } 209 210 tree = &bpparser.File{ 211 Defs: file.defs, 212 Comments: file.comments, 213 } 214 215 // check for common supported but undesirable structures and clean them up 216 fixer := bpfix.NewFixer(tree) 217 fixedTree, fixerErr := fixer.Fix(bpfix.NewFixRequest().AddAll()) 218 if fixerErr != nil { 219 errs = append(errs, fixerErr) 220 } else { 221 tree = fixedTree 222 } 223 224 out, err := bpparser.Print(tree) 225 if err != nil { 226 errs = append(errs, err) 227 return "", errs 228 } 229 230 return string(out), errs 231} 232 233func renameVariableWithInvalidCharacters(name string) string { 234 renamed := "" 235 for invalid, replacement := range invalidVariableStringToReplacement { 236 if strings.Contains(name, invalid) { 237 renamed = strings.ReplaceAll(name, invalid, replacement) 238 } 239 } 240 241 return renamed 242} 243 244func invalidVariableStrings() string { 245 invalidStrings := make([]string, 0, len(invalidVariableStringToReplacement)) 246 for s := range invalidVariableStringToReplacement { 247 invalidStrings = append(invalidStrings, "\""+s+"\"") 248 } 249 return strings.Join(invalidStrings, ", ") 250} 251 252func handleAssignment(file *bpFile, assignment *mkparser.Assignment, c *conditional) { 253 if !assignment.Name.Const() { 254 file.errorf(assignment, "unsupported non-const variable name") 255 return 256 } 257 258 if assignment.Target != nil { 259 file.errorf(assignment, "unsupported target assignment") 260 return 261 } 262 263 name := assignment.Name.Value(nil) 264 prefix := "" 265 266 if newName := renameVariableWithInvalidCharacters(name); newName != "" { 267 file.warnf("Variable names cannot contain: %s. Renamed \"%s\" to \"%s\"", invalidVariableStrings(), name, newName) 268 file.variableRenames[name] = newName 269 name = newName 270 } 271 272 if strings.HasPrefix(name, "LOCAL_") { 273 for _, x := range propertyPrefixes { 274 if strings.HasSuffix(name, "_"+x.mk) { 275 name = strings.TrimSuffix(name, "_"+x.mk) 276 prefix = x.bp 277 break 278 } 279 } 280 281 if c != nil { 282 if prefix != "" { 283 file.errorf(assignment, "prefix assignment inside conditional, skipping conditional") 284 } else { 285 var ok bool 286 if prefix, ok = conditionalTranslations[c.cond][c.eq]; !ok { 287 panic("unknown conditional") 288 } 289 } 290 } 291 } else { 292 if c != nil { 293 eq := "eq" 294 if !c.eq { 295 eq = "neq" 296 } 297 file.errorf(assignment, "conditional %s %s on global assignment", eq, c.cond) 298 } 299 } 300 301 appendVariable := assignment.Type == "+=" 302 303 var err error 304 if prop, ok := rewriteProperties[name]; ok { 305 err = prop(variableAssignmentContext{file, prefix, assignment.Value, appendVariable}) 306 } else { 307 switch { 308 case name == "LOCAL_ARM_MODE": 309 // This is a hack to get the LOCAL_ARM_MODE value inside 310 // of an arch: { arm: {} } block. 311 armModeAssign := assignment 312 armModeAssign.Name = mkparser.SimpleMakeString("LOCAL_ARM_MODE_HACK_arm", assignment.Name.Pos()) 313 handleAssignment(file, armModeAssign, c) 314 case strings.HasPrefix(name, "LOCAL_"): 315 file.errorf(assignment, "unsupported assignment to %s", name) 316 return 317 default: 318 var val bpparser.Expression 319 val, err = makeVariableToBlueprint(file, assignment.Value, bpparser.ListType) 320 if err == nil { 321 err = setVariable(file, appendVariable, prefix, name, val, false) 322 } 323 } 324 } 325 if err != nil { 326 file.errorf(assignment, err.Error()) 327 } 328} 329 330func handleModuleConditionals(file *bpFile, directive *mkparser.Directive, conds []*conditional) { 331 for _, c := range conds { 332 if c == nil { 333 continue 334 } 335 336 if _, ok := conditionalTranslations[c.cond]; !ok { 337 panic("unknown conditional " + c.cond) 338 } 339 340 disabledPrefix := conditionalTranslations[c.cond][!c.eq] 341 342 // Create a fake assignment with enabled = false 343 val, err := makeVariableToBlueprint(file, mkparser.SimpleMakeString("false", mkparser.NoPos), bpparser.BoolType) 344 if err == nil { 345 err = setVariable(file, false, disabledPrefix, "enabled", val, true) 346 } 347 if err != nil { 348 file.errorf(directive, err.Error()) 349 } 350 } 351} 352 353func makeModule(file *bpFile, t string) { 354 file.module.Type = t 355 file.module.TypePos = file.module.LBracePos 356 file.module.RBracePos = file.bpPos 357 file.defs = append(file.defs, file.module) 358 file.inModule = false 359} 360 361func resetModule(file *bpFile) { 362 file.module = &bpparser.Module{} 363 file.module.LBracePos = file.bpPos 364 file.localAssignments = make(map[string]*bpparser.Property) 365 file.inModule = true 366} 367 368func makeVariableToBlueprint(file *bpFile, val *mkparser.MakeString, 369 typ bpparser.Type) (bpparser.Expression, error) { 370 371 var exp bpparser.Expression 372 var err error 373 switch typ { 374 case bpparser.ListType: 375 exp, err = makeToListExpression(val, file) 376 case bpparser.StringType: 377 exp, err = makeToStringExpression(val, file) 378 case bpparser.BoolType: 379 exp, err = makeToBoolExpression(val, file) 380 default: 381 panic("unknown type") 382 } 383 384 if err != nil { 385 return nil, err 386 } 387 388 return exp, nil 389} 390 391func setVariable(file *bpFile, plusequals bool, prefix, name string, value bpparser.Expression, local bool) error { 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 if _, ok := file.globalAssignments[name]; ok { 462 return fmt.Errorf("cannot assign a variable multiple times: \"%s\"", name) 463 } 464 a := &bpparser.Assignment{ 465 Name: name, 466 NamePos: pos, 467 Value: value, 468 OrigValue: value, 469 EqualsPos: pos, 470 Assigner: "=", 471 } 472 file.globalAssignments[name] = &a.Value 473 file.defs = append(file.defs, a) 474 } 475 } 476 return nil 477} 478