github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/internal/merger/merger.go (about) 1 /* Copyright 2016 The Bazel Authors. 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 16 // Package merger provides methods for merging parsed BUILD files. 17 package merger 18 19 import ( 20 "fmt" 21 "log" 22 "sort" 23 "strings" 24 25 "github.com/bazelbuild/bazel-gazelle/internal/config" 26 "github.com/bazelbuild/bazel-gazelle/internal/label" 27 bf "github.com/bazelbuild/buildtools/build" 28 ) 29 30 const keep = "keep" // marker in srcs or deps to tell gazelle to preserve. 31 32 // MergableAttrs is the set of attribute names for each kind of rule that 33 // may be merged. When an attribute is mergeable, a generated value may 34 // replace or augment an existing value. If an attribute is not mergeable, 35 // existing values are preserved. Generated non-mergeable attributes may 36 // still be added to a rule if there is no corresponding existing attribute. 37 type MergeableAttrs map[string]map[string]bool 38 39 var ( 40 // PreResolveAttrs is the set of attributes that should be merged before 41 // dependency resolution, i.e., everything except deps. 42 PreResolveAttrs MergeableAttrs 43 44 // PostResolveAttrs is the set of attributes that should be merged after 45 // dependency resolution, i.e., deps. 46 PostResolveAttrs MergeableAttrs 47 48 // RepoAttrs is the set of attributes that should be merged in repository 49 // rules in WORKSPACE. 50 RepoAttrs MergeableAttrs 51 52 // nonEmptyAttrs is the set of attributes that disqualify a rule from being 53 // deleted after merge. 54 nonEmptyAttrs MergeableAttrs 55 ) 56 57 func init() { 58 PreResolveAttrs = make(MergeableAttrs) 59 PostResolveAttrs = make(MergeableAttrs) 60 RepoAttrs = make(MergeableAttrs) 61 nonEmptyAttrs = make(MergeableAttrs) 62 for _, set := range []struct { 63 mergeableAttrs MergeableAttrs 64 kinds, attrs []string 65 }{ 66 { 67 mergeableAttrs: PreResolveAttrs, 68 kinds: []string{ 69 "go_library", 70 "go_binary", 71 "go_test", 72 "go_proto_library", 73 "proto_library", 74 }, 75 attrs: []string{ 76 "srcs", 77 }, 78 }, { 79 mergeableAttrs: PreResolveAttrs, 80 kinds: []string{ 81 "go_library", 82 "go_proto_library", 83 }, 84 attrs: []string{ 85 "importpath", 86 }, 87 }, { 88 mergeableAttrs: PreResolveAttrs, 89 kinds: []string{ 90 "go_library", 91 "go_binary", 92 "go_test", 93 "go_proto_library", 94 }, 95 attrs: []string{ 96 "cgo", 97 "clinkopts", 98 "copts", 99 "embed", 100 }, 101 }, { 102 mergeableAttrs: PreResolveAttrs, 103 kinds: []string{ 104 "go_proto_library", 105 }, 106 attrs: []string{ 107 "proto", 108 }, 109 }, { 110 mergeableAttrs: PostResolveAttrs, 111 kinds: []string{ 112 "go_library", 113 "go_binary", 114 "go_test", 115 "go_proto_library", 116 "proto_library", 117 }, 118 attrs: []string{ 119 "deps", 120 config.GazelleImportsKey, 121 }, 122 }, { 123 mergeableAttrs: RepoAttrs, 124 kinds: []string{ 125 "go_repository", 126 }, 127 attrs: []string{ 128 "commit", 129 "importpath", 130 "remote", 131 "sha256", 132 "strip_prefix", 133 "tag", 134 "type", 135 "urls", 136 "vcs", 137 }, 138 }, { 139 mergeableAttrs: nonEmptyAttrs, 140 kinds: []string{ 141 "go_binary", 142 "go_library", 143 "go_test", 144 "proto_library", 145 }, 146 attrs: []string{ 147 "srcs", 148 "deps", 149 }, 150 }, { 151 mergeableAttrs: nonEmptyAttrs, 152 kinds: []string{ 153 "go_binary", 154 "go_library", 155 "go_test", 156 }, 157 attrs: []string{ 158 "embed", 159 }, 160 }, { 161 mergeableAttrs: nonEmptyAttrs, 162 kinds: []string{ 163 "go_proto_library", 164 }, 165 attrs: []string{ 166 "proto", 167 }, 168 }, 169 } { 170 for _, kind := range set.kinds { 171 if set.mergeableAttrs[kind] == nil { 172 set.mergeableAttrs[kind] = make(map[string]bool) 173 } 174 for _, attr := range set.attrs { 175 set.mergeableAttrs[kind][attr] = true 176 } 177 } 178 } 179 } 180 181 // MergeFile merges the rules in genRules with matching rules in oldFile and 182 // adds unmatched rules to the end of the merged file. MergeFile also merges 183 // rules in empty with matching rules in oldFile and deletes rules that 184 // are empty after merging. attrs is the set of attributes to merge. Attributes 185 // not in this set will be left alone if they already exist. 186 func MergeFile(genRules []bf.Expr, empty []bf.Expr, oldFile *bf.File, attrs MergeableAttrs) (mergedFile *bf.File, mergedRules []bf.Expr) { 187 // Merge empty rules into the file and delete any rules which become empty. 188 mergedFile = new(bf.File) 189 *mergedFile = *oldFile 190 mergedFile.Stmt = append([]bf.Expr{}, oldFile.Stmt...) 191 var deletedIndices []int 192 for _, s := range empty { 193 emptyCall := s.(*bf.CallExpr) 194 if oldCall, i, _ := match(oldFile.Stmt, emptyCall); oldCall != nil { 195 mergedRule := mergeRule(emptyCall, oldCall, attrs, oldFile.Path) 196 if isRuleEmpty(mergedRule) { 197 deletedIndices = append(deletedIndices, i) 198 } else { 199 mergedFile.Stmt[i] = mergedRule 200 } 201 } 202 } 203 if len(deletedIndices) > 0 { 204 sort.Ints(deletedIndices) 205 mergedFile.Stmt = deleteIndices(mergedFile.Stmt, deletedIndices) 206 } 207 208 // Match generated rules with existing rules in the file. Keep track of 209 // rules with non-standard names. 210 matchIndices := make([]int, len(genRules)) 211 matchErrors := make([]error, len(genRules)) 212 substitutions := make(map[string]string) 213 for i, s := range genRules { 214 genCall := s.(*bf.CallExpr) 215 oldCall, oldIndex, err := match(mergedFile.Stmt, genCall) 216 if err != nil { 217 // TODO(jayconrod): add a verbose mode and log errors. They are too chatty 218 // to print by default. 219 matchErrors[i] = err 220 continue 221 } 222 matchIndices[i] = oldIndex // < 0 indicates no match 223 if oldCall != nil { 224 oldRule := bf.Rule{Call: oldCall} 225 genRule := bf.Rule{Call: genCall} 226 oldName := oldRule.Name() 227 genName := genRule.Name() 228 if oldName != genName { 229 substitutions[genName] = oldName 230 } 231 } 232 } 233 234 // Rename labels in generated rules that refer to other generated rules. 235 if len(substitutions) > 0 { 236 genRules = append([]bf.Expr{}, genRules...) 237 for i, s := range genRules { 238 genRules[i] = substituteRule(s.(*bf.CallExpr), substitutions) 239 } 240 } 241 242 // Merge generated rules with existing rules or append to the end of the file. 243 for i := range genRules { 244 if matchErrors[i] != nil { 245 continue 246 } 247 if matchIndices[i] < 0 { 248 mergedFile.Stmt = append(mergedFile.Stmt, genRules[i]) 249 mergedRules = append(mergedRules, genRules[i]) 250 } else { 251 mergedRule := mergeRule(genRules[i].(*bf.CallExpr), mergedFile.Stmt[matchIndices[i]].(*bf.CallExpr), attrs, oldFile.Path) 252 mergedFile.Stmt[matchIndices[i]] = mergedRule 253 mergedRules = append(mergedRules, mergedRule) 254 } 255 } 256 257 return mergedFile, mergedRules 258 } 259 260 // mergeRule combines information from gen and old and returns an updated rule. 261 // Both rules must be non-nil and must have the same kind and same name. 262 // attrs is the set of attributes which may be merged. 263 // If nil is returned, the rule should be deleted. 264 func mergeRule(gen, old *bf.CallExpr, attrs MergeableAttrs, filename string) bf.Expr { 265 if old != nil && shouldKeep(old) { 266 return old 267 } 268 269 genRule := bf.Rule{Call: gen} 270 oldRule := bf.Rule{Call: old} 271 merged := *old 272 merged.List = nil 273 mergedRule := bf.Rule{Call: &merged} 274 275 // Copy unnamed arguments from the old rule without merging. The only rule 276 // generated with unnamed arguments is go_prefix, which we currently 277 // leave in place. 278 // TODO: maybe gazelle should allow the prefix to be changed. 279 for _, a := range old.List { 280 if b, ok := a.(*bf.BinaryExpr); ok && b.Op == "=" { 281 break 282 } 283 merged.List = append(merged.List, a) 284 } 285 286 // Merge attributes from the old rule. Preserve comments on old attributes. 287 // Assume generated attributes have no comments. 288 kind := oldRule.Kind() 289 for _, k := range oldRule.AttrKeys() { 290 oldAttr := oldRule.AttrDefn(k) 291 if !attrs[kind][k] || shouldKeep(oldAttr) { 292 merged.List = append(merged.List, oldAttr) 293 continue 294 } 295 296 oldExpr := oldAttr.Y 297 genExpr := genRule.Attr(k) 298 mergedExpr, err := mergeExpr(genExpr, oldExpr) 299 if err != nil { 300 start, end := oldExpr.Span() 301 log.Printf("%s:%d.%d-%d.%d: could not merge expression", filename, start.Line, start.LineRune, end.Line, end.LineRune) 302 mergedExpr = oldExpr 303 } 304 if mergedExpr != nil { 305 mergedAttr := *oldAttr 306 mergedAttr.Y = mergedExpr 307 merged.List = append(merged.List, &mergedAttr) 308 } 309 } 310 311 // Merge attributes from genRule that we haven't processed already. 312 for _, k := range genRule.AttrKeys() { 313 if mergedRule.Attr(k) == nil { 314 mergedRule.SetAttr(k, genRule.Attr(k)) 315 } 316 } 317 318 return &merged 319 } 320 321 // mergeExpr combines information from gen and old and returns an updated 322 // expression. The following kinds of expressions are recognized: 323 // 324 // * nil 325 // * strings (can only be merged with strings) 326 // * lists of strings 327 // * a call to select with a dict argument. The dict keys must be strings, 328 // and the values must be lists of strings. 329 // * a list of strings combined with a select call using +. The list must 330 // be the left operand. 331 // 332 // An error is returned if the expressions can't be merged, for example 333 // because they are not in one of the above formats. 334 func mergeExpr(gen, old bf.Expr) (bf.Expr, error) { 335 if shouldKeep(old) { 336 return old, nil 337 } 338 if gen == nil && (old == nil || isScalar(old)) { 339 return nil, nil 340 } 341 if isScalar(gen) { 342 return gen, nil 343 } 344 345 genExprs, err := extractPlatformStringsExprs(gen) 346 if err != nil { 347 return nil, err 348 } 349 oldExprs, err := extractPlatformStringsExprs(old) 350 if err != nil { 351 return nil, err 352 } 353 mergedExprs, err := mergePlatformStringsExprs(genExprs, oldExprs) 354 if err != nil { 355 return nil, err 356 } 357 return makePlatformStringsExpr(mergedExprs), nil 358 } 359 360 // platformStringsExprs is a set of sub-expressions that match the structure 361 // of package.PlatformStrings. rules.Generator produces expressions that 362 // follow this structure for srcs, deps, and other attributes, so this matches 363 // all non-scalar expressions generated by Gazelle. 364 // 365 // The matched expression has the form: 366 // 367 // [] + select({}) + select({}) + select({}) 368 // 369 // The four collections may appear in any order, and some or all of them may 370 // be omitted (all fields are nil for a nil expression). 371 type platformStringsExprs struct { 372 generic *bf.ListExpr 373 os, arch, platform *bf.DictExpr 374 } 375 376 // extractPlatformStringsExprs matches an expression and attempts to extract 377 // sub-expressions in platformStringsExprs. The sub-expressions can then be 378 // merged with corresponding sub-expressions. Any field in the returned 379 // structure may be nil. An error is returned if the given expression does 380 // not follow the pattern described by platformStringsExprs. 381 func extractPlatformStringsExprs(expr bf.Expr) (platformStringsExprs, error) { 382 var ps platformStringsExprs 383 if expr == nil { 384 return ps, nil 385 } 386 387 // Break the expression into a sequence of expressions combined with +. 388 var parts []bf.Expr 389 for { 390 binop, ok := expr.(*bf.BinaryExpr) 391 if !ok { 392 parts = append(parts, expr) 393 break 394 } 395 parts = append(parts, binop.Y) 396 expr = binop.X 397 } 398 399 // Process each part. They may be in any order. 400 for _, part := range parts { 401 switch part := part.(type) { 402 case *bf.ListExpr: 403 if ps.generic != nil { 404 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: multiple list expressions") 405 } 406 ps.generic = part 407 408 case *bf.CallExpr: 409 x, ok := part.X.(*bf.LiteralExpr) 410 if !ok || x.Token != "select" || len(part.List) != 1 { 411 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: callee other than select or wrong number of args") 412 } 413 arg, ok := part.List[0].(*bf.DictExpr) 414 if !ok { 415 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: select argument not dict") 416 } 417 var dict **bf.DictExpr 418 for _, item := range arg.List { 419 kv := item.(*bf.KeyValueExpr) // parser guarantees this 420 k, ok := kv.Key.(*bf.StringExpr) 421 if !ok { 422 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict keys are not all strings") 423 } 424 if k.Value == "//conditions:default" { 425 continue 426 } 427 key, err := label.Parse(k.Value) 428 if err != nil { 429 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict key is not label: %q", k.Value) 430 } 431 if config.KnownOSSet[key.Name] { 432 dict = &ps.os 433 break 434 } 435 if config.KnownArchSet[key.Name] { 436 dict = &ps.arch 437 break 438 } 439 osArch := strings.Split(key.Name, "_") 440 if len(osArch) != 2 || !config.KnownOSSet[osArch[0]] || !config.KnownArchSet[osArch[1]] { 441 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict key contains unknown platform: %q", k.Value) 442 } 443 dict = &ps.platform 444 break 445 } 446 if dict == nil { 447 // We could not identify the dict because it's empty or only contains 448 // //conditions:default. We'll call it the platform dict to avoid 449 // dropping it. 450 dict = &ps.platform 451 } 452 if *dict != nil { 453 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: multiple selects that are either os-specific, arch-specific, or platform-specific") 454 } 455 *dict = arg 456 } 457 } 458 return ps, nil 459 } 460 461 // makePlatformStringsExpr constructs a single expression from the 462 // sub-expressions in ps. 463 func makePlatformStringsExpr(ps platformStringsExprs) bf.Expr { 464 makeSelect := func(dict *bf.DictExpr) bf.Expr { 465 return &bf.CallExpr{ 466 X: &bf.LiteralExpr{Token: "select"}, 467 List: []bf.Expr{dict}, 468 } 469 } 470 forceMultiline := func(e bf.Expr) { 471 switch e := e.(type) { 472 case *bf.ListExpr: 473 e.ForceMultiLine = true 474 case *bf.CallExpr: 475 e.List[0].(*bf.DictExpr).ForceMultiLine = true 476 } 477 } 478 479 var parts []bf.Expr 480 if ps.generic != nil { 481 parts = append(parts, ps.generic) 482 } 483 if ps.os != nil { 484 parts = append(parts, makeSelect(ps.os)) 485 } 486 if ps.arch != nil { 487 parts = append(parts, makeSelect(ps.arch)) 488 } 489 if ps.platform != nil { 490 parts = append(parts, makeSelect(ps.platform)) 491 } 492 493 if len(parts) == 0 { 494 return nil 495 } 496 if len(parts) == 1 { 497 return parts[0] 498 } 499 expr := parts[0] 500 forceMultiline(expr) 501 for _, part := range parts[1:] { 502 forceMultiline(part) 503 expr = &bf.BinaryExpr{ 504 Op: "+", 505 X: expr, 506 Y: part, 507 } 508 } 509 return expr 510 } 511 512 func mergePlatformStringsExprs(gen, old platformStringsExprs) (platformStringsExprs, error) { 513 var ps platformStringsExprs 514 var err error 515 ps.generic = mergeList(gen.generic, old.generic) 516 if ps.os, err = mergeDict(gen.os, old.os); err != nil { 517 return platformStringsExprs{}, err 518 } 519 if ps.arch, err = mergeDict(gen.arch, old.arch); err != nil { 520 return platformStringsExprs{}, err 521 } 522 if ps.platform, err = mergeDict(gen.platform, old.platform); err != nil { 523 return platformStringsExprs{}, err 524 } 525 return ps, nil 526 } 527 528 func mergeList(gen, old *bf.ListExpr) *bf.ListExpr { 529 if old == nil { 530 return gen 531 } 532 if gen == nil { 533 gen = &bf.ListExpr{List: []bf.Expr{}} 534 } 535 536 // Build a list of strings from the gen list and keep matching strings 537 // in the old list. This preserves comments. Also keep anything with 538 // a "# keep" comment, whether or not it's in the gen list. 539 genSet := make(map[string]bool) 540 for _, v := range gen.List { 541 if s := stringValue(v); s != "" { 542 genSet[s] = true 543 } 544 } 545 546 var merged []bf.Expr 547 kept := make(map[string]bool) 548 keepComment := false 549 for _, v := range old.List { 550 s := stringValue(v) 551 if keep := shouldKeep(v); keep || genSet[s] { 552 keepComment = keepComment || keep 553 merged = append(merged, v) 554 if s != "" { 555 kept[s] = true 556 } 557 } 558 } 559 560 // Add anything in the gen list that wasn't kept. 561 for _, v := range gen.List { 562 if s := stringValue(v); kept[s] { 563 continue 564 } 565 merged = append(merged, v) 566 } 567 568 if len(merged) == 0 { 569 return nil 570 } 571 return &bf.ListExpr{ 572 List: merged, 573 ForceMultiLine: gen.ForceMultiLine || old.ForceMultiLine || keepComment, 574 } 575 } 576 577 func mergeDict(gen, old *bf.DictExpr) (*bf.DictExpr, error) { 578 if old == nil { 579 return gen, nil 580 } 581 if gen == nil { 582 gen = &bf.DictExpr{List: []bf.Expr{}} 583 } 584 585 var entries []*dictEntry 586 entryMap := make(map[string]*dictEntry) 587 588 for _, kv := range old.List { 589 k, v, err := dictEntryKeyValue(kv) 590 if err != nil { 591 return nil, err 592 } 593 if _, ok := entryMap[k]; ok { 594 return nil, fmt.Errorf("old dict contains more than one case named %q", k) 595 } 596 e := &dictEntry{key: k, oldValue: v} 597 entries = append(entries, e) 598 entryMap[k] = e 599 } 600 601 for _, kv := range gen.List { 602 k, v, err := dictEntryKeyValue(kv) 603 if err != nil { 604 return nil, err 605 } 606 e, ok := entryMap[k] 607 if !ok { 608 e = &dictEntry{key: k} 609 entries = append(entries, e) 610 entryMap[k] = e 611 } 612 e.genValue = v 613 } 614 615 keys := make([]string, 0, len(entries)) 616 haveDefault := false 617 for _, e := range entries { 618 e.mergedValue = mergeList(e.genValue, e.oldValue) 619 if e.key == "//conditions:default" { 620 // Keep the default case, even if it's empty. 621 haveDefault = true 622 if e.mergedValue == nil { 623 e.mergedValue = &bf.ListExpr{} 624 } 625 } else if e.mergedValue != nil { 626 keys = append(keys, e.key) 627 } 628 } 629 if len(keys) == 0 && (!haveDefault || len(entryMap["//conditions:default"].mergedValue.List) == 0) { 630 return nil, nil 631 } 632 sort.Strings(keys) 633 // Always put the default case last. 634 if haveDefault { 635 keys = append(keys, "//conditions:default") 636 } 637 638 mergedEntries := make([]bf.Expr, len(keys)) 639 for i, k := range keys { 640 e := entryMap[k] 641 mergedEntries[i] = &bf.KeyValueExpr{ 642 Key: &bf.StringExpr{Value: e.key}, 643 Value: e.mergedValue, 644 } 645 } 646 647 return &bf.DictExpr{List: mergedEntries, ForceMultiLine: true}, nil 648 } 649 650 type dictEntry struct { 651 key string 652 oldValue, genValue, mergedValue *bf.ListExpr 653 } 654 655 func dictEntryKeyValue(e bf.Expr) (string, *bf.ListExpr, error) { 656 kv, ok := e.(*bf.KeyValueExpr) 657 if !ok { 658 return "", nil, fmt.Errorf("dict entry was not a key-value pair: %#v", e) 659 } 660 k, ok := kv.Key.(*bf.StringExpr) 661 if !ok { 662 return "", nil, fmt.Errorf("dict key was not string: %#v", kv.Key) 663 } 664 v, ok := kv.Value.(*bf.ListExpr) 665 if !ok { 666 return "", nil, fmt.Errorf("dict value was not list: %#v", kv.Value) 667 } 668 return k.Value, v, nil 669 } 670 671 // substituteAttrs contains a list of attributes for each kind that should be 672 // processed by substituteRule and substituteExpr. Note that "name" does not 673 // need to be substituted since it's not mergeable. 674 var substituteAttrs = map[string][]string{ 675 "go_binary": {"embed"}, 676 "go_library": {"embed"}, 677 "go_test": {"embed"}, 678 "go_proto_library": {"proto"}, 679 } 680 681 // substituteRule replaces local labels (those beginning with ":", referring to 682 // targets in the same package) according to a substitution map. This is used 683 // to update generated rules before merging when the corresponding existing 684 // rules have different names. If substituteRule replaces a string, it returns 685 // a new expression; it will not modify the original expression. 686 func substituteRule(call *bf.CallExpr, substitutions map[string]string) *bf.CallExpr { 687 rule := bf.Rule{Call: call} 688 attrs, ok := substituteAttrs[rule.Kind()] 689 if !ok { 690 return call 691 } 692 693 didCopy := false 694 for i, arg := range call.List { 695 kv, ok := arg.(*bf.BinaryExpr) 696 if !ok || kv.Op != "=" { 697 continue 698 } 699 key, ok := kv.X.(*bf.LiteralExpr) 700 shouldRename := false 701 for _, k := range attrs { 702 shouldRename = shouldRename || key.Token == k 703 } 704 if !shouldRename { 705 continue 706 } 707 708 value := substituteExpr(kv.Y, substitutions) 709 if value != kv.Y { 710 if !didCopy { 711 didCopy = true 712 callCopy := *call 713 callCopy.List = append([]bf.Expr{}, call.List...) 714 call = &callCopy 715 } 716 kvCopy := *kv 717 kvCopy.Y = value 718 call.List[i] = &kvCopy 719 } 720 } 721 return call 722 } 723 724 // substituteExpr replaces local labels according to a substitution map. 725 // It only supports string and list expressions (which should be sufficient 726 // for generated rules). If it replaces a string, it returns a new expression; 727 // otherwise, it returns e. 728 func substituteExpr(e bf.Expr, substitutions map[string]string) bf.Expr { 729 switch e := e.(type) { 730 case *bf.StringExpr: 731 if rename, ok := substitutions[strings.TrimPrefix(e.Value, ":")]; ok { 732 return &bf.StringExpr{Value: ":" + rename} 733 } 734 case *bf.ListExpr: 735 var listCopy *bf.ListExpr 736 for i, elem := range e.List { 737 renamed := substituteExpr(elem, substitutions) 738 if renamed != elem { 739 if listCopy == nil { 740 listCopy = new(bf.ListExpr) 741 *listCopy = *e 742 listCopy.List = append([]bf.Expr{}, e.List...) 743 } 744 listCopy.List[i] = renamed 745 } 746 } 747 if listCopy != nil { 748 return listCopy 749 } 750 } 751 return e 752 } 753 754 // shouldKeep returns whether an expression from the original file should be 755 // preserved. This is true if it has a prefix or end-of-line comment "keep". 756 // Note that bf.Rewrite recognizes "keep sorted" comments which are different, 757 // so we don't recognize comments that only start with "keep". 758 func shouldKeep(e bf.Expr) bool { 759 for _, c := range append(e.Comment().Before, e.Comment().Suffix...) { 760 text := strings.TrimSpace(strings.TrimPrefix(c.Token, "#")) 761 if text == keep { 762 return true 763 } 764 } 765 return false 766 } 767 768 // matchAttrs contains lists of attributes for each kind that are used in 769 // matching. For example, importpath attributes can be used to match go_library 770 // rules, even when the names are different. 771 var matchAttrs = map[string][]string{ 772 "go_library": {"importpath"}, 773 "go_proto_library": {"importpath"}, 774 "go_repository": {"importpath"}, 775 } 776 777 // matchAny is a set of kinds which may be matched regardless of attributes. 778 // For example, if there is only one go_binary in a package, any go_binary 779 // rule will match. 780 var matchAny = map[string]bool{"go_binary": true} 781 782 // match searches for a rule that can be merged with x in stmts. 783 // 784 // A rule is considered a match if its kind is equal to x's kind AND either its 785 // name is equal OR at least one of the attributes in matchAttrs is equal. 786 // 787 // If there are no matches, nil, -1, and nil are returned. 788 // 789 // If a rule has the same name but a different kind, nil, -1, and an error 790 // are returned. 791 // 792 // If there is exactly one match, the rule, its index in stmts, and nil 793 // are returned. 794 // 795 // If there are multiple matches, match will attempt to disambiguate, based on 796 // the quality of the match (name match is best, then attribute match in the 797 // order that attributes are listed). If disambiguation is successful, 798 // the rule, its index in stmts, and nil are returned. Otherwise, nil, -1, 799 // and an error are returned. 800 func match(stmts []bf.Expr, x *bf.CallExpr) (*bf.CallExpr, int, error) { 801 type matchInfo struct { 802 rule bf.Rule 803 index int 804 } 805 806 xr := bf.Rule{Call: x} 807 xname := xr.Name() 808 xkind := xr.Kind() 809 var nameMatches []matchInfo 810 var kindMatches []matchInfo 811 for i, s := range stmts { 812 y, ok := s.(*bf.CallExpr) 813 if !ok { 814 continue 815 } 816 yr := bf.Rule{Call: y} 817 if xname == yr.Name() { 818 nameMatches = append(nameMatches, matchInfo{yr, i}) 819 } 820 if xkind == yr.Kind() { 821 kindMatches = append(kindMatches, matchInfo{yr, i}) 822 } 823 } 824 825 if len(nameMatches) == 1 { 826 if ykind := nameMatches[0].rule.Kind(); xkind != ykind { 827 return nil, -1, fmt.Errorf("could not merge %s(%s): a rule of the same name has kind %s", xkind, xname, ykind) 828 } 829 return nameMatches[0].rule.Call, nameMatches[0].index, nil 830 } 831 if len(nameMatches) > 1 { 832 return nil, -1, fmt.Errorf("could not merge %s(%s): multiple rules have the same name", xname) 833 } 834 835 attrs := matchAttrs[xr.Kind()] 836 for _, key := range attrs { 837 var attrMatches []matchInfo 838 xvalue := xr.AttrString(key) 839 if xvalue == "" { 840 continue 841 } 842 for _, m := range kindMatches { 843 if xvalue == m.rule.AttrString(key) { 844 attrMatches = append(attrMatches, m) 845 } 846 } 847 if len(attrMatches) == 1 { 848 return attrMatches[0].rule.Call, attrMatches[0].index, nil 849 } else if len(attrMatches) > 1 { 850 return nil, -1, fmt.Errorf("could not merge %s(%s): multiple rules have the same attribute %s = %q", xkind, xname, key, xvalue) 851 } 852 } 853 854 if matchAny[xkind] { 855 if len(kindMatches) == 1 { 856 return kindMatches[0].rule.Call, kindMatches[0].index, nil 857 } else if len(kindMatches) > 1 { 858 return nil, -1, fmt.Errorf("could not merge %s(%s): multiple rules have the same kind but different names", xkind, xname) 859 } 860 } 861 862 return nil, -1, nil 863 } 864 865 func kind(c *bf.CallExpr) string { 866 return (&bf.Rule{Call: c}).Kind() 867 } 868 869 func name(c *bf.CallExpr) string { 870 return (&bf.Rule{Call: c}).Name() 871 } 872 873 // isRuleEmpty returns true if a rule cannot be built because it has no sources, 874 // dependencies, or embeds after merging. This is based on a per-kind whitelist 875 // of attributes. Other attributes, like "name" and "visibility" don't affect 876 // emptiness. Always returns false for expressions that aren't in the known 877 // set of rules. 878 func isRuleEmpty(e bf.Expr) bool { 879 c, ok := e.(*bf.CallExpr) 880 if !ok { 881 return false 882 } 883 r := bf.Rule{Call: c} 884 kind := r.Kind() 885 if nonEmptyAttrs[kind] == nil { 886 return false 887 } 888 for _, attr := range r.AttrKeys() { 889 if nonEmptyAttrs[kind][attr] { 890 return false 891 } 892 } 893 return true 894 } 895 896 func isScalar(e bf.Expr) bool { 897 switch e.(type) { 898 case *bf.StringExpr, *bf.LiteralExpr: 899 return true 900 default: 901 return false 902 } 903 } 904 905 func stringValue(e bf.Expr) string { 906 s, ok := e.(*bf.StringExpr) 907 if !ok { 908 return "" 909 } 910 return s.Value 911 } 912 913 // deleteIndices copies a list, dropping elements at deletedIndices. 914 // deletedIndices must be sorted. 915 func deleteIndices(stmt []bf.Expr, deletedIndices []int) []bf.Expr { 916 kept := make([]bf.Expr, 0, len(stmt)-len(deletedIndices)) 917 di := 0 918 for i, s := range stmt { 919 if di < len(deletedIndices) && i == deletedIndices[di] { 920 di++ 921 continue 922 } 923 kept = append(kept, s) 924 } 925 return kept 926 }