| /* | |
| Copyright 2016 Google LLC | |
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| you may not use this file except in compliance with the License. | |
| You may obtain a copy of the License at | |
| https://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| // Rule-level API for inspecting and modifying a build.File syntax tree. | |
| package build | |
| import ( | |
| "path/filepath" | |
| "strings" | |
| ) | |
| // A Rule represents a single BUILD rule. | |
| type Rule struct { | |
| Call *CallExpr | |
| ImplicitName string // The name which should be used if the name attribute is not set. See the comment on File.implicitRuleName. | |
| } | |
| // NewRule is a simple constructor for Rule. | |
| func NewRule(call *CallExpr) *Rule { | |
| return &Rule{call, ""} | |
| } | |
| func (f *File) Rule(call *CallExpr) *Rule { | |
| r := &Rule{call, ""} | |
| if r.AttrString("name") == "" { | |
| r.ImplicitName = f.implicitRuleName() | |
| } | |
| return r | |
| } | |
| func (f *File) rules(p func(r *Rule) bool) []*Rule { | |
| var all []*Rule | |
| for _, stmt := range f.Stmt { | |
| Walk(stmt, func(x Expr, stk []Expr) { | |
| call, ok := x.(*CallExpr) | |
| if !ok { | |
| return | |
| } | |
| // Skip nested calls. | |
| for _, frame := range stk { | |
| if _, ok := frame.(*CallExpr); ok { | |
| return | |
| } | |
| } | |
| // Check if the rule is correct. | |
| rule := f.Rule(call) | |
| if !p(rule) { | |
| return | |
| } | |
| all = append(all, rule) | |
| }) | |
| } | |
| return all | |
| } | |
| // Rules returns the rules in the file of the given kind (such as "go_library"). | |
| // If kind == "", Rules returns all rules in the file. | |
| func (f *File) Rules(kind string) []*Rule { | |
| return f.rules(func(rule *Rule) bool { | |
| // Check if the rule kind is correct. | |
| return kind == "" || rule.Kind() == kind | |
| }) | |
| } | |
| // RuleAt returns the rule in the file that starts at the specified line, or null if no such rule. | |
| func (f *File) RuleAt(linenum int) *Rule { | |
| all := f.rules(func(rule *Rule) bool { | |
| start, end := rule.Call.X.Span() | |
| return start.Line <= linenum && linenum <= end.Line | |
| }) | |
| if len(all) != 1 { | |
| return nil | |
| } | |
| return all[0] | |
| } | |
| // RuleNamed returns the rule in the file that has the specified name, or null if no such rule. | |
| func (f *File) RuleNamed(name string) *Rule { | |
| all := f.rules(func(rule *Rule) bool { | |
| return rule.Name() == name | |
| }) | |
| if len(all) != 1 { | |
| return nil | |
| } | |
| return all[0] | |
| } | |
| // DelRules removes rules with the given kind and name from the file. | |
| // An empty kind matches all kinds; an empty name matches all names. | |
| // It returns the number of rules that were deleted. | |
| func (f *File) DelRules(kind, name string) int { | |
| var i int | |
| for _, stmt := range f.Stmt { | |
| if call, ok := stmt.(*CallExpr); ok { | |
| r := f.Rule(call) | |
| if (kind == "" || r.Kind() == kind) && | |
| (name == "" || r.Name() == name) { | |
| continue | |
| } | |
| } | |
| f.Stmt[i] = stmt | |
| i++ | |
| } | |
| n := len(f.Stmt) - i | |
| f.Stmt = f.Stmt[:i] | |
| return n | |
| } | |
| // If a build file contains exactly one unnamed rule, and no rules in the file explicitly have the | |
| // same name as the name of the directory the build file is in, we treat the unnamed rule as if it | |
| // had the name of the directory containing the BUILD file. | |
| // This is following a convention used in the Pants build system to cut down on boilerplate. | |
| func (f *File) implicitRuleName() string { | |
| // We disallow empty names in the top-level BUILD files. | |
| dir := filepath.Dir(f.Path) | |
| if dir == "." { | |
| return "" | |
| } | |
| sawAnonymousRule := false | |
| possibleImplicitName := filepath.Base(dir) | |
| for _, stmt := range f.Stmt { | |
| call, ok := stmt.(*CallExpr) | |
| if !ok { | |
| continue | |
| } | |
| temp := &Rule{call, ""} | |
| if temp.AttrString("name") == possibleImplicitName { | |
| // A target explicitly has the name of the dir, so no implicit targets are allowed. | |
| return "" | |
| } | |
| if temp.Kind() != "" && temp.AttrString("name") == "" { | |
| if sawAnonymousRule { | |
| return "" | |
| } | |
| sawAnonymousRule = true | |
| } | |
| } | |
| if sawAnonymousRule { | |
| return possibleImplicitName | |
| } | |
| return "" | |
| } | |
| // Kind returns the rule's kind (such as "go_library"). | |
| // The kind of the rule may be given by a literal or it may be a sequence of dot expressions that | |
| // begins with a literal, if the call expression does not conform to either of these forms, an | |
| // empty string will be returned | |
| func (r *Rule) Kind() string { | |
| var names []string | |
| expr := r.Call.X | |
| for { | |
| x, ok := expr.(*DotExpr) | |
| if !ok { | |
| break | |
| } | |
| names = append(names, x.Name) | |
| expr = x.X | |
| } | |
| x, ok := expr.(*Ident) | |
| if !ok { | |
| return "" | |
| } | |
| names = append(names, x.Name) | |
| // Reverse the elements since the deepest expression contains the leading literal | |
| for l, r := 0, len(names)-1; l < r; l, r = l+1, r-1 { | |
| names[l], names[r] = names[r], names[l] | |
| } | |
| return strings.Join(names, ".") | |
| } | |
| // SetKind changes rule's kind (such as "go_library"). | |
| func (r *Rule) SetKind(kind string) { | |
| names := strings.Split(kind, ".") | |
| var startPos Position | |
| if x := r.Call.X; x != nil { | |
| startPos, _ = x.Span() | |
| } | |
| var expr Expr | |
| expr = &Ident{Name: names[0], NamePos: startPos} | |
| for _, name := range names[1:] { | |
| expr = &DotExpr{X: expr, Name: name, NamePos: startPos} | |
| } | |
| r.Call.X = expr | |
| } | |
| // ExplicitName returns the rule's target name if it's explicitly provided as a string value, "" otherwise. | |
| func (r *Rule) ExplicitName() string { | |
| return r.AttrString("name") | |
| } | |
| // Name returns the rule's target name. | |
| // If the rule has no explicit target name, Name returns the implicit name if there is one, else the empty string. | |
| func (r *Rule) Name() string { | |
| explicitName := r.ExplicitName() | |
| if explicitName == "" && r.Kind() != "package" { | |
| return r.ImplicitName | |
| } | |
| return explicitName | |
| } | |
| // AttrKeys returns the keys of all the rule's attributes. | |
| func (r *Rule) AttrKeys() []string { | |
| var keys []string | |
| for _, expr := range r.Call.List { | |
| if as, ok := expr.(*AssignExpr); ok { | |
| if keyExpr, ok := as.LHS.(*Ident); ok { | |
| keys = append(keys, keyExpr.Name) | |
| } | |
| } | |
| } | |
| return keys | |
| } | |
| // AttrDefn returns the AssignExpr defining the rule's attribute with the given key. | |
| // If the rule has no such attribute, AttrDefn returns nil. | |
| func (r *Rule) AttrDefn(key string) *AssignExpr { | |
| for _, kv := range r.Call.List { | |
| as, ok := kv.(*AssignExpr) | |
| if !ok { | |
| continue | |
| } | |
| k, ok := as.LHS.(*Ident) | |
| if !ok || k.Name != key { | |
| continue | |
| } | |
| return as | |
| } | |
| return nil | |
| } | |
| // Attr returns the value of the rule's attribute with the given key | |
| // (such as "name" or "deps"). | |
| // If the rule has no such attribute, Attr returns nil. | |
| func (r *Rule) Attr(key string) Expr { | |
| as := r.AttrDefn(key) | |
| if as == nil { | |
| return nil | |
| } | |
| return as.RHS | |
| } | |
| // DelAttr deletes the rule's attribute with the named key. | |
| // It returns the old value of the attribute, or nil if the attribute was not found. | |
| func (r *Rule) DelAttr(key string) Expr { | |
| list := r.Call.List | |
| for i, kv := range list { | |
| as, ok := kv.(*AssignExpr) | |
| if !ok { | |
| continue | |
| } | |
| k, ok := as.LHS.(*Ident) | |
| if !ok || k.Name != key { | |
| continue | |
| } | |
| copy(list[i:], list[i+1:]) | |
| r.Call.List = list[:len(list)-1] | |
| return as.RHS | |
| } | |
| return nil | |
| } | |
| // SetAttr sets the rule's attribute with the given key to value. | |
| // If the rule has no attribute with the key, SetAttr appends | |
| // one to the end of the rule's attribute list. | |
| func (r *Rule) SetAttr(key string, val Expr) { | |
| as := r.AttrDefn(key) | |
| if as != nil { | |
| as.RHS = val | |
| return | |
| } | |
| r.Call.List = append(r.Call.List, | |
| &AssignExpr{ | |
| LHS: &Ident{Name: key}, | |
| Op: "=", | |
| RHS: val, | |
| }, | |
| ) | |
| } | |
| // AttrLiteral returns the literal form of the rule's attribute | |
| // with the given key (such as "cc_api_version"), only when | |
| // that value is an identifier or number. | |
| // If the rule has no such attribute or the attribute is not an identifier or number, | |
| // AttrLiteral returns "". | |
| func (r *Rule) AttrLiteral(key string) string { | |
| value := r.Attr(key) | |
| if ident, ok := value.(*Ident); ok { | |
| return ident.Name | |
| } | |
| if literal, ok := value.(*LiteralExpr); ok { | |
| return literal.Token | |
| } | |
| return "" | |
| } | |
| // AttrString returns the value of the rule's attribute | |
| // with the given key (such as "name"), as a string. | |
| // If the rule has no such attribute or the attribute has a non-string value, | |
| // Attr returns the empty string. | |
| func (r *Rule) AttrString(key string) string { | |
| str, ok := r.Attr(key).(*StringExpr) | |
| if !ok { | |
| return "" | |
| } | |
| return str.Value | |
| } | |
| // AttrStrings returns the value of the rule's attribute | |
| // with the given key (such as "srcs"), as a []string. | |
| // If the rule has no such attribute or the attribute is not | |
| // a list of strings, AttrStrings returns a nil slice. | |
| func (r *Rule) AttrStrings(key string) []string { | |
| return Strings(r.Attr(key)) | |
| } | |
| // Strings returns expr as a []string. | |
| // If expr is not a list of string literals, | |
| // Strings returns a nil slice instead. | |
| // If expr is an empty list of string literals, | |
| // returns a non-nil empty slice. | |
| // (this allows differentiating between these two cases) | |
| func Strings(expr Expr) []string { | |
| list, ok := expr.(*ListExpr) | |
| if !ok { | |
| return nil | |
| } | |
| all := []string{} // not nil | |
| for _, l := range list.List { | |
| str, ok := l.(*StringExpr) | |
| if !ok { | |
| return nil | |
| } | |
| all = append(all, str.Value) | |
| } | |
| return all | |
| } |