@@ -26,6 +26,7 @@ import (
26
26
"io"
27
27
"log"
28
28
"os"
29
+ "path"
29
30
"path/filepath"
30
31
"regexp"
31
32
"runtime"
@@ -1104,7 +1105,7 @@ func getGlobalVariables(exprs []build.Expr) (vars map[string]*build.AssignExpr)
1104
1105
1105
1106
// BuildFileNames is exported so that users that want to override it
1106
1107
// in scripts are free to do so.
1107
- var BuildFileNames = [... ]string {"BUILD.bazel " , "BUILD" , "BUCK" }
1108
+ var BuildFileNames = [... ]string {"BUILD" , "BUILD.bazel " , "BUCK" }
1108
1109
1109
1110
// Buildifier formats the build file using the buildifier logic.
1110
1111
type Buildifier interface {
@@ -1183,11 +1184,9 @@ func rewrite(opts *Options, commandsForFile commandsForFile) *rewriteResult {
1183
1184
}
1184
1185
var errs []error
1185
1186
changed := false
1186
- for _ , commands := range commandsForFile .commands {
1187
- target := commands .target
1188
- commands := commands .commands
1189
- _ , _ , absPkg , rule := InterpretLabelForWorkspaceLocation (opts .RootDir , target )
1190
- if label := labels .Parse (target ); label .Package == stdinPackageName {
1187
+ for _ , cft := range commandsForFile .commands {
1188
+ _ , _ , absPkg , rule := InterpretLabelForWorkspaceLocation (opts .RootDir , cft .target )
1189
+ if label := labels .Parse (cft .target ); label .Package == stdinPackageName {
1191
1190
// Special-case: This is already absolute
1192
1191
absPkg = stdinPackageName
1193
1192
}
@@ -1198,48 +1197,28 @@ func rewrite(opts *Options, commandsForFile commandsForFile) *rewriteResult {
1198
1197
1199
1198
targets , err := expandTargets (f , rule )
1200
1199
if err != nil {
1201
- cerr := commandError (commands , target , err )
1200
+ cerr := commandError (cft . commands , cft . target , err )
1202
1201
errs = append (errs , cerr )
1203
1202
if ! opts .KeepGoing {
1204
1203
return & rewriteResult {file : name , errs : errs , records : records }
1205
1204
}
1206
1205
}
1207
1206
targets = filterRules (opts , targets )
1208
- for _ , cmd := range commands {
1209
- cmdInfo := AllCommands [cmd .tokens [0 ]]
1210
- // Depending on whether a transformation is rule-specific or not, it should be applied to
1211
- // every rule that satisfies the filter or just once to the file.
1212
- cmdTargets := targets
1213
- if ! cmdInfo .PerRule {
1214
- cmdTargets = []* build.Rule {nil }
1215
- }
1216
- for _ , r := range cmdTargets {
1217
- record := & apipb.Output_Record {}
1218
- newf , err := cmdInfo .Fn (opts , CmdEnvironment {f , r , vars , absPkg , cmd .tokens [1 :], record })
1219
- if len (record .Fields ) != 0 {
1220
- records = append (records , record )
1221
- }
1222
- if err != nil {
1223
- cerr := commandError ([]command {cmd }, target , err )
1224
- if opts .KeepGoing {
1225
- errs = append (errs , cerr )
1226
- } else {
1227
- return & rewriteResult {file : name , errs : []error {cerr }, records : records }
1228
- }
1229
- }
1230
- if newf != nil {
1231
- changed = true
1232
- f = newf
1233
- }
1234
- }
1207
+
1208
+ newf , err := executeCommandsInFile (opts , f , cft , targets , & records , vars , absPkg , & errs )
1209
+ if err != nil {
1210
+ return & rewriteResult {file : name , errs : []error {err }, records : records }
1211
+ }
1212
+ if newf != nil {
1213
+ changed = true
1214
+ f = newf
1235
1215
}
1236
1216
}
1237
1217
if ! changed {
1238
1218
return & rewriteResult {file : name , errs : errs , records : records }
1239
1219
}
1240
- f = RemoveEmptyPackage (f )
1241
- f = RemoveEmptyUseRepoCalls (f )
1242
- ndata , err := buildifier .Buildify (opts , f )
1220
+
1221
+ ndata , err := cleanAndBuildify (opts , f )
1243
1222
if err != nil {
1244
1223
return & rewriteResult {file : name , errs : []error {fmt .Errorf ("running buildifier: %v" , err )}, records : records }
1245
1224
}
@@ -1264,6 +1243,58 @@ func rewrite(opts *Options, commandsForFile commandsForFile) *rewriteResult {
1264
1243
return & rewriteResult {file : name , errs : errs , modified : true , records : records }
1265
1244
}
1266
1245
1246
+ // executeCommandsInFile executes the provided commandsForTarget in the provided build.File.
1247
+ func executeCommandsInFile (
1248
+ opts * Options ,
1249
+ f * build.File ,
1250
+ cft commandsForTarget ,
1251
+ rules []* build.Rule ,
1252
+ records * []* apipb.Output_Record ,
1253
+ vars map [string ]* build.AssignExpr ,
1254
+ absPkg string ,
1255
+ errs * []error ,
1256
+ ) (* build.File , error ) {
1257
+ changed := false
1258
+ for _ , cmd := range cft .commands {
1259
+ cmdInfo := AllCommands [cmd .tokens [0 ]]
1260
+ // Depending on whether a transformation is rule-specific or not, it should be applied to
1261
+ // every rule that satisfies the filter or just once to the file.
1262
+ cmdTargets := rules
1263
+ if ! cmdInfo .PerRule {
1264
+ cmdTargets = []* build.Rule {nil }
1265
+ }
1266
+ for _ , r := range cmdTargets {
1267
+ record := & apipb.Output_Record {}
1268
+ newf , err := cmdInfo .Fn (opts , CmdEnvironment {f , r , vars , absPkg , cmd .tokens [1 :], record })
1269
+ if len (record .Fields ) != 0 {
1270
+ * records = append (* records , record )
1271
+ }
1272
+ if err != nil {
1273
+ cerr := commandError ([]command {cmd }, cft .target , err )
1274
+ if opts .KeepGoing {
1275
+ * errs = append (* errs , cerr )
1276
+ } else {
1277
+ return nil , cerr
1278
+ }
1279
+ }
1280
+ if newf != nil {
1281
+ f = newf
1282
+ changed = true
1283
+ }
1284
+ }
1285
+ }
1286
+ if changed {
1287
+ return f , nil
1288
+ }
1289
+ return nil , nil
1290
+ }
1291
+
1292
+ func cleanAndBuildify (opts * Options , f * build.File ) ([]byte , error ) {
1293
+ f = RemoveEmptyPackage (f )
1294
+ f = RemoveEmptyUseRepoCalls (f )
1295
+ return buildifier .Buildify (opts , f )
1296
+ }
1297
+
1267
1298
// EditFile is a function that does any prework needed before editing a file.
1268
1299
// e.g. "checking out for write" from a locking source control repo.
1269
1300
var EditFile = func (fi os.FileInfo , name string ) error {
@@ -1547,3 +1578,83 @@ func Buildozer(opts *Options, args []string) int {
1547
1578
}
1548
1579
return 0
1549
1580
}
1581
+
1582
+ // ExecuteCommandsOnInlineFile executes the given commands on the given file content.
1583
+ // Returns the new file content after applying the commands.
1584
+ func ExecuteCommandsOnInlineFile (fileContent []byte , commands []string ) ([]byte , error ) {
1585
+ opts := Options {}
1586
+ commandsByTargetName , filename , err := groupCommandsForInlineFile (commands , opts )
1587
+ if err != nil {
1588
+ return nil , err
1589
+ }
1590
+ f , err := build .Parse (* filename , fileContent )
1591
+ if err != nil {
1592
+ return nil , err
1593
+ }
1594
+ if f .Type == build .TypeDefault {
1595
+ // Buildozer is unable to infer the file type, fall back to BUILD by default.
1596
+ f .Type = build .TypeBuild
1597
+ }
1598
+ for _ , cft := range commandsByTargetName {
1599
+ rules , err := expandTargets (f , cft .target )
1600
+ if err != nil {
1601
+ return nil , err
1602
+ }
1603
+ newf , err := executeCommandsInFile (
1604
+ & opts ,
1605
+ f ,
1606
+ cft ,
1607
+ rules ,
1608
+ // Output records are ignored in inline file execution.
1609
+ nil ,
1610
+ // Global variables not supported in inline file execution.
1611
+ nil ,
1612
+ f .Pkg ,
1613
+ // Errors-list is ignored since opts.keepGoing is always false.
1614
+ nil ,
1615
+ )
1616
+ if err != nil {
1617
+ return nil , err
1618
+ }
1619
+ if newf != nil {
1620
+ f = newf
1621
+ }
1622
+ }
1623
+
1624
+ outputFileContent , err := cleanAndBuildify (& opts , f )
1625
+ if err != nil {
1626
+ return nil , err
1627
+ }
1628
+ return outputFileContent , nil
1629
+ }
1630
+
1631
+ // groupCommandsForInlineFile groups the given commands by file and returns the commands for a
1632
+ // single file. Returns an error if the commands modify multiple files or if commands are invalid.
1633
+ func groupCommandsForInlineFile (commands []string , opts Options ) ([]commandsForTarget , * string , error ) {
1634
+ commandsByFile := make (map [string ][]commandsForTarget )
1635
+ commandReader := strings .NewReader (strings .Join (commands , "\n " ))
1636
+ err := appendCommandsFromReader (& opts , commandReader , commandsByFile , nil )
1637
+ if err != nil {
1638
+ return nil , nil , fmt .Errorf ("error parsing commands %s" , err )
1639
+ }
1640
+ if len (commandsByFile ) != 1 {
1641
+ return nil , nil , fmt .Errorf ("invalid input commands, expected all commands to reference a single file" )
1642
+ }
1643
+ for filepath , commandsForTargets := range commandsByFile {
1644
+ for i := range commandsForTargets {
1645
+ cft := & commandsForTargets [i ]
1646
+ splitTarget := strings .Split (cft .target , ":" )
1647
+ switch len (splitTarget ) {
1648
+ case 1 : // No-op
1649
+ case 2 :
1650
+ // Only keeps target (what is after the ":" character) since path is redundant.
1651
+ cft .target = splitTarget [1 ]
1652
+ default :
1653
+ return nil , nil , fmt .Errorf ("invalid target name %q" , cft .target )
1654
+ }
1655
+ }
1656
+ _ , filename := path .Split (filepath )
1657
+ return commandsForTargets , & filename , nil
1658
+ }
1659
+ panic ("unreachable" )
1660
+ }
0 commit comments