fix.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  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. // Functions to clean and fix BUILD files
  14. package edit
  15. import (
  16. "regexp"
  17. "sort"
  18. "strings"
  19. "github.com/bazelbuild/buildtools/build"
  20. )
  21. // splitOptionsWithSpaces is a cleanup function.
  22. // It splits options strings that contain a space. This change
  23. // should be safe as Blaze is splitting those strings, but we will
  24. // eventually get rid of this misfeature.
  25. // eg. it converts from:
  26. // copts = ["-Dfoo -Dbar"]
  27. // to:
  28. // copts = ["-Dfoo", "-Dbar"]
  29. func splitOptionsWithSpaces(_ *build.File, r *build.Rule, _ string) bool {
  30. var attrToRewrite = []string{
  31. "copts",
  32. "linkopts",
  33. }
  34. fixed := false
  35. for _, attrName := range attrToRewrite {
  36. attr := r.Attr(attrName)
  37. if attr != nil {
  38. for _, li := range AllLists(attr) {
  39. fixed = splitStrings(li) || fixed
  40. }
  41. }
  42. }
  43. return fixed
  44. }
  45. func splitStrings(list *build.ListExpr) bool {
  46. var all []build.Expr
  47. fixed := false
  48. for _, e := range list.List {
  49. str, ok := e.(*build.StringExpr)
  50. if !ok {
  51. all = append(all, e)
  52. continue
  53. }
  54. if strings.Contains(str.Value, " ") && !strings.Contains(str.Value, "'\"") {
  55. fixed = true
  56. for i, substr := range strings.Fields(str.Value) {
  57. item := &build.StringExpr{Value: substr}
  58. if i == 0 {
  59. item.Comments = str.Comments
  60. }
  61. all = append(all, item)
  62. }
  63. } else {
  64. all = append(all, str)
  65. }
  66. }
  67. list.List = all
  68. return fixed
  69. }
  70. // shortenLabels rewrites the labels in the rule using the short notation.
  71. func shortenLabels(_ *build.File, r *build.Rule, pkg string) bool {
  72. fixed := false
  73. for _, attr := range r.AttrKeys() {
  74. e := r.Attr(attr)
  75. if !ContainsLabels(attr) {
  76. continue
  77. }
  78. for _, li := range AllLists(e) {
  79. for _, elem := range li.List {
  80. str, ok := elem.(*build.StringExpr)
  81. if ok && str.Value != ShortenLabel(str.Value, pkg) {
  82. str.Value = ShortenLabel(str.Value, pkg)
  83. fixed = true
  84. }
  85. }
  86. }
  87. }
  88. return fixed
  89. }
  90. // removeVisibility removes useless visibility attributes.
  91. func removeVisibility(f *build.File, r *build.Rule, pkg string) bool {
  92. pkgDecl := PackageDeclaration(f)
  93. defaultVisibility := pkgDecl.AttrStrings("default_visibility")
  94. // If no default_visibility is given, it is implicitly private.
  95. if len(defaultVisibility) == 0 {
  96. defaultVisibility = []string{"//visibility:private"}
  97. }
  98. visibility := r.AttrStrings("visibility")
  99. if len(visibility) == 0 || len(visibility) != len(defaultVisibility) {
  100. return false
  101. }
  102. sort.Strings(defaultVisibility)
  103. sort.Strings(visibility)
  104. for i, vis := range visibility {
  105. if vis != defaultVisibility[i] {
  106. return false
  107. }
  108. }
  109. r.DelAttr("visibility")
  110. return true
  111. }
  112. // removeTestOnly removes the useless testonly attributes.
  113. func removeTestOnly(f *build.File, r *build.Rule, pkg string) bool {
  114. pkgDecl := PackageDeclaration(f)
  115. def := strings.HasSuffix(r.Kind(), "_test") || r.Kind() == "test_suite"
  116. if !def {
  117. if pkgDecl.Attr("default_testonly") == nil {
  118. def = strings.HasPrefix(pkg, "javatests/")
  119. } else if pkgDecl.AttrLiteral("default_testonly") == "1" {
  120. def = true
  121. } else if pkgDecl.AttrLiteral("default_testonly") != "0" {
  122. // Non-literal value: it's not safe to do a change.
  123. return false
  124. }
  125. }
  126. testonly := r.AttrLiteral("testonly")
  127. if def && testonly == "1" {
  128. r.DelAttr("testonly")
  129. return true
  130. }
  131. if !def && testonly == "0" {
  132. r.DelAttr("testonly")
  133. return true
  134. }
  135. return false
  136. }
  137. func genruleRenameDepsTools(_ *build.File, r *build.Rule, _ string) bool {
  138. return r.Kind() == "genrule" && RenameAttribute(r, "deps", "tools") == nil
  139. }
  140. // explicitHeuristicLabels adds $(location ...) for each label in the string s.
  141. func explicitHeuristicLabels(s string, labels map[string]bool) string {
  142. // Regexp comes from LABEL_CHAR_MATCHER in
  143. // java/com/google/devtools/build/lib/analysis/LabelExpander.java
  144. re := regexp.MustCompile("[a-zA-Z0-9:/_.+-]+|[^a-zA-Z0-9:/_.+-]+")
  145. parts := re.FindAllString(s, -1)
  146. changed := false
  147. canChange := true
  148. for i, part := range parts {
  149. // We don't want to add $(location when it's already present.
  150. // So we skip the next label when we see location(s).
  151. if part == "location" || part == "locations" {
  152. canChange = false
  153. }
  154. if !labels[part] {
  155. if labels[":"+part] { // leading colon is often missing
  156. part = ":" + part
  157. } else {
  158. continue
  159. }
  160. }
  161. if !canChange {
  162. canChange = true
  163. continue
  164. }
  165. parts[i] = "$(location " + part + ")"
  166. changed = true
  167. }
  168. if changed {
  169. return strings.Join(parts, "")
  170. }
  171. return s
  172. }
  173. func addLabels(r *build.Rule, attr string, labels map[string]bool) {
  174. a := r.Attr(attr)
  175. if a == nil {
  176. return
  177. }
  178. for _, li := range AllLists(a) {
  179. for _, item := range li.List {
  180. if str, ok := item.(*build.StringExpr); ok {
  181. labels[str.Value] = true
  182. }
  183. }
  184. }
  185. }
  186. // genruleFixHeuristicLabels modifies the cmd attribute of genrules, so
  187. // that they don't rely on heuristic label expansion anymore.
  188. // Label expansion is made explicit with the $(location ...) command.
  189. func genruleFixHeuristicLabels(_ *build.File, r *build.Rule, _ string) bool {
  190. if r.Kind() != "genrule" {
  191. return false
  192. }
  193. cmd := r.Attr("cmd")
  194. if cmd == nil {
  195. return false
  196. }
  197. labels := make(map[string]bool)
  198. addLabels(r, "tools", labels)
  199. addLabels(r, "srcs", labels)
  200. fixed := false
  201. for _, str := range AllStrings(cmd) {
  202. newVal := explicitHeuristicLabels(str.Value, labels)
  203. if newVal != str.Value {
  204. fixed = true
  205. str.Value = newVal
  206. }
  207. }
  208. return fixed
  209. }
  210. // sortExportsFiles sorts the first argument of exports_files if it is a list.
  211. func sortExportsFiles(_ *build.File, r *build.Rule, _ string) bool {
  212. if r.Kind() != "exports_files" || len(r.Call.List) == 0 {
  213. return false
  214. }
  215. build.SortStringList(r.Call.List[0])
  216. return true
  217. }
  218. // removeVarref replaces all varref('x') with '$(x)'.
  219. // The goal is to eventually remove varref from the build language.
  220. func removeVarref(_ *build.File, r *build.Rule, _ string) bool {
  221. fixed := false
  222. EditFunction(r.Call, "varref", func(call *build.CallExpr, stk []build.Expr) build.Expr {
  223. if len(call.List) != 1 {
  224. return nil
  225. }
  226. str, ok := (call.List[0]).(*build.StringExpr)
  227. if !ok {
  228. return nil
  229. }
  230. fixed = true
  231. str.Value = "$(" + str.Value + ")"
  232. // Preserve suffix comments from the function call
  233. str.Comment().Suffix = append(str.Comment().Suffix, call.Comment().Suffix...)
  234. return str
  235. })
  236. return fixed
  237. }
  238. // sortGlob sorts the list argument to glob.
  239. func sortGlob(_ *build.File, r *build.Rule, _ string) bool {
  240. fixed := false
  241. EditFunction(r.Call, "glob", func(call *build.CallExpr, stk []build.Expr) build.Expr {
  242. if len(call.List) == 0 {
  243. return nil
  244. }
  245. build.SortStringList(call.List[0])
  246. fixed = true
  247. return call
  248. })
  249. return fixed
  250. }
  251. func evaluateListConcatenation(expr build.Expr) build.Expr {
  252. if _, ok := expr.(*build.ListExpr); ok {
  253. return expr
  254. }
  255. bin, ok := expr.(*build.BinaryExpr)
  256. if !ok || bin.Op != "+" {
  257. return expr
  258. }
  259. li1, ok1 := evaluateListConcatenation(bin.X).(*build.ListExpr)
  260. li2, ok2 := evaluateListConcatenation(bin.Y).(*build.ListExpr)
  261. if !ok1 || !ok2 {
  262. return expr
  263. }
  264. res := *li1
  265. res.List = append(li1.List, li2.List...)
  266. return &res
  267. }
  268. // mergeLiteralLists evaluates the concatenation of two literal lists.
  269. // e.g. [1, 2] + [3, 4] -> [1, 2, 3, 4]
  270. func mergeLiteralLists(_ *build.File, r *build.Rule, _ string) bool {
  271. fixed := false
  272. build.Edit(r.Call, func(expr build.Expr, stk []build.Expr) build.Expr {
  273. newexpr := evaluateListConcatenation(expr)
  274. fixed = fixed || (newexpr != expr)
  275. return newexpr
  276. })
  277. return fixed
  278. }
  279. // usePlusEqual replaces uses of extend and append with the += operator.
  280. // e.g. foo.extend(bar) => foo += bar
  281. // foo.append(bar) => foo += [bar]
  282. func usePlusEqual(f *build.File) bool {
  283. fixed := false
  284. for i, stmt := range f.Stmt {
  285. call, ok := stmt.(*build.CallExpr)
  286. if !ok {
  287. continue
  288. }
  289. dot, ok := call.X.(*build.DotExpr)
  290. if !ok || len(call.List) != 1 {
  291. continue
  292. }
  293. obj, ok := dot.X.(*build.Ident)
  294. if !ok {
  295. continue
  296. }
  297. var fix *build.AssignExpr
  298. if dot.Name == "extend" {
  299. fix = &build.AssignExpr{LHS: obj, Op: "+=", RHS: call.List[0]}
  300. } else if dot.Name == "append" {
  301. list := &build.ListExpr{List: []build.Expr{call.List[0]}}
  302. fix = &build.AssignExpr{LHS: obj, Op: "+=", RHS: list}
  303. } else {
  304. continue
  305. }
  306. fix.Comments = call.Comments // Keep original comments
  307. f.Stmt[i] = fix
  308. fixed = true
  309. }
  310. return fixed
  311. }
  312. func isNonemptyComment(comment *build.Comments) bool {
  313. return len(comment.Before)+len(comment.Suffix)+len(comment.After) > 0
  314. }
  315. // Checks whether a load statement or any of its arguments have a comment
  316. func hasComment(load *build.LoadStmt) bool {
  317. if isNonemptyComment(load.Comment()) {
  318. return true
  319. }
  320. if isNonemptyComment(load.Module.Comment()) {
  321. return true
  322. }
  323. for i := range load.From {
  324. if isNonemptyComment(load.From[i].Comment()) || isNonemptyComment(load.To[i].Comment()) {
  325. return true
  326. }
  327. }
  328. return false
  329. }
  330. // cleanUnusedLoads removes symbols from load statements that are not used in the file.
  331. // It also cleans symbols loaded multiple times, sorts symbol list, and removes load
  332. // statements when the list is empty.
  333. func cleanUnusedLoads(f *build.File) bool {
  334. symbols := UsedSymbols(f)
  335. fixed := false
  336. var all []build.Expr
  337. for _, stmt := range f.Stmt {
  338. load, ok := stmt.(*build.LoadStmt)
  339. if !ok || hasComment(load) {
  340. all = append(all, stmt)
  341. continue
  342. }
  343. var fromSymbols, toSymbols []*build.Ident
  344. for i := range load.From {
  345. fromSymbol := load.From[i]
  346. toSymbol := load.To[i]
  347. if symbols[toSymbol.Name] {
  348. // The symbol is actually used
  349. fromSymbols = append(fromSymbols, fromSymbol)
  350. toSymbols = append(toSymbols, toSymbol)
  351. // If the same symbol is loaded twice, we'll remove it.
  352. delete(symbols, toSymbol.Name)
  353. } else {
  354. fixed = true
  355. }
  356. }
  357. if len(toSymbols) > 0 { // Keep the load statement if it loads at least one symbol.
  358. sort.Sort(loadArgs{fromSymbols, toSymbols})
  359. load.From = fromSymbols
  360. load.To = toSymbols
  361. all = append(all, load)
  362. } else {
  363. fixed = true
  364. }
  365. }
  366. f.Stmt = all
  367. return fixed
  368. }
  369. // movePackageDeclarationToTheTop ensures that the call to package() is done
  370. // before everything else (except comments).
  371. func movePackageDeclarationToTheTop(f *build.File) bool {
  372. pkg := ExistingPackageDeclaration(f)
  373. if pkg == nil {
  374. return false
  375. }
  376. all := []build.Expr{}
  377. inserted := false // true when the package declaration has been inserted
  378. for _, stmt := range f.Stmt {
  379. _, isComment := stmt.(*build.CommentBlock)
  380. _, isString := stmt.(*build.StringExpr) // typically a docstring
  381. _, isAssignExpr := stmt.(*build.AssignExpr) // e.g. variable declaration
  382. _, isLoad := stmt.(*build.LoadStmt)
  383. if isComment || isString || isAssignExpr || isLoad {
  384. all = append(all, stmt)
  385. continue
  386. }
  387. if stmt == pkg.Call {
  388. if inserted {
  389. // remove the old package
  390. continue
  391. }
  392. return false // the file was ok
  393. }
  394. if !inserted {
  395. all = append(all, pkg.Call)
  396. inserted = true
  397. }
  398. all = append(all, stmt)
  399. }
  400. f.Stmt = all
  401. return true
  402. }
  403. // moveToPackage is an auxilliary function used by moveLicensesAndDistribs.
  404. // The function shouldn't appear more than once in the file (depot cleanup has
  405. // been done).
  406. func moveToPackage(f *build.File, attrname string) bool {
  407. var all []build.Expr
  408. fixed := false
  409. for _, stmt := range f.Stmt {
  410. rule, ok := ExprToRule(stmt, attrname)
  411. if !ok || len(rule.Call.List) != 1 {
  412. all = append(all, stmt)
  413. continue
  414. }
  415. pkgDecl := PackageDeclaration(f)
  416. pkgDecl.SetAttr(attrname, rule.Call.List[0])
  417. pkgDecl.AttrDefn(attrname).Comments = *stmt.Comment()
  418. fixed = true
  419. }
  420. f.Stmt = all
  421. return fixed
  422. }
  423. // moveLicensesAndDistribs replaces the 'licenses' and 'distribs' functions
  424. // with an attribute in package.
  425. // Before: licenses(["notice"])
  426. // After: package(licenses = ["notice"])
  427. func moveLicensesAndDistribs(f *build.File) bool {
  428. fixed1 := moveToPackage(f, "licenses")
  429. fixed2 := moveToPackage(f, "distribs")
  430. return fixed1 || fixed2
  431. }
  432. // AllRuleFixes is a list of all Buildozer fixes that can be applied on a rule.
  433. var AllRuleFixes = []struct {
  434. Name string
  435. Fn func(file *build.File, rule *build.Rule, pkg string) bool
  436. Message string
  437. }{
  438. {"sortGlob", sortGlob,
  439. "Sort the list in a call to glob"},
  440. {"splitOptions", splitOptionsWithSpaces,
  441. "Each option should be given separately in the list"},
  442. {"shortenLabels", shortenLabels,
  443. "Style: Use the canonical label notation"},
  444. {"removeVisibility", removeVisibility,
  445. "This visibility attribute is useless (it corresponds to the default value)"},
  446. {"removeTestOnly", removeTestOnly,
  447. "This testonly attribute is useless (it corresponds to the default value)"},
  448. {"genruleRenameDepsTools", genruleRenameDepsTools,
  449. "'deps' attribute in genrule has been renamed 'tools'"},
  450. {"genruleFixHeuristicLabels", genruleFixHeuristicLabels,
  451. "$(location) should be called explicitely"},
  452. {"sortExportsFiles", sortExportsFiles,
  453. "Files in exports_files should be sorted"},
  454. {"varref", removeVarref,
  455. "All varref('foo') should be replaced with '$foo'"},
  456. {"mergeLiteralLists", mergeLiteralLists,
  457. "Remove useless list concatenation"},
  458. }
  459. // FileLevelFixes is a list of all Buildozer fixes that apply on the whole file.
  460. var FileLevelFixes = []struct {
  461. Name string
  462. Fn func(file *build.File) bool
  463. Message string
  464. }{
  465. {"movePackageToTop", movePackageDeclarationToTheTop,
  466. "The package declaration should be the first rule in a file"},
  467. {"usePlusEqual", usePlusEqual,
  468. "Prefer '+=' over 'extend' or 'append'"},
  469. {"unusedLoads", cleanUnusedLoads,
  470. "Remove unused symbols from load statements"},
  471. {"moveLicensesAndDistribs", moveLicensesAndDistribs,
  472. "Move licenses and distribs to the package function"},
  473. }
  474. // FixRule aims to fix errors in BUILD files, remove deprecated features, and
  475. // simplify the code.
  476. func FixRule(f *build.File, pkg string, rule *build.Rule, fixes []string) *build.File {
  477. fixesAsMap := make(map[string]bool)
  478. for _, fix := range fixes {
  479. fixesAsMap[fix] = true
  480. }
  481. fixed := false
  482. for _, fix := range AllRuleFixes {
  483. if len(fixes) == 0 || fixesAsMap[fix.Name] {
  484. fixed = fix.Fn(f, rule, pkg) || fixed
  485. }
  486. }
  487. if !fixed {
  488. return nil
  489. }
  490. return f
  491. }
  492. // FixFile fixes everything it can in the BUILD file.
  493. func FixFile(f *build.File, pkg string, fixes []string) *build.File {
  494. fixesAsMap := make(map[string]bool)
  495. for _, fix := range fixes {
  496. fixesAsMap[fix] = true
  497. }
  498. fixed := false
  499. for _, rule := range f.Rules("") {
  500. res := FixRule(f, pkg, rule, fixes)
  501. if res != nil {
  502. fixed = true
  503. f = res
  504. }
  505. }
  506. for _, fix := range FileLevelFixes {
  507. if len(fixes) == 0 || fixesAsMap[fix.Name] {
  508. fixed = fix.Fn(f) || fixed
  509. }
  510. }
  511. if !fixed {
  512. return nil
  513. }
  514. return f
  515. }
  516. // A wrapper for a LoadStmt's From and To slices for consistent sorting of their contents.
  517. // It's assumed that the following slices have the same length, the contents are sorted by
  518. // the `To` attribute, the items of `From` are swapped exactly the same way as the items of `To`.
  519. type loadArgs struct {
  520. From []*build.Ident
  521. To []*build.Ident
  522. }
  523. func (args loadArgs) Len() int {
  524. return len(args.From)
  525. }
  526. func (args loadArgs) Swap(i, j int) {
  527. args.From[i], args.From[j] = args.From[j], args.From[i]
  528. args.To[i], args.To[j] = args.To[j], args.To[i]
  529. }
  530. func (args loadArgs) Less(i, j int) bool {
  531. return args.To[i].Name < args.To[j].Name
  532. }