Home | History | Annotate | Download | only in cmd
      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