github.com/wolfd/bazel-gazelle@v0.14.0/internal/rule/rule.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 provides tools for editing Bazel build files. It is intended to 17 // be a more powerful replacement for 18 // github.com/bazelbuild/buildtools/build.Rule, adapted for Gazelle's usage. It 19 // is language agnostic, but it may be used for language-specific rules by 20 // providing configuration. 21 // 22 // File is the primary interface to this package. Rule and Load are used to 23 // create, read, update, and delete rules. Once modifications are performed, 24 // File.Sync() may be called to write the changes back to the original AST, 25 // which may then be formatted and written back to a file. 26 package rule 27 28 import ( 29 "io/ioutil" 30 "os" 31 "path/filepath" 32 "sort" 33 "strings" 34 35 bzl "github.com/bazelbuild/buildtools/build" 36 bt "github.com/bazelbuild/buildtools/tables" 37 ) 38 39 // File provides editing functionality on top of a Skylark syntax tree. This 40 // is the primary interface Gazelle uses for reading and updating build files. 41 // To use, create a new file with EmptyFile or wrap a syntax tree with 42 // LoadFile. Perform edits on Loads and Rules, then call Sync() to write 43 // changes back to the AST. 44 type File struct { 45 // File is the underlying build file syntax tree. Some editing operations 46 // may modify this, but editing is not complete until Sync() is called. 47 File *bzl.File 48 49 // Pkg is the Bazel package this build file defines. 50 Pkg string 51 52 // Path is the file system path to the build file (same as File.Path). 53 Path string 54 55 // Directives is a list of configuration directives found in top-level 56 // comments in the file. This should not be modified after the file is read. 57 Directives []Directive 58 59 // Loads is a list of load statements within the file. This should not 60 // be modified directly; use Load methods instead. 61 Loads []*Load 62 63 // Rules is a list of rules within the file (or function calls that look like 64 // rules). This should not be modified directly; use Rule methods instead. 65 Rules []*Rule 66 } 67 68 // EmptyFile creates a File wrapped around an empty syntax tree. 69 func EmptyFile(path, pkg string) *File { 70 return &File{ 71 File: &bzl.File{Path: path}, 72 Path: path, 73 Pkg: pkg, 74 } 75 } 76 77 // LoadFile loads a build file from disk, parses it, and scans for rules and 78 // load statements. The syntax tree within the returned File will be modified 79 // by editing methods. 80 // 81 // This function returns I/O and parse errors without modification. It's safe 82 // to use os.IsNotExist and similar predicates. 83 func LoadFile(path, pkg string) (*File, error) { 84 data, err := ioutil.ReadFile(path) 85 if err != nil { 86 return nil, err 87 } 88 return LoadData(path, pkg, data) 89 } 90 91 // LoadData parses a build file from a byte slice and scans it for rules and 92 // load statements. The syntax tree within the returned File will be modified 93 // by editing methods. 94 func LoadData(path, pkg string, data []byte) (*File, error) { 95 ast, err := bzl.Parse(path, data) 96 if err != nil { 97 return nil, err 98 } 99 return ScanAST(pkg, ast), nil 100 } 101 102 // ScanAST creates a File wrapped around the given syntax tree. This tree 103 // will be modified by editing methods. 104 func ScanAST(pkg string, bzlFile *bzl.File) *File { 105 f := &File{ 106 File: bzlFile, 107 Pkg: pkg, 108 Path: bzlFile.Path, 109 } 110 for i, stmt := range f.File.Stmt { 111 call, ok := stmt.(*bzl.CallExpr) 112 if !ok { 113 continue 114 } 115 x, ok := call.X.(*bzl.LiteralExpr) 116 if !ok { 117 continue 118 } 119 if x.Token == "load" { 120 if l := loadFromExpr(i, call); l != nil { 121 f.Loads = append(f.Loads, l) 122 } 123 } else { 124 if r := ruleFromExpr(i, call); r != nil { 125 f.Rules = append(f.Rules, r) 126 } 127 } 128 } 129 f.Directives = ParseDirectives(bzlFile) 130 return f 131 } 132 133 // MatchBuildFileName looks for a file in files that has a name from names. 134 // If there is at least one matching file, a path will be returned by joining 135 // dir and the first matching name. If there are no matching files, the 136 // empty string is returned. 137 func MatchBuildFileName(dir string, names []string, files []os.FileInfo) string { 138 for _, name := range names { 139 for _, fi := range files { 140 if fi.Name() == name && !fi.IsDir() { 141 return filepath.Join(dir, name) 142 } 143 } 144 } 145 return "" 146 } 147 148 // Sync writes all changes back to the wrapped syntax tree. This should be 149 // called after editing operations, before reading the syntax tree again. 150 func (f *File) Sync() { 151 var inserts, deletes, stmts []*stmt 152 var r, w int 153 for r, w = 0, 0; r < len(f.Loads); r++ { 154 s := f.Loads[r] 155 s.sync() 156 if s.deleted { 157 deletes = append(deletes, &s.stmt) 158 continue 159 } 160 if s.inserted { 161 inserts = append(inserts, &s.stmt) 162 s.inserted = false 163 } else { 164 stmts = append(stmts, &s.stmt) 165 } 166 f.Loads[w] = s 167 w++ 168 } 169 f.Loads = f.Loads[:w] 170 for r, w = 0, 0; r < len(f.Rules); r++ { 171 s := f.Rules[r] 172 s.sync() 173 if s.deleted { 174 deletes = append(deletes, &s.stmt) 175 continue 176 } 177 if s.inserted { 178 inserts = append(inserts, &s.stmt) 179 s.inserted = false 180 } else { 181 stmts = append(stmts, &s.stmt) 182 } 183 f.Rules[w] = s 184 w++ 185 } 186 f.Rules = f.Rules[:w] 187 sort.Stable(byIndex(deletes)) 188 sort.Stable(byIndex(inserts)) 189 sort.Stable(byIndex(stmts)) 190 191 oldStmt := f.File.Stmt 192 f.File.Stmt = make([]bzl.Expr, 0, len(oldStmt)-len(deletes)+len(inserts)) 193 var ii, di, si int 194 for i, stmt := range oldStmt { 195 for ii < len(inserts) && inserts[ii].index == i { 196 inserts[ii].index = len(f.File.Stmt) 197 f.File.Stmt = append(f.File.Stmt, inserts[ii].call) 198 ii++ 199 } 200 if di < len(deletes) && deletes[di].index == i { 201 di++ 202 continue 203 } 204 if si < len(stmts) && stmts[si].call == stmt { 205 stmts[si].index = len(f.File.Stmt) 206 si++ 207 } 208 f.File.Stmt = append(f.File.Stmt, stmt) 209 } 210 for ii < len(inserts) { 211 inserts[ii].index = len(f.File.Stmt) 212 f.File.Stmt = append(f.File.Stmt, inserts[ii].call) 213 ii++ 214 } 215 } 216 217 // Format formats the build file in a form that can be written to disk. 218 // This method calls Sync internally. 219 func (f *File) Format() []byte { 220 f.Sync() 221 return bzl.Format(f.File) 222 } 223 224 // Save writes the build file to disk. This method calls Sync internally. 225 func (f *File) Save(path string) error { 226 f.Sync() 227 data := bzl.Format(f.File) 228 return ioutil.WriteFile(path, data, 0666) 229 } 230 231 type stmt struct { 232 index int 233 deleted, inserted, updated bool 234 call *bzl.CallExpr 235 } 236 237 // Index returns the index for this statement within the build file. For 238 // inserted rules, this is where the rule will be inserted (rules with the 239 // same index will be inserted in the order Insert was called). For existing 240 // rules, this is the index of the original statement. 241 func (s *stmt) Index() int { return s.index } 242 243 // Delete marks this statement for deletion. It will be removed from the 244 // syntax tree when File.Sync is called. 245 func (s *stmt) Delete() { s.deleted = true } 246 247 type byIndex []*stmt 248 249 func (s byIndex) Len() int { 250 return len(s) 251 } 252 253 func (s byIndex) Less(i, j int) bool { 254 return s[i].index < s[j].index 255 } 256 257 func (s byIndex) Swap(i, j int) { 258 s[i], s[j] = s[j], s[i] 259 } 260 261 // Load represents a load statement within a build file. 262 type Load struct { 263 stmt 264 name string 265 symbols map[string]bzl.Expr 266 } 267 268 // NewLoad creates a new, empty load statement for the given file name. 269 func NewLoad(name string) *Load { 270 return &Load{ 271 stmt: stmt{ 272 call: &bzl.CallExpr{ 273 X: &bzl.LiteralExpr{Token: "load"}, 274 List: []bzl.Expr{&bzl.StringExpr{Value: name}}, 275 ForceCompact: true, 276 }, 277 }, 278 name: name, 279 symbols: make(map[string]bzl.Expr), 280 } 281 } 282 283 func loadFromExpr(index int, call *bzl.CallExpr) *Load { 284 l := &Load{ 285 stmt: stmt{index: index, call: call}, 286 symbols: make(map[string]bzl.Expr), 287 } 288 if len(call.List) == 0 { 289 return nil 290 } 291 name, ok := call.List[0].(*bzl.StringExpr) 292 if !ok { 293 return nil 294 } 295 l.name = name.Value 296 for _, arg := range call.List[1:] { 297 switch arg := arg.(type) { 298 case *bzl.StringExpr: 299 l.symbols[arg.Value] = arg 300 case *bzl.BinaryExpr: 301 x, ok := arg.X.(*bzl.LiteralExpr) 302 if !ok { 303 return nil 304 } 305 if _, ok := arg.Y.(*bzl.StringExpr); !ok { 306 return nil 307 } 308 l.symbols[x.Token] = arg 309 default: 310 return nil 311 } 312 } 313 return l 314 } 315 316 // Name returns the name of the file this statement loads. 317 func (l *Load) Name() string { 318 return l.name 319 } 320 321 // Symbols returns a list of symbols this statement loads. 322 func (l *Load) Symbols() []string { 323 syms := make([]string, 0, len(l.symbols)) 324 for sym := range l.symbols { 325 syms = append(syms, sym) 326 } 327 sort.Strings(syms) 328 return syms 329 } 330 331 // Has returns true if sym is loaded by this statement. 332 func (l *Load) Has(sym string) bool { 333 _, ok := l.symbols[sym] 334 return ok 335 } 336 337 // Add inserts a new symbol into the load statement. This has no effect if 338 // the symbol is already loaded. Symbols will be sorted, so the order 339 // doesn't matter. 340 func (l *Load) Add(sym string) { 341 if _, ok := l.symbols[sym]; !ok { 342 l.symbols[sym] = &bzl.StringExpr{Value: sym} 343 l.updated = true 344 } 345 } 346 347 // Remove deletes a symbol from the load statement. This has no effect if 348 // the symbol is not loaded. 349 func (l *Load) Remove(sym string) { 350 if _, ok := l.symbols[sym]; ok { 351 delete(l.symbols, sym) 352 l.updated = true 353 } 354 } 355 356 // IsEmpty returns whether this statement loads any symbols. 357 func (l *Load) IsEmpty() bool { 358 return len(l.symbols) == 0 359 } 360 361 // Insert marks this statement for insertion at the given index. If multiple 362 // statements are inserted at the same index, they will be inserted in the 363 // order Insert is called. 364 func (l *Load) Insert(f *File, index int) { 365 l.index = index 366 l.inserted = true 367 f.Loads = append(f.Loads, l) 368 } 369 370 func (l *Load) sync() { 371 if !l.updated { 372 return 373 } 374 l.updated = false 375 376 args := make([]*bzl.StringExpr, 0, len(l.symbols)) 377 kwargs := make([]*bzl.BinaryExpr, 0, len(l.symbols)) 378 for _, e := range l.symbols { 379 if a, ok := e.(*bzl.StringExpr); ok { 380 args = append(args, a) 381 } else { 382 kwargs = append(kwargs, e.(*bzl.BinaryExpr)) 383 } 384 } 385 sort.Slice(args, func(i, j int) bool { 386 return args[i].Value < args[j].Value 387 }) 388 sort.Slice(kwargs, func(i, j int) bool { 389 return kwargs[i].X.(*bzl.StringExpr).Value < kwargs[j].Y.(*bzl.StringExpr).Value 390 }) 391 392 list := make([]bzl.Expr, 0, 1+len(l.symbols)) 393 list = append(list, l.call.List[0]) 394 for _, a := range args { 395 list = append(list, a) 396 } 397 for _, a := range kwargs { 398 list = append(list, a) 399 } 400 l.call.List = list 401 l.call.ForceCompact = len(kwargs) == 0 402 } 403 404 // Rule represents a rule statement within a build file. 405 type Rule struct { 406 stmt 407 kind string 408 args []bzl.Expr 409 attrs map[string]*bzl.BinaryExpr 410 private map[string]interface{} 411 } 412 413 // NewRule creates a new, empty rule with the given kind and name. 414 func NewRule(kind, name string) *Rule { 415 nameAttr := &bzl.BinaryExpr{ 416 X: &bzl.LiteralExpr{Token: "name"}, 417 Y: &bzl.StringExpr{Value: name}, 418 Op: "=", 419 } 420 r := &Rule{ 421 stmt: stmt{ 422 call: &bzl.CallExpr{ 423 X: &bzl.LiteralExpr{Token: kind}, 424 List: []bzl.Expr{nameAttr}, 425 }, 426 }, 427 kind: kind, 428 attrs: map[string]*bzl.BinaryExpr{"name": nameAttr}, 429 private: map[string]interface{}{}, 430 } 431 return r 432 } 433 434 func ruleFromExpr(index int, expr bzl.Expr) *Rule { 435 call, ok := expr.(*bzl.CallExpr) 436 if !ok { 437 return nil 438 } 439 x, ok := call.X.(*bzl.LiteralExpr) 440 if !ok { 441 return nil 442 } 443 kind := x.Token 444 var args []bzl.Expr 445 attrs := make(map[string]*bzl.BinaryExpr) 446 for _, arg := range call.List { 447 attr, ok := arg.(*bzl.BinaryExpr) 448 if ok && attr.Op == "=" { 449 key := attr.X.(*bzl.LiteralExpr) // required by parser 450 attrs[key.Token] = attr 451 } else { 452 args = append(args, arg) 453 } 454 } 455 return &Rule{ 456 stmt: stmt{ 457 index: index, 458 call: call, 459 }, 460 kind: kind, 461 args: args, 462 attrs: attrs, 463 private: map[string]interface{}{}, 464 } 465 } 466 467 // ShouldKeep returns whether the rule is marked with a "# keep" comment. Rules 468 // that are kept should not be modified. This does not check whether 469 // subexpressions within the rule should be kept. 470 func (r *Rule) ShouldKeep() bool { 471 return ShouldKeep(r.call) 472 } 473 474 func (r *Rule) Kind() string { 475 return r.kind 476 } 477 478 func (r *Rule) SetKind(kind string) { 479 r.kind = kind 480 r.updated = true 481 } 482 483 func (r *Rule) Name() string { 484 return r.AttrString("name") 485 } 486 487 func (r *Rule) SetName(name string) { 488 r.SetAttr("name", name) 489 } 490 491 // AttrKeys returns a sorted list of attribute keys used in this rule. 492 func (r *Rule) AttrKeys() []string { 493 keys := make([]string, 0, len(r.attrs)) 494 for k := range r.attrs { 495 keys = append(keys, k) 496 } 497 sort.SliceStable(keys, func(i, j int) bool { 498 if cmp := bt.NamePriority[keys[i]] - bt.NamePriority[keys[j]]; cmp != 0 { 499 return cmp < 0 500 } 501 return keys[i] < keys[j] 502 }) 503 return keys 504 } 505 506 // Attr returns the value of the named attribute. nil is returned when the 507 // attribute is not set. 508 func (r *Rule) Attr(key string) bzl.Expr { 509 attr, ok := r.attrs[key] 510 if !ok { 511 return nil 512 } 513 return attr.Y 514 } 515 516 // AttrString returns the value of the named attribute if it is a scalar string. 517 // "" is returned if the attribute is not set or is not a string. 518 func (r *Rule) AttrString(key string) string { 519 attr, ok := r.attrs[key] 520 if !ok { 521 return "" 522 } 523 str, ok := attr.Y.(*bzl.StringExpr) 524 if !ok { 525 return "" 526 } 527 return str.Value 528 } 529 530 // AttrStrings returns the string values of an attribute if it is a list. 531 // nil is returned if the attribute is not set or is not a list. Non-string 532 // values within the list won't be returned. 533 func (r *Rule) AttrStrings(key string) []string { 534 attr, ok := r.attrs[key] 535 if !ok { 536 return nil 537 } 538 list, ok := attr.Y.(*bzl.ListExpr) 539 if !ok { 540 return nil 541 } 542 strs := make([]string, 0, len(list.List)) 543 for _, e := range list.List { 544 if str, ok := e.(*bzl.StringExpr); ok { 545 strs = append(strs, str.Value) 546 } 547 } 548 return strs 549 } 550 551 // DelAttr removes the named attribute from the rule. 552 func (r *Rule) DelAttr(key string) { 553 delete(r.attrs, key) 554 r.updated = true 555 } 556 557 // SetAttr adds or replaces the named attribute with an expression produced 558 // by ExprFromValue. 559 func (r *Rule) SetAttr(key string, value interface{}) { 560 y := ExprFromValue(value) 561 if attr, ok := r.attrs[key]; ok { 562 attr.Y = y 563 } else { 564 r.attrs[key] = &bzl.BinaryExpr{ 565 X: &bzl.LiteralExpr{Token: key}, 566 Y: y, 567 Op: "=", 568 } 569 } 570 r.updated = true 571 } 572 573 // PrivateAttrKeys returns a sorted list of private attribute names. 574 func (r *Rule) PrivateAttrKeys() []string { 575 keys := make([]string, 0, len(r.private)) 576 for k := range r.private { 577 keys = append(keys, k) 578 } 579 sort.Strings(keys) 580 return keys 581 } 582 583 // PrivateAttr return the private value associated with a key. 584 func (r *Rule) PrivateAttr(key string) interface{} { 585 return r.private[key] 586 } 587 588 // SetPrivateAttr associates a value with a key. Unlike SetAttr, this value 589 // is not converted to a build syntax tree and will not be written to a build 590 // file. 591 func (r *Rule) SetPrivateAttr(key string, value interface{}) { 592 r.private[key] = value 593 } 594 595 // Args returns positional arguments passed to a rule. 596 func (r *Rule) Args() []bzl.Expr { 597 return r.args 598 } 599 600 // Insert marks this statement for insertion at the end of the file. Multiple 601 // statements will be inserted in the order Insert is called. 602 func (r *Rule) Insert(f *File) { 603 // TODO(jayconrod): should rules always be inserted at the end? Should there 604 // be some sort order? 605 r.index = len(f.File.Stmt) 606 r.inserted = true 607 f.Rules = append(f.Rules, r) 608 } 609 610 // IsEmpty returns true when the rule contains none of the attributes in attrs 611 // for its kind. attrs should contain attributes that make the rule buildable 612 // like srcs or deps and not descriptive attributes like name or visibility. 613 func (r *Rule) IsEmpty(info KindInfo) bool { 614 if info.NonEmptyAttrs == nil { 615 return false 616 } 617 for k := range info.NonEmptyAttrs { 618 if _, ok := r.attrs[k]; ok { 619 return false 620 } 621 } 622 return true 623 } 624 625 func (r *Rule) IsEmptyOld(attrs map[string]bool) bool { 626 if attrs == nil { 627 return false 628 } 629 for k := range attrs { 630 if _, ok := r.attrs[k]; ok { 631 return false 632 } 633 } 634 return true 635 } 636 637 func (r *Rule) sync() { 638 if !r.updated { 639 return 640 } 641 r.updated = false 642 643 for _, k := range []string{"srcs", "deps"} { 644 if attr, ok := r.attrs[k]; ok { 645 bzl.Walk(attr.Y, sortExprLabels) 646 } 647 } 648 649 call := r.call 650 call.X.(*bzl.LiteralExpr).Token = r.kind 651 652 list := make([]bzl.Expr, 0, len(r.args)+len(r.attrs)) 653 list = append(list, r.args...) 654 for _, attr := range r.attrs { 655 list = append(list, attr) 656 } 657 sortedAttrs := list[len(r.args):] 658 key := func(e bzl.Expr) string { return e.(*bzl.BinaryExpr).X.(*bzl.LiteralExpr).Token } 659 sort.SliceStable(sortedAttrs, func(i, j int) bool { 660 ki := key(sortedAttrs[i]) 661 kj := key(sortedAttrs[j]) 662 if cmp := bt.NamePriority[ki] - bt.NamePriority[kj]; cmp != 0 { 663 return cmp < 0 664 } 665 return ki < kj 666 }) 667 668 r.call.List = list 669 r.updated = false 670 } 671 672 // ShouldKeep returns whether e is marked with a "# keep" comment. Kept 673 // expressions should not be removed or modified. 674 func ShouldKeep(e bzl.Expr) bool { 675 for _, c := range append(e.Comment().Before, e.Comment().Suffix...) { 676 text := strings.TrimSpace(strings.TrimPrefix(c.Token, "#")) 677 if text == "keep" { 678 return true 679 } 680 } 681 return false 682 } 683 684 type byAttrName []KeyValue 685 686 var _ sort.Interface = byAttrName{} 687 688 func (s byAttrName) Len() int { 689 return len(s) 690 } 691 692 func (s byAttrName) Less(i, j int) bool { 693 if cmp := bt.NamePriority[s[i].Key] - bt.NamePriority[s[j].Key]; cmp != 0 { 694 return cmp < 0 695 } 696 return s[i].Key < s[j].Key 697 } 698 699 func (s byAttrName) Swap(i, j int) { 700 s[i], s[j] = s[j], s[i] 701 }