1// Copyright 2016 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 15package bootstrap 16 17import ( 18 "bytes" 19 "fmt" 20 "path/filepath" 21 "strings" 22 23 "github.com/google/blueprint" 24 "github.com/google/blueprint/deptools" 25 "github.com/google/blueprint/pathtools" 26) 27 28// This file supports globbing source files in Blueprints files. 29// 30// The build.ninja file needs to be regenerated any time a file matching the glob is added 31// or removed. The naive solution is to have the build.ninja file depend on all the 32// traversed directories, but this will cause the regeneration step to run every time a 33// non-matching file is added to a traversed directory, including backup files created by 34// editors. 35// 36// The solution implemented here optimizes out regenerations when the directory modifications 37// don't match the glob by having the build.ninja file depend on an intermedate file that 38// is only updated when a file matching the glob is added or removed. The intermediate file 39// depends on the traversed directories via a depfile. The depfile is used to avoid build 40// errors if a directory is deleted - a direct dependency on the deleted directory would result 41// in a build failure with a "missing and no known rule to make it" error. 42 43var ( 44 globCmd = filepath.Join(miniBootstrapDir, "bpglob") 45 46 // globRule rule traverses directories to produce a list of files that match $glob 47 // and writes it to $out if it has changed, and writes the directories to $out.d 48 GlobRule = pctx.StaticRule("GlobRule", 49 blueprint.RuleParams{ 50 Command: fmt.Sprintf(`%s -o $out $excludes "$glob"`, globCmd), 51 CommandDeps: []string{globCmd}, 52 Description: "glob $glob", 53 54 Restat: true, 55 Deps: blueprint.DepsGCC, 56 Depfile: "$out.d", 57 }, 58 "glob", "excludes") 59) 60 61// GlobFileContext is the subset of ModuleContext and SingletonContext needed by GlobFile 62type GlobFileContext interface { 63 Build(pctx blueprint.PackageContext, params blueprint.BuildParams) 64} 65 66// GlobFile creates a rule to write to fileListFile a list of the files that match the specified 67// pattern but do not match any of the patterns specified in excludes. The file will include 68// appropriate dependencies written to depFile to regenerate the file if and only if the list of 69// matching files has changed. 70func GlobFile(ctx GlobFileContext, pattern string, excludes []string, 71 fileListFile, depFile string) { 72 73 ctx.Build(pctx, blueprint.BuildParams{ 74 Rule: GlobRule, 75 Outputs: []string{fileListFile}, 76 Args: map[string]string{ 77 "glob": pattern, 78 "excludes": joinWithPrefixAndQuote(excludes, "-e "), 79 }, 80 }) 81} 82 83func joinWithPrefixAndQuote(strs []string, prefix string) string { 84 if len(strs) == 0 { 85 return "" 86 } 87 88 if len(strs) == 1 { 89 return prefix + `"` + strs[0] + `"` 90 } 91 92 n := len(" ") * (len(strs) - 1) 93 for _, s := range strs { 94 n += len(prefix) + len(s) + len(`""`) 95 } 96 97 ret := make([]byte, 0, n) 98 for i, s := range strs { 99 if i != 0 { 100 ret = append(ret, ' ') 101 } 102 ret = append(ret, prefix...) 103 ret = append(ret, '"') 104 ret = append(ret, s...) 105 ret = append(ret, '"') 106 } 107 return string(ret) 108} 109 110// globSingleton collects any glob patterns that were seen by Context and writes out rules to 111// re-evaluate them whenever the contents of the searched directories change, and retrigger the 112// primary builder if the results change. 113type globSingleton struct { 114 globLister func() []blueprint.GlobPath 115 writeRule bool 116} 117 118func globSingletonFactory(ctx *blueprint.Context) func() blueprint.Singleton { 119 return func() blueprint.Singleton { 120 return &globSingleton{ 121 globLister: ctx.Globs, 122 } 123 } 124} 125 126func (s *globSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) { 127 for _, g := range s.globLister() { 128 fileListFile := filepath.Join(BuildDir, ".glob", g.Name) 129 130 if s.writeRule { 131 depFile := fileListFile + ".d" 132 133 fileList := strings.Join(g.Files, "\n") + "\n" 134 pathtools.WriteFileIfChanged(fileListFile, []byte(fileList), 0666) 135 deptools.WriteDepFile(depFile, fileListFile, g.Deps) 136 137 GlobFile(ctx, g.Pattern, g.Excludes, fileListFile, depFile) 138 } else { 139 // Make build.ninja depend on the fileListFile 140 ctx.AddNinjaFileDeps(fileListFile) 141 } 142 } 143} 144 145func generateGlobNinjaFile(globLister func() []blueprint.GlobPath) ([]byte, []error) { 146 ctx := blueprint.NewContext() 147 ctx.RegisterSingletonType("glob", func() blueprint.Singleton { 148 return &globSingleton{ 149 globLister: globLister, 150 writeRule: true, 151 } 152 }) 153 154 extraDeps, errs := ctx.ResolveDependencies(nil) 155 if len(extraDeps) > 0 { 156 return nil, []error{fmt.Errorf("shouldn't have extra deps")} 157 } 158 if len(errs) > 0 { 159 return nil, errs 160 } 161 162 extraDeps, errs = ctx.PrepareBuildActions(nil) 163 if len(extraDeps) > 0 { 164 return nil, []error{fmt.Errorf("shouldn't have extra deps")} 165 } 166 if len(errs) > 0 { 167 return nil, errs 168 } 169 170 buf := bytes.NewBuffer(nil) 171 err := ctx.WriteBuildFile(buf) 172 if err != nil { 173 return nil, []error{err} 174 } 175 176 return buf.Bytes(), nil 177} 178