github.com/wolfd/bazel-gazelle@v0.14.0/internal/rule/merge.go (about) 1 /* Copyright 2018 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 rule 17 18 import ( 19 "errors" 20 "fmt" 21 "log" 22 "sort" 23 24 bzl "github.com/bazelbuild/buildtools/build" 25 ) 26 27 // MergeRules copies information from src into dst, usually discarding 28 // information in dst when they have the same attributes. 29 // 30 // If dst is marked with a "# keep" comment, either above the rule or as 31 // a suffix, nothing will be changed. 32 // 33 // If src has an attribute that is not in dst, it will be copied into dst. 34 // 35 // If src and dst have the same attribute and the attribute is mergeable and the 36 // attribute in dst is not marked with a "# keep" comment, values in the dst 37 // attribute not marked with a "# keep" comment will be dropped, and values from 38 // src will be copied in. 39 // 40 // If dst has an attribute not in src, and the attribute is mergeable and not 41 // marked with a "# keep" comment, values in the attribute not marked with 42 // a "# keep" comment will be dropped. If the attribute is empty afterward, 43 // it will be deleted. 44 func MergeRules(src, dst *Rule, mergeable map[string]bool, filename string) { 45 if dst.ShouldKeep() { 46 return 47 } 48 49 // Process attributes that are in dst but not in src. 50 for key, dstAttr := range dst.attrs { 51 if _, ok := src.attrs[key]; ok || !mergeable[key] || ShouldKeep(dstAttr) { 52 continue 53 } 54 dstValue := dstAttr.Y 55 if mergedValue, err := mergeExprs(nil, dstValue); err != nil { 56 start, end := dstValue.Span() 57 log.Printf("%s:%d.%d-%d.%d: could not merge expression", filename, start.Line, start.LineRune, end.Line, end.LineRune) 58 } else if mergedValue == nil { 59 dst.DelAttr(key) 60 } else { 61 dst.SetAttr(key, mergedValue) 62 } 63 } 64 65 // Merge attributes from src into dst. 66 for key, srcAttr := range src.attrs { 67 srcValue := srcAttr.Y 68 if dstAttr, ok := dst.attrs[key]; !ok { 69 dst.SetAttr(key, srcValue) 70 } else if mergeable[key] && !ShouldKeep(dstAttr) { 71 dstValue := dstAttr.Y 72 if mergedValue, err := mergeExprs(srcValue, dstValue); err != nil { 73 start, end := dstValue.Span() 74 log.Printf("%s:%d.%d-%d.%d: could not merge expression", filename, start.Line, start.LineRune, end.Line, end.LineRune) 75 } else { 76 dst.SetAttr(key, mergedValue) 77 } 78 } 79 } 80 } 81 82 // mergeExprs combines information from src and dst and returns a merged 83 // expression. dst may be modified during this process. The returned expression 84 // may be different from dst when a structural change is needed. 85 // 86 // The following kinds of expressions are recognized. 87 // 88 // * nil 89 // * strings (can only be merged with strings) 90 // * lists of strings 91 // * a call to select with a dict argument. The dict keys must be strings, 92 // and the values must be lists of strings. 93 // * a list of strings combined with a select call using +. The list must 94 // be the left operand. 95 // 96 // An error is returned if the expressions can't be merged, for example 97 // because they are not in one of the above formats. 98 func mergeExprs(src, dst bzl.Expr) (bzl.Expr, error) { 99 if ShouldKeep(dst) { 100 return nil, nil 101 } 102 if src == nil && (dst == nil || isScalar(dst)) { 103 return nil, nil 104 } 105 if isScalar(src) { 106 return src, nil 107 } 108 109 srcExprs, err := extractPlatformStringsExprs(src) 110 if err != nil { 111 return nil, err 112 } 113 dstExprs, err := extractPlatformStringsExprs(dst) 114 if err != nil { 115 return nil, err 116 } 117 mergedExprs, err := mergePlatformStringsExprs(srcExprs, dstExprs) 118 if err != nil { 119 return nil, err 120 } 121 return makePlatformStringsExpr(mergedExprs), nil 122 } 123 124 func mergePlatformStringsExprs(src, dst platformStringsExprs) (platformStringsExprs, error) { 125 var ps platformStringsExprs 126 var err error 127 ps.generic = mergeList(src.generic, dst.generic) 128 if ps.os, err = mergeDict(src.os, dst.os); err != nil { 129 return platformStringsExprs{}, err 130 } 131 if ps.arch, err = mergeDict(src.arch, dst.arch); err != nil { 132 return platformStringsExprs{}, err 133 } 134 if ps.platform, err = mergeDict(src.platform, dst.platform); err != nil { 135 return platformStringsExprs{}, err 136 } 137 return ps, nil 138 } 139 140 func mergeList(src, dst *bzl.ListExpr) *bzl.ListExpr { 141 if dst == nil { 142 return src 143 } 144 if src == nil { 145 src = &bzl.ListExpr{List: []bzl.Expr{}} 146 } 147 148 // Build a list of strings from the src list and keep matching strings 149 // in the dst list. This preserves comments. Also keep anything with 150 // a "# keep" comment, whether or not it's in the src list. 151 srcSet := make(map[string]bool) 152 for _, v := range src.List { 153 if s := stringValue(v); s != "" { 154 srcSet[s] = true 155 } 156 } 157 158 var merged []bzl.Expr 159 kept := make(map[string]bool) 160 keepComment := false 161 for _, v := range dst.List { 162 s := stringValue(v) 163 if keep := ShouldKeep(v); keep || srcSet[s] { 164 keepComment = keepComment || keep 165 merged = append(merged, v) 166 if s != "" { 167 kept[s] = true 168 } 169 } 170 } 171 172 // Add anything in the src list that wasn't kept. 173 for _, v := range src.List { 174 if s := stringValue(v); kept[s] { 175 continue 176 } 177 merged = append(merged, v) 178 } 179 180 if len(merged) == 0 { 181 return nil 182 } 183 return &bzl.ListExpr{ 184 List: merged, 185 ForceMultiLine: src.ForceMultiLine || dst.ForceMultiLine || keepComment, 186 } 187 } 188 189 func mergeDict(src, dst *bzl.DictExpr) (*bzl.DictExpr, error) { 190 if dst == nil { 191 return src, nil 192 } 193 if src == nil { 194 src = &bzl.DictExpr{List: []bzl.Expr{}} 195 } 196 197 var entries []*dictEntry 198 entryMap := make(map[string]*dictEntry) 199 200 for _, kv := range dst.List { 201 k, v, err := dictEntryKeyValue(kv) 202 if err != nil { 203 return nil, err 204 } 205 if _, ok := entryMap[k]; ok { 206 return nil, fmt.Errorf("dst dict contains more than one case named %q", k) 207 } 208 e := &dictEntry{key: k, dstValue: v} 209 entries = append(entries, e) 210 entryMap[k] = e 211 } 212 213 for _, kv := range src.List { 214 k, v, err := dictEntryKeyValue(kv) 215 if err != nil { 216 return nil, err 217 } 218 e, ok := entryMap[k] 219 if !ok { 220 e = &dictEntry{key: k} 221 entries = append(entries, e) 222 entryMap[k] = e 223 } 224 e.srcValue = v 225 } 226 227 keys := make([]string, 0, len(entries)) 228 haveDefault := false 229 for _, e := range entries { 230 e.mergedValue = mergeList(e.srcValue, e.dstValue) 231 if e.key == "//conditions:default" { 232 // Keep the default case, even if it's empty. 233 haveDefault = true 234 if e.mergedValue == nil { 235 e.mergedValue = &bzl.ListExpr{} 236 } 237 } else if e.mergedValue != nil { 238 keys = append(keys, e.key) 239 } 240 } 241 if len(keys) == 0 && (!haveDefault || len(entryMap["//conditions:default"].mergedValue.List) == 0) { 242 return nil, nil 243 } 244 sort.Strings(keys) 245 // Always put the default case last. 246 if haveDefault { 247 keys = append(keys, "//conditions:default") 248 } 249 250 mergedEntries := make([]bzl.Expr, len(keys)) 251 for i, k := range keys { 252 e := entryMap[k] 253 mergedEntries[i] = &bzl.KeyValueExpr{ 254 Key: &bzl.StringExpr{Value: e.key}, 255 Value: e.mergedValue, 256 } 257 } 258 259 return &bzl.DictExpr{List: mergedEntries, ForceMultiLine: true}, nil 260 } 261 262 type dictEntry struct { 263 key string 264 dstValue, srcValue, mergedValue *bzl.ListExpr 265 } 266 267 // SquashRules copies information from src into dst without discarding 268 // information in dst. SquashRules detects duplicate elements in lists and 269 // dictionaries, but it doesn't sort elements after squashing. If squashing 270 // fails because the expression is not understood, an error is returned, 271 // and neither rule is modified. 272 func SquashRules(src, dst *Rule, filename string) error { 273 if dst.ShouldKeep() { 274 return nil 275 } 276 277 for key, srcAttr := range src.attrs { 278 srcValue := srcAttr.Y 279 if dstAttr, ok := dst.attrs[key]; !ok { 280 dst.SetAttr(key, srcValue) 281 } else if !ShouldKeep(dstAttr) { 282 dstValue := dstAttr.Y 283 if squashedValue, err := squashExprs(srcValue, dstValue); err != nil { 284 start, end := dstValue.Span() 285 return fmt.Errorf("%s:%d.%d-%d.%d: could not squash expression", filename, start.Line, start.LineRune, end.Line, end.LineRune) 286 } else { 287 dst.SetAttr(key, squashedValue) 288 } 289 } 290 } 291 dst.call.Comments.Before = append(dst.call.Comments.Before, src.call.Comments.Before...) 292 dst.call.Comments.Suffix = append(dst.call.Comments.Suffix, src.call.Comments.Suffix...) 293 dst.call.Comments.After = append(dst.call.Comments.After, src.call.Comments.After...) 294 return nil 295 } 296 297 func squashExprs(src, dst bzl.Expr) (bzl.Expr, error) { 298 if ShouldKeep(dst) { 299 return dst, nil 300 } 301 if isScalar(dst) { 302 // may lose src, but they should always be the same. 303 return dst, nil 304 } 305 srcExprs, err := extractPlatformStringsExprs(src) 306 if err != nil { 307 return nil, err 308 } 309 dstExprs, err := extractPlatformStringsExprs(dst) 310 if err != nil { 311 return nil, err 312 } 313 squashedExprs, err := squashPlatformStringsExprs(srcExprs, dstExprs) 314 if err != nil { 315 return nil, err 316 } 317 return makePlatformStringsExpr(squashedExprs), nil 318 } 319 320 func squashPlatformStringsExprs(x, y platformStringsExprs) (platformStringsExprs, error) { 321 var ps platformStringsExprs 322 var err error 323 if ps.generic, err = squashList(x.generic, y.generic); err != nil { 324 return platformStringsExprs{}, err 325 } 326 if ps.os, err = squashDict(x.os, y.os); err != nil { 327 return platformStringsExprs{}, err 328 } 329 if ps.arch, err = squashDict(x.arch, y.arch); err != nil { 330 return platformStringsExprs{}, err 331 } 332 if ps.platform, err = squashDict(x.platform, y.platform); err != nil { 333 return platformStringsExprs{}, err 334 } 335 return ps, nil 336 } 337 338 func squashList(x, y *bzl.ListExpr) (*bzl.ListExpr, error) { 339 if x == nil { 340 return y, nil 341 } 342 if y == nil { 343 return x, nil 344 } 345 346 ls := makeListSquasher() 347 for _, e := range x.List { 348 s, ok := e.(*bzl.StringExpr) 349 if !ok { 350 return nil, errors.New("could not squash non-string") 351 } 352 ls.add(s) 353 } 354 for _, e := range y.List { 355 s, ok := e.(*bzl.StringExpr) 356 if !ok { 357 return nil, errors.New("could not squash non-string") 358 } 359 ls.add(s) 360 } 361 squashed := ls.list() 362 squashed.Comments.Before = append(x.Comments.Before, y.Comments.Before...) 363 squashed.Comments.Suffix = append(x.Comments.Suffix, y.Comments.Suffix...) 364 squashed.Comments.After = append(x.Comments.After, y.Comments.After...) 365 return squashed, nil 366 } 367 368 func squashDict(x, y *bzl.DictExpr) (*bzl.DictExpr, error) { 369 if x == nil { 370 return y, nil 371 } 372 if y == nil { 373 return x, nil 374 } 375 376 cases := make(map[string]*bzl.KeyValueExpr) 377 addCase := func(e bzl.Expr) error { 378 kv := e.(*bzl.KeyValueExpr) 379 key, ok := kv.Key.(*bzl.StringExpr) 380 if !ok { 381 return errors.New("could not squash non-string dict key") 382 } 383 if _, ok := kv.Value.(*bzl.ListExpr); !ok { 384 return errors.New("could not squash non-list dict value") 385 } 386 if c, ok := cases[key.Value]; ok { 387 if sq, err := squashList(kv.Value.(*bzl.ListExpr), c.Value.(*bzl.ListExpr)); err != nil { 388 return err 389 } else { 390 c.Value = sq 391 } 392 } else { 393 kvCopy := *kv 394 cases[key.Value] = &kvCopy 395 } 396 return nil 397 } 398 399 for _, e := range x.List { 400 if err := addCase(e); err != nil { 401 return nil, err 402 } 403 } 404 for _, e := range y.List { 405 if err := addCase(e); err != nil { 406 return nil, err 407 } 408 } 409 410 keys := make([]string, 0, len(cases)) 411 haveDefault := false 412 for k := range cases { 413 if k == "//conditions:default" { 414 haveDefault = true 415 continue 416 } 417 keys = append(keys, k) 418 } 419 sort.Strings(keys) 420 if haveDefault { 421 keys = append(keys, "//conditions:default") // must be last 422 } 423 424 squashed := *x 425 squashed.Comments.Before = append(x.Comments.Before, y.Comments.Before...) 426 squashed.Comments.Suffix = append(x.Comments.Suffix, y.Comments.Suffix...) 427 squashed.Comments.After = append(x.Comments.After, y.Comments.After...) 428 squashed.List = make([]bzl.Expr, 0, len(cases)) 429 for _, k := range keys { 430 squashed.List = append(squashed.List, cases[k]) 431 } 432 return &squashed, nil 433 } 434 435 // listSquasher builds a sorted, deduplicated list of string expressions. If 436 // a string expression is added multiple times, comments are consolidated. 437 // The original expressions are not modified. 438 type listSquasher struct { 439 unique map[string]*bzl.StringExpr 440 seenComments map[elemComment]bool 441 } 442 443 type elemComment struct { 444 elem, com string 445 } 446 447 func makeListSquasher() listSquasher { 448 return listSquasher{ 449 unique: make(map[string]*bzl.StringExpr), 450 seenComments: make(map[elemComment]bool), 451 } 452 } 453 454 func (ls *listSquasher) add(s *bzl.StringExpr) { 455 sCopy, ok := ls.unique[s.Value] 456 if !ok { 457 // Make a copy of s. We may modify it when we consolidate comments from 458 // duplicate strings. We don't want to modify the original in case this 459 // function fails (due to a later failed pattern match). 460 sCopy = new(bzl.StringExpr) 461 *sCopy = *s 462 sCopy.Comments.Before = make([]bzl.Comment, 0, len(s.Comments.Before)) 463 sCopy.Comments.Suffix = make([]bzl.Comment, 0, len(s.Comments.Suffix)) 464 ls.unique[s.Value] = sCopy 465 } 466 for _, c := range s.Comment().Before { 467 if key := (elemComment{s.Value, c.Token}); !ls.seenComments[key] { 468 sCopy.Comments.Before = append(sCopy.Comments.Before, c) 469 ls.seenComments[key] = true 470 } 471 } 472 for _, c := range s.Comment().Suffix { 473 if key := (elemComment{s.Value, c.Token}); !ls.seenComments[key] { 474 sCopy.Comments.Suffix = append(sCopy.Comments.Suffix, c) 475 ls.seenComments[key] = true 476 } 477 } 478 } 479 480 func (ls *listSquasher) list() *bzl.ListExpr { 481 sortedExprs := make([]bzl.Expr, 0, len(ls.unique)) 482 for _, e := range ls.unique { 483 sortedExprs = append(sortedExprs, e) 484 } 485 sort.Slice(sortedExprs, func(i, j int) bool { 486 return sortedExprs[i].(*bzl.StringExpr).Value < sortedExprs[j].(*bzl.StringExpr).Value 487 }) 488 return &bzl.ListExpr{List: sortedExprs} 489 }