buildozer.go 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148
  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. // Buildozer is a tool for programmatically editing BUILD files.
  14. package edit
  15. import (
  16. "bufio"
  17. "bytes"
  18. "errors"
  19. "fmt"
  20. "io"
  21. "io/ioutil"
  22. "log"
  23. "os"
  24. "os/exec"
  25. "path"
  26. "path/filepath"
  27. "regexp"
  28. "runtime"
  29. "strconv"
  30. "strings"
  31. apipb "github.com/bazelbuild/buildtools/api_proto"
  32. "github.com/bazelbuild/buildtools/build"
  33. "github.com/bazelbuild/buildtools/file"
  34. "github.com/golang/protobuf/proto"
  35. )
  36. // Options represents choices about how buildozer should behave.
  37. type Options struct {
  38. Stdout bool // write changed BUILD file to stdout
  39. Buildifier string // path to buildifier binary
  40. Parallelism int // number of cores to use for concurrent actions
  41. NumIO int // number of concurrent actions
  42. CommandsFile string // file name to read commands from, use '-' for stdin (format:|-separated command line arguments to buildozer, excluding flags
  43. KeepGoing bool // apply all commands, even if there are failures
  44. FilterRuleTypes []string // list of rule types to change, empty means all
  45. PreferEOLComments bool // when adding a new comment, put it on the same line if possible
  46. RootDir string // If present, use this folder rather than $PWD to find the root dir
  47. Quiet bool // suppress informational messages.
  48. EditVariables bool // for attributes that simply assign a variable (e.g. hdrs = LIB_HDRS), edit the build variable instead of appending to the attribute.
  49. IsPrintingProto bool // output serialized devtools.buildozer.Output protos instead of human-readable strings
  50. }
  51. // NewOpts returns a new Options struct with some defaults set.
  52. func NewOpts() *Options {
  53. return &Options{NumIO: 200, PreferEOLComments: true}
  54. }
  55. // Usage is a user-overridden func to print the program usage.
  56. var Usage = func() {}
  57. var fileModified = false // set to true when a file has been fixed
  58. const stdinPackageName = "-" // the special package name to represent stdin
  59. // CmdEnvironment stores the information the commands below have access to.
  60. type CmdEnvironment struct {
  61. File *build.File // the AST
  62. Rule *build.Rule // the rule to modify
  63. Vars map[string]*build.AssignExpr // global variables set in the build file
  64. Pkg string // the full package name
  65. Args []string // the command-line arguments
  66. output *apipb.Output_Record // output proto, stores whatever a command wants to print
  67. }
  68. // The cmdXXX functions implement the various commands.
  69. func cmdAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
  70. attr := env.Args[0]
  71. for _, val := range env.Args[1:] {
  72. if IsIntList(attr) {
  73. AddValueToListAttribute(env.Rule, attr, env.Pkg, &build.LiteralExpr{Token: val}, &env.Vars)
  74. continue
  75. }
  76. strVal := getStringExpr(val, env.Pkg)
  77. AddValueToListAttribute(env.Rule, attr, env.Pkg, strVal, &env.Vars)
  78. }
  79. ResolveAttr(env.Rule, attr, env.Pkg)
  80. return env.File, nil
  81. }
  82. func cmdComment(opts *Options, env CmdEnvironment) (*build.File, error) {
  83. // The comment string is always the last argument in the list.
  84. str := env.Args[len(env.Args)-1]
  85. str = strings.Replace(str, "\\n", "\n", -1)
  86. // Multiline comments should go on a separate line.
  87. fullLine := !opts.PreferEOLComments || strings.Contains(str, "\n")
  88. comment := []build.Comment{}
  89. for _, line := range strings.Split(str, "\n") {
  90. comment = append(comment, build.Comment{Token: "# " + line})
  91. }
  92. // The comment might be attached to a rule, an attribute, or a value in a list,
  93. // depending on how many arguments are passed.
  94. switch len(env.Args) {
  95. case 1: // Attach to a rule
  96. env.Rule.Call.Comments.Before = comment
  97. case 2: // Attach to an attribute
  98. if attr := env.Rule.AttrDefn(env.Args[0]); attr != nil {
  99. if fullLine {
  100. attr.LHS.Comment().Before = comment
  101. } else {
  102. attr.RHS.Comment().Suffix = comment
  103. }
  104. }
  105. case 3: // Attach to a specific value in a list
  106. if attr := env.Rule.Attr(env.Args[0]); attr != nil {
  107. if expr := ListFind(attr, env.Args[1], env.Pkg); expr != nil {
  108. if fullLine {
  109. expr.Comments.Before = comment
  110. } else {
  111. expr.Comments.Suffix = comment
  112. }
  113. }
  114. }
  115. default:
  116. panic("cmdComment")
  117. }
  118. return env.File, nil
  119. }
  120. // commentsText concatenates comments into a single line.
  121. func commentsText(comments []build.Comment) string {
  122. var segments []string
  123. for _, comment := range comments {
  124. token := comment.Token
  125. if strings.HasPrefix(token, "#") {
  126. token = token[1:]
  127. }
  128. segments = append(segments, strings.TrimSpace(token))
  129. }
  130. return strings.Replace(strings.Join(segments, " "), "\n", " ", -1)
  131. }
  132. func cmdPrintComment(opts *Options, env CmdEnvironment) (*build.File, error) {
  133. attrError := func() error {
  134. return fmt.Errorf("rule \"//%s:%s\" has no attribute \"%s\"", env.Pkg, env.Rule.Name(), env.Args[0])
  135. }
  136. switch len(env.Args) {
  137. case 0: // Print rule comment.
  138. env.output.Fields = []*apipb.Output_Record_Field{
  139. {Value: &apipb.Output_Record_Field_Text{commentsText(env.Rule.Call.Comments.Before)}},
  140. }
  141. case 1: // Print attribute comment.
  142. attr := env.Rule.AttrDefn(env.Args[0])
  143. if attr == nil {
  144. return nil, attrError()
  145. }
  146. comments := append(attr.Before, attr.Suffix...)
  147. env.output.Fields = []*apipb.Output_Record_Field{
  148. {Value: &apipb.Output_Record_Field_Text{commentsText(comments)}},
  149. }
  150. case 2: // Print comment of a specific value in a list.
  151. attr := env.Rule.Attr(env.Args[0])
  152. if attr == nil {
  153. return nil, attrError()
  154. }
  155. value := env.Args[1]
  156. expr := ListFind(attr, value, env.Pkg)
  157. if expr == nil {
  158. return nil, fmt.Errorf("attribute \"%s\" has no value \"%s\"", env.Args[0], value)
  159. }
  160. comments := append(expr.Comments.Before, expr.Comments.Suffix...)
  161. env.output.Fields = []*apipb.Output_Record_Field{
  162. {Value: &apipb.Output_Record_Field_Text{commentsText(comments)}},
  163. }
  164. default:
  165. panic("cmdPrintComment")
  166. }
  167. return nil, nil
  168. }
  169. func cmdDelete(opts *Options, env CmdEnvironment) (*build.File, error) {
  170. return DeleteRule(env.File, env.Rule), nil
  171. }
  172. func cmdMove(opts *Options, env CmdEnvironment) (*build.File, error) {
  173. oldAttr := env.Args[0]
  174. newAttr := env.Args[1]
  175. if len(env.Args) == 3 && env.Args[2] == "*" {
  176. if err := MoveAllListAttributeValues(env.Rule, oldAttr, newAttr, env.Pkg, &env.Vars); err != nil {
  177. return nil, err
  178. }
  179. return env.File, nil
  180. }
  181. fixed := false
  182. for _, val := range env.Args[2:] {
  183. if deleted := ListAttributeDelete(env.Rule, oldAttr, val, env.Pkg); deleted != nil {
  184. AddValueToListAttribute(env.Rule, newAttr, env.Pkg, deleted, &env.Vars)
  185. fixed = true
  186. }
  187. }
  188. if fixed {
  189. return env.File, nil
  190. }
  191. return nil, nil
  192. }
  193. func cmdNew(opts *Options, env CmdEnvironment) (*build.File, error) {
  194. kind := env.Args[0]
  195. name := env.Args[1]
  196. addAtEOF, insertionIndex, err := findInsertionIndex(env)
  197. if err != nil {
  198. return nil, err
  199. }
  200. if FindRuleByName(env.File, name) != nil {
  201. return nil, fmt.Errorf("rule '%s' already exists", name)
  202. }
  203. call := &build.CallExpr{X: &build.Ident{Name: kind}}
  204. rule := &build.Rule{call, ""}
  205. rule.SetAttr("name", &build.StringExpr{Value: name})
  206. if addAtEOF {
  207. env.File.Stmt = InsertAfterLastOfSameKind(env.File.Stmt, rule.Call)
  208. } else {
  209. env.File.Stmt = InsertAfter(insertionIndex, env.File.Stmt, call)
  210. }
  211. return env.File, nil
  212. }
  213. // findInsertionIndex is used by cmdNew to find the place at which to insert the new rule.
  214. func findInsertionIndex(env CmdEnvironment) (bool, int, error) {
  215. if len(env.Args) < 4 {
  216. return true, 0, nil
  217. }
  218. relativeToRuleName := env.Args[3]
  219. ruleIdx, _ := IndexOfRuleByName(env.File, relativeToRuleName)
  220. if ruleIdx == -1 {
  221. return true, 0, nil
  222. }
  223. switch env.Args[2] {
  224. case "before":
  225. return false, ruleIdx - 1, nil
  226. case "after":
  227. return false, ruleIdx, nil
  228. default:
  229. return true, 0, fmt.Errorf("Unknown relative operator '%s'; allowed: 'before', 'after'", env.Args[1])
  230. }
  231. }
  232. func cmdNewLoad(opts *Options, env CmdEnvironment) (*build.File, error) {
  233. from := env.Args[1:]
  234. to := append([]string{}, from...)
  235. for i := range from {
  236. if s := strings.SplitN(from[i], "=", 2); len(s) == 2 {
  237. to[i] = s[0]
  238. from[i] = s[1]
  239. }
  240. }
  241. env.File.Stmt = InsertLoad(env.File.Stmt, env.Args[0], from, to)
  242. return env.File, nil
  243. }
  244. func cmdPrint(opts *Options, env CmdEnvironment) (*build.File, error) {
  245. format := env.Args
  246. if len(format) == 0 {
  247. format = []string{"name", "kind"}
  248. }
  249. fields := make([]*apipb.Output_Record_Field, len(format))
  250. for i, str := range format {
  251. value := env.Rule.Attr(str)
  252. if str == "kind" {
  253. fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{env.Rule.Kind()}}
  254. } else if str == "name" {
  255. fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{env.Rule.Name()}}
  256. } else if str == "label" {
  257. if env.Rule.Name() != "" {
  258. fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{fmt.Sprintf("//%s:%s", env.Pkg, env.Rule.Name())}}
  259. } else {
  260. return nil, nil
  261. }
  262. } else if str == "rule" {
  263. fields[i] = &apipb.Output_Record_Field{
  264. Value: &apipb.Output_Record_Field_Text{build.FormatString(env.Rule.Call)},
  265. }
  266. } else if str == "startline" {
  267. fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Number{int32(env.Rule.Call.ListStart.Line)}}
  268. } else if str == "endline" {
  269. fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Number{int32(env.Rule.Call.End.Pos.Line)}}
  270. } else if value == nil {
  271. fmt.Fprintf(os.Stderr, "rule \"//%s:%s\" has no attribute \"%s\"\n",
  272. env.Pkg, env.Rule.Name(), str)
  273. fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Error{Error: apipb.Output_Record_Field_MISSING}}
  274. } else if lit, ok := value.(*build.LiteralExpr); ok {
  275. fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{lit.Token}}
  276. } else if lit, ok := value.(*build.Ident); ok {
  277. fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{lit.Name}}
  278. } else if string, ok := value.(*build.StringExpr); ok {
  279. fields[i] = &apipb.Output_Record_Field{
  280. Value: &apipb.Output_Record_Field_Text{string.Value},
  281. QuoteWhenPrinting: true,
  282. }
  283. } else if strList := env.Rule.AttrStrings(str); strList != nil {
  284. fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_List{List: &apipb.RepeatedString{Strings: strList}}}
  285. } else {
  286. // Some other Expr we haven't listed above. Just print it.
  287. fields[i] = &apipb.Output_Record_Field{Value: &apipb.Output_Record_Field_Text{build.FormatString(value)}}
  288. }
  289. }
  290. env.output.Fields = fields
  291. return nil, nil
  292. }
  293. func attrKeysForPattern(rule *build.Rule, pattern string) []string {
  294. if pattern == "*" {
  295. return rule.AttrKeys()
  296. }
  297. return []string{pattern}
  298. }
  299. func cmdRemove(opts *Options, env CmdEnvironment) (*build.File, error) {
  300. if len(env.Args) == 1 { // Remove the attribute
  301. if env.Rule.DelAttr(env.Args[0]) != nil {
  302. return env.File, nil
  303. }
  304. } else { // Remove values in the attribute.
  305. fixed := false
  306. for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
  307. for _, val := range env.Args[1:] {
  308. ListAttributeDelete(env.Rule, key, val, env.Pkg)
  309. fixed = true
  310. }
  311. ResolveAttr(env.Rule, key, env.Pkg)
  312. }
  313. if fixed {
  314. return env.File, nil
  315. }
  316. }
  317. return nil, nil
  318. }
  319. func cmdRemoveComment(opts *Options, env CmdEnvironment) (*build.File, error) {
  320. switch len(env.Args) {
  321. case 0: // Remove comment attached to rule
  322. env.Rule.Call.Comments.Before = nil
  323. env.Rule.Call.Comments.Suffix = nil
  324. env.Rule.Call.Comments.After = nil
  325. case 1: // Remove comment attached to attr
  326. if attr := env.Rule.AttrDefn(env.Args[0]); attr != nil {
  327. attr.Comments.Before = nil
  328. attr.Comments.Suffix = nil
  329. attr.Comments.After = nil
  330. attr.LHS.Comment().Before = nil
  331. attr.LHS.Comment().Suffix = nil
  332. attr.LHS.Comment().After = nil
  333. attr.RHS.Comment().Before = nil
  334. attr.RHS.Comment().Suffix = nil
  335. attr.RHS.Comment().After = nil
  336. }
  337. case 2: // Remove comment attached to value
  338. if attr := env.Rule.Attr(env.Args[0]); attr != nil {
  339. if expr := ListFind(attr, env.Args[1], env.Pkg); expr != nil {
  340. expr.Comments.Before = nil
  341. expr.Comments.Suffix = nil
  342. expr.Comments.After = nil
  343. }
  344. }
  345. default:
  346. panic("cmdRemoveComment")
  347. }
  348. return env.File, nil
  349. }
  350. func cmdRename(opts *Options, env CmdEnvironment) (*build.File, error) {
  351. oldAttr := env.Args[0]
  352. newAttr := env.Args[1]
  353. if err := RenameAttribute(env.Rule, oldAttr, newAttr); err != nil {
  354. return nil, err
  355. }
  356. return env.File, nil
  357. }
  358. func cmdReplace(opts *Options, env CmdEnvironment) (*build.File, error) {
  359. oldV := env.Args[1]
  360. newV := env.Args[2]
  361. for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
  362. attr := env.Rule.Attr(key)
  363. if e, ok := attr.(*build.StringExpr); ok {
  364. if LabelsEqual(e.Value, oldV, env.Pkg) {
  365. env.Rule.SetAttr(key, getAttrValueExpr(key, []string{newV}, env))
  366. }
  367. } else {
  368. ListReplace(attr, oldV, newV, env.Pkg)
  369. }
  370. }
  371. return env.File, nil
  372. }
  373. func cmdSubstitute(opts *Options, env CmdEnvironment) (*build.File, error) {
  374. oldRegexp, err := regexp.Compile(env.Args[1])
  375. if err != nil {
  376. return nil, err
  377. }
  378. newTemplate := env.Args[2]
  379. for _, key := range attrKeysForPattern(env.Rule, env.Args[0]) {
  380. attr := env.Rule.Attr(key)
  381. e, ok := attr.(*build.StringExpr)
  382. if !ok {
  383. ListSubstitute(attr, oldRegexp, newTemplate)
  384. continue
  385. }
  386. if newValue, ok := stringSubstitute(e.Value, oldRegexp, newTemplate); ok {
  387. env.Rule.SetAttr(key, getAttrValueExpr(key, []string{newValue}, env))
  388. }
  389. }
  390. return env.File, nil
  391. }
  392. func cmdSet(opts *Options, env CmdEnvironment) (*build.File, error) {
  393. attr := env.Args[0]
  394. args := env.Args[1:]
  395. if attr == "kind" {
  396. env.Rule.SetKind(args[0])
  397. } else {
  398. env.Rule.SetAttr(attr, getAttrValueExpr(attr, args, env))
  399. }
  400. return env.File, nil
  401. }
  402. func cmdSetIfAbsent(opts *Options, env CmdEnvironment) (*build.File, error) {
  403. attr := env.Args[0]
  404. args := env.Args[1:]
  405. if attr == "kind" {
  406. return nil, fmt.Errorf("setting 'kind' is not allowed for set_if_absent. Got %s", env.Args)
  407. }
  408. if env.Rule.Attr(attr) == nil {
  409. env.Rule.SetAttr(attr, getAttrValueExpr(attr, args, env))
  410. }
  411. return env.File, nil
  412. }
  413. func getAttrValueExpr(attr string, args []string, env CmdEnvironment) build.Expr {
  414. switch {
  415. case attr == "kind":
  416. return nil
  417. case IsIntList(attr):
  418. var list []build.Expr
  419. for _, i := range args {
  420. list = append(list, &build.LiteralExpr{Token: i})
  421. }
  422. return &build.ListExpr{List: list}
  423. case IsList(attr) && !(len(args) == 1 && strings.HasPrefix(args[0], "glob(")):
  424. var list []build.Expr
  425. for _, arg := range args {
  426. list = append(list, getStringExpr(arg, env.Pkg))
  427. }
  428. return &build.ListExpr{List: list}
  429. case len(args) == 0:
  430. // Expected a non-list argument, nothing provided
  431. return &build.Ident{Name: "None"}
  432. case IsString(attr):
  433. return getStringExpr(args[0], env.Pkg)
  434. default:
  435. return &build.Ident{Name: args[0]}
  436. }
  437. }
  438. func getStringExpr(value, pkg string) build.Expr {
  439. unquoted, triple, err := build.Unquote(value)
  440. if err == nil {
  441. return &build.StringExpr{Value: ShortenLabel(unquoted, pkg), TripleQuote: triple}
  442. }
  443. return &build.StringExpr{Value: ShortenLabel(value, pkg)}
  444. }
  445. func cmdCopy(opts *Options, env CmdEnvironment) (*build.File, error) {
  446. attrName := env.Args[0]
  447. from := env.Args[1]
  448. return copyAttributeBetweenRules(env, attrName, from)
  449. }
  450. func cmdCopyNoOverwrite(opts *Options, env CmdEnvironment) (*build.File, error) {
  451. attrName := env.Args[0]
  452. from := env.Args[1]
  453. if env.Rule.Attr(attrName) != nil {
  454. return env.File, nil
  455. }
  456. return copyAttributeBetweenRules(env, attrName, from)
  457. }
  458. // cmdDictAdd adds a key to a dict, if that key does _not_ exit already.
  459. func cmdDictAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
  460. attr := env.Args[0]
  461. args := env.Args[1:]
  462. dict := &build.DictExpr{}
  463. currDict, ok := env.Rule.Attr(attr).(*build.DictExpr)
  464. if ok {
  465. dict = currDict
  466. }
  467. for _, x := range args {
  468. kv := strings.Split(x, ":")
  469. expr := getStringExpr(kv[1], env.Pkg)
  470. prev := DictionaryGet(dict, kv[0])
  471. if prev == nil {
  472. // Only set the value if the value is currently unset.
  473. DictionarySet(dict, kv[0], expr)
  474. }
  475. }
  476. env.Rule.SetAttr(attr, dict)
  477. return env.File, nil
  478. }
  479. // cmdDictSet adds a key to a dict, overwriting any previous values.
  480. func cmdDictSet(opts *Options, env CmdEnvironment) (*build.File, error) {
  481. attr := env.Args[0]
  482. args := env.Args[1:]
  483. dict := &build.DictExpr{}
  484. currDict, ok := env.Rule.Attr(attr).(*build.DictExpr)
  485. if ok {
  486. dict = currDict
  487. }
  488. for _, x := range args {
  489. kv := strings.Split(x, ":")
  490. expr := getStringExpr(kv[1], env.Pkg)
  491. // Set overwrites previous values.
  492. DictionarySet(dict, kv[0], expr)
  493. }
  494. env.Rule.SetAttr(attr, dict)
  495. return env.File, nil
  496. }
  497. // cmdDictRemove removes a key from a dict.
  498. func cmdDictRemove(opts *Options, env CmdEnvironment) (*build.File, error) {
  499. attr := env.Args[0]
  500. args := env.Args[1:]
  501. thing := env.Rule.Attr(attr)
  502. dictAttr, ok := thing.(*build.DictExpr)
  503. if !ok {
  504. return env.File, nil
  505. }
  506. for _, x := range args {
  507. // should errors here be flagged?
  508. DictionaryDelete(dictAttr, x)
  509. env.Rule.SetAttr(attr, dictAttr)
  510. }
  511. // If the removal results in the dict having no contents, delete the attribute (stay clean!)
  512. if dictAttr == nil || len(dictAttr.List) == 0 {
  513. env.Rule.DelAttr(attr)
  514. }
  515. return env.File, nil
  516. }
  517. func copyAttributeBetweenRules(env CmdEnvironment, attrName string, from string) (*build.File, error) {
  518. fromRule := FindRuleByName(env.File, from)
  519. if fromRule == nil {
  520. return nil, fmt.Errorf("could not find rule '%s'", from)
  521. }
  522. attr := fromRule.Attr(attrName)
  523. if attr == nil {
  524. return nil, fmt.Errorf("rule '%s' does not have attribute '%s'", from, attrName)
  525. }
  526. ast, err := build.ParseBuild("" /* filename */, []byte(build.FormatString(attr)))
  527. if err != nil {
  528. return nil, fmt.Errorf("could not parse attribute value %v", build.FormatString(attr))
  529. }
  530. env.Rule.SetAttr(attrName, ast.Stmt[0])
  531. return env.File, nil
  532. }
  533. func cmdFix(opts *Options, env CmdEnvironment) (*build.File, error) {
  534. // Fix the whole file
  535. if env.Rule.Kind() == "package" {
  536. return FixFile(env.File, env.Pkg, env.Args), nil
  537. }
  538. // Fix a specific rule
  539. return FixRule(env.File, env.Pkg, env.Rule, env.Args), nil
  540. }
  541. // CommandInfo provides a command function and info on incoming arguments.
  542. type CommandInfo struct {
  543. Fn func(*Options, CmdEnvironment) (*build.File, error)
  544. PerRule bool
  545. MinArg int
  546. MaxArg int
  547. Template string
  548. }
  549. // AllCommands associates the command names with their function and number
  550. // of arguments.
  551. var AllCommands = map[string]CommandInfo{
  552. "add": {cmdAdd, true, 2, -1, "<attr> <value(s)>"},
  553. "new_load": {cmdNewLoad, false, 1, -1, "<path> <[to=]from(s)>"},
  554. "comment": {cmdComment, true, 1, 3, "<attr>? <value>? <comment>"},
  555. "print_comment": {cmdPrintComment, true, 0, 2, "<attr>? <value>?"},
  556. "delete": {cmdDelete, true, 0, 0, ""},
  557. "fix": {cmdFix, true, 0, -1, "<fix(es)>?"},
  558. "move": {cmdMove, true, 3, -1, "<old_attr> <new_attr> <value(s)>"},
  559. "new": {cmdNew, false, 2, 4, "<rule_kind> <rule_name> [(before|after) <relative_rule_name>]"},
  560. "print": {cmdPrint, true, 0, -1, "<attribute(s)>"},
  561. "remove": {cmdRemove, true, 1, -1, "<attr> <value(s)>"},
  562. "remove_comment": {cmdRemoveComment, true, 0, 2, "<attr>? <value>?"},
  563. "rename": {cmdRename, true, 2, 2, "<old_attr> <new_attr>"},
  564. "replace": {cmdReplace, true, 3, 3, "<attr> <old_value> <new_value>"},
  565. "substitute": {cmdSubstitute, true, 3, 3, "<attr> <old_regexp> <new_template>"},
  566. "set": {cmdSet, true, 1, -1, "<attr> <value(s)>"},
  567. "set_if_absent": {cmdSetIfAbsent, true, 1, -1, "<attr> <value(s)>"},
  568. "copy": {cmdCopy, true, 2, 2, "<attr> <from_rule>"},
  569. "copy_no_overwrite": {cmdCopyNoOverwrite, true, 2, 2, "<attr> <from_rule>"},
  570. "dict_add": {cmdDictAdd, true, 2, -1, "<attr> <(key:value)(s)>"},
  571. "dict_set": {cmdDictSet, true, 2, -1, "<attr> <(key:value)(s)>"},
  572. "dict_remove": {cmdDictRemove, true, 2, -1, "<attr> <key(s)>"},
  573. }
  574. func expandTargets(f *build.File, rule string) ([]*build.Rule, error) {
  575. if r := FindRuleByName(f, rule); r != nil {
  576. return []*build.Rule{r}, nil
  577. } else if r := FindExportedFile(f, rule); r != nil {
  578. return []*build.Rule{r}, nil
  579. } else if rule == "all" || rule == "*" {
  580. // "all" is a valid name, it is a wildcard only if no such rule is found.
  581. return f.Rules(""), nil
  582. } else if strings.HasPrefix(rule, "%") {
  583. // "%java_library" will match all java_library functions in the package
  584. // "%<LINENUM>" will match the rule which begins at LINENUM.
  585. // This is for convenience, "%" is not a valid character in bazel targets.
  586. kind := rule[1:]
  587. if linenum, err := strconv.Atoi(kind); err == nil {
  588. if r := f.RuleAt(linenum); r != nil {
  589. return []*build.Rule{r}, nil
  590. }
  591. } else {
  592. return f.Rules(kind), nil
  593. }
  594. }
  595. return nil, fmt.Errorf("rule '%s' not found", rule)
  596. }
  597. func filterRules(opts *Options, rules []*build.Rule) (result []*build.Rule) {
  598. if len(opts.FilterRuleTypes) == 0 {
  599. return rules
  600. }
  601. for _, rule := range rules {
  602. for _, filterType := range opts.FilterRuleTypes {
  603. if rule.Kind() == filterType {
  604. result = append(result, rule)
  605. break
  606. }
  607. }
  608. }
  609. return
  610. }
  611. // command contains a list of tokens that describe a buildozer command.
  612. type command struct {
  613. tokens []string
  614. }
  615. // checkCommandUsage checks the number of argument of a command.
  616. // It prints an error and usage when it is not valid.
  617. func checkCommandUsage(name string, cmd CommandInfo, count int) {
  618. if count >= cmd.MinArg && (cmd.MaxArg == -1 || count <= cmd.MaxArg) {
  619. return
  620. }
  621. if count < cmd.MinArg {
  622. fmt.Fprintf(os.Stderr, "Too few arguments for command '%s', expected at least %d.\n",
  623. name, cmd.MinArg)
  624. } else {
  625. fmt.Fprintf(os.Stderr, "Too many arguments for command '%s', expected at most %d.\n",
  626. name, cmd.MaxArg)
  627. }
  628. Usage()
  629. os.Exit(1)
  630. }
  631. // Match text that only contains spaces if they're escaped with '\'.
  632. var spaceRegex = regexp.MustCompile(`(\\ |[^ ])+`)
  633. // SplitOnSpaces behaves like strings.Fields, except that spaces can be escaped.
  634. // " some dummy\\ string" -> ["some", "dummy string"]
  635. func SplitOnSpaces(input string) []string {
  636. result := spaceRegex.FindAllString(input, -1)
  637. for i, s := range result {
  638. result[i] = strings.Replace(s, `\ `, " ", -1)
  639. }
  640. return result
  641. }
  642. // parseCommands parses commands and targets they should be applied on from
  643. // a list of arguments.
  644. // Each argument can be either:
  645. // - a command (as defined by AllCommands) and its parameters, separated by
  646. // whitespace
  647. // - a target all commands that are parsed during one call to parseCommands
  648. // should be applied on
  649. func parseCommands(args []string) (commands []command, targets []string) {
  650. for _, arg := range args {
  651. commandTokens := SplitOnSpaces(arg)
  652. cmd, found := AllCommands[commandTokens[0]]
  653. if found {
  654. checkCommandUsage(commandTokens[0], cmd, len(commandTokens)-1)
  655. commands = append(commands, command{commandTokens})
  656. } else {
  657. targets = append(targets, arg)
  658. }
  659. }
  660. return
  661. }
  662. // commandsForTarget contains commands to be executed on the given target.
  663. type commandsForTarget struct {
  664. target string
  665. commands []command
  666. }
  667. // commandsForFile contains the file name and all commands that should be
  668. // applied on that file, indexed by their target.
  669. type commandsForFile struct {
  670. file string
  671. commands []commandsForTarget
  672. }
  673. // commandError returns an error that formats 'err' in the context of the
  674. // commands to be executed on the given target.
  675. func commandError(commands []command, target string, err error) error {
  676. return fmt.Errorf("error while executing commands %s on target %s: %s", commands, target, err)
  677. }
  678. // rewriteResult contains the outcome of applying fixes to a single file.
  679. type rewriteResult struct {
  680. file string
  681. errs []error
  682. modified bool
  683. records []*apipb.Output_Record
  684. }
  685. // getGlobalVariables returns the global variable assignments in the provided list of expressions.
  686. // That is, for each variable assignment of the form
  687. // a = v
  688. // vars["a"] will contain the AssignExpr whose RHS value is the assignment "a = v".
  689. func getGlobalVariables(exprs []build.Expr) (vars map[string]*build.AssignExpr) {
  690. vars = make(map[string]*build.AssignExpr)
  691. for _, expr := range exprs {
  692. if as, ok := expr.(*build.AssignExpr); ok {
  693. if lhs, ok := as.LHS.(*build.Ident); ok {
  694. vars[lhs.Name] = as
  695. }
  696. }
  697. }
  698. return vars
  699. }
  700. // When checking the filesystem, we need to look for any of the
  701. // possible buildFileNames. For historical reasons, the
  702. // parts of the tool that generate paths that we may want to examine
  703. // continue to assume that build files are all named "BUILD".
  704. var buildFileNames = [...]string{"BUILD.bazel", "BUILD", "BUCK"}
  705. var buildFileNamesSet = map[string]bool{
  706. "BUILD.bazel": true,
  707. "BUILD": true,
  708. "BUCK": true,
  709. }
  710. // rewrite parses the BUILD file for the given file, transforms the AST,
  711. // and write the changes back in the file (or on stdout).
  712. func rewrite(opts *Options, commandsForFile commandsForFile) *rewriteResult {
  713. name := commandsForFile.file
  714. var data []byte
  715. var err error
  716. var fi os.FileInfo
  717. records := []*apipb.Output_Record{}
  718. if name == stdinPackageName { // read on stdin
  719. data, err = ioutil.ReadAll(os.Stdin)
  720. if err != nil {
  721. return &rewriteResult{file: name, errs: []error{err}}
  722. }
  723. } else {
  724. origName := name
  725. for _, suffix := range buildFileNames {
  726. if strings.HasSuffix(name, "/"+suffix) {
  727. name = strings.TrimSuffix(name, suffix)
  728. break
  729. }
  730. }
  731. for _, suffix := range buildFileNames {
  732. name = name + suffix
  733. data, fi, err = file.ReadFile(name)
  734. if err == nil {
  735. break
  736. }
  737. name = strings.TrimSuffix(name, suffix)
  738. }
  739. if err != nil {
  740. data, fi, err = file.ReadFile(name)
  741. }
  742. if err != nil {
  743. err = errors.New("file not found or not readable")
  744. return &rewriteResult{file: origName, errs: []error{err}}
  745. }
  746. }
  747. f, err := build.ParseBuild(name, data)
  748. if err != nil {
  749. return &rewriteResult{file: name, errs: []error{err}}
  750. }
  751. vars := map[string]*build.AssignExpr{}
  752. if opts.EditVariables {
  753. vars = getGlobalVariables(f.Stmt)
  754. }
  755. var errs []error
  756. changed := false
  757. for _, commands := range commandsForFile.commands {
  758. target := commands.target
  759. commands := commands.commands
  760. _, absPkg, rule := InterpretLabelForWorkspaceLocation(opts.RootDir, target)
  761. _, pkg, _ := ParseLabel(target)
  762. if pkg == stdinPackageName { // Special-case: This is already absolute
  763. absPkg = stdinPackageName
  764. }
  765. targets, err := expandTargets(f, rule)
  766. if err != nil {
  767. cerr := commandError(commands, target, err)
  768. errs = append(errs, cerr)
  769. if !opts.KeepGoing {
  770. return &rewriteResult{file: name, errs: errs, records: records}
  771. }
  772. }
  773. targets = filterRules(opts, targets)
  774. for _, cmd := range commands {
  775. cmdInfo := AllCommands[cmd.tokens[0]]
  776. // Depending on whether a transformation is rule-specific or not, it should be applied to
  777. // every rule that satisfies the filter or just once to the file.
  778. cmdTargets := targets
  779. if !cmdInfo.PerRule {
  780. cmdTargets = []*build.Rule{nil}
  781. }
  782. for _, r := range cmdTargets {
  783. record := &apipb.Output_Record{}
  784. newf, err := cmdInfo.Fn(opts, CmdEnvironment{f, r, vars, absPkg, cmd.tokens[1:], record})
  785. if len(record.Fields) != 0 {
  786. records = append(records, record)
  787. }
  788. if err != nil {
  789. cerr := commandError([]command{cmd}, target, err)
  790. if opts.KeepGoing {
  791. errs = append(errs, cerr)
  792. } else {
  793. return &rewriteResult{file: name, errs: []error{cerr}, records: records}
  794. }
  795. }
  796. if newf != nil {
  797. changed = true
  798. f = newf
  799. }
  800. }
  801. }
  802. }
  803. if !changed {
  804. return &rewriteResult{file: name, errs: errs, records: records}
  805. }
  806. f = RemoveEmptyPackage(f)
  807. ndata, err := runBuildifier(opts, f)
  808. if err != nil {
  809. return &rewriteResult{file: name, errs: []error{fmt.Errorf("running buildifier: %v", err)}, records: records}
  810. }
  811. if opts.Stdout || name == stdinPackageName {
  812. os.Stdout.Write(ndata)
  813. return &rewriteResult{file: name, errs: errs, records: records}
  814. }
  815. if bytes.Equal(data, ndata) {
  816. return &rewriteResult{file: name, errs: errs, records: records}
  817. }
  818. if err := EditFile(fi, name); err != nil {
  819. return &rewriteResult{file: name, errs: []error{err}, records: records}
  820. }
  821. if err := file.WriteFile(name, ndata); err != nil {
  822. return &rewriteResult{file: name, errs: []error{err}, records: records}
  823. }
  824. fileModified = true
  825. return &rewriteResult{file: name, errs: errs, modified: true, records: records}
  826. }
  827. // EditFile is a function that does any prework needed before editing a file.
  828. // e.g. "checking out for write" from a locking source control repo.
  829. var EditFile = func(fi os.FileInfo, name string) error {
  830. return nil
  831. }
  832. // runBuildifier formats the build file f.
  833. // Runs opts.Buildifier if it's non-empty, otherwise uses built-in formatter.
  834. // opts.Buildifier is useful to force consistency with other tools that call Buildifier.
  835. func runBuildifier(opts *Options, f *build.File) ([]byte, error) {
  836. if opts.Buildifier == "" {
  837. build.Rewrite(f, nil)
  838. return build.Format(f), nil
  839. }
  840. cmd := exec.Command(opts.Buildifier, "--type=build")
  841. data := build.Format(f)
  842. cmd.Stdin = bytes.NewBuffer(data)
  843. stdout := bytes.NewBuffer(nil)
  844. stderr := bytes.NewBuffer(nil)
  845. cmd.Stdout = stdout
  846. cmd.Stderr = stderr
  847. err := cmd.Run()
  848. if stderr.Len() > 0 {
  849. return nil, fmt.Errorf("%s", stderr.Bytes())
  850. }
  851. if err != nil {
  852. return nil, err
  853. }
  854. return stdout.Bytes(), nil
  855. }
  856. // Given a target, whose package may contain a trailing "/...", returns all
  857. // extisting BUILD file paths which match the package.
  858. func targetExpressionToBuildFiles(opts *Options, target string) []string {
  859. file, _, _ := InterpretLabelForWorkspaceLocation(opts.RootDir, target)
  860. if opts.RootDir == "" {
  861. var err error
  862. if file, err = filepath.Abs(file); err != nil {
  863. fmt.Printf("Cannot make path absolute: %s\n", err.Error())
  864. os.Exit(1)
  865. }
  866. }
  867. if !strings.HasSuffix(file, "/.../BUILD") {
  868. return []string{file}
  869. }
  870. var buildFiles []string
  871. searchDirs := []string{strings.TrimSuffix(file, "/.../BUILD")}
  872. for len(searchDirs) != 0 {
  873. lastIndex := len(searchDirs) - 1
  874. dir := searchDirs[lastIndex]
  875. searchDirs = searchDirs[:lastIndex]
  876. dirFiles, err := ioutil.ReadDir(dir)
  877. if err != nil {
  878. continue
  879. }
  880. for _, dirFile := range dirFiles {
  881. if dirFile.IsDir() {
  882. searchDirs = append(searchDirs, path.Join(dir, dirFile.Name()))
  883. } else if _, ok := buildFileNamesSet[dirFile.Name()]; ok {
  884. buildFiles = append(buildFiles, path.Join(dir, dirFile.Name()))
  885. }
  886. }
  887. }
  888. return buildFiles
  889. }
  890. // appendCommands adds the given commands to be applied to each of the given targets
  891. // via the commandMap.
  892. func appendCommands(opts *Options, commandMap map[string][]commandsForTarget, args []string) {
  893. commands, targets := parseCommands(args)
  894. for _, target := range targets {
  895. if strings.HasSuffix(target, "/BUILD") {
  896. target = strings.TrimSuffix(target, "/BUILD") + ":__pkg__"
  897. }
  898. var buildFiles []string
  899. _, pkg, _ := ParseLabel(target)
  900. if pkg == stdinPackageName {
  901. buildFiles = []string{stdinPackageName}
  902. } else {
  903. buildFiles = targetExpressionToBuildFiles(opts, target)
  904. }
  905. for _, file := range buildFiles {
  906. commandMap[file] = append(commandMap[file], commandsForTarget{target, commands})
  907. }
  908. }
  909. }
  910. func appendCommandsFromFile(opts *Options, commandsByFile map[string][]commandsForTarget, fileName string) {
  911. var reader io.Reader
  912. if opts.CommandsFile == stdinPackageName {
  913. reader = os.Stdin
  914. } else {
  915. rc := file.OpenReadFile(opts.CommandsFile)
  916. reader = rc
  917. defer rc.Close()
  918. }
  919. appendCommandsFromReader(opts, reader, commandsByFile)
  920. }
  921. func appendCommandsFromReader(opts *Options, reader io.Reader, commandsByFile map[string][]commandsForTarget) {
  922. r := bufio.NewReader(reader)
  923. atEof := false
  924. for !atEof {
  925. line, err := r.ReadString('\n')
  926. if err == io.EOF {
  927. atEof = true
  928. err = nil
  929. }
  930. if err != nil {
  931. fmt.Fprintf(os.Stderr, "Error while reading commands file: %v", err)
  932. return
  933. }
  934. line = strings.TrimSuffix(line, "\n")
  935. if line == "" {
  936. continue
  937. }
  938. args := strings.Split(line, "|")
  939. appendCommands(opts, commandsByFile, args)
  940. }
  941. }
  942. func printRecord(writer io.Writer, record *apipb.Output_Record) {
  943. fields := record.Fields
  944. line := make([]string, len(fields))
  945. for i, field := range fields {
  946. switch value := field.Value.(type) {
  947. case *apipb.Output_Record_Field_Text:
  948. if field.QuoteWhenPrinting && strings.ContainsRune(value.Text, ' ') {
  949. line[i] = fmt.Sprintf("%q", value.Text)
  950. } else {
  951. line[i] = value.Text
  952. }
  953. break
  954. case *apipb.Output_Record_Field_Number:
  955. line[i] = strconv.Itoa(int(value.Number))
  956. break
  957. case *apipb.Output_Record_Field_Error:
  958. switch value.Error {
  959. case apipb.Output_Record_Field_UNKNOWN:
  960. line[i] = "(unknown)"
  961. break
  962. case apipb.Output_Record_Field_MISSING:
  963. line[i] = "(missing)"
  964. break
  965. }
  966. break
  967. case *apipb.Output_Record_Field_List:
  968. line[i] = fmt.Sprintf("[%s]", strings.Join(value.List.Strings, " "))
  969. break
  970. }
  971. }
  972. fmt.Fprint(writer, strings.Join(line, " ")+"\n")
  973. }
  974. // Buildozer loops over all arguments on the command line fixing BUILD files.
  975. func Buildozer(opts *Options, args []string) int {
  976. commandsByFile := make(map[string][]commandsForTarget)
  977. if opts.CommandsFile != "" {
  978. appendCommandsFromFile(opts, commandsByFile, opts.CommandsFile)
  979. } else {
  980. if len(args) == 0 {
  981. Usage()
  982. }
  983. appendCommands(opts, commandsByFile, args)
  984. }
  985. numFiles := len(commandsByFile)
  986. if opts.Parallelism > 0 {
  987. runtime.GOMAXPROCS(opts.Parallelism)
  988. }
  989. results := make(chan *rewriteResult, numFiles)
  990. data := make(chan commandsForFile)
  991. for i := 0; i < opts.NumIO; i++ {
  992. go func(results chan *rewriteResult, data chan commandsForFile) {
  993. for commandsForFile := range data {
  994. results <- rewrite(opts, commandsForFile)
  995. }
  996. }(results, data)
  997. }
  998. for file, commands := range commandsByFile {
  999. data <- commandsForFile{file, commands}
  1000. }
  1001. close(data)
  1002. records := []*apipb.Output_Record{}
  1003. hasErrors := false
  1004. for i := 0; i < numFiles; i++ {
  1005. fileResults := <-results
  1006. if fileResults == nil {
  1007. continue
  1008. }
  1009. hasErrors = hasErrors || len(fileResults.errs) > 0
  1010. for _, err := range fileResults.errs {
  1011. fmt.Fprintf(os.Stderr, "%s: %s\n", fileResults.file, err)
  1012. }
  1013. if fileResults.modified && !opts.Quiet {
  1014. fmt.Fprintf(os.Stderr, "fixed %s\n", fileResults.file)
  1015. }
  1016. if fileResults.records != nil {
  1017. records = append(records, fileResults.records...)
  1018. }
  1019. }
  1020. if opts.IsPrintingProto {
  1021. data, err := proto.Marshal(&apipb.Output{Records: records})
  1022. if err != nil {
  1023. log.Fatal("marshaling error: ", err)
  1024. }
  1025. fmt.Fprintf(os.Stdout, "%s", data)
  1026. } else {
  1027. for _, record := range records {
  1028. printRecord(os.Stdout, record)
  1029. }
  1030. }
  1031. if hasErrors {
  1032. return 2
  1033. }
  1034. if !fileModified && !opts.Stdout {
  1035. return 3
  1036. }
  1037. return 0
  1038. }