github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/internal/merger/fix.go (about) 1 /* Copyright 2017 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 17 18 import ( 19 "log" 20 "sort" 21 22 "github.com/bazelbuild/bazel-gazelle/internal/config" 23 bf "github.com/bazelbuild/buildtools/build" 24 ) 25 26 // Much of this file could be simplified by using 27 // github.com/bazelbuild/buildtools/edit. However, through a transitive 28 // dependency, that library depends on a proto in Bazel itself, which is 29 // a 95MB download. Not worth it. 30 31 // FixFile updates rules in oldFile that were generated by an older version of 32 // Gazelle to a newer form that can be merged with freshly generated rules. 33 // 34 // FixLoads should be called after this, since it will fix load 35 // statements that may be broken by transformations applied by this function. 36 func FixFile(c *config.Config, oldFile *bf.File) *bf.File { 37 fixedFile := squashCgoLibrary(oldFile) 38 return removeLegacyProto(c, fixedFile) 39 } 40 41 // squashCgoLibrary removes cgo_library rules with the default name and 42 // merges their attributes with go_library with the default name. If no 43 // go_library rule exists, a new one will be created. 44 // 45 // Note that the library attribute is disregarded, so cgo_library and 46 // go_library attributes will be squashed even if the cgo_library was unlinked. 47 // MergeWithExisting will remove unused values and attributes later. 48 func squashCgoLibrary(oldFile *bf.File) *bf.File { 49 // Find the default cgo_library and go_library rules. 50 var cgoLibrary, goLibrary bf.Rule 51 cgoLibraryIndex := -1 52 goLibraryIndex := -1 53 54 for i, stmt := range oldFile.Stmt { 55 c, ok := stmt.(*bf.CallExpr) 56 if !ok { 57 continue 58 } 59 r := bf.Rule{Call: c} 60 if r.Kind() == "cgo_library" && r.Name() == config.DefaultCgoLibName && !shouldKeep(c) { 61 if cgoLibrary.Call != nil { 62 log.Printf("%s: when fixing existing file, multiple cgo_library rules with default name found", oldFile.Path) 63 continue 64 } 65 cgoLibrary = r 66 cgoLibraryIndex = i 67 continue 68 } 69 if r.Kind() == "go_library" && r.Name() == config.DefaultLibName { 70 if goLibrary.Call != nil { 71 log.Printf("%s: when fixing existing file, multiple go_library rules with default name referencing cgo_library found", oldFile.Path) 72 continue 73 } 74 goLibrary = r 75 goLibraryIndex = i 76 } 77 } 78 79 if cgoLibrary.Call == nil { 80 return oldFile 81 } 82 83 // If go_library has a '# keep' comment, just delete cgo_library. 84 if goLibrary.Call != nil && shouldKeep(goLibrary.Call) { 85 fixedFile := *oldFile 86 fixedFile.Stmt = append(fixedFile.Stmt[:cgoLibraryIndex], fixedFile.Stmt[cgoLibraryIndex+1:]...) 87 return &fixedFile 88 } 89 90 // Copy the comments and attributes from cgo_library into go_library. If no 91 // go_library exists, create an empty one. 92 var fixedGoLibraryExpr bf.CallExpr 93 fixedGoLibrary := bf.Rule{Call: &fixedGoLibraryExpr} 94 if goLibrary.Call == nil { 95 fixedGoLibrary.SetKind("go_library") 96 fixedGoLibrary.SetAttr("name", &bf.StringExpr{Value: config.DefaultLibName}) 97 if vis := cgoLibrary.Attr("visibility"); vis != nil { 98 fixedGoLibrary.SetAttr("visibility", vis) 99 } 100 } else { 101 fixedGoLibraryExpr = *goLibrary.Call 102 fixedGoLibraryExpr.List = append([]bf.Expr{}, goLibrary.Call.List...) 103 } 104 105 fixedGoLibrary.DelAttr("embed") 106 fixedGoLibrary.SetAttr("cgo", &bf.LiteralExpr{Token: "True"}) 107 108 fixedGoLibraryExpr.Comments.Before = append(fixedGoLibraryExpr.Comments.Before, cgoLibrary.Call.Comments.Before...) 109 fixedGoLibraryExpr.Comments.Suffix = append(fixedGoLibraryExpr.Comments.Suffix, cgoLibrary.Call.Comments.Suffix...) 110 fixedGoLibraryExpr.Comments.After = append(fixedGoLibraryExpr.Comments.After, cgoLibrary.Call.Comments.After...) 111 112 for _, key := range []string{"cdeps", "clinkopts", "copts", "data", "deps", "gc_goopts", "srcs"} { 113 goLibraryAttr := fixedGoLibrary.Attr(key) 114 cgoLibraryAttr := cgoLibrary.Attr(key) 115 if cgoLibraryAttr == nil { 116 continue 117 } 118 if fixedAttr, err := squashExpr(goLibraryAttr, cgoLibraryAttr); err == nil { 119 fixedGoLibrary.SetAttr(key, fixedAttr) 120 } 121 } 122 123 // Rebuild the file with the cgo_library removed and the go_library replaced. 124 // If the go_library didn't already exist, it will replace cgo_library. 125 fixedFile := *oldFile 126 if goLibrary.Call == nil { 127 fixedFile.Stmt = append([]bf.Expr{}, oldFile.Stmt...) 128 fixedFile.Stmt[cgoLibraryIndex] = &fixedGoLibraryExpr 129 } else { 130 fixedFile.Stmt = append(oldFile.Stmt[:cgoLibraryIndex], oldFile.Stmt[cgoLibraryIndex+1:]...) 131 if goLibraryIndex > cgoLibraryIndex { 132 goLibraryIndex-- 133 } 134 fixedFile.Stmt[goLibraryIndex] = &fixedGoLibraryExpr 135 } 136 return &fixedFile 137 } 138 139 // squashExpr combines two expressions. Unlike mergeExpr, squashExpr does not 140 // discard information from an "old" expression. It does not sort or de-duplicate 141 // elements. Any non-scalar expressions that mergeExpr understands can be 142 // squashed. 143 func squashExpr(x, y bf.Expr) (bf.Expr, error) { 144 xExprs, err := extractPlatformStringsExprs(x) 145 if err != nil { 146 return nil, err 147 } 148 yExprs, err := extractPlatformStringsExprs(y) 149 if err != nil { 150 return nil, err 151 } 152 squashedExprs, err := squashPlatformStringsExprs(xExprs, yExprs) 153 if err != nil { 154 return nil, err 155 } 156 return makePlatformStringsExpr(squashedExprs), nil 157 } 158 159 func squashPlatformStringsExprs(x, y platformStringsExprs) (platformStringsExprs, error) { 160 var ps platformStringsExprs 161 var err error 162 ps.generic = squashList(x.generic, y.generic) 163 if ps.os, err = squashDict(x.os, y.os); err != nil { 164 return platformStringsExprs{}, err 165 } 166 if ps.arch, err = squashDict(x.arch, y.arch); err != nil { 167 return platformStringsExprs{}, err 168 } 169 if ps.platform, err = squashDict(x.platform, y.platform); err != nil { 170 return platformStringsExprs{}, err 171 } 172 return ps, nil 173 } 174 175 func squashList(x, y *bf.ListExpr) *bf.ListExpr { 176 if x == nil { 177 return y 178 } 179 if y == nil { 180 return x 181 } 182 squashed := *x 183 squashed.Comments.Before = append(x.Comments.Before, y.Comments.Before...) 184 squashed.Comments.Suffix = append(x.Comments.Suffix, y.Comments.Suffix...) 185 squashed.Comments.After = append(x.Comments.After, y.Comments.After...) 186 squashed.List = append(x.List, y.List...) 187 return &squashed 188 } 189 190 func squashDict(x, y *bf.DictExpr) (*bf.DictExpr, error) { 191 if x == nil { 192 return y, nil 193 } 194 if y == nil { 195 return x, nil 196 } 197 198 squashed := *x 199 squashed.Comments.Before = append(x.Comments.Before, y.Comments.Before...) 200 squashed.Comments.Suffix = append(x.Comments.Suffix, y.Comments.Suffix...) 201 squashed.Comments.After = append(x.Comments.After, y.Comments.After...) 202 203 xCaseIndex := make(map[string]int) 204 for i, e := range x.List { 205 kv, ok := e.(*bf.KeyValueExpr) 206 if !ok { 207 continue 208 } 209 key, ok := kv.Key.(*bf.StringExpr) 210 if !ok { 211 continue 212 } 213 xCaseIndex[key.Value] = i 214 } 215 216 for _, e := range y.List { 217 kv, ok := e.(*bf.KeyValueExpr) 218 if !ok { 219 squashed.List = append(squashed.List, e) 220 continue 221 } 222 key, ok := e.(*bf.StringExpr) 223 if !ok { 224 squashed.List = append(squashed.List, e) 225 continue 226 } 227 i, ok := xCaseIndex[key.Value] 228 if !ok { 229 squashed.List = append(squashed.List, e) 230 continue 231 } 232 squashedElem, err := squashExpr(x.List[i], kv.Value) 233 if err != nil { 234 return nil, err 235 } 236 x.List[i] = squashedElem 237 } 238 239 return &squashed, nil 240 } 241 242 // removeLegacyProto removes uses of the old proto rules. It deletes loads 243 // from go_proto_library.bzl. It deletes proto filegroups. It removes 244 // go_proto_library attributes which are no longer recognized. New rules 245 // are generated in place of the deleted rules, but attributes and comments 246 // are not migrated. 247 func removeLegacyProto(c *config.Config, oldFile *bf.File) *bf.File { 248 // Don't fix if the proto mode was set to something other than the default. 249 if c.ProtoMode != config.DefaultProtoMode { 250 return oldFile 251 } 252 253 // Scan for definitions to delete. 254 var deletedIndices []int 255 var protoIndices []int 256 shouldDeleteProtos := false 257 for i, stmt := range oldFile.Stmt { 258 c, ok := stmt.(*bf.CallExpr) 259 if !ok { 260 continue 261 } 262 x, ok := c.X.(*bf.LiteralExpr) 263 if !ok { 264 continue 265 } 266 267 if x.Token == "load" && len(c.List) > 0 { 268 if name, ok := c.List[0].(*bf.StringExpr); ok && name.Value == "@io_bazel_rules_go//proto:go_proto_library.bzl" { 269 deletedIndices = append(deletedIndices, i) 270 shouldDeleteProtos = true 271 } 272 continue 273 } 274 if x.Token == "filegroup" { 275 r := bf.Rule{Call: c} 276 if r.Name() == config.DefaultProtosName { 277 deletedIndices = append(deletedIndices, i) 278 } 279 continue 280 } 281 if x.Token == "go_proto_library" { 282 protoIndices = append(protoIndices, i) 283 } 284 } 285 if len(deletedIndices) == 0 { 286 return oldFile 287 } 288 289 // Rebuild the file without deleted statements. Only delete go_proto_library 290 // rules if we deleted a load. 291 if shouldDeleteProtos { 292 deletedIndices = append(deletedIndices, protoIndices...) 293 sort.Ints(deletedIndices) 294 } 295 fixedFile := *oldFile 296 fixedFile.Stmt = deleteIndices(oldFile.Stmt, deletedIndices) 297 return &fixedFile 298 } 299 300 // FixFileMinor updates rules in oldFile that were generated by an older version 301 // of Gazelle to a newer form that can be merged with freshly generated rules. 302 // 303 // FixFileMinor includes only small, low-risk fixes that can be applied in 304 // update mode. When both FixFileMinor and FixFile are called, FixFileMinor 305 // should be called first. 306 // 307 // FixLoads should be called after this, since it will fix load 308 // statements that may be broken by transformations applied by this function. 309 func FixFileMinor(c *config.Config, oldFile *bf.File) *bf.File { 310 fixedFile := migrateLibraryEmbed(c, oldFile) 311 fixedFile = migrateGrpcCompilers(c, fixedFile) 312 return removeBinaryImportPath(c, fixedFile) 313 } 314 315 // migrateLibraryEmbed converts "library" attributes to "embed" attributes, 316 // preserving comments. This only applies to Go rules, and only if there is 317 // no keep comment on "library" and no existing "embed" attribute. 318 func migrateLibraryEmbed(c *config.Config, oldFile *bf.File) *bf.File { 319 fixed := false 320 fixedFile := *oldFile 321 for i, stmt := range fixedFile.Stmt { 322 call, ok := stmt.(*bf.CallExpr) 323 if !ok { 324 continue 325 } 326 rule := bf.Rule{Call: call} 327 if kind := rule.Kind(); !isGoRule(kind) || shouldKeep(stmt) { 328 continue 329 } 330 libExpr := rule.Attr("library") 331 if libExpr == nil || shouldKeep(libExpr) || rule.Attr("embed") != nil { 332 continue 333 } 334 335 fixedCall := *call 336 rule.Call = &fixedCall 337 rule.DelAttr("library") 338 rule.SetAttr("embed", &bf.ListExpr{List: []bf.Expr{libExpr}}) 339 fixedFile.Stmt[i] = &fixedCall 340 fixed = true 341 } 342 if !fixed { 343 return oldFile 344 } 345 return &fixedFile 346 } 347 348 // migrateGrpcCompilers converts "go_grpc_library" rules into "go_proto_library" 349 // rules with a "compilers" attribute. 350 func migrateGrpcCompilers(c *config.Config, oldFile *bf.File) *bf.File { 351 fixed := false 352 fixedFile := *oldFile 353 for i, stmt := range fixedFile.Stmt { 354 call, ok := stmt.(*bf.CallExpr) 355 if !ok { 356 continue 357 } 358 rule := bf.Rule{Call: call} 359 if rule.Kind() != "go_grpc_library" || shouldKeep(stmt) || rule.Attr("compilers") != nil { 360 continue 361 } 362 363 fixedCall := *call 364 fixedCall.List = make([]bf.Expr, len(call.List)) 365 copy(fixedCall.List, call.List) 366 rule.Call = &fixedCall 367 rule.SetKind("go_proto_library") 368 rule.SetAttr("compilers", &bf.ListExpr{ 369 List: []bf.Expr{&bf.StringExpr{Value: config.GrpcCompilerLabel}}, 370 }) 371 fixedFile.Stmt[i] = &fixedCall 372 fixed = true 373 } 374 if !fixed { 375 return oldFile 376 } 377 return &fixedFile 378 } 379 380 // removeBinaryImportPath removes "importpath" attributes from "go_binary" 381 // and "go_test" rules. These are now deprecated. 382 func removeBinaryImportPath(c *config.Config, oldFile *bf.File) *bf.File { 383 fixed := false 384 fixedFile := *oldFile 385 for i, stmt := range fixedFile.Stmt { 386 call, ok := stmt.(*bf.CallExpr) 387 if !ok { 388 continue 389 } 390 rule := bf.Rule{Call: call} 391 if rule.Kind() != "go_binary" && rule.Kind() != "go_test" || rule.Attr("importpath") == nil { 392 continue 393 } 394 395 fixedCall := *call 396 fixedCall.List = make([]bf.Expr, len(call.List)) 397 copy(fixedCall.List, call.List) 398 rule.Call = &fixedCall 399 rule.DelAttr("importpath") 400 fixedFile.Stmt[i] = &fixedCall 401 fixed = true 402 } 403 if !fixed { 404 return oldFile 405 } 406 return &fixedFile 407 } 408 409 // FixLoads removes loads of unused go rules and adds loads of newly used rules. 410 // This should be called after FixFile and MergeWithExisting, since symbols 411 // may be introduced that aren't loaded. 412 func FixLoads(oldFile *bf.File) *bf.File { 413 // Make a list of load statements in the file. Keep track of loads of known 414 // files, since these may be changed. Keep track of known symbols loaded from 415 // unknown files; we will not add loads for these. 416 type loadInfo struct { 417 index int 418 file string 419 old, fixed *bf.CallExpr 420 } 421 var loads []loadInfo 422 otherLoadedKinds := make(map[string]bool) 423 for i, stmt := range oldFile.Stmt { 424 c, ok := stmt.(*bf.CallExpr) 425 if !ok { 426 continue 427 } 428 x, ok := c.X.(*bf.LiteralExpr) 429 if !ok || x.Token != "load" { 430 continue 431 } 432 433 if len(c.List) == 0 { 434 continue 435 } 436 label, ok := c.List[0].(*bf.StringExpr) 437 if !ok { 438 continue 439 } 440 441 if knownFiles[label.Value] { 442 loads = append(loads, loadInfo{index: i, file: label.Value, old: c}) 443 continue 444 } 445 for _, arg := range c.List[1:] { 446 switch sym := arg.(type) { 447 case *bf.StringExpr: 448 otherLoadedKinds[sym.Value] = true 449 case *bf.BinaryExpr: 450 if sym.Op != "=" { 451 continue 452 } 453 if x, ok := sym.X.(*bf.LiteralExpr); ok { 454 otherLoadedKinds[x.Token] = true 455 } 456 } 457 } 458 } 459 460 // Make a map of all the symbols from known files used in this file. 461 usedKinds := make(map[string]map[string]bool) 462 for _, stmt := range oldFile.Stmt { 463 c, ok := stmt.(*bf.CallExpr) 464 if !ok { 465 continue 466 } 467 x, ok := c.X.(*bf.LiteralExpr) 468 if !ok { 469 continue 470 } 471 472 kind := x.Token 473 if file, ok := knownKinds[kind]; ok && !otherLoadedKinds[kind] { 474 if usedKinds[file] == nil { 475 usedKinds[file] = make(map[string]bool) 476 } 477 usedKinds[file][kind] = true 478 } 479 } 480 481 // Fix the load statements. The order is important, so we iterate over 482 // knownLoads instead of knownFiles. 483 changed := false 484 var newFirstLoads []*bf.CallExpr 485 for _, l := range knownLoads { 486 file := l.file 487 first := true 488 for i, _ := range loads { 489 li := &loads[i] 490 if li.file != file { 491 continue 492 } 493 if first { 494 li.fixed = fixLoad(li.old, file, usedKinds[file]) 495 first = false 496 } else { 497 li.fixed = fixLoad(li.old, file, nil) 498 } 499 changed = changed || li.fixed != li.old 500 } 501 if first { 502 load := fixLoad(nil, file, usedKinds[file]) 503 if load != nil { 504 newFirstLoads = append(newFirstLoads, load) 505 changed = true 506 } 507 } 508 } 509 if !changed { 510 return oldFile 511 } 512 513 // Rebuild the file. 514 fixedFile := *oldFile 515 fixedFile.Stmt = make([]bf.Expr, 0, len(oldFile.Stmt)+len(newFirstLoads)) 516 for _, l := range newFirstLoads { 517 fixedFile.Stmt = append(fixedFile.Stmt, l) 518 } 519 loadIndex := 0 520 for i, stmt := range oldFile.Stmt { 521 if loadIndex < len(loads) && i == loads[loadIndex].index { 522 if loads[loadIndex].fixed != nil { 523 fixedFile.Stmt = append(fixedFile.Stmt, loads[loadIndex].fixed) 524 } 525 loadIndex++ 526 continue 527 } 528 fixedFile.Stmt = append(fixedFile.Stmt, stmt) 529 } 530 return &fixedFile 531 } 532 533 // knownLoads is a list of files Gazelle will generate loads from and 534 // the symbols it knows about. All symbols Gazelle ever generated 535 // loads for are present, including symbols it no longer uses (e.g., 536 // cgo_library). Manually loaded symbols (e.g., go_embed_data) are not 537 // included. The order of the files here will match the order of 538 // generated load statements. The symbols should be sorted 539 // lexicographically. 540 var knownLoads = []struct { 541 file string 542 kinds []string 543 }{ 544 { 545 "@io_bazel_rules_go//go:def.bzl", 546 []string{ 547 "cgo_library", 548 "go_binary", 549 "go_library", 550 "go_prefix", 551 "go_repository", 552 "go_test", 553 }, 554 }, { 555 "@io_bazel_rules_go//proto:def.bzl", 556 []string{ 557 "go_grpc_library", 558 "go_proto_library", 559 }, 560 }, 561 } 562 563 // knownFiles is the set of labels for files that Gazelle loads symbols from. 564 var knownFiles map[string]bool 565 566 // knownKinds is a map from symbols to labels of the files they are loaded 567 // from. 568 var knownKinds map[string]string 569 570 func init() { 571 knownFiles = make(map[string]bool) 572 knownKinds = make(map[string]string) 573 for _, l := range knownLoads { 574 knownFiles[l.file] = true 575 for _, k := range l.kinds { 576 knownKinds[k] = l.file 577 } 578 } 579 } 580 581 // fixLoad updates a load statement. load must be a load statement for 582 // the Go rules or nil. If nil, a new statement may be created. Symbols in 583 // kinds are added if they are not already present, symbols in knownKinds 584 // are removed if they are not in kinds, and other symbols and arguments 585 // are preserved. nil is returned if the statement should be deleted because 586 // it is empty. 587 func fixLoad(load *bf.CallExpr, file string, kinds map[string]bool) *bf.CallExpr { 588 var fixed bf.CallExpr 589 if load == nil { 590 fixed = bf.CallExpr{ 591 X: &bf.LiteralExpr{Token: "load"}, 592 List: []bf.Expr{ 593 &bf.StringExpr{Value: file}, 594 }, 595 ForceCompact: true, 596 } 597 } else { 598 fixed = *load 599 } 600 601 var symbols []*bf.StringExpr 602 var otherArgs []bf.Expr 603 loadedKinds := make(map[string]bool) 604 var added, removed int 605 for _, arg := range fixed.List[1:] { 606 if s, ok := arg.(*bf.StringExpr); ok { 607 if knownKinds[s.Value] == "" || kinds != nil && kinds[s.Value] { 608 symbols = append(symbols, s) 609 loadedKinds[s.Value] = true 610 } else { 611 removed++ 612 } 613 } else { 614 otherArgs = append(otherArgs, arg) 615 } 616 } 617 if kinds != nil { 618 for kind, _ := range kinds { 619 if _, ok := loadedKinds[kind]; !ok { 620 symbols = append(symbols, &bf.StringExpr{Value: kind}) 621 added++ 622 } 623 } 624 } 625 if added == 0 && removed == 0 { 626 if load != nil && len(load.List) == 1 { 627 // Special case: delete existing empty load. 628 return nil 629 } 630 return load 631 } 632 633 sort.Stable(byString(symbols)) 634 fixed.List = fixed.List[:1] 635 for _, sym := range symbols { 636 fixed.List = append(fixed.List, sym) 637 } 638 fixed.List = append(fixed.List, otherArgs...) 639 if len(fixed.List) == 1 { 640 return nil 641 } 642 return &fixed 643 } 644 645 type byString []*bf.StringExpr 646 647 func (s byString) Len() int { 648 return len(s) 649 } 650 651 func (s byString) Less(i, j int) bool { 652 return s[i].Value < s[j].Value 653 } 654 655 func (s byString) Swap(i, j int) { 656 s[i], s[j] = s[j], s[i] 657 } 658 659 func isGoRule(kind string) bool { 660 return kind == "go_library" || 661 kind == "go_binary" || 662 kind == "go_test" || 663 kind == "go_proto_library" || 664 kind == "go_grpc_library" 665 }