edit.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. /*
  2. Copyright 2016 Google Inc. All Rights Reserved.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package edit provides high-level auxiliary functions for AST manipulation
  14. // on BUILD files.
  15. package edit
  16. import (
  17. "fmt"
  18. "os"
  19. "path"
  20. "path/filepath"
  21. "regexp"
  22. "sort"
  23. "strconv"
  24. "strings"
  25. "github.com/bazelbuild/buildtools/build"
  26. "github.com/bazelbuild/buildtools/tables"
  27. "github.com/bazelbuild/buildtools/wspace"
  28. )
  29. var (
  30. // ShortenLabelsFlag if true converts added labels to short form , e.g. //foo:bar => :bar
  31. ShortenLabelsFlag = true
  32. // DeleteWithComments if true a list attribute will be be deleted in ListDelete, even if there is a comment attached to it
  33. DeleteWithComments = true
  34. )
  35. // ParseLabel parses a Blaze label (eg. //devtools/buildozer:rule), and returns
  36. // the repo name ("" for the main repo), package (with leading slashes trimmed)
  37. // and rule name (e.g. ["", "devtools/buildozer", "rule"]).
  38. func ParseLabel(target string) (string, string, string) {
  39. repo := ""
  40. if strings.HasPrefix(target, "@") {
  41. target = strings.TrimLeft(target, "@")
  42. parts := strings.SplitN(target, "/", 2)
  43. if len(parts) == 1 {
  44. // "@foo" -> "foo", "", "foo" (ie @foo//:foo)
  45. return target, "", target
  46. }
  47. repo = parts[0]
  48. target = "/" + parts[1]
  49. }
  50. // TODO(bazel-team): check if the next line can now be deleted
  51. target = strings.TrimRight(target, ":") // labels can end with ':'
  52. parts := strings.SplitN(target, ":", 2)
  53. parts[0] = strings.TrimPrefix(parts[0], "//")
  54. if len(parts) == 1 {
  55. if strings.HasPrefix(target, "//") || tables.StripLabelLeadingSlashes {
  56. // "//absolute/pkg" -> "absolute/pkg", "pkg"
  57. return repo, parts[0], path.Base(parts[0])
  58. }
  59. // "relative/label" -> "", "relative/label"
  60. return repo, "", parts[0]
  61. }
  62. return repo, parts[0], parts[1]
  63. }
  64. // ShortenLabel rewrites labels to use the canonical form (the form
  65. // recommended by build-style). This behavior can be disabled using the
  66. // --noshorten_labels flag for projects that consistently use long-form labels.
  67. // "//foo/bar:bar" => "//foo/bar", or ":bar" when possible.
  68. func ShortenLabel(label string, pkg string) string {
  69. if !ShortenLabelsFlag {
  70. return label
  71. }
  72. if !strings.HasPrefix(label, "//") {
  73. // It doesn't look like a long label, so we preserve it.
  74. return label
  75. }
  76. repo, labelPkg, rule := ParseLabel(label)
  77. if repo == "" && labelPkg == pkg { // local label
  78. return ":" + rule
  79. }
  80. slash := strings.LastIndex(labelPkg, "/")
  81. if (slash >= 0 && labelPkg[slash+1:] == rule) || labelPkg == rule {
  82. return "//" + labelPkg
  83. }
  84. return label
  85. }
  86. // LabelsEqual returns true if label1 and label2 are equal. The function
  87. // takes care of the optional ":" prefix and differences between long-form
  88. // labels and local labels.
  89. func LabelsEqual(label1, label2, pkg string) bool {
  90. str1 := strings.TrimPrefix(ShortenLabel(label1, pkg), ":")
  91. str2 := strings.TrimPrefix(ShortenLabel(label2, pkg), ":")
  92. return str1 == str2
  93. }
  94. // isFile returns true if the path refers to a regular file after following
  95. // symlinks.
  96. func isFile(path string) bool {
  97. path, err := filepath.EvalSymlinks(path)
  98. if err != nil {
  99. return false
  100. }
  101. info, err := os.Stat(path)
  102. if err != nil {
  103. return false
  104. }
  105. return info.Mode().IsRegular()
  106. }
  107. // InterpretLabelForWorkspaceLocation returns the name of the BUILD file to
  108. // edit, the full package name, and the rule. It takes a workspace-rooted
  109. // directory to use.
  110. func InterpretLabelForWorkspaceLocation(root string, target string) (buildFile string, pkg string, rule string) {
  111. repo, pkg, rule := ParseLabel(target)
  112. rootDir, relativePath := wspace.FindWorkspaceRoot(root)
  113. if repo != "" {
  114. files, err := wspace.FindRepoBuildFiles(rootDir)
  115. if err == nil {
  116. if buildFile, ok := files[repo]; ok {
  117. return buildFile, pkg, rule
  118. }
  119. }
  120. // TODO(rodrigoq): report error for other repos
  121. }
  122. if strings.HasPrefix(target, "//") {
  123. buildFile = path.Join(rootDir, pkg, "BUILD")
  124. return
  125. }
  126. if isFile(pkg) {
  127. // allow operation on other files like WORKSPACE
  128. buildFile = pkg
  129. pkg = path.Join(relativePath, filepath.Dir(pkg))
  130. return
  131. }
  132. if pkg != "" {
  133. buildFile = pkg + "/BUILD"
  134. } else {
  135. buildFile = "BUILD"
  136. }
  137. pkg = path.Join(relativePath, pkg)
  138. return
  139. }
  140. // InterpretLabel returns the name of the BUILD file to edit, the full
  141. // package name, and the rule. It uses the pwd for resolving workspace file paths.
  142. func InterpretLabel(target string) (buildFile string, pkg string, rule string) {
  143. return InterpretLabelForWorkspaceLocation("", target)
  144. }
  145. // ExprToRule returns a Rule from an Expr.
  146. // The boolean is false iff the Expr is not a function call, or does not have
  147. // the expected kind.
  148. func ExprToRule(expr build.Expr, kind string) (*build.Rule, bool) {
  149. call, ok := expr.(*build.CallExpr)
  150. if !ok {
  151. return nil, false
  152. }
  153. k, ok := call.X.(*build.LiteralExpr)
  154. if !ok || k.Token != kind {
  155. return nil, false
  156. }
  157. return &build.Rule{call, ""}, true
  158. }
  159. // ExistingPackageDeclaration returns the package declaration, or nil if there is none.
  160. func ExistingPackageDeclaration(f *build.File) *build.Rule {
  161. for _, stmt := range f.Stmt {
  162. if rule, ok := ExprToRule(stmt, "package"); ok {
  163. return rule
  164. }
  165. }
  166. return nil
  167. }
  168. // PackageDeclaration returns the package declaration. If it doesn't
  169. // exist, it is created at the top of the BUILD file, after leading
  170. // comments.
  171. func PackageDeclaration(f *build.File) *build.Rule {
  172. if pkg := ExistingPackageDeclaration(f); pkg != nil {
  173. return pkg
  174. }
  175. all := []build.Expr{}
  176. added := false
  177. call := &build.CallExpr{X: &build.LiteralExpr{Token: "package"}}
  178. // Skip CommentBlocks and find a place to insert the package declaration.
  179. for _, stmt := range f.Stmt {
  180. _, ok := stmt.(*build.CommentBlock)
  181. if !ok && !added {
  182. all = append(all, call)
  183. added = true
  184. }
  185. all = append(all, stmt)
  186. }
  187. if !added { // In case the file is empty.
  188. all = append(all, call)
  189. }
  190. f.Stmt = all
  191. return &build.Rule{call, ""}
  192. }
  193. // RemoveEmptyPackage removes empty package declarations from the file, i.e.:
  194. // package()
  195. // This might appear because of a buildozer transformation (e.g. when removing a package
  196. // attribute). Removing it is required for the file to be valid.
  197. func RemoveEmptyPackage(f *build.File) *build.File {
  198. var all []build.Expr
  199. for _, stmt := range f.Stmt {
  200. if call, ok := stmt.(*build.CallExpr); ok {
  201. functionName, ok := call.X.(*build.LiteralExpr)
  202. if ok && functionName.Token == "package" && len(call.List) == 0 {
  203. continue
  204. }
  205. }
  206. all = append(all, stmt)
  207. }
  208. return &build.File{Path: f.Path, Comments: f.Comments, Stmt: all}
  209. }
  210. // InsertAfter inserts an expression after index i.
  211. func InsertAfter(i int, stmt []build.Expr, expr build.Expr) []build.Expr {
  212. i = i + 1 // index after the element at i
  213. result := make([]build.Expr, len(stmt)+1)
  214. copy(result[0:i], stmt[0:i])
  215. result[i] = expr
  216. copy(result[i+1:], stmt[i:])
  217. return result
  218. }
  219. // IndexOfLast finds the index of the last expression of a specific kind.
  220. func IndexOfLast(stmt []build.Expr, Kind string) int {
  221. lastIndex := -1
  222. for i, s := range stmt {
  223. sAsCallExpr, ok := s.(*build.CallExpr)
  224. if !ok {
  225. continue
  226. }
  227. literal, ok := sAsCallExpr.X.(*build.LiteralExpr)
  228. if ok && literal.Token == Kind {
  229. lastIndex = i
  230. }
  231. }
  232. return lastIndex
  233. }
  234. // InsertAfterLastOfSameKind inserts an expression after the last expression of the same kind.
  235. func InsertAfterLastOfSameKind(stmt []build.Expr, expr *build.CallExpr) []build.Expr {
  236. index := IndexOfLast(stmt, expr.X.(*build.LiteralExpr).Token)
  237. if index == -1 {
  238. return InsertAtEnd(stmt, expr)
  239. }
  240. return InsertAfter(index, stmt, expr)
  241. }
  242. // InsertAtEnd inserts an expression at the end of a list, before trailing comments.
  243. func InsertAtEnd(stmt []build.Expr, expr build.Expr) []build.Expr {
  244. var i int
  245. for i = len(stmt) - 1; i >= 0; i-- {
  246. _, ok := stmt[i].(*build.CommentBlock)
  247. if !ok {
  248. break
  249. }
  250. }
  251. return InsertAfter(i, stmt, expr)
  252. }
  253. // FindRuleByName returns the rule in the file that has the given name.
  254. // If the name is "__pkg__", it returns the global package declaration.
  255. func FindRuleByName(f *build.File, name string) *build.Rule {
  256. if name == "__pkg__" {
  257. return PackageDeclaration(f)
  258. }
  259. _, rule := IndexOfRuleByName(f, name)
  260. return rule
  261. }
  262. // IndexOfRuleByName returns the index (in f.Stmt) of the CallExpr which defines a rule named `name`, or -1 if it doesn't exist.
  263. func IndexOfRuleByName(f *build.File, name string) (int, *build.Rule) {
  264. linenum := -1
  265. if strings.HasPrefix(name, "%") {
  266. // "%<LINENUM>" will match the rule which begins at LINENUM.
  267. // This is for convenience, "%" is not a valid character in bazel targets.
  268. if result, err := strconv.Atoi(name[1:]); err == nil {
  269. linenum = result
  270. }
  271. }
  272. for i, stmt := range f.Stmt {
  273. call, ok := stmt.(*build.CallExpr)
  274. if !ok {
  275. continue
  276. }
  277. r := f.Rule(call)
  278. start, _ := call.X.Span()
  279. if r.Name() == name || start.Line == linenum {
  280. return i, r
  281. }
  282. }
  283. return -1, nil
  284. }
  285. // FindExportedFile returns the first exports_files call which contains the
  286. // file 'name', or nil if not found
  287. func FindExportedFile(f *build.File, name string) *build.Rule {
  288. for _, r := range f.Rules("exports_files") {
  289. if len(r.Call.List) == 0 {
  290. continue
  291. }
  292. pkg := "" // Files are not affected by the package name
  293. if ListFind(r.Call.List[0], name, pkg) != nil {
  294. return r
  295. }
  296. }
  297. return nil
  298. }
  299. // DeleteRule returns the AST without the specified rule
  300. func DeleteRule(f *build.File, rule *build.Rule) *build.File {
  301. var all []build.Expr
  302. for _, stmt := range f.Stmt {
  303. if stmt == rule.Call {
  304. continue
  305. }
  306. all = append(all, stmt)
  307. }
  308. return &build.File{Path: f.Path, Comments: f.Comments, Stmt: all}
  309. }
  310. // DeleteRuleByName returns the AST without the rules that have the
  311. // given name.
  312. func DeleteRuleByName(f *build.File, name string) *build.File {
  313. var all []build.Expr
  314. for _, stmt := range f.Stmt {
  315. call, ok := stmt.(*build.CallExpr)
  316. if !ok {
  317. all = append(all, stmt)
  318. continue
  319. }
  320. r := f.Rule(call)
  321. if r.Name() != name {
  322. all = append(all, stmt)
  323. }
  324. }
  325. return &build.File{Path: f.Path, Comments: f.Comments, Stmt: all}
  326. }
  327. // DeleteRuleByKind removes the rules of the specified kind from the AST.
  328. // Returns an updated copy of f.
  329. func DeleteRuleByKind(f *build.File, kind string) *build.File {
  330. var all []build.Expr
  331. for _, stmt := range f.Stmt {
  332. call, ok := stmt.(*build.CallExpr)
  333. if !ok {
  334. all = append(all, stmt)
  335. continue
  336. }
  337. k, ok := call.X.(*build.LiteralExpr)
  338. if !ok || k.Token != kind {
  339. all = append(all, stmt)
  340. }
  341. }
  342. return &build.File{Path: f.Path, Comments: f.Comments, Stmt: all}
  343. }
  344. // AllLists returns all the lists concatenated in an expression.
  345. // For example, in: glob(["*.go"]) + [":rule"]
  346. // the function will return [[":rule"]].
  347. func AllLists(e build.Expr) []*build.ListExpr {
  348. switch e := e.(type) {
  349. case *build.ListExpr:
  350. return []*build.ListExpr{e}
  351. case *build.BinaryExpr:
  352. if e.Op == "+" {
  353. return append(AllLists(e.X), AllLists(e.Y)...)
  354. }
  355. }
  356. return nil
  357. }
  358. // FirstList works in the same way as AllLists, except that it
  359. // returns only one list, or nil.
  360. func FirstList(e build.Expr) *build.ListExpr {
  361. switch e := e.(type) {
  362. case *build.ListExpr:
  363. return e
  364. case *build.BinaryExpr:
  365. if e.Op == "+" {
  366. li := FirstList(e.X)
  367. if li == nil {
  368. return FirstList(e.Y)
  369. }
  370. return li
  371. }
  372. }
  373. return nil
  374. }
  375. // AllStrings returns all the string literals concatenated in an expression.
  376. // For example, in: "foo" + x + "bar"
  377. // the function will return ["foo", "bar"].
  378. func AllStrings(e build.Expr) []*build.StringExpr {
  379. switch e := e.(type) {
  380. case *build.StringExpr:
  381. return []*build.StringExpr{e}
  382. case *build.BinaryExpr:
  383. if e.Op == "+" {
  384. return append(AllStrings(e.X), AllStrings(e.Y)...)
  385. }
  386. }
  387. return nil
  388. }
  389. // ListFind looks for a string in the list expression (which may be a
  390. // concatenation of lists). It returns the element if it is found. nil
  391. // otherwise.
  392. func ListFind(e build.Expr, item string, pkg string) *build.StringExpr {
  393. item = ShortenLabel(item, pkg)
  394. for _, li := range AllLists(e) {
  395. for _, elem := range li.List {
  396. str, ok := elem.(*build.StringExpr)
  397. if ok && LabelsEqual(str.Value, item, pkg) {
  398. return str
  399. }
  400. }
  401. }
  402. return nil
  403. }
  404. // hasComments returns whether the StringExpr literal has a comment attached to it.
  405. func hasComments(literal *build.StringExpr) bool {
  406. return len(literal.Before) > 0 || len(literal.Suffix) > 0
  407. }
  408. // ContainsComments returns whether the expr has a comment that includes str.
  409. func ContainsComments(expr build.Expr, str string) bool {
  410. str = strings.ToLower(str)
  411. com := expr.Comment()
  412. comments := append(com.Before, com.Suffix...)
  413. comments = append(comments, com.After...)
  414. for _, c := range comments {
  415. if strings.Contains(strings.ToLower(c.Token), str) {
  416. return true
  417. }
  418. }
  419. return false
  420. }
  421. // ListDelete deletes the item from a list expression in e and returns
  422. // the StringExpr deleted, or nil otherwise.
  423. func ListDelete(e build.Expr, item, pkg string) (deleted *build.StringExpr) {
  424. deleted = nil
  425. item = ShortenLabel(item, pkg)
  426. for _, li := range AllLists(e) {
  427. var all []build.Expr
  428. for _, elem := range li.List {
  429. if str, ok := elem.(*build.StringExpr); ok {
  430. if LabelsEqual(str.Value, item, pkg) && (DeleteWithComments || !hasComments(str)) {
  431. deleted = str
  432. continue
  433. }
  434. }
  435. all = append(all, elem)
  436. }
  437. li.List = all
  438. }
  439. return deleted
  440. }
  441. // ListAttributeDelete deletes string item from list attribute attr, deletes attr if empty,
  442. // and returns the StringExpr deleted, or nil otherwise.
  443. func ListAttributeDelete(rule *build.Rule, attr, item, pkg string) *build.StringExpr {
  444. deleted := ListDelete(rule.Attr(attr), item, pkg)
  445. if deleted != nil {
  446. if listExpr, ok := rule.Attr(attr).(*build.ListExpr); ok && len(listExpr.List) == 0 {
  447. rule.DelAttr(attr)
  448. }
  449. }
  450. return deleted
  451. }
  452. // ListReplace replaces old with value in all lists in e and returns a Boolean
  453. // to indicate whether the replacement was successful.
  454. func ListReplace(e build.Expr, old, value, pkg string) bool {
  455. replaced := false
  456. old = ShortenLabel(old, pkg)
  457. for _, li := range AllLists(e) {
  458. for k, elem := range li.List {
  459. str, ok := elem.(*build.StringExpr)
  460. if !ok || !LabelsEqual(str.Value, old, pkg) {
  461. continue
  462. }
  463. li.List[k] = &build.StringExpr{Value: ShortenLabel(value, pkg), Comments: *elem.Comment()}
  464. replaced = true
  465. }
  466. }
  467. return replaced
  468. }
  469. // ListSubstitute replaces strings matching a regular expression in all lists
  470. // in e and returns a Boolean to indicate whether the replacement was
  471. // successful.
  472. func ListSubstitute(e build.Expr, oldRegexp *regexp.Regexp, newTemplate string) bool {
  473. substituted := false
  474. for _, li := range AllLists(e) {
  475. for k, elem := range li.List {
  476. str, ok := elem.(*build.StringExpr)
  477. if !ok {
  478. continue
  479. }
  480. newValue, ok := stringSubstitute(str.Value, oldRegexp, newTemplate)
  481. if ok {
  482. li.List[k] = &build.StringExpr{Value: newValue, Comments: *elem.Comment()}
  483. substituted = true
  484. }
  485. }
  486. }
  487. return substituted
  488. }
  489. func stringSubstitute(oldValue string, oldRegexp *regexp.Regexp, newTemplate string) (string, bool) {
  490. match := oldRegexp.FindStringSubmatchIndex(oldValue)
  491. if match == nil {
  492. return oldValue, false
  493. }
  494. newValue := string(oldRegexp.ExpandString(nil, newTemplate, oldValue, match))
  495. if match[0] > 0 {
  496. newValue = oldValue[:match[0]] + newValue
  497. }
  498. if match[1] < len(oldValue) {
  499. newValue = newValue + oldValue[match[1]:]
  500. }
  501. return newValue, true
  502. }
  503. // isExprLessThan compares two Expr statements. Currently, only labels are supported.
  504. func isExprLessThan(x1, x2 build.Expr) bool {
  505. str1, ok1 := x1.(*build.StringExpr)
  506. str2, ok2 := x2.(*build.StringExpr)
  507. if ok1 != ok2 {
  508. return ok2
  509. }
  510. if ok1 && ok2 {
  511. // Labels starting with // are put at the end.
  512. pre1 := strings.HasPrefix(str1.Value, "//")
  513. pre2 := strings.HasPrefix(str2.Value, "//")
  514. if pre1 != pre2 {
  515. return pre2
  516. }
  517. return str1.Value < str2.Value
  518. }
  519. return false
  520. }
  521. func sortedInsert(list []build.Expr, item build.Expr) []build.Expr {
  522. i := 0
  523. for ; i < len(list); i++ {
  524. if isExprLessThan(item, list[i]) {
  525. break
  526. }
  527. }
  528. res := make([]build.Expr, 0, len(list)+1)
  529. res = append(res, list[:i]...)
  530. res = append(res, item)
  531. res = append(res, list[i:]...)
  532. return res
  533. }
  534. // attributeMustNotBeSorted returns true if the list in the attribute cannot be
  535. // sorted. For some attributes, it makes sense to try to do a sorted insert
  536. // (e.g. deps), even when buildifier will not sort it for conservative reasons.
  537. // For a few attributes, sorting will never make sense.
  538. func attributeMustNotBeSorted(rule, attr string) bool {
  539. // TODO(bazel-team): Come up with a more complete list.
  540. return attr == "args"
  541. }
  542. // getVariable returns the binary expression that assignes a variable to expr, if expr is
  543. // an identifier of a variable that vars contains a mapping for.
  544. func getVariable(expr build.Expr, vars *map[string]*build.BinaryExpr) (varAssignment *build.BinaryExpr) {
  545. if vars == nil {
  546. return nil
  547. }
  548. if literal, ok := expr.(*build.LiteralExpr); ok {
  549. if varAssignment = (*vars)[literal.Token]; varAssignment != nil {
  550. return varAssignment
  551. }
  552. }
  553. return nil
  554. }
  555. // AddValueToList adds a value to a list. If the expression is
  556. // not a list, a list with a single element is appended to the original
  557. // expression.
  558. func AddValueToList(oldList build.Expr, pkg string, item build.Expr, sorted bool) build.Expr {
  559. if oldList == nil {
  560. return &build.ListExpr{List: []build.Expr{item}}
  561. }
  562. str, ok := item.(*build.StringExpr)
  563. if ok && ListFind(oldList, str.Value, pkg) != nil {
  564. // The value is already in the list.
  565. return oldList
  566. }
  567. li := FirstList(oldList)
  568. if li != nil {
  569. if sorted {
  570. li.List = sortedInsert(li.List, item)
  571. } else {
  572. li.List = append(li.List, item)
  573. }
  574. return oldList
  575. }
  576. list := &build.ListExpr{List: []build.Expr{item}}
  577. concat := &build.BinaryExpr{Op: "+", X: oldList, Y: list}
  578. return concat
  579. }
  580. // AddValueToListAttribute adds the given item to the list attribute identified by name and pkg.
  581. func AddValueToListAttribute(r *build.Rule, name string, pkg string, item build.Expr, vars *map[string]*build.BinaryExpr) {
  582. old := r.Attr(name)
  583. sorted := !attributeMustNotBeSorted(r.Kind(), name)
  584. if varAssignment := getVariable(old, vars); varAssignment != nil {
  585. varAssignment.Y = AddValueToList(varAssignment.Y, pkg, item, sorted)
  586. } else {
  587. r.SetAttr(name, AddValueToList(old, pkg, item, sorted))
  588. }
  589. }
  590. // MoveAllListAttributeValues moves all values from list attribute oldAttr to newAttr,
  591. // and deletes oldAttr.
  592. func MoveAllListAttributeValues(rule *build.Rule, oldAttr, newAttr, pkg string, vars *map[string]*build.BinaryExpr) error {
  593. if rule.Attr(oldAttr) == nil {
  594. return fmt.Errorf("no attribute %s found in %s", oldAttr, rule.Name())
  595. }
  596. if rule.Attr(newAttr) == nil {
  597. RenameAttribute(rule, oldAttr, newAttr)
  598. return nil
  599. }
  600. if listExpr, ok := rule.Attr(oldAttr).(*build.ListExpr); ok {
  601. for _, val := range listExpr.List {
  602. AddValueToListAttribute(rule, newAttr, pkg, val, vars)
  603. }
  604. rule.DelAttr(oldAttr)
  605. return nil
  606. }
  607. return fmt.Errorf("%s already exists and %s is not a simple list", newAttr, oldAttr)
  608. }
  609. // DictionarySet looks for the key in the dictionary expression. If value is not nil,
  610. // it replaces the current value with it. In all cases, it returns the current value.
  611. func DictionarySet(dict *build.DictExpr, key string, value build.Expr) build.Expr {
  612. for _, e := range dict.List {
  613. kv, _ := e.(*build.KeyValueExpr)
  614. if k, ok := kv.Key.(*build.StringExpr); ok && k.Value == key {
  615. if value != nil {
  616. kv.Value = value
  617. }
  618. return kv.Value
  619. }
  620. }
  621. if value != nil {
  622. kv := &build.KeyValueExpr{Key: &build.StringExpr{Value: key}, Value: value}
  623. dict.List = append(dict.List, kv)
  624. }
  625. return nil
  626. }
  627. // RenameAttribute renames an attribute in a rule.
  628. func RenameAttribute(r *build.Rule, oldName, newName string) error {
  629. if r.Attr(newName) != nil {
  630. return fmt.Errorf("attribute %s already exists in rule %s", newName, r.Name())
  631. }
  632. for _, kv := range r.Call.List {
  633. as, ok := kv.(*build.BinaryExpr)
  634. if !ok || as.Op != "=" {
  635. continue
  636. }
  637. k, ok := as.X.(*build.LiteralExpr)
  638. if !ok || k.Token != oldName {
  639. continue
  640. }
  641. k.Token = newName
  642. return nil
  643. }
  644. return fmt.Errorf("no attribute %s found in rule %s", oldName, r.Name())
  645. }
  646. // EditFunction is a wrapper around build.Edit. The callback is called only on
  647. // functions 'name'.
  648. func EditFunction(v build.Expr, name string, f func(x *build.CallExpr, stk []build.Expr) build.Expr) build.Expr {
  649. return build.Edit(v, func(expr build.Expr, stk []build.Expr) build.Expr {
  650. call, ok := expr.(*build.CallExpr)
  651. if !ok {
  652. return nil
  653. }
  654. fct, ok := call.X.(*build.LiteralExpr)
  655. if !ok || fct.Token != name {
  656. return nil
  657. }
  658. return f(call, stk)
  659. })
  660. }
  661. // UsedSymbols returns the set of symbols used in the BUILD file (variables, function names).
  662. func UsedSymbols(f *build.File) map[string]bool {
  663. symbols := make(map[string]bool)
  664. build.Walk(f, func(expr build.Expr, stack []build.Expr) {
  665. literal, ok := expr.(*build.LiteralExpr)
  666. if !ok {
  667. return
  668. }
  669. // Check if we are on the left-side of an assignment
  670. for _, e := range stack {
  671. if as, ok := e.(*build.BinaryExpr); ok {
  672. if as.Op == "=" && as.X == expr {
  673. return
  674. }
  675. }
  676. }
  677. symbols[literal.Token] = true
  678. })
  679. return symbols
  680. }
  681. func newLoad(args []string) *build.CallExpr {
  682. load := &build.CallExpr{
  683. X: &build.LiteralExpr{
  684. Token: "load",
  685. },
  686. List: []build.Expr{},
  687. ForceCompact: true,
  688. }
  689. for _, a := range args {
  690. load.List = append(load.List, &build.StringExpr{Value: a})
  691. }
  692. return load
  693. }
  694. // appendLoad tries to find an existing load location and append symbols to it.
  695. func appendLoad(stmts []build.Expr, args []string) bool {
  696. if len(args) == 0 {
  697. return false
  698. }
  699. location := args[0]
  700. symbolsToLoad := make(map[string]bool)
  701. for _, s := range args[1:] {
  702. symbolsToLoad[s] = true
  703. }
  704. var lastLoad *build.CallExpr
  705. for _, s := range stmts {
  706. call, ok := s.(*build.CallExpr)
  707. if !ok {
  708. continue
  709. }
  710. if l, ok := call.X.(*build.LiteralExpr); !ok || l.Token != "load" {
  711. continue
  712. }
  713. if len(call.List) < 2 {
  714. continue
  715. }
  716. if s, ok := call.List[0].(*build.StringExpr); !ok || s.Value != location {
  717. continue // Loads a different file.
  718. }
  719. for _, arg := range call.List[1:] {
  720. if s, ok := arg.(*build.StringExpr); ok {
  721. delete(symbolsToLoad, s.Value) // Already loaded.
  722. }
  723. }
  724. // Remember the last insert location, but potentially remove more symbols
  725. // that are already loaded in other subsequent calls.
  726. lastLoad = call
  727. }
  728. if lastLoad == nil {
  729. return false
  730. }
  731. // Append the remaining loads to the last load location.
  732. sortedSymbols := []string{}
  733. for s := range symbolsToLoad {
  734. sortedSymbols = append(sortedSymbols, s)
  735. }
  736. sort.Strings(sortedSymbols)
  737. for _, s := range sortedSymbols {
  738. lastLoad.List = append(lastLoad.List, &build.StringExpr{Value: s})
  739. }
  740. return true
  741. }
  742. // InsertLoad inserts a load statement at the top of the list of statements.
  743. // The load statement is constructed using args. Symbols that are already loaded
  744. // from the given filepath are ignored. If stmts already contains a load for the
  745. // location in arguments, appends the symbols to load to it.
  746. func InsertLoad(stmts []build.Expr, args []string) []build.Expr {
  747. if appendLoad(stmts, args) {
  748. return stmts
  749. }
  750. load := newLoad(args)
  751. var all []build.Expr
  752. added := false
  753. for _, stmt := range stmts {
  754. _, isComment := stmt.(*build.CommentBlock)
  755. if isComment || added {
  756. all = append(all, stmt)
  757. continue
  758. }
  759. all = append(all, load)
  760. all = append(all, stmt)
  761. added = true
  762. }
  763. if !added { // Empty file or just comments.
  764. all = append(all, load)
  765. }
  766. return all
  767. }