merger.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  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 functions for merging generated rules into
  13. // existing build files.
  14. //
  15. // Gazelle's normal workflow is roughly as follows:
  16. //
  17. // 1. Read metadata from sources.
  18. //
  19. // 2. Generate new rules.
  20. //
  21. // 3. Merge newly generated rules with rules in the existing build file
  22. // if there is one.
  23. //
  24. // 4. Build an index of merged library rules for dependency resolution.
  25. //
  26. // 5. Resolve dependencies (i.e., convert import strings to deps labels).
  27. //
  28. // 6. Merge the newly resolved dependencies.
  29. //
  30. // 7. Write the merged file back to disk.
  31. //
  32. // This package is used for sets 3 and 6 above.
  33. package merger
  34. import (
  35. "fmt"
  36. "strings"
  37. "github.com/bazelbuild/bazel-gazelle/rule"
  38. )
  39. // Phase indicates which attributes should be merged in matching rules.
  40. type Phase int
  41. const (
  42. // The pre-resolve merge is performed before rules are indexed for dependency
  43. // resolution. All attributes not related to dependencies are merged
  44. // (i.e., rule.KindInfo.MergeableAttrs). This merge must be performed
  45. // before indexing because attributes related to indexing (e.g.,
  46. // srcs, importpath) will be affected.
  47. PreResolve Phase = iota
  48. // The post-resolve merge is performed after rules are indexed. All attributes
  49. // related to dependencies are merged (i.e., rule.KindInfo.ResolveAttrs).
  50. PostResolve
  51. )
  52. // MergeFile combines information from newly generated rules with matching
  53. // rules in an existing build file. MergeFile can also delete rules which
  54. // are empty after merging.
  55. //
  56. // oldFile is the file to merge. It must not be nil.
  57. //
  58. // emptyRules is a list of stub rules (with no attributes other than name)
  59. // which were not generated. These are merged with matching rules. The merged
  60. // rules are deleted if they contain no attributes that make them buildable
  61. // (e.g., srcs, deps, anything in rule.KindInfo.NonEmptyAttrs).
  62. //
  63. // genRules is a list of newly generated rules. These are merged with
  64. // matching rules. A rule matches if it has the same kind and name or if
  65. // some other attribute in rule.KindInfo.MatchAttrs matches (e.g.,
  66. // "importpath" in go_library). Elements of genRules that don't match
  67. // any existing rule are appended to the end of oldFile.
  68. //
  69. // phase indicates whether this is a pre- or post-resolve merge. Different
  70. // attributes (rule.KindInfo.MergeableAttrs or ResolveAttrs) will be merged.
  71. //
  72. // kinds maps rule kinds (e.g., "go_library") to metadata that helps merge
  73. // rules of that kind.
  74. //
  75. // When a generated and existing rule are merged, each attribute is merged
  76. // separately. If an attribute is mergeable (according to KindInfo), values
  77. // from the existing attribute are replaced by values from the generated
  78. // attribute. Comments are preserved on values that are present in both
  79. // versions of the attribute. If at attribute is not mergeable, the generated
  80. // version of the attribute will be added if no existing attribute is present;
  81. // otherwise, the existing attribute will be preserved.
  82. //
  83. // Note that "# keep" comments affect merging. If a value within an existing
  84. // attribute is marked with a "# keep" comment, it will not be removed.
  85. // If an attribute is marked with a "# keep" comment, it will not be merged.
  86. // If a rule is marked with a "# keep" comment, the whole rule will not
  87. // be modified.
  88. func MergeFile(oldFile *rule.File, emptyRules, genRules []*rule.Rule, phase Phase, kinds map[string]rule.KindInfo) {
  89. getMergeAttrs := func(r *rule.Rule) map[string]bool {
  90. if phase == PreResolve {
  91. return kinds[r.Kind()].MergeableAttrs
  92. } else {
  93. return kinds[r.Kind()].ResolveAttrs
  94. }
  95. }
  96. // Merge empty rules into the file and delete any rules which become empty.
  97. for _, emptyRule := range emptyRules {
  98. if oldRule, _ := Match(oldFile.Rules, emptyRule, kinds[emptyRule.Kind()]); oldRule != nil {
  99. if oldRule.ShouldKeep() {
  100. continue
  101. }
  102. rule.MergeRules(emptyRule, oldRule, getMergeAttrs(emptyRule), oldFile.Path)
  103. if oldRule.IsEmpty(kinds[oldRule.Kind()]) {
  104. oldRule.Delete()
  105. }
  106. }
  107. }
  108. oldFile.Sync()
  109. // Match generated rules with existing rules in the file. Keep track of
  110. // rules with non-standard names.
  111. matchRules := make([]*rule.Rule, len(genRules))
  112. matchErrors := make([]error, len(genRules))
  113. substitutions := make(map[string]string)
  114. for i, genRule := range genRules {
  115. oldRule, err := Match(oldFile.Rules, genRule, kinds[genRule.Kind()])
  116. if err != nil {
  117. // TODO(jayconrod): add a verbose mode and log errors. They are too chatty
  118. // to print by default.
  119. matchErrors[i] = err
  120. continue
  121. }
  122. matchRules[i] = oldRule
  123. if oldRule != nil {
  124. if oldRule.Name() != genRule.Name() {
  125. substitutions[genRule.Name()] = oldRule.Name()
  126. }
  127. }
  128. }
  129. // Rename labels in generated rules that refer to other generated rules.
  130. if len(substitutions) > 0 {
  131. for _, genRule := range genRules {
  132. substituteRule(genRule, substitutions, kinds[genRule.Kind()])
  133. }
  134. }
  135. // Merge generated rules with existing rules or append to the end of the file.
  136. for i, genRule := range genRules {
  137. if matchErrors[i] != nil {
  138. continue
  139. }
  140. if matchRules[i] == nil {
  141. genRule.Insert(oldFile)
  142. } else {
  143. rule.MergeRules(genRule, matchRules[i], getMergeAttrs(genRule), oldFile.Path)
  144. }
  145. }
  146. }
  147. // substituteRule replaces local labels (those beginning with ":", referring to
  148. // targets in the same package) according to a substitution map. This is used
  149. // to update generated rules before merging when the corresponding existing
  150. // rules have different names. If substituteRule replaces a string, it returns
  151. // a new expression; it will not modify the original expression.
  152. func substituteRule(r *rule.Rule, substitutions map[string]string, info rule.KindInfo) {
  153. for attr := range info.SubstituteAttrs {
  154. if expr := r.Attr(attr); expr != nil {
  155. expr = rule.MapExprStrings(expr, func(s string) string {
  156. if rename, ok := substitutions[strings.TrimPrefix(s, ":")]; ok {
  157. return ":" + rename
  158. } else {
  159. return s
  160. }
  161. })
  162. r.SetAttr(attr, expr)
  163. }
  164. }
  165. }
  166. // Match searches for a rule that can be merged with x in rules.
  167. //
  168. // A rule is considered a match if its kind is equal to x's kind AND either its
  169. // name is equal OR at least one of the attributes in matchAttrs is equal.
  170. //
  171. // If there are no matches, nil and nil are returned.
  172. //
  173. // If a rule has the same name but a different kind, nill and an error
  174. // are returned.
  175. //
  176. // If there is exactly one match, the rule and nil are returned.
  177. //
  178. // If there are multiple matches, match will attempt to disambiguate, based on
  179. // the quality of the match (name match is best, then attribute match in the
  180. // order that attributes are listed). If disambiguation is successful,
  181. // the rule and nil are returned. Otherwise, nil and an error are returned.
  182. func Match(rules []*rule.Rule, x *rule.Rule, info rule.KindInfo) (*rule.Rule, error) {
  183. xname := x.Name()
  184. xkind := x.Kind()
  185. var nameMatches []*rule.Rule
  186. var kindMatches []*rule.Rule
  187. for _, y := range rules {
  188. if xname == y.Name() {
  189. nameMatches = append(nameMatches, y)
  190. }
  191. if xkind == y.Kind() {
  192. kindMatches = append(kindMatches, y)
  193. }
  194. }
  195. if len(nameMatches) == 1 {
  196. y := nameMatches[0]
  197. if xkind != y.Kind() {
  198. return nil, fmt.Errorf("could not merge %s(%s): a rule of the same name has kind %s", xkind, xname, y.Kind())
  199. }
  200. return y, nil
  201. }
  202. if len(nameMatches) > 1 {
  203. return nil, fmt.Errorf("could not merge %s(%s): multiple rules have the same name", xkind, xname)
  204. }
  205. for _, key := range info.MatchAttrs {
  206. var attrMatches []*rule.Rule
  207. xvalue := x.AttrString(key)
  208. if xvalue == "" {
  209. continue
  210. }
  211. for _, y := range kindMatches {
  212. if xvalue == y.AttrString(key) {
  213. attrMatches = append(attrMatches, y)
  214. }
  215. }
  216. if len(attrMatches) == 1 {
  217. return attrMatches[0], nil
  218. } else if len(attrMatches) > 1 {
  219. return nil, fmt.Errorf("could not merge %s(%s): multiple rules have the same attribute %s = %q", xkind, xname, key, xvalue)
  220. }
  221. }
  222. if info.MatchAny {
  223. if len(kindMatches) == 1 {
  224. return kindMatches[0], nil
  225. } else if len(kindMatches) > 1 {
  226. return nil, fmt.Errorf("could not merge %s(%s): multiple rules have the same kind but different names", xkind, xname)
  227. }
  228. }
  229. return nil, nil
  230. }