github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/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. A File represents an 23 // individual build file. It comprises a list of Rules and a list of Loads. 24 // Rules and Loads may be inserted, modified, or deleted. When all changes 25 // are done, File.Save() may be called to write changes back to a file. 26 package rule 27 28 import ( 29 "fmt" 30 "io/fs" 31 "os" 32 "path/filepath" 33 "sort" 34 "strings" 35 36 bzl "github.com/bazelbuild/buildtools/build" 37 bt "github.com/bazelbuild/buildtools/tables" 38 ) 39 40 // File provides editing functionality for a build file. You can create a 41 // new file with EmptyFile or load an existing file with LoadFile. After 42 // changes have been made, call Save to write changes back to a file. 43 type File struct { 44 // File is the underlying build file syntax tree. Some editing operations 45 // may modify this, but editing is not complete until Sync() is called. 46 File *bzl.File 47 48 // function is the underlying syntax tree of a bzl file function. 49 // This is used for editing the bzl file function specified by the 50 // update-repos -to_macro option. 51 function *function 52 53 // Pkg is the Bazel package this build file defines. 54 Pkg string 55 56 // Path is the file system path to the build file (same as File.Path). 57 Path string 58 59 // DefName is the name of the function definition this File refers to 60 // if loaded with LoadMacroFile or a similar function. Normally empty. 61 DefName string 62 63 // Directives is a list of configuration directives found in top-level 64 // comments in the file. This should not be modified after the file is read. 65 Directives []Directive 66 67 // Loads is a list of load statements within the file. This should not 68 // be modified directly; use Load methods instead. 69 Loads []*Load 70 71 // Rules is a list of rules within the file (or function calls that look like 72 // rules). This should not be modified directly; use Rule methods instead. 73 Rules []*Rule 74 75 // Content is the file's underlying disk content, which is recorded when the 76 // file is initially loaded and whenever it is saved back to disk. If the file 77 // is modified outside of Rule methods, Content must be manually updated in 78 // order to keep it in sync. 79 Content []byte 80 } 81 82 // EmptyFile creates a File wrapped around an empty syntax tree. 83 func EmptyFile(path, pkg string) *File { 84 return &File{ 85 File: &bzl.File{Path: path, Type: bzl.TypeBuild}, 86 Path: path, 87 Pkg: pkg, 88 } 89 } 90 91 // LoadFile loads a build file from disk, parses it, and scans for rules and 92 // load statements. The syntax tree within the returned File will be modified 93 // by editing methods. 94 // 95 // This function returns I/O and parse errors without modification. It's safe 96 // to use os.IsNotExist and similar predicates. 97 func LoadFile(path, pkg string) (*File, error) { 98 data, err := os.ReadFile(path) 99 if err != nil { 100 return nil, err 101 } 102 return LoadData(path, pkg, data) 103 } 104 105 // LoadWorkspaceFile is similar to LoadFile but parses the file as a WORKSPACE 106 // file. 107 func LoadWorkspaceFile(path, pkg string) (*File, error) { 108 data, err := os.ReadFile(path) 109 if err != nil { 110 return nil, err 111 } 112 return LoadWorkspaceData(path, pkg, data) 113 } 114 115 // LoadMacroFile loads a bzl file from disk, parses it, then scans for the load 116 // statements and the rules called from the given Starlark function. If there is 117 // no matching function name, then a new function with that name will be created. 118 // The function's syntax tree will be returned within File and can be modified by 119 // Sync and Save calls. 120 func LoadMacroFile(path, pkg, defName string) (*File, error) { 121 data, err := os.ReadFile(path) 122 if err != nil { 123 return nil, err 124 } 125 return LoadMacroData(path, pkg, defName, data) 126 } 127 128 // EmptyMacroFile creates a bzl file at the given path and within the file creates 129 // a Starlark function with the provided name. The function can then be modified 130 // by Sync and Save calls. 131 func EmptyMacroFile(path, pkg, defName string) (*File, error) { 132 _, err := os.Create(path) 133 if err != nil { 134 return nil, err 135 } 136 return LoadMacroData(path, pkg, defName, nil) 137 } 138 139 // LoadData parses a build file from a byte slice and scans it for rules and 140 // load statements. The syntax tree within the returned File will be modified 141 // by editing methods. 142 func LoadData(path, pkg string, data []byte) (*File, error) { 143 ast, err := bzl.ParseBuild(path, data) 144 if err != nil { 145 return nil, err 146 } 147 f := ScanAST(pkg, ast) 148 if err := checkFile(f); err != nil { 149 return nil, err 150 } 151 f.Content = data 152 return f, nil 153 } 154 155 // LoadWorkspaceData is similar to LoadData but parses the data as a 156 // WORKSPACE file. 157 func LoadWorkspaceData(path, pkg string, data []byte) (*File, error) { 158 ast, err := bzl.ParseWorkspace(path, data) 159 if err != nil { 160 return nil, err 161 } 162 f := ScanAST(pkg, ast) 163 if err := checkFile(f); err != nil { 164 return nil, err 165 } 166 f.Content = data 167 return f, nil 168 } 169 170 // LoadMacroData parses a bzl file from a byte slice and scans for the load 171 // statements and the rules called from the given Starlark function. If there is 172 // no matching function name, then a new function will be created, and added to the 173 // File the next time Sync is called. The function's syntax tree will be returned 174 // within File and can be modified by Sync and Save calls. 175 func LoadMacroData(path, pkg, defName string, data []byte) (*File, error) { 176 ast, err := bzl.ParseBzl(path, data) 177 if err != nil { 178 return nil, err 179 } 180 f := ScanASTBody(pkg, defName, ast) 181 if err := checkFile(f); err != nil { 182 return nil, err 183 } 184 f.Content = data 185 return f, nil 186 } 187 188 // ScanAST creates a File wrapped around the given syntax tree. This tree 189 // will be modified by editing methods. 190 func ScanAST(pkg string, bzlFile *bzl.File) *File { 191 return ScanASTBody(pkg, "", bzlFile) 192 } 193 194 type function struct { 195 stmt *bzl.DefStmt 196 inserted, hasPass bool 197 } 198 199 // ScanASTBody creates a File wrapped around the given syntax tree. It will also 200 // scan the AST for a function matching the given defName, and if the function 201 // does not exist it will create a new one and mark it to be added to the File 202 // the next time Sync is called. 203 func ScanASTBody(pkg, defName string, bzlFile *bzl.File) *File { 204 f := &File{ 205 File: bzlFile, 206 Pkg: pkg, 207 Path: bzlFile.Path, 208 DefName: defName, 209 } 210 var defStmt *bzl.DefStmt 211 f.Rules, f.Loads, defStmt = scanExprs(defName, bzlFile.Stmt) 212 if defStmt != nil { 213 f.Rules, _, _ = scanExprs("", defStmt.Body) 214 f.function = &function{ 215 stmt: defStmt, 216 inserted: true, 217 } 218 if len(defStmt.Body) == 1 { 219 if v, ok := defStmt.Body[0].(*bzl.BranchStmt); ok && v.Token == "pass" { 220 f.function.hasPass = true 221 } 222 } 223 } else if defName != "" { 224 f.function = &function{ 225 stmt: &bzl.DefStmt{Name: defName}, 226 inserted: false, 227 } 228 } 229 if f.function != nil { 230 f.Directives = ParseDirectivesFromMacro(f.function.stmt) 231 } else { 232 f.Directives = ParseDirectives(bzlFile) 233 } 234 return f 235 } 236 237 func scanExprs(defName string, stmt []bzl.Expr) (rules []*Rule, loads []*Load, fn *bzl.DefStmt) { 238 for i, expr := range stmt { 239 switch expr := expr.(type) { 240 case *bzl.LoadStmt: 241 l := loadFromExpr(i, expr) 242 loads = append(loads, l) 243 case *bzl.CallExpr: 244 if r := ruleFromExpr(i, expr); r != nil { 245 rules = append(rules, r) 246 } 247 case *bzl.DefStmt: 248 if expr.Name == defName { 249 fn = expr 250 } 251 } 252 } 253 return rules, loads, fn 254 } 255 256 // MatchBuildFile looks for a file in files that has a name from names. 257 // If there is at least one matching file, a path will be returned by joining 258 // dir and the first matching name. If there are no matching files, the 259 // empty string is returned. 260 func MatchBuildFile(dir string, names []string, ents []fs.DirEntry) string { 261 for _, name := range names { 262 for _, ent := range ents { 263 if ent.Name() == name && !ent.IsDir() { 264 return filepath.Join(dir, name) 265 } 266 } 267 } 268 return "" 269 } 270 271 // Deprecated: Prefer MatchBuildFile, it's more efficient to fetch a []fs.DirEntry 272 func MatchBuildFileName(dir string, names []string, files []os.FileInfo) string { 273 for _, name := range names { 274 for _, fi := range files { 275 if fi.Name() == name && !fi.IsDir() { 276 return filepath.Join(dir, name) 277 } 278 } 279 } 280 return "" 281 } 282 283 // SyncMacroFile syncs the file's syntax tree with another file's. This is 284 // useful for keeping multiple macro definitions from the same .bzl file in sync. 285 func (f *File) SyncMacroFile(from *File) { 286 fromFunc := *from.function.stmt 287 _, _, toFunc := scanExprs(from.function.stmt.Name, f.File.Stmt) 288 if toFunc != nil { 289 *toFunc = fromFunc 290 } else { 291 f.File.Stmt = append(f.File.Stmt, &fromFunc) 292 } 293 } 294 295 // MacroName returns the name of the macro function that this file is editing, 296 // or an empty string if a macro function is not being edited. 297 func (f *File) MacroName() string { 298 if f.function != nil && f.function.stmt != nil { 299 return f.function.stmt.Name 300 } 301 return "" 302 } 303 304 // Sync writes all changes back to the wrapped syntax tree. This should be 305 // called after editing operations, before reading the syntax tree again. 306 func (f *File) Sync() { 307 var loadInserts, loadDeletes, loadStmts []*stmt 308 var r, w int 309 for r, w = 0, 0; r < len(f.Loads); r++ { 310 s := f.Loads[r] 311 s.sync() 312 if s.deleted { 313 loadDeletes = append(loadDeletes, &s.stmt) 314 continue 315 } 316 if s.inserted { 317 loadInserts = append(loadInserts, &s.stmt) 318 s.inserted = false 319 } else { 320 loadStmts = append(loadStmts, &s.stmt) 321 } 322 f.Loads[w] = s 323 w++ 324 } 325 f.Loads = f.Loads[:w] 326 var ruleInserts, ruleDeletes, ruleStmts []*stmt 327 for r, w = 0, 0; r < len(f.Rules); r++ { 328 s := f.Rules[r] 329 s.sync() 330 if s.deleted { 331 ruleDeletes = append(ruleDeletes, &s.stmt) 332 continue 333 } 334 if s.inserted { 335 ruleInserts = append(ruleInserts, &s.stmt) 336 s.inserted = false 337 } else { 338 ruleStmts = append(ruleStmts, &s.stmt) 339 } 340 f.Rules[w] = s 341 w++ 342 } 343 f.Rules = f.Rules[:w] 344 345 if f.function == nil { 346 deletes := append(loadDeletes, ruleDeletes...) 347 inserts := append(loadInserts, ruleInserts...) 348 stmts := append(loadStmts, ruleStmts...) 349 updateStmt(&f.File.Stmt, inserts, deletes, stmts) 350 } else { 351 updateStmt(&f.File.Stmt, loadInserts, loadDeletes, loadStmts) 352 if f.function.hasPass && len(ruleInserts) > 0 { 353 f.function.stmt.Body = []bzl.Expr{} 354 f.function.hasPass = false 355 } 356 updateStmt(&f.function.stmt.Body, ruleInserts, ruleDeletes, ruleStmts) 357 if len(f.function.stmt.Body) == 0 { 358 f.function.stmt.Body = append(f.function.stmt.Body, &bzl.BranchStmt{Token: "pass"}) 359 f.function.hasPass = true 360 } 361 if !f.function.inserted { 362 f.File.Stmt = append(f.File.Stmt, f.function.stmt) 363 f.function.inserted = true 364 } 365 } 366 } 367 368 func updateStmt(oldStmt *[]bzl.Expr, inserts, deletes, stmts []*stmt) { 369 sort.Stable(byIndex(deletes)) 370 sort.Stable(byIndex(inserts)) 371 sort.Stable(byIndex(stmts)) 372 cap := len(*oldStmt) - len(deletes) + len(inserts) 373 if cap < 0 { 374 cap = 0 375 } 376 newStmt := make([]bzl.Expr, 0, cap) 377 var ii, di, si int 378 for i, stmt := range *oldStmt { 379 for ii < len(inserts) && inserts[ii].index == i { 380 inserts[ii].index = len(newStmt) 381 newStmt = append(newStmt, inserts[ii].expr) 382 ii++ 383 } 384 if di < len(deletes) && deletes[di].index == i { 385 di++ 386 continue 387 } 388 if si < len(stmts) && stmts[si].expr == stmt { 389 stmts[si].index = len(newStmt) 390 si++ 391 } 392 newStmt = append(newStmt, stmt) 393 } 394 for ii < len(inserts) { 395 inserts[ii].index = len(newStmt) 396 newStmt = append(newStmt, inserts[ii].expr) 397 ii++ 398 } 399 *oldStmt = newStmt 400 } 401 402 // Format formats the build file in a form that can be written to disk. 403 // This method calls Sync internally. 404 func (f *File) Format() []byte { 405 f.Sync() 406 return bzl.Format(f.File) 407 } 408 409 // SortMacro sorts rules and loads in the macro of this File. It doesn't sort the rules if 410 // this File does not have a macro, e.g., WORKSPACE. 411 // This method calls Sync internally. 412 func (f *File) SortMacro() { 413 f.Sync() 414 415 if f.function == nil { 416 panic(fmt.Sprintf("%s: not loaded as macro file", f.Path)) 417 } 418 419 sort.Stable(loadsByName{f.Loads, f.File.Stmt}) 420 sort.Stable(rulesByKindAndName{f.Rules, f.function.stmt.Body}) 421 } 422 423 // Save writes the build file to disk. This method calls Sync internally. 424 func (f *File) Save(path string) error { 425 f.Sync() 426 f.Content = bzl.Format(f.File) 427 return os.WriteFile(path, f.Content, 0o666) 428 } 429 430 // HasDefaultVisibility returns whether the File contains a "package" rule with 431 // a "default_visibility" attribute. Rules generated by Gazelle should not 432 // have their own visibility attributes if this is the case. 433 func (f *File) HasDefaultVisibility() bool { 434 for _, r := range f.Rules { 435 if r.Kind() == "package" && r.Attr("default_visibility") != nil { 436 return true 437 } 438 } 439 return false 440 } 441 442 type stmt struct { 443 index int 444 deleted, inserted, updated bool 445 comments []string 446 commentsUpdated bool 447 expr bzl.Expr 448 } 449 450 // Index returns the index for this statement within the build file. For 451 // inserted rules, this is where the rule will be inserted (rules with the 452 // same index will be inserted in the order Insert was called). For existing 453 // rules, this is the index of the original statement. 454 func (s *stmt) Index() int { return s.index } 455 456 // Delete marks this statement for deletion. It will be removed from the 457 // syntax tree when File.Sync is called. 458 func (s *stmt) Delete() { s.deleted = true } 459 460 // Comments returns the text of the comments that appear before the statement. 461 // Each comment includes the leading "#". 462 func (s *stmt) Comments() []string { 463 return s.comments 464 } 465 466 // AddComment adds a new comment above the statement, after other comments. 467 // The new comment must start with "#". 468 func (s *stmt) AddComment(token string) { 469 if !strings.HasPrefix(token, "#") { 470 panic(fmt.Sprintf("comment must start with '#': got %q", token)) 471 } 472 s.comments = append(s.comments, token) 473 s.commentsUpdated = true 474 } 475 476 func commentsFromExpr(e bzl.Expr) []string { 477 before := e.Comment().Before 478 tokens := make([]string, len(before)) 479 for i, c := range before { 480 tokens[i] = c.Token 481 } 482 return tokens 483 } 484 485 func (s *stmt) syncComments() { 486 if !s.commentsUpdated { 487 return 488 } 489 s.commentsUpdated = false 490 before := make([]bzl.Comment, len(s.comments)) 491 for i, token := range s.comments { 492 before[i].Token = token 493 } 494 s.expr.Comment().Before = before 495 } 496 497 type byIndex []*stmt 498 499 func (s byIndex) Len() int { 500 return len(s) 501 } 502 503 func (s byIndex) Less(i, j int) bool { 504 return s[i].index < s[j].index 505 } 506 507 func (s byIndex) Swap(i, j int) { 508 s[i], s[j] = s[j], s[i] 509 } 510 511 type rulesByKindAndName struct { 512 rules []*Rule 513 exprs []bzl.Expr 514 } 515 516 // type checking 517 var _ sort.Interface = rulesByKindAndName{} 518 519 func (s rulesByKindAndName) Len() int { 520 return len(s.rules) 521 } 522 523 func (s rulesByKindAndName) Less(i, j int) bool { 524 if s.rules[i].Kind() == s.rules[j].Kind() { 525 return s.rules[i].Name() < s.rules[j].Name() 526 } 527 return s.rules[i].Kind() < s.rules[j].Kind() 528 } 529 530 func (s rulesByKindAndName) Swap(i, j int) { 531 s.exprs[s.rules[i].index], s.exprs[s.rules[j].index] = s.exprs[s.rules[j].index], s.exprs[s.rules[i].index] 532 s.rules[i].index, s.rules[j].index = s.rules[j].index, s.rules[i].index 533 s.rules[i], s.rules[j] = s.rules[j], s.rules[i] 534 } 535 536 type loadsByName struct { 537 loads []*Load 538 exprs []bzl.Expr 539 } 540 541 // type checking 542 var _ sort.Interface = loadsByName{} 543 544 func (s loadsByName) Len() int { 545 return len(s.loads) 546 } 547 548 func (s loadsByName) Less(i, j int) bool { 549 return s.loads[i].Name() < s.loads[j].Name() 550 } 551 552 func (s loadsByName) Swap(i, j int) { 553 s.exprs[s.loads[i].index], s.exprs[s.loads[j].index] = s.exprs[s.loads[j].index], s.exprs[s.loads[i].index] 554 s.loads[i].index, s.loads[j].index = s.loads[j].index, s.loads[i].index 555 s.loads[i], s.loads[j] = s.loads[j], s.loads[i] 556 } 557 558 // identPair represents one symbol, with or without remapping, in a load 559 // statement within a build file. 560 type identPair struct { 561 to, from *bzl.Ident 562 } 563 564 // Load represents a load statement within a build file. 565 type Load struct { 566 stmt 567 name string 568 symbols map[string]identPair 569 } 570 571 // NewLoad creates a new, empty load statement for the given file name. 572 func NewLoad(name string) *Load { 573 return &Load{ 574 stmt: stmt{ 575 expr: &bzl.LoadStmt{ 576 Module: &bzl.StringExpr{Value: name}, 577 ForceCompact: true, 578 }, 579 }, 580 name: name, 581 symbols: make(map[string]identPair), 582 } 583 } 584 585 func loadFromExpr(index int, loadStmt *bzl.LoadStmt) *Load { 586 l := &Load{ 587 stmt: stmt{ 588 index: index, 589 expr: loadStmt, 590 comments: commentsFromExpr(loadStmt), 591 }, 592 name: loadStmt.Module.Value, 593 symbols: make(map[string]identPair), 594 } 595 for i := range loadStmt.From { 596 to, from := loadStmt.To[i], loadStmt.From[i] 597 l.symbols[to.Name] = identPair{to: to, from: from} 598 } 599 return l 600 } 601 602 // Name returns the name of the file this statement loads. 603 func (l *Load) Name() string { 604 return l.name 605 } 606 607 // Symbols returns a sorted list of symbols this statement loads. 608 // If the symbol is loaded with a name different from its definition, the 609 // loaded name is returned, not the original name. 610 func (l *Load) Symbols() []string { 611 syms := make([]string, 0, len(l.symbols)) 612 for sym := range l.symbols { 613 syms = append(syms, sym) 614 } 615 sort.Strings(syms) 616 return syms 617 } 618 619 // SymbolPairs returns a list of symbol pairs loaded by this statement. 620 // Each pair contains the symbol defined in the loaded module (From) and the 621 // symbol declared in the loading module (To). The pairs are sorted by To 622 // (same order as Symbols). 623 func (l *Load) SymbolPairs() []struct{ From, To string } { 624 toSyms := l.Symbols() 625 pairs := make([]struct{ From, To string }, 0, len(toSyms)) 626 for _, toSym := range toSyms { 627 pairs = append(pairs, struct{ From, To string }{l.symbols[toSym].from.Name, toSym}) 628 } 629 return pairs 630 } 631 632 // Has returns true if sym is loaded by this statement. 633 func (l *Load) Has(sym string) bool { 634 _, ok := l.symbols[sym] 635 return ok 636 } 637 638 // Unalias returns the original (from) name of a (to) Symbol from a load. 639 func (l *Load) Unalias(sym string) string { 640 return l.symbols[sym].from.Name 641 } 642 643 // Add inserts a new symbol into the load statement. This has no effect if 644 // the symbol is already loaded. Symbols will be sorted, so the order 645 // doesn't matter. 646 func (l *Load) Add(sym string) { 647 if _, ok := l.symbols[sym]; !ok { 648 i := &bzl.Ident{Name: sym} 649 l.symbols[sym] = identPair{to: i, from: i} 650 l.updated = true 651 } 652 } 653 654 // AddAlias inserts a new aliased symbol into the load statement. This has 655 // no effect if the symbol is already loaded. Symbols will be sorted, so the order 656 // doesn't matter. 657 func (l *Load) AddAlias(sym, to string) { 658 if _, ok := l.symbols[sym]; !ok { 659 l.symbols[sym] = identPair{ 660 to: &bzl.Ident{Name: to}, 661 from: &bzl.Ident{Name: sym}, 662 } 663 l.updated = true 664 } 665 } 666 667 // Remove deletes a symbol from the load statement. This has no effect if 668 // the symbol is not loaded. 669 func (l *Load) Remove(sym string) { 670 if _, ok := l.symbols[sym]; ok { 671 delete(l.symbols, sym) 672 l.updated = true 673 } 674 } 675 676 // IsEmpty returns whether this statement loads any symbols. 677 func (l *Load) IsEmpty() bool { 678 return len(l.symbols) == 0 679 } 680 681 // Insert marks this statement for insertion at the given index. If multiple 682 // statements are inserted at the same index, they will be inserted in the 683 // order Insert is called. 684 func (l *Load) Insert(f *File, index int) { 685 l.index = index 686 l.inserted = true 687 f.Loads = append(f.Loads, l) 688 } 689 690 func (l *Load) sync() { 691 l.syncComments() 692 if !l.updated { 693 return 694 } 695 l.updated = false 696 697 // args1 and args2 are two different sort groups based on whether a remap of the identifier is present. 698 var args1, args2, args []string 699 for sym, pair := range l.symbols { 700 if pair.from.Name == pair.to.Name { 701 args1 = append(args1, sym) 702 } else { 703 args2 = append(args2, sym) 704 } 705 } 706 sort.Strings(args1) 707 sort.Strings(args2) 708 args = append(args, args1...) 709 args = append(args, args2...) 710 711 loadStmt := l.expr.(*bzl.LoadStmt) 712 loadStmt.Module.Value = l.name 713 loadStmt.From = make([]*bzl.Ident, 0, len(args)) 714 loadStmt.To = make([]*bzl.Ident, 0, len(args)) 715 for _, sym := range args { 716 pair := l.symbols[sym] 717 loadStmt.From = append(loadStmt.From, pair.from) 718 loadStmt.To = append(loadStmt.To, pair.to) 719 if pair.from.Name != pair.to.Name { 720 loadStmt.ForceCompact = false 721 } 722 } 723 } 724 725 // Rule represents a rule statement within a build file. 726 type Rule struct { 727 stmt 728 kind bzl.Expr 729 args []bzl.Expr 730 attrs map[string]attrValue 731 private map[string]interface{} 732 sortedAttrs []string 733 } 734 735 type attrValue struct { 736 // expr is the expression that defines the attribute assignment. If mergeable 737 // this will be replaced with a call to the merge function. 738 expr *bzl.AssignExpr 739 // val is the value of the attribute. If the attribute is mergeable 740 // the value must implement the Merger interface. could be nil. 741 val interface{} 742 } 743 744 // NewRule creates a new, empty rule with the given kind and name. 745 func NewRule(kind, name string) *Rule { 746 kindIdent := createDotExpr(kind) 747 call := &bzl.CallExpr{X: kindIdent} 748 749 r := &Rule{ 750 stmt: stmt{expr: call}, 751 kind: kindIdent, 752 attrs: map[string]attrValue{}, 753 private: map[string]interface{}{}, 754 sortedAttrs: []string{"deps", "srcs"}, 755 } 756 if name != "" { 757 nameAttr := attrValue{ 758 expr: &bzl.AssignExpr{ 759 LHS: &bzl.Ident{Name: "name"}, 760 RHS: &bzl.StringExpr{Value: name}, 761 Op: "=", 762 }, 763 val: name} 764 call.List = []bzl.Expr{nameAttr.expr} 765 r.attrs["name"] = nameAttr 766 } 767 return r 768 } 769 770 // Creates `bzl.Expr` for a kind which 771 // is either `*bzl.DotExpr` or `*bzl.Ident`. 772 // 773 // For `myKind` kind it returns: 774 // 775 // &bzl.Ident{ 776 // Name: "myKind" 777 // } 778 // 779 // For `myKind.inner` kind it returns: 780 // 781 // &bzl.DotExpr{ 782 // Name: "inner", 783 // X: &bzl.Ident { 784 // Name: "myKind" 785 // } 786 // } 787 func createDotExpr(kind string) bzl.Expr { 788 var expr bzl.Expr 789 parts := strings.Split(kind, ".") 790 791 if len(parts) > 1 { 792 // last `parts` element is the main bzl.DotExpr 793 var dotExpr *bzl.DotExpr = &bzl.DotExpr{Name: parts[len(parts)-1]} 794 795 _pDot := dotExpr 796 797 for idx := len(parts) - 2; idx > 0; idx-- { 798 d := &bzl.DotExpr{Name: parts[idx]} 799 _pDot.X = d 800 _pDot = d 801 } 802 803 // first `parts` element is the identifier 804 _pDot.X = &bzl.Ident{Name: parts[0]} 805 expr = dotExpr 806 } else { 807 expr = &bzl.Ident{Name: kind} 808 } 809 810 return expr 811 } 812 813 func isNestedDotOrIdent(expr bzl.Expr) bool { 814 if _, ok := expr.(*bzl.Ident); ok { 815 return true 816 } 817 818 dot, ok := expr.(*bzl.DotExpr) 819 if !ok { 820 return false 821 } 822 823 return isNestedDotOrIdent(dot.X) 824 } 825 826 func ruleFromExpr(index int, expr bzl.Expr) *Rule { 827 call, ok := expr.(*bzl.CallExpr) 828 if !ok { 829 return nil 830 } 831 832 kind := call.X 833 if !isNestedDotOrIdent(kind) { 834 return nil 835 } 836 837 var args []bzl.Expr 838 attrs := make(map[string]attrValue, len(call.List)) 839 for _, arg := range call.List { 840 if attr, ok := arg.(*bzl.AssignExpr); ok { 841 key := attr.LHS.(*bzl.Ident) // required by parser 842 attrs[key.Name] = attrValue{expr: attr} 843 } else { 844 args = append(args, arg) 845 } 846 } 847 return &Rule{ 848 stmt: stmt{ 849 index: index, 850 expr: call, 851 comments: commentsFromExpr(expr), 852 }, 853 kind: kind, 854 args: args, 855 attrs: attrs, 856 private: map[string]interface{}{}, 857 } 858 } 859 860 // ShouldKeep returns whether the rule is marked with a "# keep" comment. Rules 861 // that are kept should not be modified. This does not check whether 862 // subexpressions within the rule should be kept. 863 func (r *Rule) ShouldKeep() bool { 864 return ShouldKeep(r.expr) 865 } 866 867 // Kind returns the kind of rule this is (for example, "go_library"). 868 func (r *Rule) Kind() string { 869 return bzl.FormatString(r.kind) 870 } 871 872 // SetKind changes the kind of rule this is. 873 func (r *Rule) SetKind(kind string) { 874 r.kind = &bzl.Ident{Name: kind} 875 r.updated = true 876 } 877 878 // Name returns the value of the rule's "name" attribute if it is a string 879 // or "" if the attribute does not exist or is not a string. 880 func (r *Rule) Name() string { 881 return r.AttrString("name") 882 } 883 884 // SetName sets the value of the rule's "name" attribute. 885 func (r *Rule) SetName(name string) { 886 r.SetAttr("name", name) 887 } 888 889 // AttrKeys returns a sorted list of attribute keys used in this rule. 890 func (r *Rule) AttrKeys() []string { 891 keys := make([]string, 0, len(r.attrs)) 892 for k := range r.attrs { 893 keys = append(keys, k) 894 } 895 sort.SliceStable(keys, func(i, j int) bool { 896 if cmp := bt.NamePriority[keys[i]] - bt.NamePriority[keys[j]]; cmp != 0 { 897 return cmp < 0 898 } 899 return keys[i] < keys[j] 900 }) 901 return keys 902 } 903 904 // Attr returns the value of the named attribute. nil is returned when the 905 // attribute is not set. 906 func (r *Rule) Attr(key string) bzl.Expr { 907 attr, ok := r.attrs[key] 908 if !ok { 909 return nil 910 } 911 return attr.expr.RHS 912 } 913 914 // AttrString returns the value of the named attribute if it is a scalar string. 915 // "" is returned if the attribute is not set or is not a string. 916 func (r *Rule) AttrString(key string) string { 917 attr, ok := r.attrs[key] 918 if !ok { 919 return "" 920 } 921 str, ok := attr.expr.RHS.(*bzl.StringExpr) 922 if !ok { 923 return "" 924 } 925 return str.Value 926 } 927 928 // AttrStrings returns the string values of an attribute if it is a list. 929 // nil is returned if the attribute is not set or is not a list. Non-string 930 // values within the list won't be returned. 931 func (r *Rule) AttrStrings(key string) []string { 932 attr, ok := r.attrs[key] 933 if !ok { 934 return nil 935 } 936 list, ok := attr.expr.RHS.(*bzl.ListExpr) 937 if !ok { 938 return nil 939 } 940 strs := make([]string, 0, len(list.List)) 941 for _, e := range list.List { 942 if str, ok := e.(*bzl.StringExpr); ok { 943 strs = append(strs, str.Value) 944 } 945 } 946 return strs 947 } 948 949 // DelAttr removes the named attribute from the rule. 950 func (r *Rule) DelAttr(key string) { 951 delete(r.attrs, key) 952 r.updated = true 953 } 954 955 // SetAttr adds or replaces the named attribute with value. If the attribute is 956 // mergeable, then the value must implement the Merger interface, or an error will 957 // be returned. 958 func (r *Rule) SetAttr(key string, value interface{}) { 959 rhs := ExprFromValue(value) 960 if attr, ok := r.attrs[key]; ok { 961 attr.expr.RHS = rhs 962 attr.val = value 963 } else { 964 r.attrs[key] = attrValue{ 965 expr: &bzl.AssignExpr{ 966 LHS: &bzl.Ident{Name: key}, 967 RHS: rhs, 968 Op: "=", 969 }, 970 val: value, 971 } 972 } 973 r.updated = true 974 } 975 976 // AttrComments returns the comments for an attribute. 977 // It can be used to attach comments like "do not sort". 978 func (r *Rule) AttrComments(key string) *bzl.Comments { 979 attr, ok := r.attrs[key] 980 if !ok { 981 return nil 982 } 983 return attr.expr.Comment() 984 } 985 986 // PrivateAttrKeys returns a sorted list of private attribute names. 987 func (r *Rule) PrivateAttrKeys() []string { 988 keys := make([]string, 0, len(r.private)) 989 for k := range r.private { 990 keys = append(keys, k) 991 } 992 sort.Strings(keys) 993 return keys 994 } 995 996 // PrivateAttr return the private value associated with a key. 997 func (r *Rule) PrivateAttr(key string) interface{} { 998 return r.private[key] 999 } 1000 1001 // SetPrivateAttr associates a value with a key. Unlike SetAttr, this value 1002 // is not converted to a build syntax tree and will not be written to a build 1003 // file. 1004 func (r *Rule) SetPrivateAttr(key string, value interface{}) { 1005 r.private[key] = value 1006 } 1007 1008 // Args returns positional arguments passed to a rule. 1009 func (r *Rule) Args() []bzl.Expr { 1010 return r.args 1011 } 1012 1013 // AddArg adds a positional argument to the rule. 1014 func (r *Rule) AddArg(value bzl.Expr) { 1015 r.args = append(r.args, value) 1016 r.updated = true 1017 } 1018 1019 // UpdateArg replaces an existing arg with a new value. 1020 func (r *Rule) UpdateArg(index int, value bzl.Expr) error { 1021 if len(r.args) < index { 1022 return fmt.Errorf("can't update argument at index %d, only have %d args", index, len(r.args)) 1023 } 1024 r.args[index] = value 1025 r.updated = true 1026 return nil 1027 } 1028 1029 // SortedAttrs returns the keys of attributes whose values will be sorted 1030 func (r *Rule) SortedAttrs() []string { 1031 return r.sortedAttrs 1032 } 1033 1034 // SetSortedAttrs sets the keys of attributes whose values will be sorted 1035 func (r *Rule) SetSortedAttrs(keys []string) { 1036 r.sortedAttrs = keys 1037 r.updated = true 1038 } 1039 1040 // Insert marks this statement for insertion at the end of the file. Multiple 1041 // statements will be inserted in the order Insert is called. 1042 func (r *Rule) Insert(f *File) { 1043 var stmt []bzl.Expr 1044 if f.function == nil { 1045 stmt = f.File.Stmt 1046 } else { 1047 stmt = f.function.stmt.Body 1048 } 1049 r.InsertAt(f, len(stmt)) 1050 } 1051 1052 // InsertAt marks this statement for insertion before the statement at index. 1053 // Multiple rules inserted at the same index will be inserted in the order 1054 // Insert is called. Loads inserted at the same index will be inserted first. 1055 func (r *Rule) InsertAt(f *File, index int) { 1056 r.index = index 1057 r.inserted = true 1058 f.Rules = append(f.Rules, r) 1059 } 1060 1061 // IsEmpty returns true when the rule contains none of the attributes in attrs 1062 // for its kind. attrs should contain attributes that make the rule buildable 1063 // like srcs or deps and not descriptive attributes like name or visibility. 1064 func (r *Rule) IsEmpty(info KindInfo) bool { 1065 if info.NonEmptyAttrs == nil { 1066 return false 1067 } 1068 for k := range info.NonEmptyAttrs { 1069 if _, ok := r.attrs[k]; ok { 1070 return false 1071 } 1072 } 1073 return true 1074 } 1075 1076 func (r *Rule) sync() { 1077 r.syncComments() 1078 if !r.updated { 1079 return 1080 } 1081 r.updated = false 1082 1083 for _, k := range r.sortedAttrs { 1084 attr, ok := r.attrs[k] 1085 _, isUnsorted := attr.val.(UnsortedStrings) 1086 if ok && !isUnsorted { 1087 bzl.Walk(attr.expr.RHS, sortExprLabels) 1088 } 1089 } 1090 1091 call := r.expr.(*bzl.CallExpr) 1092 1093 // update `call.X` (e.g.: "# gazelle:map_kind") 1094 call.X = createDotExpr(r.Kind()) 1095 1096 if len(r.attrs) > 1 { 1097 call.ForceMultiLine = true 1098 } 1099 1100 list := make([]bzl.Expr, 0, len(r.args)+len(r.attrs)) 1101 list = append(list, r.args...) 1102 for _, attr := range r.attrs { 1103 list = append(list, attr.expr) 1104 } 1105 sortedAttrs := list[len(r.args):] 1106 key := func(e bzl.Expr) string { return e.(*bzl.AssignExpr).LHS.(*bzl.Ident).Name } 1107 sort.SliceStable(sortedAttrs, func(i, j int) bool { 1108 ki := key(sortedAttrs[i]) 1109 kj := key(sortedAttrs[j]) 1110 if cmp := bt.NamePriority[ki] - bt.NamePriority[kj]; cmp != 0 { 1111 return cmp < 0 1112 } 1113 return ki < kj 1114 }) 1115 1116 call.List = list 1117 r.updated = false 1118 } 1119 1120 // ShouldKeep returns whether e is marked with a "# keep" comment. Kept 1121 // expressions should not be removed or modified. 1122 func ShouldKeep(e bzl.Expr) bool { 1123 for _, c := range append(e.Comment().Before, e.Comment().Suffix...) { 1124 text := strings.TrimSpace(strings.TrimPrefix(c.Token, "#")) 1125 if text == "keep" || strings.HasPrefix(text, "keep: ") { 1126 return true 1127 } 1128 } 1129 return false 1130 } 1131 1132 // CheckInternalVisibility overrides the given visibility if the package is 1133 // internal. 1134 func CheckInternalVisibility(rel, visibility string) string { 1135 if strings.HasSuffix(rel, "/internal") { 1136 visibility = fmt.Sprintf("//%s:__subpackages__", rel[:len(rel)-len("/internal")]) 1137 } else if i := strings.LastIndex(rel, "/internal/"); i >= 0 { 1138 visibility = fmt.Sprintf("//%s:__subpackages__", rel[:i]) 1139 } else if strings.HasPrefix(rel, "internal/") || rel == "internal" { 1140 visibility = "//:__subpackages__" 1141 } 1142 return visibility 1143 } 1144 1145 type byAttrName []KeyValue 1146 1147 var _ sort.Interface = byAttrName{} 1148 1149 func (s byAttrName) Len() int { 1150 return len(s) 1151 } 1152 1153 func (s byAttrName) Less(i, j int) bool { 1154 if cmp := bt.NamePriority[s[i].Key] - bt.NamePriority[s[j].Key]; cmp != 0 { 1155 return cmp < 0 1156 } 1157 return s[i].Key < s[j].Key 1158 } 1159 1160 func (s byAttrName) Swap(i, j int) { 1161 s[i], s[j] = s[j], s[i] 1162 } 1163 1164 func checkFile(f *File) error { 1165 names := make(map[string]bool) 1166 for _, r := range f.Rules { 1167 name := r.Name() 1168 if name == "" { 1169 continue 1170 } 1171 if names[name] { 1172 return fmt.Errorf("%s: multiple rules have the name %q", f.Path, name) 1173 } 1174 names[name] = true 1175 } 1176 return nil 1177 }