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