1 // Copyright 2017 Google Inc. All rights reserved. 2 // 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 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // This file provides a command-line interface to bpfix 16 17 // TODO(jeffrygaston) should this file be consolidated with bpfmt.go? 18 19 package main 20 21 import ( 22 "bytes" 23 "flag" 24 "fmt" 25 "io" 26 "io/ioutil" 27 "os" 28 "os/exec" 29 "path/filepath" 30 31 "github.com/google/blueprint/parser" 32 33 "android/soong/bpfix/bpfix" 34 ) 35 36 var ( 37 // main operation modes 38 list = flag.Bool("l", false, "list files whose formatting differs from bpfmt's") 39 write = flag.Bool("w", false, "write result to (source) file instead of stdout") 40 doDiff = flag.Bool("d", false, "display diffs instead of rewriting files") 41 ) 42 43 var ( 44 exitCode = 0 45 ) 46 47 func report(err error) { 48 fmt.Fprintln(os.Stderr, err) 49 exitCode = 2 50 } 51 52 func openAndProcess(filename string, out io.Writer, fixRequest bpfix.FixRequest) error { 53 f, err := os.Open(filename) 54 if err != nil { 55 return err 56 } 57 defer f.Close() 58 return processFile(filename, f, out, fixRequest) 59 } 60 61 // If in == nil, the source is the contents of the file with the given filename. 62 func processFile(filename string, in io.Reader, out io.Writer, fixRequest bpfix.FixRequest) error { 63 // load the input file 64 src, err := ioutil.ReadAll(in) 65 if err != nil { 66 return err 67 } 68 r := bytes.NewBuffer(src) 69 file, errs := parser.Parse(filename, r, parser.NewScope(nil)) 70 if len(errs) > 0 { 71 for _, err := range errs { 72 fmt.Fprintln(os.Stderr, err) 73 } 74 return fmt.Errorf("%d parsing errors", len(errs)) 75 } 76 77 // compute and apply any requested fixes 78 fixed, err := bpfix.FixTree(file, fixRequest) 79 if err != nil { 80 return err 81 } 82 83 // output the results 84 res, err := parser.Print(fixed) 85 if err != nil { 86 return err 87 } 88 if !bytes.Equal(src, res) { 89 // contents have changed 90 if *list { 91 fmt.Fprintln(out, filename) 92 } 93 if *write { 94 err = ioutil.WriteFile(filename, res, 0644) 95 if err != nil { 96 return err 97 } 98 } 99 if *doDiff { 100 data, err := diff(src, res) 101 if err != nil { 102 return fmt.Errorf("computing diff: %s", err) 103 } 104 fmt.Printf("diff %s bpfix/%s\n", filename, filename) 105 out.Write(data) 106 } 107 } 108 if !*list && !*write && !*doDiff { 109 _, err = out.Write(res) 110 } 111 return err 112 } 113 114 func makeFileVisitor(fixRequest bpfix.FixRequest) func(string, os.FileInfo, error) error { 115 return func(path string, f os.FileInfo, err error) error { 116 if err == nil && (f.Name() == "Blueprints" || f.Name() == "Android.bp") { 117 err = openAndProcess(path, os.Stdout, fixRequest) 118 } 119 if err != nil { 120 report(err) 121 } 122 return nil 123 } 124 } 125 126 func walkDir(path string, fixRequest bpfix.FixRequest) { 127 filepath.Walk(path, makeFileVisitor(fixRequest)) 128 } 129 130 func main() { 131 flag.Parse() 132 133 fixRequest := bpfix.NewFixRequest().AddAll() 134 135 if flag.NArg() == 0 { 136 if *write { 137 fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input") 138 exitCode = 2 139 return 140 } 141 if err := processFile("<standard input>", os.Stdin, os.Stdout, fixRequest); err != nil { 142 report(err) 143 } 144 return 145 } 146 147 for i := 0; i < flag.NArg(); i++ { 148 path := flag.Arg(i) 149 switch dir, err := os.Stat(path); { 150 case err != nil: 151 report(err) 152 case dir.IsDir(): 153 walkDir(path, fixRequest) 154 default: 155 if err := openAndProcess(path, os.Stdout, fixRequest); err != nil { 156 report(err) 157 } 158 } 159 } 160 } 161 162 func diff(b1, b2 []byte) (data []byte, err error) { 163 f1, err := ioutil.TempFile("", "bpfix") 164 if err != nil { 165 return 166 } 167 defer os.Remove(f1.Name()) 168 defer f1.Close() 169 170 f2, err := ioutil.TempFile("", "bpfix") 171 if err != nil { 172 return 173 } 174 defer os.Remove(f2.Name()) 175 defer f2.Close() 176 177 f1.Write(b1) 178 f2.Write(b2) 179 180 data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput() 181 if len(data) > 0 { 182 // diff exits with a non-zero status when the files don't match. 183 // Ignore that failure as long as we get output. 184 err = nil 185 } 186 return 187 188 } 189