1// Copyright 2015 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// bpglob is the command line tool that checks if the list of files matching a glob has 16// changed, and only updates the output file list if it has changed. It is used to optimize 17// out build.ninja regenerations when non-matching files are added. See 18// github.com/google/blueprint/bootstrap/glob.go for a longer description. 19package main 20 21import ( 22 "flag" 23 "fmt" 24 "io/ioutil" 25 "os" 26 "time" 27 28 "github.com/google/blueprint/deptools" 29 "github.com/google/blueprint/pathtools" 30) 31 32var ( 33 out = flag.String("o", "", "file to write list of files that match glob") 34 35 globs []globArg 36) 37 38func init() { 39 flag.Var((*patternsArgs)(&globs), "p", "pattern to include in results") 40 flag.Var((*excludeArgs)(&globs), "e", "pattern to exclude from results from the most recent pattern") 41} 42 43// A glob arg holds a single -p argument with zero or more following -e arguments. 44type globArg struct { 45 pattern string 46 excludes []string 47} 48 49// patternsArgs implements flag.Value to handle -p arguments by adding a new globArg to the list. 50type patternsArgs []globArg 51 52func (p *patternsArgs) String() string { return `""` } 53 54func (p *patternsArgs) Set(s string) error { 55 globs = append(globs, globArg{ 56 pattern: s, 57 }) 58 return nil 59} 60 61// excludeArgs implements flag.Value to handle -e arguments by adding to the last globArg in the 62// list. 63type excludeArgs []globArg 64 65func (e *excludeArgs) String() string { return `""` } 66 67func (e *excludeArgs) Set(s string) error { 68 if len(*e) == 0 { 69 return fmt.Errorf("-p argument is required before the first -e argument") 70 } 71 72 glob := &(*e)[len(*e)-1] 73 glob.excludes = append(glob.excludes, s) 74 return nil 75} 76 77func usage() { 78 fmt.Fprintln(os.Stderr, "usage: bpglob -o out -p glob [-e excludes ...] [-p glob ...]") 79 flag.PrintDefaults() 80 os.Exit(2) 81} 82 83func main() { 84 flag.Parse() 85 86 if *out == "" { 87 fmt.Fprintln(os.Stderr, "error: -o is required") 88 usage() 89 } 90 91 if flag.NArg() > 0 { 92 usage() 93 } 94 95 err := globsWithDepFile(*out, *out+".d", globs) 96 if err != nil { 97 // Globs here were already run in the primary builder without error. The only errors here should be if the glob 98 // pattern was made invalid by a change in the pathtools glob implementation, in which case the primary builder 99 // needs to be rerun anyways. Update the output file with something that will always cause the primary builder 100 // to rerun. 101 writeErrorOutput(*out, err) 102 } 103} 104 105// writeErrorOutput writes an error to the output file with a timestamp to ensure that it is 106// considered dirty by ninja. 107func writeErrorOutput(path string, globErr error) { 108 s := fmt.Sprintf("%s: error: %s\n", time.Now().Format(time.StampNano), globErr.Error()) 109 err := ioutil.WriteFile(path, []byte(s), 0666) 110 if err != nil { 111 fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 112 os.Exit(1) 113 } 114} 115 116// globsWithDepFile finds all files and directories that match glob. Directories 117// will have a trailing '/'. It compares the list of matches against the 118// contents of fileListFile, and rewrites fileListFile if it has changed. It 119// also writes all of the directories it traversed as dependencies on fileListFile 120// to depFile. 121// 122// The format of glob is either path/*.ext for a single directory glob, or 123// path/**/*.ext for a recursive glob. 124func globsWithDepFile(fileListFile, depFile string, globs []globArg) error { 125 var results pathtools.MultipleGlobResults 126 for _, glob := range globs { 127 result, err := pathtools.Glob(glob.pattern, glob.excludes, pathtools.FollowSymlinks) 128 if err != nil { 129 return err 130 } 131 results = append(results, result) 132 } 133 134 // Only write the output file if it has changed. 135 err := pathtools.WriteFileIfChanged(fileListFile, results.FileList(), 0666) 136 if err != nil { 137 return fmt.Errorf("failed to write file list to %q: %w", fileListFile, err) 138 } 139 140 // The depfile can be written unconditionally as its timestamp doesn't affect ninja's restat 141 // feature. 142 err = deptools.WriteDepFile(depFile, fileListFile, results.Deps()) 143 if err != nil { 144 return fmt.Errorf("failed to write dep file to %q: %w", depFile, err) 145 } 146 147 return nil 148} 149