merger.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /* Copyright 2016 The Bazel Authors. All rights reserved.
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. // Package merger provides methods for merging parsed BUILD files.
  13. package merger
  14. import (
  15. "fmt"
  16. "strings"
  17. "github.com/bazelbuild/bazel-gazelle/internal/rule"
  18. )
  19. // Phase indicates which attributes should be merged in matching rules.
  20. //
  21. // The pre-resolve merge is performed before rules are indexed for dependency
  22. // resolution. All attributes not related to dependencies are merged. This
  23. // merge must be performed indexing because attributes related to indexing
  24. // (e.g., srcs, importpath) will be affected.
  25. //
  26. // The post-resolve merge is performed after rules are indexed. All attributes
  27. // related to dependencies are merged.
  28. type Phase int
  29. const (
  30. PreResolve Phase = iota
  31. PostResolve
  32. )
  33. // MergeFile merges the rules in genRules with matching rules in f and
  34. // adds unmatched rules to the end of the merged file. MergeFile also merges
  35. // rules in empty with matching rules in f and deletes rules that
  36. // are empty after merging. attrs is the set of attributes to merge. Attributes
  37. // not in this set will be left alone if they already exist.
  38. func MergeFile(oldFile *rule.File, emptyRules, genRules []*rule.Rule, phase Phase, kinds map[string]rule.KindInfo) {
  39. getMergeAttrs := func(r *rule.Rule) map[string]bool {
  40. if phase == PreResolve {
  41. return kinds[r.Kind()].MergeableAttrs
  42. } else {
  43. return kinds[r.Kind()].ResolveAttrs
  44. }
  45. }
  46. // Merge empty rules into the file and delete any rules which become empty.
  47. for _, emptyRule := range emptyRules {
  48. if oldRule, _ := match(oldFile.Rules, emptyRule, kinds[emptyRule.Kind()]); oldRule != nil {
  49. if oldRule.ShouldKeep() {
  50. continue
  51. }
  52. rule.MergeRules(emptyRule, oldRule, getMergeAttrs(emptyRule), oldFile.Path)
  53. if oldRule.IsEmpty(kinds[oldRule.Kind()]) {
  54. oldRule.Delete()
  55. }
  56. }
  57. }
  58. oldFile.Sync()
  59. // Match generated rules with existing rules in the file. Keep track of
  60. // rules with non-standard names.
  61. matchRules := make([]*rule.Rule, len(genRules))
  62. matchErrors := make([]error, len(genRules))
  63. substitutions := make(map[string]string)
  64. for i, genRule := range genRules {
  65. oldRule, err := match(oldFile.Rules, genRule, kinds[genRule.Kind()])
  66. if err != nil {
  67. // TODO(jayconrod): add a verbose mode and log errors. They are too chatty
  68. // to print by default.
  69. matchErrors[i] = err
  70. continue
  71. }
  72. matchRules[i] = oldRule
  73. if oldRule != nil {
  74. if oldRule.Name() != genRule.Name() {
  75. substitutions[genRule.Name()] = oldRule.Name()
  76. }
  77. }
  78. }
  79. // Rename labels in generated rules that refer to other generated rules.
  80. if len(substitutions) > 0 {
  81. for _, genRule := range genRules {
  82. substituteRule(genRule, substitutions, kinds[genRule.Kind()])
  83. }
  84. }
  85. // Merge generated rules with existing rules or append to the end of the file.
  86. for i, genRule := range genRules {
  87. if matchErrors[i] != nil {
  88. continue
  89. }
  90. if matchRules[i] == nil {
  91. genRule.Insert(oldFile)
  92. } else {
  93. rule.MergeRules(genRule, matchRules[i], getMergeAttrs(genRule), oldFile.Path)
  94. }
  95. }
  96. }
  97. // substituteRule replaces local labels (those beginning with ":", referring to
  98. // targets in the same package) according to a substitution map. This is used
  99. // to update generated rules before merging when the corresponding existing
  100. // rules have different names. If substituteRule replaces a string, it returns
  101. // a new expression; it will not modify the original expression.
  102. func substituteRule(r *rule.Rule, substitutions map[string]string, info rule.KindInfo) {
  103. for attr := range info.SubstituteAttrs {
  104. if expr := r.Attr(attr); expr != nil {
  105. expr = rule.MapExprStrings(expr, func(s string) string {
  106. if rename, ok := substitutions[strings.TrimPrefix(s, ":")]; ok {
  107. return ":" + rename
  108. } else {
  109. return s
  110. }
  111. })
  112. r.SetAttr(attr, expr)
  113. }
  114. }
  115. }
  116. // match searches for a rule that can be merged with x in rules.
  117. //
  118. // A rule is considered a match if its kind is equal to x's kind AND either its
  119. // name is equal OR at least one of the attributes in matchAttrs is equal.
  120. //
  121. // If there are no matches, nil and nil are returned.
  122. //
  123. // If a rule has the same name but a different kind, nill and an error
  124. // are returned.
  125. //
  126. // If there is exactly one match, the rule and nil are returned.
  127. //
  128. // If there are multiple matches, match will attempt to disambiguate, based on
  129. // the quality of the match (name match is best, then attribute match in the
  130. // order that attributes are listed). If disambiguation is successful,
  131. // the rule and nil are returned. Otherwise, nil and an error are returned.
  132. func match(rules []*rule.Rule, x *rule.Rule, info rule.KindInfo) (*rule.Rule, error) {
  133. xname := x.Name()
  134. xkind := x.Kind()
  135. var nameMatches []*rule.Rule
  136. var kindMatches []*rule.Rule
  137. for _, y := range rules {
  138. if xname == y.Name() {
  139. nameMatches = append(nameMatches, y)
  140. }
  141. if xkind == y.Kind() {
  142. kindMatches = append(kindMatches, y)
  143. }
  144. }
  145. if len(nameMatches) == 1 {
  146. y := nameMatches[0]
  147. if xkind != y.Kind() {
  148. return nil, fmt.Errorf("could not merge %s(%s): a rule of the same name has kind %s", xkind, xname, y.Kind())
  149. }
  150. return y, nil
  151. }
  152. if len(nameMatches) > 1 {
  153. return nil, fmt.Errorf("could not merge %s(%s): multiple rules have the same name", xkind, xname)
  154. }
  155. for _, key := range info.MatchAttrs {
  156. var attrMatches []*rule.Rule
  157. xvalue := x.AttrString(key)
  158. if xvalue == "" {
  159. continue
  160. }
  161. for _, y := range kindMatches {
  162. if xvalue == y.AttrString(key) {
  163. attrMatches = append(attrMatches, y)
  164. }
  165. }
  166. if len(attrMatches) == 1 {
  167. return attrMatches[0], nil
  168. } else if len(attrMatches) > 1 {
  169. return nil, fmt.Errorf("could not merge %s(%s): multiple rules have the same attribute %s = %q", xkind, xname, key, xvalue)
  170. }
  171. }
  172. if info.MatchAny {
  173. if len(kindMatches) == 1 {
  174. return kindMatches[0], nil
  175. } else if len(kindMatches) > 1 {
  176. return nil, fmt.Errorf("could not merge %s(%s): multiple rules have the same kind but different names", xkind, xname)
  177. }
  178. }
  179. return nil, nil
  180. }