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 bpfix command-line library 16 17// TODO(jeffrygaston) should this file be consolidated with bpfmt.go? 18 19package cmd_lib 20 21import ( 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 36var ( 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 43var ( 44 exitCode = 0 45) 46 47func report(err error) { 48 fmt.Fprintln(os.Stderr, err) 49 exitCode = 2 50} 51 52func 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. 62func 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(append([]byte(nil), 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 fixer := bpfix.NewFixer(file) 79 file, err = fixer.Fix(fixRequest) 80 if err != nil { 81 return err 82 } 83 84 // output the results 85 res, err := parser.Print(file) 86 if err != nil { 87 return err 88 } 89 if !bytes.Equal(src, res) { 90 // contents have changed 91 if *list { 92 fmt.Fprintln(out, filename) 93 } 94 if *write { 95 err = ioutil.WriteFile(filename, res, 0644) 96 if err != nil { 97 return err 98 } 99 } 100 if *doDiff { 101 data, err := diff(src, res) 102 if err != nil { 103 return fmt.Errorf("computing diff: %s", err) 104 } 105 fmt.Printf("diff %s bpfix/%s\n", filename, filename) 106 out.Write(data) 107 } 108 } 109 if !*list && !*write && !*doDiff { 110 _, err = out.Write(res) 111 } 112 return err 113} 114 115func makeFileVisitor(fixRequest bpfix.FixRequest) func(string, os.FileInfo, error) error { 116 return func(path string, f os.FileInfo, err error) error { 117 if err == nil && (f.Name() == "Blueprints" || f.Name() == "Android.bp") { 118 err = openAndProcess(path, os.Stdout, fixRequest) 119 } 120 if err != nil { 121 report(err) 122 } 123 return nil 124 } 125} 126 127func walkDir(path string, fixRequest bpfix.FixRequest) { 128 filepath.Walk(path, makeFileVisitor(fixRequest)) 129} 130 131func Run() { 132 flag.Parse() 133 134 fixRequest := bpfix.NewFixRequest().AddAll() 135 136 if flag.NArg() == 0 { 137 if *write { 138 fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input") 139 exitCode = 2 140 return 141 } 142 if err := processFile("<standard input>", os.Stdin, os.Stdout, fixRequest); err != nil { 143 report(err) 144 } 145 return 146 } 147 148 for i := 0; i < flag.NArg(); i++ { 149 path := flag.Arg(i) 150 switch dir, err := os.Stat(path); { 151 case err != nil: 152 report(err) 153 case dir.IsDir(): 154 walkDir(path, fixRequest) 155 default: 156 if err := openAndProcess(path, os.Stdout, fixRequest); err != nil { 157 report(err) 158 } 159 } 160 } 161} 162 163func diff(b1, b2 []byte) (data []byte, err error) { 164 f1, err := ioutil.TempFile("", "bpfix") 165 if err != nil { 166 return 167 } 168 defer os.Remove(f1.Name()) 169 defer f1.Close() 170 171 f2, err := ioutil.TempFile("", "bpfix") 172 if err != nil { 173 return 174 } 175 defer os.Remove(f2.Name()) 176 defer f2.Close() 177 178 f1.Write(b1) 179 f2.Write(b2) 180 181 data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput() 182 if len(data) > 0 { 183 // diff exits with a non-zero status when the files don't match. 184 // Ignore that failure as long as we get output. 185 err = nil 186 } 187 return 188 189} 190