1
16
17
18 package main
19
20 import (
21 "bytes"
22 "flag"
23 "fmt"
24 "io/ioutil"
25 "os"
26 "path/filepath"
27 "runtime"
28
29 "github.com/bazelbuild/buildtools/build"
30 "github.com/bazelbuild/buildtools/buildifier/config"
31 "github.com/bazelbuild/buildtools/buildifier/utils"
32 "github.com/bazelbuild/buildtools/differ"
33 "github.com/bazelbuild/buildtools/wspace"
34 )
35
36 var buildVersion = "redacted"
37 var buildScmRevision = "redacted"
38
39 func usage() {
40 fmt.Fprintf(flag.CommandLine.Output(), `usage: buildifier [-d] [-v] [-r] [-config=path.json] [-diff_command=command] [-help] [-multi_diff] [-mode=mode] [-lint=lint_mode] [-path=path] [files...]
41
42 Buildifier applies standard formatting to the named Starlark files. The mode
43 flag selects the processing: check, diff, fix, or print_if_changed. In check
44 mode, buildifier prints a list of files that need reformatting. In diff mode,
45 buildifier shows the diffs that it would make. It creates the diffs by running
46 a diff command, which can be specified using the -diff_command flag. You can
47 indicate that the diff command can show differences between more than two files
48 in the manner of tkdiff by specifying the -multi_diff flag. In fix mode,
49 buildifier updates the files that need reformatting and, if the -v flag is
50 given, prints their names to standard error. In print_if_changed mode,
51 buildifier shows the file contents it would write. The default mode is fix. -d
52 is an alias for -mode=diff.
53
54 The lint flag selects the lint mode to be used: off, warn, fix.
55 In off mode, the linting is not performed.
56 In warn mode, buildifier prints warnings for common mistakes and suboptimal
57 coding practices that include links providing more context and fix suggestions.
58 In fix mode, buildifier updates the files with all warning resolutions produced
59 by automated fixes.
60 The default lint mode is off.
61
62 If no files are listed, buildifier reads a Starlark file from standard
63 input. In fix mode, it writes the reformatted Starlark file to standard output,
64 even if no changes are necessary.
65
66 Buildifier's reformatting depends in part on the path to the file relative
67 to the workspace directory. Normally buildifier deduces that path from the
68 file names given, but the path can be given explicitly with the -path
69 argument. This is especially useful when reformatting standard input,
70 or in scripts that reformat a temporary copy of a file.
71
72 Return codes used by buildifier:
73
74 0: success, everything went well
75 1: syntax errors in input
76 2: usage errors: invoked incorrectly
77 3: unexpected runtime errors: file I/O problems or internal bugs
78 4: check mode failed (reformat is needed)
79
80 Full list of flags with their defaults:
81 `)
82 flag.PrintDefaults()
83
84 fmt.Fprintf(flag.CommandLine.Output(), `
85 Buildifier can be also be configured via a JSON file. The location of the file
86 is given by the -config flag, the BUILDIFIER_CONFIG environment variable, or
87 a file named '.buildifier.json' at the root of the workspace (e.g., in the same
88 directory as the WORKSPACE file). The PWD environment variable or process
89 working directory is used to help find the workspace root. If present, the file
90 is loaded into memory and becomes the base configuration that command line flags
91 override. A sample configuration file can be printed to stdout by running
92 buildifier -config=example. The config file feature can be disabled completely
93 with -config=off.
94 `)
95 }
96
97 func main() {
98 c := config.New()
99
100 flags := c.FlagSet("buildifier", flag.ExitOnError)
101 flag.CommandLine = flags
102 flag.Usage = usage
103 flags.Parse(os.Args[1:])
104 args := flags.Args()
105
106 if c.Help {
107 flag.CommandLine.SetOutput(os.Stdout)
108 usage()
109 fmt.Println()
110 os.Exit(0)
111 }
112
113 if c.Version {
114 fmt.Printf("buildifier version: %s \n", buildVersion)
115 fmt.Printf("buildifier scm revision: %s \n", buildScmRevision)
116 os.Exit(0)
117 }
118
119 if c.ConfigPath == "" {
120 c.ConfigPath = config.FindConfigPath("")
121 }
122 if c.ConfigPath != "" {
123 if c.ConfigPath == "example" {
124 fmt.Println(config.Example().String())
125 os.Exit(0)
126 }
127 if c.ConfigPath != "off" {
128 if err := c.LoadFile(); err != nil {
129 fmt.Fprintf(os.Stderr, "buildifier: %s\n", err)
130 os.Exit(2)
131 }
132
133 flags = c.FlagSet("buildifier", flag.ExitOnError)
134 flag.CommandLine = flags
135 flag.Usage = usage
136 flags.Parse(os.Args[1:])
137 }
138 }
139
140 if err := c.Validate(args); err != nil {
141 fmt.Fprintf(os.Stderr, "buildifier: %s\n", err)
142 os.Exit(2)
143 }
144
145
146 build.DisableRewrites = c.DisableRewrites
147 build.AllowSort = c.AllowSort
148
149 differ, deprecationWarning := differ.Find()
150 if c.DiffCommand != "" {
151 differ.Cmd = c.DiffCommand
152 differ.MultiDiff = c.MultiDiff
153 } else {
154 if deprecationWarning && c.Mode == "diff" {
155 fmt.Fprintf(os.Stderr, "buildifier: selecting diff program with the BUILDIFIER_DIFF, BUILDIFIER_MULTIDIFF, and DISPLAY environment variables is deprecated, use flags -diff_command and -multi_diff instead\n")
156 }
157 }
158
159 b := buildifier{c, differ}
160 exitCode := b.run(args)
161
162 os.Exit(exitCode)
163 }
164
165 type buildifier struct {
166 config *config.Config
167 differ *differ.Differ
168 }
169
170 func (b *buildifier) run(args []string) int {
171 tf := &utils.TempFile{}
172 defer tf.Clean()
173
174 exitCode := 0
175 var diagnostics *utils.Diagnostics
176 if len(args) == 0 || (len(args) == 1 && (args)[0] == "-") {
177
178 data, err := ioutil.ReadAll(os.Stdin)
179 if err != nil {
180 fmt.Fprintf(os.Stderr, "buildifier: reading stdin: %v\n", err)
181 return 2
182 }
183 if b.config.Mode == "fix" {
184 b.config.Mode = "pipe"
185 }
186 var fileDiagnostics *utils.FileDiagnostics
187 fileDiagnostics, exitCode = b.processFile("", data, false, tf)
188 diagnostics = utils.NewDiagnostics(fileDiagnostics)
189 } else {
190 files := args
191 if b.config.Recursive {
192 var err error
193 files, err = utils.ExpandDirectories(&args)
194 if err != nil {
195 fmt.Fprintf(os.Stderr, "buildifier: %v\n", err)
196 return 3
197 }
198 }
199 diagnostics, exitCode = b.processFiles(files, tf)
200 }
201
202 diagnosticsOutput := diagnostics.Format(b.config.Format, b.config.Verbose)
203 if b.config.Format != "" {
204
205 fmt.Print(diagnosticsOutput)
206
207 exitCode = 0
208 } else {
209
210 fmt.Fprint(os.Stderr, diagnosticsOutput)
211 }
212
213 if err := b.differ.Run(); err != nil {
214 fmt.Fprintf(os.Stderr, "%v\n", err)
215 return 2
216 }
217
218 return exitCode
219 }
220
221 func (b *buildifier) processFiles(files []string, tf *utils.TempFile) (*utils.Diagnostics, int) {
222
223
224 nworker := 100
225 if n := (len(files) + 9) / 10; nworker > n {
226 nworker = n
227 }
228 runtime.GOMAXPROCS(nworker + 1)
229
230
231
232
233
234 type result struct {
235 file string
236 data []byte
237 err error
238 }
239
240 ch := make([]chan result, nworker)
241 for i := 0; i < nworker; i++ {
242 ch[i] = make(chan result, 1)
243 go func(i int) {
244 for j := i; j < len(files); j += nworker {
245 file := files[j]
246 data, err := ioutil.ReadFile(file)
247 ch[i] <- result{file, data, err}
248 }
249 }(i)
250 }
251
252 exitCode := 0
253 fileDiagnostics := []*utils.FileDiagnostics{}
254
255
256
257
258
259 for i, file := range files {
260 res := <-ch[i%nworker]
261 if res.file != file {
262 fmt.Fprintf(os.Stderr, "buildifier: internal phase error: got %s for %s", res.file, file)
263 os.Exit(3)
264 }
265 if res.err != nil {
266 fmt.Fprintf(os.Stderr, "buildifier: %v\n", res.err)
267 exitCode = 3
268 continue
269 }
270 fd, newExitCode := b.processFile(file, res.data, len(files) > 1, tf)
271 if fd != nil {
272 fileDiagnostics = append(fileDiagnostics, fd)
273 }
274 if newExitCode != 0 {
275 exitCode = newExitCode
276 }
277 }
278 return utils.NewDiagnostics(fileDiagnostics...), exitCode
279 }
280
281
282
283 func (b *buildifier) processFile(filename string, data []byte, displayFileNames bool, tf *utils.TempFile) (*utils.FileDiagnostics, int) {
284 var exitCode int
285
286 displayFilename := filename
287 if b.config.WorkspaceRelativePath != "" {
288 displayFilename = b.config.WorkspaceRelativePath
289 }
290
291 parser := utils.GetParser(b.config.InputType)
292
293 f, err := parser(displayFilename, data)
294 if err != nil {
295
296
297
298 fmt.Fprintf(os.Stderr, "%v\n", err)
299 if exitCode < 1 {
300 exitCode = 1
301 }
302 return utils.InvalidFileDiagnostics(displayFilename), exitCode
303 }
304
305 if absoluteFilename, err := filepath.Abs(displayFilename); err == nil {
306 f.WorkspaceRoot, f.Pkg, f.Label = wspace.SplitFilePath(absoluteFilename)
307 }
308
309 warnings := utils.Lint(f, b.config.Lint, &b.config.LintWarnings, b.config.Verbose)
310 if len(warnings) > 0 {
311 exitCode = 4
312 }
313 fileDiagnostics := utils.NewFileDiagnostics(f.DisplayPath(), warnings)
314
315 ndata := build.Format(f)
316
317 switch b.config.Mode {
318 case "check":
319
320 if !bytes.Equal(data, ndata) {
321 fileDiagnostics.Formatted = false
322 return fileDiagnostics, 4
323 }
324
325 case "diff":
326
327 if bytes.Equal(data, ndata) {
328 return fileDiagnostics, exitCode
329 }
330 outfile, err := tf.WriteTemp(ndata)
331 if err != nil {
332 fmt.Fprintf(os.Stderr, "buildifier: %v\n", err)
333 return fileDiagnostics, 3
334 }
335 infile := filename
336 if filename == "" {
337
338
339 infile, err = tf.WriteTemp(data)
340 if err != nil {
341 fmt.Fprintf(os.Stderr, "buildifier: %v\n", err)
342 return fileDiagnostics, 3
343 }
344 }
345 if displayFileNames {
346 fmt.Fprintf(os.Stderr, "%v:\n", f.DisplayPath())
347 }
348 if err := b.differ.Show(infile, outfile); err != nil {
349 fmt.Fprintf(os.Stderr, "%v\n", err)
350 return fileDiagnostics, 4
351 }
352
353 case "pipe":
354
355
356 os.Stdout.Write(ndata)
357
358 case "fix":
359
360 if bytes.Equal(data, ndata) {
361 return fileDiagnostics, exitCode
362 }
363
364 err := ioutil.WriteFile(filename, ndata, 0666)
365 if err != nil {
366 fmt.Fprintf(os.Stderr, "buildifier: %s\n", err)
367 return fileDiagnostics, 3
368 }
369
370 if b.config.Verbose {
371 fmt.Fprintf(os.Stderr, "fixed %s\n", f.DisplayPath())
372 }
373 case "print_if_changed":
374 if bytes.Equal(data, ndata) {
375 return fileDiagnostics, exitCode
376 }
377
378 if _, err := os.Stdout.Write(ndata); err != nil {
379 fmt.Fprintf(os.Stderr, "buildifier: error writing output: %v\n", err)
380 return fileDiagnostics, 3
381 }
382 }
383 return fileDiagnostics, exitCode
384 }
385
View as plain text