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